Files
Interpreter/tests/src/linker_test.cpp
T
Admin 6be2873c88 阶段3-1:编译器语言层 V2 完成(19 类型/字面量扩展/禁隐式转换/配置驱动 FB)
Lexer V2:
- 19 类型关键字(BOOL..DT);FB 名不再设关键字(IDENT)
- FLOAT_LIT(1.5/2.5e3)、进制前缀(2#/8#/16# 换算十进制)
- 日期字面量 D#/TOD#/DT#(历法换算 1970 纪元、闰年、值域检查)

Parser V2:
- TypeRef 字符串化(TypeKind{Scalar,FbBuiltin,FbUser},名字小写)
- ExprKind 增 LitReal/LitDate/LitTod/LitDt;Expr.double_value
- IDENT 类型统一 FbUser(内建判定在 Linker 查 fb 表)

Typecheck V2:
- TType 19 + Unresolved(字面量未定型)
- 禁隐式转换(赋值/算术/比较严格同型)
- resolve_to:未定型→消费点定型+值域检查(BOOL 0/1、无符号负数、
  溢出、浮点仅 REAL/LREAL);算术按 kind 允许(TIME 仅 ±)
- Call 实参按函数参数类型;check_project 加 cfg

Linker V2:
- FbField.type_name 字符串化;内建 FB 布局查 machine.toml [[fb]] 表
  (删 8 个硬编码布局,带缓存);link_project 加 cfg

Codegen V2:
- 浮点/日期字面量(LOADK func 8/4/4/6;LitReal 按 REAL 装载,LREAL TODO)
- 算术/比较 func 按操作数类型(expr_func)
- TypeKind 连锁适配

main.cpp:link/check 传 cfg;--disasm 头 22 字段逐行中文注释(L5)
+ slots 节;StbView 补 offset_const/offset_funcs

测试适配:parser/linker/typecheck/vm/cases 适配新接口与 V2 语义。
ctest 11/13(vm_cycles/cases_all 待阶段 4)。codegen_test 仍剥离(阶段 3-2 重写)。
2026-08-26 09:29:31 +08:00

241 lines
7.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file linker_test.cpp
* @brief 链接测试:用例 09/10/11/12/16/19 + line1 正例
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <string>
#include <vector>
#include "compiler/Linker.h"
#include "compiler/Project.h"
#ifndef REPO_ROOT
#define REPO_ROOT "."
#endif
static int g_checks = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
return false; \
} \
++g_checks; \
} while (0)
// 从用例目录加载工程并链接
static bool link_case(const char* dir, compiler::LinkResult* out, std::string* err) {
using namespace compiler;
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
Project p;
if (!parse_project(toml, &p, err)) {
return false;
}
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
return false;
}
units.push_back(std::move(u));
}
compiler::MachineConfig cfg;
std::string cerr;
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr)) {
return false;
}
return link_project(p, units, cfg, out, err);
}
static bool expect_link_err(const char* dir, const char* keyword) {
compiler::LinkResult r;
std::string err;
if (link_case(dir, &r, &err)) {
std::printf("FAIL %s: linked ok\n", dir);
return false;
}
if (err.find("link error") != 0) {
std::printf("FAIL %s: want 'link error', got '%s'\n", dir, err.c_str());
return false;
}
if (err.find(keyword) == std::string::npos) {
std::printf("FAIL %s: want '%s', got '%s'\n", dir, keyword, err.c_str());
return false;
}
++g_checks;
return true;
}
// ---- 1. 用例 09GVL + VAR_EXTERNAL 同一槽 ----
static bool test_case09() {
using namespace compiler;
LinkResult r;
std::string err;
CHECK(link_case("09_gvl_external", &r, &err));
CHECK(r.globals.size() == 1);
CHECK(r.globals[0].name == "g1");
CHECK(r.globals[0].kind == SymbolKind::Global);
CHECK(r.globals[0].type_name == "int");
CHECK(r.globals[0].address == 0);
CHECK(r.scopes.size() == 1);
const LinkResult::PouScope& main = r.scopes[0];
CHECK(main.name == "main");
bool saw_external = false;
for (const Symbol& s : main.syms) {
if (s.name == "g1") {
saw_external = true;
CHECK(s.kind == SymbolKind::External);
CHECK(s.address == 0); // 同一槽
CHECK(s.type_name == "int");
}
}
// 符号表打印:name kind type address file
const std::string dump = dump_symbols(r);
CHECK(dump.find("g1 global int 0 ") != std::string::npos);
CHECK(dump.find("[main]") != std::string::npos);
CHECK(dump.find("g1 external int 0 ") != std::string::npos);
return true;
}
// ---- 2. 用例 10 / 11 / 12 ----
static bool test_neg_cases() {
if (!expect_link_err("10_gvl_wrong_file", "VAR_GLOBAL only allowed in gvl file")) {
return false;
}
if (!expect_link_err("11_duplicate_global", "duplicate global")) {
return false;
}
if (!expect_link_err("12_io_unknown_var", "io.var 'Nope' not in GVL")) {
return false;
}
return true;
}
// ---- 3. 用例 16:未声明实例 ----
static bool test_case16() {
if (!expect_link_err("16_fb_undeclared", "undeclared FB instance")) {
return false;
}
return true;
}
// ---- 4. 用例 19:循环调用 ----
static bool test_case19() {
if (!expect_link_err("19_recursive_call", "recursive call")) {
return false;
}
return true;
}
// ---- 5. line1 正例:全局槽序 / FB 布局 / 字段 / 实例 ----
static bool test_line1() {
using namespace compiler;
LinkResult r;
std::string err;
const std::string toml = std::string(REPO_ROOT) + "/examples/line1/project.toml";
Project p;
CHECK(parse_project(toml, &p, &err));
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
units.push_back(std::move(u));
}
compiler::MachineConfig cfg;
std::string cerr;
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr));
CHECK(link_project(p, units, cfg, &r, &err));
// 全局槽序 = 声明顺序
CHECK(r.globals.size() == 4);
CHECK(r.globals[0].name == "emergencystop");
CHECK(r.globals[1].name == "i0_0");
CHECK(r.globals[2].name == "i0_1");
CHECK(r.globals[3].name == "q0_0");
// 用户 FB 布局:MotorStarter 字段 = input/outputexternal 不是字段)
const auto it = r.fb_types.find("motorstarter");
CHECK(it != r.fb_types.end());
CHECK(it->second.fields.size() == 3);
CHECK(it->second.fields[0].name == "start");
CHECK(it->second.fields[1].name == "stop");
CHECK(it->second.fields[2].name == "q");
CHECK(it->second.fields[2].type_name == "bool");
// 内建 TON 布局(用例 17 的 t)
const FbLayout* ton = nullptr;
for (const auto& [name, lay] : r.fb_types) {
(void)name;
(void)lay;
}
// 直接构造 TON 实例验证内建表(见 case17 用)
// main 作用域:starter 实例 + 字段引用
CHECK(r.scopes.size() == 2); // motorstarter + main
const LinkResult::PouScope& main = r.scopes[1];
bool saw_starter = false;
for (const Symbol& s : main.syms) {
if (s.name == "starter") {
saw_starter = true;
CHECK(s.kind == SymbolKind::FbInstance);
CHECK(s.type_name == "motorstarter");
}
}
CHECK(saw_starter);
const auto sit = main.fb_instances.find("starter");
CHECK(sit != main.fb_instances.end());
CHECK(sit->second.fields.size() == 3);
return true;
}
// ---- 6. 内建 FB 布局(TON----
static bool test_builtin() {
using namespace compiler;
LinkResult r;
std::string err;
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/17_ton/project.toml";
Project p;
CHECK(parse_project(toml, &p, &err));
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
CHECK(load_unit(p.base_dir + "/" + f, &u, &err));
units.push_back(std::move(u));
}
compiler::MachineConfig cfg;
std::string cerr;
CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &cerr));
CHECK(link_project(p, units, cfg, &r, &err));
CHECK(r.scopes.size() == 1);
const LinkResult::PouScope& main = r.scopes[0];
const auto it = main.fb_instances.find("t");
CHECK(it != main.fb_instances.end());
CHECK(it->second.fields.size() == 4);
CHECK(it->second.fields[0].name == "in" && it->second.fields[0].type_name == "bool");
CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type_name == "time");
CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type_name == "bool");
CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type_name == "time");
return true;
}
int main() {
if (!test_case09()) return 1;
if (!test_neg_cases()) return 1;
if (!test_case16()) return 1;
if (!test_case19()) return 1;
if (!test_line1()) return 1;
if (!test_builtin()) return 1;
std::printf("linker_test: %d checks passed\n", g_checks);
return 0;
}