# 词法(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 ``` > 12.5 修订:`THEN`、`DO` 补入关键字表(`IF ... THEN` / `WHILE ... DO` 语法需要), > 与 12.1 原表合并,其余不变。 其余单词一律当标识符;`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#<单位>` 段式解析累加毫秒(`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` + 文档