实现 12.4 词法:25 关键字、小写折叠、TIME 字面量、lex error。

- Lexer.h/cpp:关键字表(大小写不敏感折小写)、(* *) 注释不嵌套、整数/T# 段式字面量、最长匹配符号
- token 带 source_file + 行列(起始位置,修了消费后行列的偏差)
- lexer_test:75 断言(line1 三文件 token 流 21/34/27、大小写、符号、注释、行列、5 类负例)
- Doc/compiler/词法.md:设计文档 + 执行计划
This commit is contained in:
2026-08-21 10:51:45 +08:00
parent 9a07f938f9
commit d4e2c1ae7c
6 changed files with 786 additions and 2 deletions
+127
View File
@@ -0,0 +1,127 @@
# 词法(12.4
compiler 模块的词法分析。输入:单个 `.st` 源文件内容;输出:稳定 token 流(带 `source_file` + 行列)。语法与字段表见 [`初步计划.md`](../初步计划.md) 12.1、12.4。
## 做 / 不做
**做**
- `(* *)` 注释、标识符、关键字、整数、`TIME` 字面量、`:=`、比较符、括号
- 关键字与标识符统一折成**小写**内部形(与 IEC 一致,大小写不敏感)
- 每个 token 带 `source_file` + 行列,供以后报错
- 非法字符 → `lex error` 硬错误(带行列)
**不做(第一版)**
- 字符串字面量、`REAL` 字面量、十六进制/科学计数
- 注释嵌套(`(*` 到第一个 `*)` 结束,未闭合报错)
- 浮点数、`_` 数字分隔符
## 关键字表(12.1 冻结,只认这些)
```text
PROGRAM FUNCTION FUNCTION_BLOCK END_PROGRAM END_FUNCTION END_FUNCTION_BLOCK
VAR VAR_INPUT VAR_OUTPUT VAR_GLOBAL VAR_EXTERNAL END_VAR
BOOL INT TIME
IF ELSIF ELSE END_IF WHILE END_WHILE
AND OR NOT
TRUE FALSE
TON TOF CTU
```
其余单词一律当标识符;`VAR_IN_OUT``REF``CLASS` 等由语法层(12.5)出明确错误。
## Token
```cpp
enum class Tok {
// 关键字(25 个,逐一对应上表)
...
// 字面量
IDENT, // text = 小写名
INT_LIT, // int_value
TIME_LIT, // int_value = 毫秒
// 符号
ASSIGN, // :=
EQ, NE, LT, LE, GT, GE, // = <> < <= > >=
PLUS, MINUS, STAR, SLASH, // + - * /
LPAREN, RPAREN, COMMA, SEMI, // ( ) , ;
COLON, DOT, // : .
END, // 文件结束
};
struct Token {
Tok type;
std::string text; // 标识符/关键字的小写文本;字面量原文
int64_t int_value; // INT_LIT / TIME_LIT(毫秒)
std::string source_file;
uint32_t line; // 从 1 起
uint32_t col; // 从 1 起
};
```
## 规则
### 大小写
- 关键字匹配**大小写不敏感**`program` / `PROGRAM` / `Program` 同义)
- 标识符统一折小写存入 `text`(内部形);原始大小写不保留(v1 无字符串字面量,报错靠行列定位)
### 字面量
- 整数:`[0-9]+``int64_t` 存储;超 `INT64_MAX``lex error`
- `TIME``T#`(不敏感)+ 一个或多个 `<整数><单位>` 段,单位不敏感:`ms s m h d`,累加为毫秒:
- `T#10ms` → 10
- `T#1s250ms` → 1250
- `T#2h` → 7200000
- 缺单位 / 未知单位 / 空段 → `lex error`
- 负数不是字面量:`-5``MINUS` + 整数,由语法层处理
### 符号
- `:=` 优先于 `:``<>` 优先于 `<`/`>``<=`/`>=` 单独匹配
### 注释
- `(* ... *)`**不嵌套**;未闭合 → `lex error`
- 注释内不产 token
## 错误
稳定前缀 `lex error`,带文件与行列:
```text
lex error: illegal character '@' (motor.st, line 3, col 7)
lex error: unterminated comment (main.st, line 1, col 1)
lex error: bad time literal 'T#3q' (main.st, line 4, col 10)
```
## 完成标准
1. 第 11 节三个文件(`examples/line1/*.st`lex 成稳定 token 流;`lexer_test` 断言数量、类型、小写化、行列、`END` 收尾
2. 非法字符 / 未闭合注释 / 坏 TIME 字面量 → `lex error` 负例过
3. 全部构建 + `ctest` 无回归
---
## 执行计划(单步确认)
1. **`Lexer.h`(新建 `compiler/include/compiler/Lexer.h`**
- `Tok` 枚举:25 关键字 + `IDENT`/`INT_LIT`/`TIME_LIT` + 符号(`:= = <> < <= > >= + - * / ( ) , ; : .`+ `END`
- `Token { type, text, int_value, source_file, line, col }`(行列从 1 起)
- `bool lex_file(path, out, err)` / `bool lex(source_file, content, out, err)`
2. **`Lexer.cpp`(重写占位)**
- 25 关键字小写映射表,大小写不敏感,标识符统一折小写
- `(* *)` 注释不嵌套、未闭合报错;非法字符报错
- 整数 `[0-9]+`(超 `INT64_MAX` 报错);`T#<int><单位>` 段式解析累加毫秒(`ms s m h d`
- `:=` / `<>` / `<=` / `>=` 最长匹配优先
- 错误前缀 `lex error`,带 file/line/col
3. **`lexer_test``tests/src/lexer_test.cpp` + CMake,链 `compiler` 库)**
- line1 三文件:token 数量 / 类型序列 / 小写化 / 行列 / `END` 收尾断言
- 负例:`@` 非法字符、未闭合注释、`T#3q` 坏字面量、超大整数 → `lex error` 类别
4. **验证**`cmake --build` + `ctest`(新增 `lexer_test` 后 5/5 全绿)
5. **提交**`Lexer.h/cpp` + `lexer_test` + 文档
+2
View File
@@ -8,6 +8,7 @@ doc/
初步计划.md
isa/指令与映像.md
compiler/编译管线.md
compiler/词法.md
vm/扫描周期.md
executor/执行器入口.md
```
@@ -17,6 +18,7 @@ doc/
| [`初步计划.md`](初步计划.md) | 全工程定案:子集 ST、toml、执行模型、第 12 节阶段 |
| [`isa/指令与映像.md`](isa/指令与映像.md) | 指令、映像、定宽类型、饱和;**规范以此为准** |
| [`compiler/编译管线.md`](compiler/编译管线.md) | 编译器边界与管线 |
| [`compiler/词法.md`](compiler/词法.md) | 12.4 词法:关键字表、token、大小写、TIME 字面量 |
| [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 |
| [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 |