- Parser.h/cpp:POU 外壳(PROGRAM/FUNCTION/FUNCTION_BLOCK)、变量段、语句(赋值/IF/WHILE/FB 调用)、表达式优先级爬升(NOT/AND/OR 短路/比较/算术/函数调用/负号) - Unit 结构:顶层 VAR_GLOBAL 段(GVL 文件形态)+ POU 列表 - 拒绝:VAR_IN_OUT/REF/CLASS/ANY 等保留名、链式比较、FB 字段赋值、顶层非 GVL 段 - THEN/DO 补入关键字表(12.5 修订,同步 12.1 与词法文档) - parser_test:98 断言(line1 三文件 AST、FUNCTION 调用、IF/WHILE/TON、9 类负例),ctest 6/6
302 lines
9.7 KiB
C++
302 lines
9.7 KiB
C++
/**
|
||
* @file parser_test.cpp
|
||
* @brief 语法测试:line1 AST 结构 + 负例
|
||
* @author
|
||
* @date 2026-08-21
|
||
*/
|
||
|
||
#include <cstdio>
|
||
#include <string>
|
||
|
||
#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::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::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::FbBuiltin);
|
||
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;
|
||
}
|
||
|
||
int main() {
|
||
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;
|
||
}
|