Files
Interpreter/tests/src/parser_test.cpp
T

333 lines
11 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::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.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::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;
}