diff --git a/Doc/compiler/词法.md b/Doc/compiler/词法.md index 8a38b98..908b864 100644 --- a/Doc/compiler/词法.md +++ b/Doc/compiler/词法.md @@ -29,6 +29,9 @@ TRUE FALSE TON TOF CTU ``` +> 12.5 修订:`THEN`、`DO` 补入关键字表(`IF ... THEN` / `WHILE ... DO` 语法需要), +> 与 12.1 原表合并,其余不变。 + 其余单词一律当标识符;`VAR_IN_OUT`、`REF`、`CLASS` 等由语法层(12.5)出明确错误。 ## Token diff --git a/Doc/compiler/语法.md b/Doc/compiler/语法.md new file mode 100644 index 0000000..11ef30f --- /dev/null +++ b/Doc/compiler/语法.md @@ -0,0 +1,183 @@ +# 语法(12.5 递归下降) + +compiler 模块的语法分析。输入:单个 `.st` 文件的 token 流(见 [`词法.md`](词法.md));输出:**AST**(不分配寄存器、不写映像)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.1、12.5。 + +## 做 / 不做 + +**做** + +- POU 外壳:`PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK` 与对应 `END_*` +- 变量段:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL` / `VAR_EXTERNAL` +- 类型名只接受 `BOOL` `INT` `TIME`;内置 FB 名 `TON` `TOF` `CTU`;标识符(用户 FB 类型) +- 语句:赋值、`IF` / `ELSIF` / `ELSE`、`WHILE`、FB 调用 `fb(in := …)`、字段 `fb.Q` +- 表达式:`NOT`、`AND` / `OR`(AST 保留短路语义,12.8 编跳转)、比较、加减乘除、一元负号、函数调用 `Add(3, 4)` +- 明确拒绝(`syntax error`,不静默):`VAR_IN_OUT`、`VAR_TEMP`、`REF`、`CLASS`、`ANY` 等保留名 + +**不做(第一版)** + +- 寄存器分配、跳转回填、映像(12.8) +- 链式比较 `a < b < c`、字符串、`REAL`、指针、`VAR_IN_OUT`、接口、`TYPE` 定义 +- 语义检查(未声明变量等归 12.6 / 12.7) + +## AST 结构 + +```cpp +// 类型引用:类型位置出现的名字 +enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser }; +struct TypeRef { + TypeKind kind; + std::string name; // FbUser 时为用户 FB 类型名(小写) +}; + +struct VarDecl { + std::string name; + TypeRef type; + bool has_init; // 可选初值(v1 仅 BOOL/INT/TIME 字面量) + int64_t init_value; // BOOL 0/1、INT 值、TIME 毫秒 + uint32_t line, col; +}; + +enum class VarSection { Local, Input, Output, Global, External }; +struct VarBlock { + VarSection section; + std::vector vars; +}; + +// 表达式 +enum class ExprKind { + LitBool, LitInt, LitTime, + VarRef, // 标识符(局部/全局/外部/实例名) + Field, // 对象.字段:fb.Q / fb.ET + Not, And, Or, + Cmp, Add, Sub, Mul, Div, // 二目;比较不连锁 + Neg, // 一元负号 + Call, // 函数调用(仅 FUNCTION,有返回值) +}; +enum class BinOp { Eq, Ne, Lt, Le, Gt, Ge, Add, Sub, Mul, Div }; + +struct Expr { + ExprKind kind; + BinOp op; // 二目运算 + std::string name; // VarRef / Field 对象名 / Call 函数名 + std::string field; // Field 字段名 + int64_t int_value; // 字面量值 + std::unique_ptr lhs, rhs, operand; // 二目 / NOT / Neg + std::vector> args; // Call 实参 +}; + +// 语句 +enum class StmtKind { Assign, If, While, FbCall }; +struct FbArg { std::string name; std::unique_ptr value; }; +struct IfBranch { std::unique_ptr cond; std::vector body; }; + +struct Stmt { + StmtKind kind; + // Assign + std::string target; // 左值标识符 + bool target_is_field; // 左值是 fb.field(v1:只允许读取,禁止赋值) + std::string field; + std::unique_ptr value; + // If / While + std::unique_ptr cond; + std::vector body; // IF/WHILE 体 + std::vector elsifs; + std::vector else_body; + // FbCall + std::string instance; + std::vector args; +}; + +enum class PouKind { Program, Function, FunctionBlock }; +struct POU { + PouKind kind; + std::string name; + TypeRef result_type; // FUNCTION 才有 + std::vector blocks; + std::vector body; + std::string source_file; +}; +``` + +## 文法(递归下降) + +```text +file := (VAR_GLOBAL vars END_VAR | POU)* +POU := PROGRAM ident var_block* body END_PROGRAM + | FUNCTION ident ':' type var_block* body END_FUNCTION + | FUNCTION_BLOCK ident var_block* body END_FUNCTION_BLOCK +var_block := VAR vars END_VAR + | VAR_INPUT vars END_VAR | VAR_OUTPUT vars END_VAR + | VAR_GLOBAL vars END_VAR | VAR_EXTERNAL vars END_VAR +vars := (ident (',' ident)* ':' type (':=' literal)? ';')* +type := BOOL | INT | TIME | TON | TOF | CTU | ident +body := stmt* +stmt := ident ('.' ident)? ':=' expr ';' (* 赋值 *) + | ident '(' arg (',' arg)* ')' ';' (* FB 调用 *) + | IF expr THEN body (ELSIF expr THEN body)* (ELSE body)? END_IF + | WHILE expr DO body END_WHILE +arg := ident ':=' expr +expr := or_expr +or_expr := and_expr (OR and_expr)* +and_expr := not_expr (AND not_expr)* +not_expr := NOT not_expr | cmp_expr +cmp_expr := add_expr (('='|'<>'|'<'|'<='|'>'|'>=') add_expr)? +add_expr := mul_expr (('+'|'-') mul_expr)* +mul_expr := unary (('*'|'/') unary)* +unary := '-' unary | primary +primary := INT_LIT | TIME_LIT | TRUE | FALSE | ident + | ident '.' ident (* fb.field *) + | ident '(' expr (',' expr)* ')' (* 函数调用 *) + | '(' expr ')' +``` + +要点: + +- `file` 顶层允许无 POU 的 `VAR_GLOBAL` 段(**GVL 文件形态**,如 `globals.st`); + 其他变量段出现在顶层 → syntax error。解析结果: + ```cpp + struct Unit { + std::vector globals; // 顶层 VAR_GLOBAL 段 + std::vector pous; + }; + ``` +- `THEN` / `DO` 是关键字(12.5 修订补入词法表,见 [`词法.md`](词法.md)) +- 赋值左侧 `ident.ident` 是字段引用,v1 只能读(右侧),左侧字段赋值报错 +- `ident(...)` 是**语句**即 FB 调用(无返回值);在**表达式**里即函数调用(有返回值)——靠上下文区分,与 12.1 语义一致 +- 比较不连锁(`a < b < c` → syntax error) +- `AND` / `OR` 短路语义保留在 AST(`And`/`Or` 节点),12.8 编成跳转 + +## 明确拒绝清单 + +`IDENT` 文本(小写后)命中下列保留名 → `syntax error: 'X' is not supported`: + +```text +var_in_out var_temp ref class any pointer interface method +``` + +## 错误 + +稳定前缀 `syntax error`,带文件与行列: + +```text +syntax error: expected 'END_PROGRAM' (main.st, line 5, col 3) +syntax error: 'VAR_IN_OUT' is not supported (motor.st, line 2, col 5) +syntax error: chained comparison not supported (main.st, line 7, col 15) +``` + +## 完成标准 + +1. `examples/line1/*.st` 三文件 parse 成功,AST 结构符合文法 +2. `parser_test`:AST 断言(POU 种类/变量段/语句/表达式优先级)+ 负例(缺 `END_*`、`VAR_IN_OUT`、链式比较、字段赋值、坏语句)→ `syntax error` +3. 全部构建 + `ctest` 无回归 +4. 用例 10 / 14 / 19 属 12.6 / 12.7 层(非语法层),不在本阶段点亮 + +--- + +## 执行计划(单步确认) + +1. **`Parser.h`(新建 `compiler/include/compiler/Parser.h`)**:上述 AST 结构 + `bool parse_pous(source_file, content, out, err)` +2. **`Parser.cpp` 前半**:POU 外壳 + 变量段 + 类型(文法 program / POU / var_block / vars / type) +3. **`Parser.cpp` 后半**:语句 + 表达式(优先级爬升)+ 拒绝清单 +4. **`parser_test`(`tests/src/parser_test.cpp` + CMake)**:line1 三文件 AST 断言 + 负例 +5. **验证**:`cmake --build` + `ctest`(新增 `parser_syntax` 后 6/6 全绿) +6. **提交**:`Parser.h/cpp` + `parser_test` + 文档 diff --git a/Doc/初步计划.md b/Doc/初步计划.md index 4b3163f..ab003b8 100644 --- a/Doc/初步计划.md +++ b/Doc/初步计划.md @@ -367,6 +367,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom `VAR` `VAR_INPUT` `VAR_OUTPUT` `VAR_GLOBAL` `VAR_EXTERNAL` `END_VAR` `BOOL` `INT` `TIME` `IF` `ELSIF` `ELSE` `END_IF` `WHILE` `END_WHILE` +`THEN` `DO` (12.5 修订补入:`IF…THEN` / `WHILE…DO` 语法需要) `AND` `OR` `NOT` `TRUE` `FALSE` `TON` `TOF` `CTU` diff --git a/Doc/索引.md b/Doc/索引.md index 3411680..c96b7b9 100644 --- a/Doc/索引.md +++ b/Doc/索引.md @@ -9,6 +9,7 @@ doc/ isa/指令与映像.md compiler/编译管线.md compiler/词法.md + compiler/语法.md vm/扫描周期.md executor/执行器入口.md ``` @@ -19,6 +20,7 @@ doc/ | [`isa/指令与映像.md`](isa/指令与映像.md) | 指令、映像、定宽类型、饱和;**规范以此为准** | | [`compiler/编译管线.md`](compiler/编译管线.md) | 编译器边界与管线 | | [`compiler/词法.md`](compiler/词法.md) | 12.4 词法:关键字表、token、大小写、TIME 字面量 | +| [`compiler/语法.md`](compiler/语法.md) | 12.5 语法:AST 结构、文法、拒绝清单 | | [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 | | [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 | diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index eba0270..7bab162 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -9,7 +9,8 @@ project(Compiler # 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里 add_library(compiler STATIC ./src/Lexer.cpp - ./src/Project.cpp) + ./src/Project.cpp + ./src/Parser.cpp) target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) diff --git a/compiler/include/compiler/Lexer.h b/compiler/include/compiler/Lexer.h index 153f5ac..0d3b4bf 100644 --- a/compiler/include/compiler/Lexer.h +++ b/compiler/include/compiler/Lexer.h @@ -37,6 +37,8 @@ namespace compiler { END_IF, WHILE, END_WHILE, + THEN, // 12.5 修订:IF/WHILE 语法需要,补入关键字表 + DO, // 同上 AND, OR, NOT, diff --git a/compiler/include/compiler/Parser.h b/compiler/include/compiler/Parser.h new file mode 100644 index 0000000..7295b86 --- /dev/null +++ b/compiler/include/compiler/Parser.h @@ -0,0 +1,133 @@ +/** + * @file Parser.h + * @brief ST 子集递归下降语法分析(AST 定义 + 入口) + * @author + * @date 2026-08-21 + */ + +#pragma once + +#include +#include +#include +#include + +namespace compiler { + + // ---- 类型引用 ---- + + enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser }; + + struct TypeRef { + TypeKind kind = TypeKind::Bool; + std::string name; // FbUser 时为用户 FB 类型名(小写) + }; + + // ---- 变量段 ---- + + struct VarDecl { + std::string name; + TypeRef type; + bool has_init = false; // 可选初值(v1 仅字面量) + int64_t init_value = 0; // BOOL 0/1、INT 值、TIME 毫秒 + uint32_t line = 0; + uint32_t col = 0; + }; + + enum class VarSection { Local, Input, Output, Global, External }; + + struct VarBlock { + VarSection section = VarSection::Local; + std::vector vars; + }; + + // ---- 表达式 ---- + + enum class ExprKind { + LitBool, // int_value 0/1 + LitInt, // int_value + LitTime, // int_value 毫秒 + VarRef, // name + Field, // name.field + Not, // operand + And, Or, // lhs, rhs(短路语义保留到 12.8) + Cmp, Add, Sub, Mul, Div, // lhs, rhs,op + Neg, // operand(一元负号) + Call, // name + args(函数调用,有返回值) + }; + + enum class BinOp { Eq, Ne, Lt, Le, Gt, Ge, Add, Sub, Mul, Div }; + + struct Expr { + ExprKind kind = ExprKind::LitBool; + BinOp op = BinOp::Eq; // 二目运算 + std::string name; // VarRef / Field 对象 / Call 函数名 + std::string field; // Field 字段名 + int64_t int_value = 0; // 字面量值 + std::unique_ptr lhs; + std::unique_ptr rhs; + std::unique_ptr operand; // Not / Neg + std::vector> args; // Call 实参 + }; + + // ---- 语句 ---- + + enum class StmtKind { Assign, If, While, FbCall }; + + struct Stmt; // 前置声明(IfBranch / Stmt 相互引用) + + struct FbArg { + std::string name; + std::unique_ptr value; + }; + + struct IfBranch { + std::unique_ptr cond; + std::vector body; + }; + + // 前置声明:Stmt 内部自引用 + struct Stmt { + StmtKind kind = StmtKind::Assign; + // Assign + std::string target; // 左值标识符 + bool target_is_field = false; // 左值 fb.field(v1 禁止赋值,语法层拒绝) + std::string field; + std::unique_ptr value; + // If / While + std::unique_ptr cond; + std::vector body; + std::vector elsifs; + std::vector else_body; + // FbCall + std::string instance; + std::vector args; + }; + + // ---- POU ---- + + enum class PouKind { Program, Function, FunctionBlock }; + + struct POU { + PouKind kind = PouKind::Program; + std::string name; + TypeRef result_type; // FUNCTION 才有 + std::vector blocks; + std::vector body; + std::string source_file; + }; + + // 一个 .st 文件的解析结果:GVL 文件形态(无 POU)时只有 globals 非空 + struct Unit { + std::vector globals; // 顶层 VAR_GLOBAL 段(仅 GVL 文件) + std::vector pous; + }; + + // 解析一个 .st 文件的全部内容(顶层 VAR_GLOBAL 段或 POU)。 + // 失败返回 false,err 前缀 "syntax error",带文件与行列。 + bool parse_pous(const std::string& source_file, const std::string& content, + Unit* out, std::string* err); + + // 读文件后解析 + bool parse_pous_file(const std::string& path, Unit* out, std::string* err); +} diff --git a/compiler/src/Lexer.cpp b/compiler/src/Lexer.cpp index c35616e..4131e17 100644 --- a/compiler/src/Lexer.cpp +++ b/compiler/src/Lexer.cpp @@ -41,6 +41,8 @@ namespace { {"end_if", Tok::END_IF}, {"while", Tok::WHILE}, {"end_while", Tok::END_WHILE}, + {"then", Tok::THEN}, + {"do", Tok::DO}, {"and", Tok::AND}, {"or", Tok::OR}, {"not", Tok::NOT}, diff --git a/compiler/src/Parser.cpp b/compiler/src/Parser.cpp new file mode 100644 index 0000000..6778934 --- /dev/null +++ b/compiler/src/Parser.cpp @@ -0,0 +1,747 @@ +/** + * @file Parser.cpp + * @brief ST 子集递归下降语法分析 + * @author + * @date 2026-08-21 + */ + +#include "compiler/Parser.h" + +#include +#include +#include + +#include "compiler/Lexer.h" + +namespace compiler { +namespace { + + // 明确拒绝的保留名(小写命中即报错,见 Doc/compiler/语法.md) + const char* const kForbidden[] = { + "var_in_out", "var_temp", "ref", "class", "any", + "pointer", "interface", "method", + }; + + bool is_forbidden(const std::string& word) { + for (const char* f : kForbidden) { + if (word == f) { + return true; + } + } + return false; + } + + class Parser { + public: + Parser(const std::string& source_file, const std::string& content, + Unit* out, std::string* err) + : err_(err), out_(out) { + std::string lex_err; + if (!lex(source_file, content, &tokens_, &lex_err)) { + ok_ = false; + *err_ = lex_err; + return; + } + pos_ = 0; + } + + bool run() { + if (!ok_) { + return false; + } + while (!check(Tok::END)) { + if (is_var_section()) { + // 顶层变量段只允许 VAR_GLOBAL(GVL 文件形态) + if (!check(Tok::VAR_GLOBAL)) { + return fail("only VAR_GLOBAL allowed at file top level"); + } + VarBlock b; + if (!parse_var_block(&b)) { + return false; + } + out_->globals.push_back(std::move(b)); + continue; + } + POU p; + if (!parse_pou(&p)) { + return false; + } + out_->pous.push_back(std::move(p)); + } + return true; + } + + private: + // ---- 错误 ---- + + bool fail(const std::string& msg) { + if (err_) { + const Token& t = cur(); + char buf[128]; + std::snprintf(buf, sizeof buf, " (%s, line %u, col %u)", + t.source_file.c_str(), t.line, t.col); + *err_ = "syntax error: " + msg + buf; + } + return false; + } + + // ---- token 视图 ---- + + const Token& cur() const { return tokens_[pos_]; } + Tok type() const { return cur().type; } + bool check(Tok t) const { return type() == t; } + + const Token& advance() { + const Token& t = tokens_[pos_]; + if (pos_ + 1 < tokens_.size()) { + ++pos_; + } + return t; + } + + bool expect(Tok t, const char* what) { + if (!check(t)) { + return fail(std::string("expected '") + what + "'"); + } + advance(); + return true; + } + + // ---- POU ---- + + bool parse_pou(POU* p) { + if (check(Tok::PROGRAM)) { + advance(); + p->kind = PouKind::Program; + } else if (check(Tok::FUNCTION)) { + advance(); + p->kind = PouKind::Function; + } else if (check(Tok::FUNCTION_BLOCK)) { + advance(); + p->kind = PouKind::FunctionBlock; + } else { + return fail("expected POU (PROGRAM / FUNCTION / FUNCTION_BLOCK)"); + } + + if (!parse_ident(&p->name)) { + return false; + } + + if (p->kind == PouKind::Function) { + if (!expect(Tok::COLON, ":")) { + return false; + } + if (!parse_type(&p->result_type)) { + return false; + } + } + + while (is_var_section()) { + VarBlock b; + if (!parse_var_block(&b)) { + return false; + } + p->blocks.push_back(std::move(b)); + } + + while (!check_end_for(p->kind)) { + Stmt s; + if (!parse_stmt(&s)) { + return false; + } + p->body.push_back(std::move(s)); + } + + const char* end_kw = nullptr; + switch (p->kind) { + case PouKind::Program: end_kw = "END_PROGRAM"; break; + case PouKind::Function: end_kw = "END_FUNCTION"; break; + case PouKind::FunctionBlock: end_kw = "END_FUNCTION_BLOCK"; break; + } + return expect(end_tok_for(p->kind), end_kw); + } + + bool is_var_section() const { + switch (type()) { + case Tok::VAR: + case Tok::VAR_INPUT: + case Tok::VAR_OUTPUT: + case Tok::VAR_GLOBAL: + case Tok::VAR_EXTERNAL: + return true; + default: + return false; + } + } + + Tok end_tok_for(PouKind k) const { + switch (k) { + case PouKind::Program: return Tok::END_PROGRAM; + case PouKind::Function: return Tok::END_FUNCTION; + case PouKind::FunctionBlock: return Tok::END_FUNCTION_BLOCK; + } + return Tok::END; + } + + bool check_end_for(PouKind k) const { return check(end_tok_for(k)); } + + // ---- 变量段 ---- + + bool parse_var_block(VarBlock* b) { + switch (type()) { + case Tok::VAR: b->section = VarSection::Local; break; + case Tok::VAR_INPUT: b->section = VarSection::Input; break; + case Tok::VAR_OUTPUT: b->section = VarSection::Output; break; + case Tok::VAR_GLOBAL: b->section = VarSection::Global; break; + case Tok::VAR_EXTERNAL: b->section = VarSection::External; break; + default: + return fail("expected variable section"); + } + advance(); + + while (!check(Tok::END_VAR)) { + std::vector line; + if (!parse_vars_line(&line)) { + return false; + } + for (VarDecl& d : line) { + b->vars.push_back(std::move(d)); + } + } + advance(); // END_VAR + return true; + } + + // ident (',' ident)* ':' type (':=' literal)? ';' —— 展开成多个 VarDecl + bool parse_vars_line(std::vector* out) { + std::vector names; + while (true) { + VarDecl d; + d.line = cur().line; + d.col = cur().col; + if (!parse_ident(&d.name)) { + return false; + } + names.push_back(std::move(d)); + if (!check(Tok::COMMA)) { + break; + } + advance(); + } + if (!expect(Tok::COLON, ":")) { + return false; + } + TypeRef t; + if (!parse_type(&t)) { + return false; + } + bool has_init = false; + int64_t init = 0; + if (check(Tok::ASSIGN)) { + advance(); + Expr e; + if (!parse_literal_expr(&e)) { + return false; + } + has_init = true; + init = e.int_value; + } + if (!expect(Tok::SEMI, ";")) { + return false; + } + for (VarDecl& d : names) { + d.type = t; + d.has_init = has_init; + d.init_value = init; + out->push_back(std::move(d)); + } + return true; + } + + // 初值只允许字面量(BOOL / INT / TIME / TRUE / FALSE) + bool parse_literal_expr(Expr* e) { + switch (type()) { + case Tok::TRUE: + e->kind = ExprKind::LitBool; + e->int_value = 1; + advance(); + return true; + case Tok::FALSE: + e->kind = ExprKind::LitBool; + e->int_value = 0; + advance(); + return true; + case Tok::INT_LIT: + e->kind = ExprKind::LitInt; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::TIME_LIT: + e->kind = ExprKind::LitTime; + e->int_value = cur().int_value; + advance(); + return true; + default: + return fail("expected literal initializer"); + } + } + + // 类型:BOOL | INT | TIME | TON | TOF | CTU | ident(用户 FB 类型) + bool parse_type(TypeRef* t) { + switch (type()) { + case Tok::BOOL: + t->kind = TypeKind::Bool; + advance(); + return true; + case Tok::INT: + t->kind = TypeKind::Int; + advance(); + return true; + case Tok::TIME: + t->kind = TypeKind::Time; + advance(); + return true; + case Tok::TON: + t->kind = TypeKind::FbBuiltin; + t->name = "ton"; + advance(); + return true; + case Tok::TOF: + t->kind = TypeKind::FbBuiltin; + t->name = "tof"; + advance(); + return true; + case Tok::CTU: + t->kind = TypeKind::FbBuiltin; + t->name = "ctu"; + advance(); + return true; + case Tok::IDENT: { + const std::string word = cur().text; + if (is_forbidden(word)) { + return fail("'" + word + "' is not supported"); + } + t->kind = TypeKind::FbUser; + t->name = word; + advance(); + return true; + } + default: + return fail("expected type name"); + } + } + + // ---- 语句 ---- + + bool parse_stmt(Stmt* s) { + if (check(Tok::IF)) { + return parse_if(s); + } + if (check(Tok::WHILE)) { + return parse_while(s); + } + if (!check(Tok::IDENT)) { + return fail("expected statement"); + } + + // ident '(' → FB 调用;ident ('.' ident)? ':=' → 赋值 + const size_t save = pos_; + advance(); + const bool is_call = check(Tok::LPAREN); + pos_ = save; + return is_call ? parse_fb_call(s) : parse_assign(s); + } + + bool parse_assign(Stmt* s) { + s->kind = StmtKind::Assign; + if (!parse_ident(&s->target)) { + return false; + } + if (check(Tok::DOT)) { + return fail("assignment to FB field is not supported"); + } + if (!expect(Tok::ASSIGN, ":=")) { + return false; + } + s->value = std::make_unique(); + if (!parse_expr(s->value.get())) { + return false; + } + return expect(Tok::SEMI, ";"); + } + + bool parse_fb_call(Stmt* s) { + s->kind = StmtKind::FbCall; + if (!parse_ident(&s->instance)) { + return false; + } + if (!expect(Tok::LPAREN, "(")) { + return false; + } + if (!check(Tok::RPAREN)) { + while (true) { + FbArg a; + if (!parse_ident(&a.name)) { + return false; + } + if (!expect(Tok::ASSIGN, ":=")) { + return false; + } + a.value = std::make_unique(); + if (!parse_expr(a.value.get())) { + return false; + } + s->args.push_back(std::move(a)); + if (!check(Tok::COMMA)) { + break; + } + advance(); + } + } + if (!expect(Tok::RPAREN, ")")) { + return false; + } + return expect(Tok::SEMI, ";"); + } + + bool parse_if(Stmt* s) { + s->kind = StmtKind::If; + advance(); // IF + s->cond = std::make_unique(); + if (!parse_expr(s->cond.get())) { + return false; + } + if (!expect(Tok::THEN, "THEN")) { + return false; + } + if (!parse_body(&s->body)) { + return false; + } + while (check(Tok::ELSIF)) { + advance(); + IfBranch b; + b.cond = std::make_unique(); + if (!parse_expr(b.cond.get())) { + return false; + } + if (!expect(Tok::THEN, "THEN")) { + return false; + } + if (!parse_body(&b.body)) { + return false; + } + s->elsifs.push_back(std::move(b)); + } + if (check(Tok::ELSE)) { + advance(); + if (!parse_body(&s->else_body)) { + return false; + } + } + return expect(Tok::END_IF, "END_IF"); + } + + bool parse_while(Stmt* s) { + s->kind = StmtKind::While; + advance(); // WHILE + s->cond = std::make_unique(); + if (!parse_expr(s->cond.get())) { + return false; + } + if (!expect(Tok::DO, "DO")) { + return false; + } + if (!parse_body(&s->body)) { + return false; + } + return expect(Tok::END_WHILE, "END_WHILE"); + } + + bool parse_body(std::vector* out) { + while (!check(Tok::ELSIF) && !check(Tok::ELSE) && !check(Tok::END_IF) && + !check(Tok::END_WHILE) && !check(Tok::END)) { + Stmt s; + if (!parse_stmt(&s)) { + return false; + } + out->push_back(std::move(s)); + } + return true; + } + + // ---- 表达式(优先级爬升)---- + + bool parse_expr(Expr* e) { return parse_or(e); } + + bool parse_or(Expr* e) { + std::unique_ptr lhs = std::make_unique(); + if (!parse_and(lhs.get())) { + return false; + } + while (check(Tok::OR)) { + advance(); + auto node = std::make_unique(); + node->kind = ExprKind::Or; + node->lhs = std::move(lhs); + node->rhs = std::make_unique(); + if (!parse_and(node->rhs.get())) { + return false; + } + lhs = std::move(node); + } + *e = std::move(*lhs); + return true; + } + + bool parse_and(Expr* e) { + std::unique_ptr lhs = std::make_unique(); + if (!parse_not(lhs.get())) { + return false; + } + while (check(Tok::AND)) { + advance(); + auto node = std::make_unique(); + node->kind = ExprKind::And; + node->lhs = std::move(lhs); + node->rhs = std::make_unique(); + if (!parse_not(node->rhs.get())) { + return false; + } + lhs = std::move(node); + } + *e = std::move(*lhs); + return true; + } + + bool parse_not(Expr* e) { + if (check(Tok::NOT)) { + advance(); + e->kind = ExprKind::Not; + e->operand = std::make_unique(); + return parse_not(e->operand.get()); + } + return parse_cmp(e); + } + + bool parse_cmp(Expr* e) { + std::unique_ptr lhs = std::make_unique(); + if (!parse_add(lhs.get())) { + return false; + } + BinOp op; + switch (type()) { + case Tok::EQ: op = BinOp::Eq; break; + case Tok::NE: op = BinOp::Ne; break; + case Tok::LT: op = BinOp::Lt; break; + case Tok::LE: op = BinOp::Le; break; + case Tok::GT: op = BinOp::Gt; break; + case Tok::GE: op = BinOp::Ge; break; + default: + *e = std::move(*lhs); + return true; + } + advance(); + auto rhs = std::make_unique(); + if (!parse_add(rhs.get())) { + return false; + } + if (is_cmp_op(type())) { + return fail("chained comparison not supported"); + } + e->kind = ExprKind::Cmp; + e->op = op; + e->lhs = std::move(lhs); + e->rhs = std::move(rhs); + return true; + } + + bool is_cmp_op(Tok t) const { + return t == Tok::EQ || t == Tok::NE || t == Tok::LT || t == Tok::LE || + t == Tok::GT || t == Tok::GE; + } + + bool parse_add(Expr* e) { + std::unique_ptr lhs = std::make_unique(); + if (!parse_mul(lhs.get())) { + return false; + } + while (true) { + ExprKind k; + if (check(Tok::PLUS)) { + k = ExprKind::Add; + } else if (check(Tok::MINUS)) { + k = ExprKind::Sub; + } else { + break; + } + advance(); + auto node = std::make_unique(); + node->kind = k; + node->lhs = std::move(lhs); + node->rhs = std::make_unique(); + if (!parse_mul(node->rhs.get())) { + return false; + } + lhs = std::move(node); + } + *e = std::move(*lhs); + return true; + } + + bool parse_mul(Expr* e) { + std::unique_ptr lhs = std::make_unique(); + if (!parse_unary(lhs.get())) { + return false; + } + while (true) { + ExprKind k; + if (check(Tok::STAR)) { + k = ExprKind::Mul; + } else if (check(Tok::SLASH)) { + k = ExprKind::Div; + } else { + break; + } + advance(); + auto node = std::make_unique(); + node->kind = k; + node->lhs = std::move(lhs); + node->rhs = std::make_unique(); + if (!parse_unary(node->rhs.get())) { + return false; + } + lhs = std::move(node); + } + *e = std::move(*lhs); + return true; + } + + bool parse_unary(Expr* e) { + if (check(Tok::MINUS)) { + advance(); + e->kind = ExprKind::Neg; + e->operand = std::make_unique(); + return parse_unary(e->operand.get()); + } + return parse_primary(e); + } + + bool parse_primary(Expr* e) { + switch (type()) { + case Tok::INT_LIT: + e->kind = ExprKind::LitInt; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::TIME_LIT: + e->kind = ExprKind::LitTime; + e->int_value = cur().int_value; + advance(); + return true; + case Tok::TRUE: + e->kind = ExprKind::LitBool; + e->int_value = 1; + advance(); + return true; + case Tok::FALSE: + e->kind = ExprKind::LitBool; + e->int_value = 0; + advance(); + return true; + case Tok::LPAREN: { + advance(); + if (!parse_expr(e)) { + return false; + } + return expect(Tok::RPAREN, ")"); + } + case Tok::IDENT: { + const std::string word = cur().text; + if (is_forbidden(word)) { + return fail("'" + word + "' is not supported"); + } + advance(); + if (check(Tok::DOT)) { + // fb.field + advance(); + e->kind = ExprKind::Field; + e->name = word; + if (!parse_ident(&e->field)) { + return false; + } + return true; + } + if (check(Tok::LPAREN)) { + // 函数调用(FUNCTION,有返回值) + e->kind = ExprKind::Call; + e->name = word; + advance(); + if (!check(Tok::RPAREN)) { + while (true) { + auto arg = std::make_unique(); + if (!parse_expr(arg.get())) { + return false; + } + e->args.push_back(std::move(arg)); + if (!check(Tok::COMMA)) { + break; + } + advance(); + } + } + return expect(Tok::RPAREN, ")"); + } + e->kind = ExprKind::VarRef; + e->name = word; + return true; + } + default: + return fail("expected expression"); + } + } + + bool parse_ident(std::string* out) { + if (!check(Tok::IDENT)) { + return fail("expected identifier"); + } + if (is_forbidden(cur().text)) { + return fail("'" + cur().text + "' is not supported"); + } + *out = cur().text; + advance(); + return true; + } + + // ---- 成员 ---- + + std::string* err_; + std::vector tokens_; + Unit* out_; + size_t pos_ = 0; + bool ok_ = true; + }; + +} // namespace + +bool parse_pous(const std::string& source_file, const std::string& content, + Unit* out, std::string* err) { + out->globals.clear(); + out->pous.clear(); + Parser p(source_file, content, out, err); + return p.run(); +} + +bool parse_pous_file(const std::string& path, Unit* out, std::string* err) { + std::ifstream in(path, std::ios::binary); + if (!in) { + if (err) { + *err = "syntax error: cannot open file (" + path + ")"; + } + return false; + } + std::string content((std::istreambuf_iterator(in)), + std::istreambuf_iterator()); + return parse_pous(path, content, out, err); +} + +} // namespace compiler diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index abc0ccb..ea1428a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,3 +34,14 @@ target_compile_definitions(lexer_test PRIVATE add_test(NAME lexer_tokens COMMAND lexer_test) + +# 语法测试:line1 AST 结构 + 负例(REPO_ROOT 注入源目录绝对路径) +add_executable(parser_test + ./src/parser_test.cpp) + +target_link_libraries(parser_test PRIVATE compiler) +target_compile_definitions(parser_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME parser_syntax + COMMAND parser_test) diff --git a/tests/src/parser_test.cpp b/tests/src/parser_test.cpp new file mode 100644 index 0000000..9f8954f --- /dev/null +++ b/tests/src/parser_test.cpp @@ -0,0 +1,301 @@ +/** + * @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::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; +}