Merge branch 'RecursivDescentParsing' into dev

This commit is contained in:
2026-08-21 11:07:51 +08:00
11 changed files with 1387 additions and 1 deletions
+3
View File
@@ -29,6 +29,9 @@ TRUE FALSE
TON TOF CTU TON TOF CTU
``` ```
> 12.5 修订:`THEN`、`DO` 补入关键字表(`IF ... THEN` / `WHILE ... DO` 语法需要),
> 与 12.1 原表合并,其余不变。
其余单词一律当标识符;`VAR_IN_OUT``REF``CLASS` 等由语法层(12.5)出明确错误。 其余单词一律当标识符;`VAR_IN_OUT``REF``CLASS` 等由语法层(12.5)出明确错误。
## Token ## Token
+183
View File
@@ -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<VarDecl> 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<Expr> lhs, rhs, operand; // 二目 / NOT / Neg
std::vector<std::unique_ptr<Expr>> args; // Call 实参
};
// 语句
enum class StmtKind { Assign, If, While, FbCall };
struct FbArg { std::string name; std::unique_ptr<Expr> value; };
struct IfBranch { std::unique_ptr<Expr> cond; std::vector<Stmt> body; };
struct Stmt {
StmtKind kind;
// Assign
std::string target; // 左值标识符
bool target_is_field; // 左值是 fb.field(v1:只允许读取,禁止赋值)
std::string field;
std::unique_ptr<Expr> value;
// If / While
std::unique_ptr<Expr> cond;
std::vector<Stmt> body; // IF/WHILE 体
std::vector<IfBranch> elsifs;
std::vector<Stmt> else_body;
// FbCall
std::string instance;
std::vector<FbArg> args;
};
enum class PouKind { Program, Function, FunctionBlock };
struct POU {
PouKind kind;
std::string name;
TypeRef result_type; // FUNCTION 才有
std::vector<VarBlock> blocks;
std::vector<Stmt> 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<VarBlock> globals; // 顶层 VAR_GLOBAL 段
std::vector<POU> 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` + 文档
+1
View File
@@ -367,6 +367,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
`VAR` `VAR_INPUT` `VAR_OUTPUT` `VAR_GLOBAL` `VAR_EXTERNAL` `END_VAR` `VAR` `VAR_INPUT` `VAR_OUTPUT` `VAR_GLOBAL` `VAR_EXTERNAL` `END_VAR`
`BOOL` `INT` `TIME` `BOOL` `INT` `TIME`
`IF` `ELSIF` `ELSE` `END_IF` `WHILE` `END_WHILE` `IF` `ELSIF` `ELSE` `END_IF` `WHILE` `END_WHILE`
`THEN` `DO` 12.5 修订补入:`IF…THEN` / `WHILE…DO` 语法需要)
`AND` `OR` `NOT` `AND` `OR` `NOT`
`TRUE` `FALSE` `TRUE` `FALSE`
`TON` `TOF` `CTU` `TON` `TOF` `CTU`
+2
View File
@@ -9,6 +9,7 @@ doc/
isa/指令与映像.md isa/指令与映像.md
compiler/编译管线.md compiler/编译管线.md
compiler/词法.md compiler/词法.md
compiler/语法.md
vm/扫描周期.md vm/扫描周期.md
executor/执行器入口.md executor/执行器入口.md
``` ```
@@ -19,6 +20,7 @@ doc/
| [`isa/指令与映像.md`](isa/指令与映像.md) | 指令、映像、定宽类型、饱和;**规范以此为准** | | [`isa/指令与映像.md`](isa/指令与映像.md) | 指令、映像、定宽类型、饱和;**规范以此为准** |
| [`compiler/编译管线.md`](compiler/编译管线.md) | 编译器边界与管线 | | [`compiler/编译管线.md`](compiler/编译管线.md) | 编译器边界与管线 |
| [`compiler/词法.md`](compiler/词法.md) | 12.4 词法:关键字表、token、大小写、TIME 字面量 | | [`compiler/词法.md`](compiler/词法.md) | 12.4 词法:关键字表、token、大小写、TIME 字面量 |
| [`compiler/语法.md`](compiler/语法.md) | 12.5 语法:AST 结构、文法、拒绝清单 |
| [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 | | [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 |
| [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 | | [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 |
+2 -1
View File
@@ -9,7 +9,8 @@ project(Compiler
# 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里 # 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里
add_library(compiler STATIC add_library(compiler STATIC
./src/Lexer.cpp ./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 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include)
+2
View File
@@ -37,6 +37,8 @@ namespace compiler {
END_IF, END_IF,
WHILE, WHILE,
END_WHILE, END_WHILE,
THEN, // 12.5 修订:IF/WHILE 语法需要,补入关键字表
DO, // 同上
AND, AND,
OR, OR,
NOT, NOT,
+133
View File
@@ -0,0 +1,133 @@
/**
* @file Parser.h
* @brief ST 子集递归下降语法分析(AST 定义 + 入口)
* @author
* @date 2026-08-21
*/
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
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<VarDecl> 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, rhsop
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<Expr> lhs;
std::unique_ptr<Expr> rhs;
std::unique_ptr<Expr> operand; // Not / Neg
std::vector<std::unique_ptr<Expr>> args; // Call 实参
};
// ---- 语句 ----
enum class StmtKind { Assign, If, While, FbCall };
struct Stmt; // 前置声明(IfBranch / Stmt 相互引用)
struct FbArg {
std::string name;
std::unique_ptr<Expr> value;
};
struct IfBranch {
std::unique_ptr<Expr> cond;
std::vector<Stmt> 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<Expr> value;
// If / While
std::unique_ptr<Expr> cond;
std::vector<Stmt> body;
std::vector<IfBranch> elsifs;
std::vector<Stmt> else_body;
// FbCall
std::string instance;
std::vector<FbArg> args;
};
// ---- POU ----
enum class PouKind { Program, Function, FunctionBlock };
struct POU {
PouKind kind = PouKind::Program;
std::string name;
TypeRef result_type; // FUNCTION 才有
std::vector<VarBlock> blocks;
std::vector<Stmt> body;
std::string source_file;
};
// 一个 .st 文件的解析结果:GVL 文件形态(无 POU)时只有 globals 非空
struct Unit {
std::vector<VarBlock> globals; // 顶层 VAR_GLOBAL 段(仅 GVL 文件)
std::vector<POU> pous;
};
// 解析一个 .st 文件的全部内容(顶层 VAR_GLOBAL 段或 POU)。
// 失败返回 falseerr 前缀 "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);
}
+2
View File
@@ -41,6 +41,8 @@ namespace {
{"end_if", Tok::END_IF}, {"end_if", Tok::END_IF},
{"while", Tok::WHILE}, {"while", Tok::WHILE},
{"end_while", Tok::END_WHILE}, {"end_while", Tok::END_WHILE},
{"then", Tok::THEN},
{"do", Tok::DO},
{"and", Tok::AND}, {"and", Tok::AND},
{"or", Tok::OR}, {"or", Tok::OR},
{"not", Tok::NOT}, {"not", Tok::NOT},
+747
View File
@@ -0,0 +1,747 @@
/**
* @file Parser.cpp
* @brief ST 子集递归下降语法分析
* @author
* @date 2026-08-21
*/
#include "compiler/Parser.h"
#include <cstdio>
#include <fstream>
#include <string>
#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<VarDecl> 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<VarDecl>* out) {
std::vector<VarDecl> 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<Expr>();
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<Expr>();
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<Expr>();
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<Expr>();
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<Expr>();
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<Stmt>* 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<Expr> lhs = std::make_unique<Expr>();
if (!parse_and(lhs.get())) {
return false;
}
while (check(Tok::OR)) {
advance();
auto node = std::make_unique<Expr>();
node->kind = ExprKind::Or;
node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>();
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<Expr> lhs = std::make_unique<Expr>();
if (!parse_not(lhs.get())) {
return false;
}
while (check(Tok::AND)) {
advance();
auto node = std::make_unique<Expr>();
node->kind = ExprKind::And;
node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>();
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<Expr>();
return parse_not(e->operand.get());
}
return parse_cmp(e);
}
bool parse_cmp(Expr* e) {
std::unique_ptr<Expr> lhs = std::make_unique<Expr>();
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<Expr>();
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<Expr> lhs = std::make_unique<Expr>();
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<Expr>();
node->kind = k;
node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>();
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<Expr> lhs = std::make_unique<Expr>();
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<Expr>();
node->kind = k;
node->lhs = std::move(lhs);
node->rhs = std::make_unique<Expr>();
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<Expr>();
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<Expr>();
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<Token> 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<char>(in)),
std::istreambuf_iterator<char>());
return parse_pous(path, content, out, err);
}
} // namespace compiler
+11
View File
@@ -34,3 +34,14 @@ target_compile_definitions(lexer_test PRIVATE
add_test(NAME lexer_tokens add_test(NAME lexer_tokens
COMMAND lexer_test) 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)
+301
View File
@@ -0,0 +1,301 @@
/**
* @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;
}