/** * @file parser_test.cpp * @brief 语法测试:line1 AST 结构 + 负例 * @author * @date 2026-08-21 */ #include #include #include "compiler/Parser.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) // ---- 1. GVL 文件(顶层 VAR_GLOBAL,无 POU)---- static bool test_gvl_file() { using namespace compiler; Unit u; std::string err; CHECK(parse_pous_file(std::string(REPO_ROOT) + "/examples/line1/globals.st", &u, &err)); CHECK(u.globals.size() == 1); CHECK(u.pous.empty()); const VarBlock& g = u.globals[0]; CHECK(g.section == VarSection::Global); CHECK(g.vars.size() == 4); CHECK(g.vars[0].name == "emergencystop"); CHECK(g.vars[0].type.kind == TypeKind::Scalar && g.vars[0].type.name == "bool"); CHECK(g.vars[0].has_init && g.vars[0].init_value == 0); // := FALSE CHECK(!g.vars[1].has_init); // I0_0 return true; } // ---- 2. motor.st:FUNCTION_BLOCK ---- static bool test_fb() { using namespace compiler; Unit u; std::string err; CHECK(parse_pous_file(std::string(REPO_ROOT) + "/examples/line1/motor.st", &u, &err)); CHECK(u.globals.empty()); CHECK(u.pous.size() == 1); const POU& p = u.pous[0]; CHECK(p.kind == PouKind::FunctionBlock); CHECK(p.name == "motorstarter"); CHECK(p.blocks.size() == 3); CHECK(p.blocks[0].section == VarSection::External); CHECK(p.blocks[0].vars[0].name == "emergencystop"); CHECK(p.blocks[1].section == VarSection::Input); CHECK(p.blocks[1].vars.size() == 2); CHECK(p.blocks[2].section == VarSection::Output); CHECK(p.blocks[2].vars[0].name == "q"); CHECK(p.body.size() == 1); const Stmt& s = p.body[0]; CHECK(s.kind == StmtKind::Assign); CHECK(s.target == "q"); CHECK(!s.target_is_field); // Q := start AND NOT stop AND NOT EmergencyStop // 左结合:(start AND NOT stop) AND NOT EmergencyStop CHECK(s.value->kind == ExprKind::And); CHECK(s.value->rhs->kind == ExprKind::Not); CHECK(s.value->rhs->operand->kind == ExprKind::VarRef && s.value->rhs->operand->name == "emergencystop"); CHECK(s.value->lhs->kind == ExprKind::And); CHECK(s.value->lhs->lhs->kind == ExprKind::VarRef && s.value->lhs->lhs->name == "start"); CHECK(s.value->lhs->rhs->kind == ExprKind::Not); CHECK(s.value->lhs->rhs->operand->kind == ExprKind::VarRef && s.value->lhs->rhs->operand->name == "stop"); return true; } // ---- 3. main.st:PROGRAM + FB 调用 + 字段读 ---- static bool test_program() { using namespace compiler; Unit u; std::string err; CHECK(parse_pous_file(std::string(REPO_ROOT) + "/examples/line1/main.st", &u, &err)); CHECK(u.pous.size() == 1); const POU& p = u.pous[0]; CHECK(p.kind == PouKind::Program); CHECK(p.name == "main"); CHECK(p.blocks.size() == 1); CHECK(p.blocks[0].section == VarSection::Local); CHECK(p.blocks[0].vars[0].name == "starter"); CHECK(p.blocks[0].vars[0].type.kind == TypeKind::FbUser); CHECK(p.blocks[0].vars[0].type.name == "motorstarter"); CHECK(p.body.size() == 2); const Stmt& call = p.body[0]; CHECK(call.kind == StmtKind::FbCall); CHECK(call.instance == "starter"); CHECK(call.args.size() == 2); CHECK(call.args[0].name == "start"); CHECK(call.args[0].value->kind == ExprKind::VarRef && call.args[0].value->name == "i0_0"); CHECK(call.args[1].name == "stop"); CHECK(call.args[1].value->kind == ExprKind::VarRef && call.args[1].value->name == "i0_1"); const Stmt& assign = p.body[1]; CHECK(assign.kind == StmtKind::Assign); CHECK(assign.target == "q0_0"); CHECK(assign.value->kind == ExprKind::Field); CHECK(assign.value->name == "starter"); CHECK(assign.value->field == "q"); return true; } // ---- 4. FUNCTION:返回值类型 + 函数调用表达式 ---- static bool test_function() { using namespace compiler; Unit u; std::string err; const char* src = "FUNCTION Add : INT\n" "VAR_INPUT\n" " a, b : INT;\n" "END_VAR\n" " Add := a + b;\n" "END_FUNCTION\n" "PROGRAM MAIN\n" "VAR\n" " x : INT;\n" "END_VAR\n" " x := Add(3, 4) * 2;\n" "END_PROGRAM\n"; CHECK(parse_pous("t.st", src, &u, &err)); CHECK(u.pous.size() == 2); const POU& f = u.pous[0]; CHECK(f.kind == PouKind::Function); CHECK(f.name == "add"); CHECK(f.result_type.kind == TypeKind::Scalar && f.result_type.name == "int"); CHECK(f.body[0].kind == StmtKind::Assign); CHECK(f.body[0].value->kind == ExprKind::Add); const POU& m = u.pous[1]; const Stmt& s = m.body[0]; CHECK(s.value->kind == ExprKind::Mul); CHECK(s.value->lhs->kind == ExprKind::Call); CHECK(s.value->lhs->name == "add"); CHECK(s.value->lhs->args.size() == 2); CHECK(s.value->lhs->args[0]->kind == ExprKind::LitInt && s.value->lhs->args[0]->int_value == 3); CHECK(s.value->lhs->args[1]->kind == ExprKind::LitInt && s.value->lhs->args[1]->int_value == 4); return true; } // ---- 5. IF / WHILE / 内建 FB 类型 ---- static bool test_control() { using namespace compiler; Unit u; std::string err; const char* src = "PROGRAM MAIN\n" "VAR\n" " sel : INT;\n" " out : INT;\n" " t : TON;\n" " n : INT;\n" "END_VAR\n" " IF sel = 0 THEN\n" " out := 10;\n" " ELSIF sel = 1 THEN\n" " out := 20;\n" " ELSE\n" " out := 30;\n" " END_IF\n" " n := 0;\n" " WHILE n < 10 DO\n" " n := n + 1;\n" " END_WHILE\n" " t(in := TRUE, pt := T#30ms);\n" "END_PROGRAM\n"; CHECK(parse_pous("t.st", src, &u, &err)); const POU& p = u.pous[0]; CHECK(p.blocks[0].vars[2].name == "t"); CHECK(p.blocks[0].vars[2].type.kind == TypeKind::FbUser); CHECK(p.blocks[0].vars[2].type.name == "ton"); CHECK(p.body.size() == 4); const Stmt& ifs = p.body[0]; CHECK(ifs.kind == StmtKind::If); CHECK(ifs.cond->kind == ExprKind::Cmp); CHECK(ifs.cond->op == BinOp::Eq); CHECK(ifs.cond->lhs->kind == ExprKind::VarRef && ifs.cond->lhs->name == "sel"); CHECK(ifs.body.size() == 1); CHECK(ifs.elsifs.size() == 1); CHECK(ifs.elsifs[0].cond->lhs->name == "sel"); CHECK(ifs.else_body.size() == 1); const Stmt& w = p.body[2]; CHECK(w.kind == StmtKind::While); CHECK(w.cond->kind == ExprKind::Cmp); CHECK(w.cond->op == BinOp::Lt); CHECK(w.body.size() == 1); const Stmt& ton = p.body[3]; CHECK(ton.kind == StmtKind::FbCall); CHECK(ton.instance == "t"); CHECK(ton.args[1].name == "pt"); CHECK(ton.args[1].value->kind == ExprKind::LitTime); CHECK(ton.args[1].value->int_value == 30); return true; } // ---- 6. 负例 ---- static bool expect_syntax_err(const std::string& src, const char* keyword) { compiler::Unit u; std::string err; if (compiler::parse_pous("t.st", src, &u, &err)) { std::printf("FAIL: parsed ok: %s\n", src.c_str()); return false; } if (err.find("syntax error") != 0) { std::printf("FAIL: want 'syntax error', got '%s'\n", err.c_str()); return false; } if (std::string(keyword).empty()) { ++g_checks; return true; } if (err.find(keyword) == std::string::npos) { std::printf("FAIL: want keyword '%s', got '%s'\n", keyword, err.c_str()); return false; } ++g_checks; return true; } static bool test_negative() { // VAR_IN_OUT / REF / CLASS / ANY 明确拒绝 if (!expect_syntax_err( "PROGRAM MAIN\nVAR_IN_OUT\nx : INT;\nEND_VAR\nEND_PROGRAM", "not supported")) { return false; } if (!expect_syntax_err("FUNCTION F : INT\n F := 1;\n REF := 2;\nEND_FUNCTION", "not supported")) { return false; } // 缺 END_* if (!expect_syntax_err("PROGRAM MAIN\nIF x THEN\ny := 1;\nEND_PROGRAM", "")) { return false; } if (!expect_syntax_err("FUNCTION_BLOCK F\nEND_PROGRAM", "")) { return false; } // 链式比较 if (!expect_syntax_err("PROGRAM MAIN\na := b < c < d;\nEND_PROGRAM", "chained comparison")) { return false; } // 字段赋值 if (!expect_syntax_err("PROGRAM MAIN\nstarter.Q := TRUE;\nEND_PROGRAM", "FB field")) { return false; } // 顶层非 VAR_GLOBAL 段 if (!expect_syntax_err("VAR\nx : INT;\nEND_VAR\n", "top level")) { return false; } // 初值非字面量 if (!expect_syntax_err("PROGRAM MAIN\nVAR\nx : INT := y;\nEND_VAR\nEND_PROGRAM", "")) { return false; } // 坏类型名 if (!expect_syntax_err("PROGRAM MAIN\nVAR\nx : ;\nEND_VAR\nEND_PROGRAM", "expected type name")) { return false; } return true; } // ---- VAR_TEMP 段:解析为 VarSection::Temp ---- static bool test_var_temp() { using namespace compiler; Unit u; std::string err; CHECK(parse_pous("t.st", "FUNCTION_BLOCK Foo\n" "VAR_INPUT\n in : BOOL;\nEND_VAR\n" "VAR_OUTPUT\n out : BOOL;\nEND_VAR\n" "VAR\n state : BOOL;\nEND_VAR\n" "VAR_TEMP\n tmp : INT;\nEND_VAR\n" " out := tmp > 0;\n" "END_FUNCTION_BLOCK", &u, &err)); CHECK(u.pous.size() == 1); const POU& p = u.pous[0]; CHECK(p.kind == PouKind::FunctionBlock); CHECK(p.blocks.size() == 4); CHECK(p.blocks[0].section == VarSection::Input); CHECK(p.blocks[1].section == VarSection::Output); CHECK(p.blocks[2].section == VarSection::Local); CHECK(p.blocks[3].section == VarSection::Temp); CHECK(p.blocks[3].vars.size() == 1); CHECK(p.blocks[3].vars[0].name == "tmp"); CHECK(p.blocks[3].vars[0].type.kind == TypeKind::Scalar); CHECK(p.blocks[3].vars[0].type.name == "int"); return true; } int main() { if (!test_var_temp()) return 1; if (!test_gvl_file()) return 1; if (!test_fb()) return 1; if (!test_program()) return 1; if (!test_function()) return 1; if (!test_control()) return 1; if (!test_negative()) return 1; std::printf("parser_test: %d checks passed\n", g_checks); return 0; }