Files
Interpreter/tests/src/parser_test.cpp
T
Admin 4ba44459e2 实现 12.5 递归下降语法:AST + 拒绝清单。
- 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
2026-08-21 11:07:34 +08:00

302 lines
9.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 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.stFUNCTION_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.stPROGRAM + 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;
}