Files
Interpreter/tests/src/linker_test.cpp
T
Admin 6f02c18bbb 实现 12.6 符号表与链接:全局定槽、引用解析、循环检测。
- Linker.h/cpp:收集导出(仅 gvl 文件顶层 VAR_GLOBAL、声明顺序定槽、重复名拒绝)
- VAR_EXTERNAL 接同一槽、类型一致;FB 实例必须先声明;字段存在性校验;函数调用图 DFS 环检测
- io.var 必须在 GVL;内建 TON/TOF/CTU 布局冻结;用户 FB 字段 = input/output/var(external 不是字段)
- 函数名赋值 = 结果值写入(用例 13 约定);dump_symbols 打印 name kind type address file
- linker_test:51 断言(用例 09/10/11/12/16/19 + line1/17 正例),ctest 7/7;20 用例扫描 15 绿 5 按期望拒
2026-08-21 11:14:03 +08:00

230 lines
7.3 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));
}
return link_project(p, units, 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));
}
CHECK(link_project(p, units, &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 == TypeKind::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));
}
CHECK(link_project(p, units, &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 == TypeKind::Bool);
CHECK(it->second.fields[1].name == "pt" && it->second.fields[1].type == TypeKind::Time);
CHECK(it->second.fields[2].name == "q" && it->second.fields[2].type == TypeKind::Bool);
CHECK(it->second.fields[3].name == "et" && it->second.fields[3].type == TypeKind::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;
}