实现 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
+11
View File
@@ -23,3 +23,14 @@ target_compile_definitions(compiler_test PRIVATE
add_test(NAME compiler_toml
COMMAND compiler_test)
# 词法测试:token 流 + 负例(REPO_ROOT 注入源目录绝对路径)
add_executable(lexer_test
./src/lexer_test.cpp)
target_link_libraries(lexer_test PRIVATE compiler)
target_compile_definitions(lexer_test PRIVATE
REPO_ROOT="${CMAKE_SOURCE_DIR}")
add_test(NAME lexer_tokens
COMMAND lexer_test)
+210
View File
@@ -0,0 +1,210 @@
/**
* @file lexer_test.cpp
* @brief 词法测试:line1 token 流 + 负例
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <string>
#include <vector>
#include "compiler/Lexer.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. 基本 token 分类与大小写折叠 ----
static bool test_basic() {
using namespace compiler;
std::vector<Token> ts;
std::string err;
CHECK(lex("t.st", "PROGRAM MAIN\nVAR\n a, b : BOOL;\nEND_VAR\nEND_PROGRAM", &ts, &err));
CHECK(ts.size() == 12);
CHECK(ts[0].type == Tok::PROGRAM);
CHECK(ts[1].type == Tok::IDENT && ts[1].text == "main");
CHECK(ts[2].type == Tok::VAR);
CHECK(ts[3].type == Tok::IDENT && ts[3].text == "a");
CHECK(ts[4].type == Tok::COMMA);
CHECK(ts[5].type == Tok::IDENT && ts[5].text == "b");
CHECK(ts[6].type == Tok::COLON);
CHECK(ts[7].type == Tok::BOOL);
CHECK(ts[8].type == Tok::SEMI);
CHECK(ts[9].type == Tok::END_VAR);
CHECK(ts[10].type == Tok::END_PROGRAM);
CHECK(ts[11].type == Tok::END);
CHECK(ts[11].line == 5);
return true;
}
// ---- 2. 大小写不敏感 ----
static bool test_case() {
using namespace compiler;
std::vector<Token> ts;
std::string err;
CHECK(lex("t.st", "PrOgRaM mAiN eNd_PrOgRaM", &ts, &err));
CHECK(ts[0].type == Tok::PROGRAM);
CHECK(ts[1].type == Tok::IDENT && ts[1].text == "main");
CHECK(ts[2].type == Tok::END_PROGRAM);
CHECK(lex("t.st", "T#10MS t#1s250Ms", &ts, &err));
CHECK(ts[0].type == Tok::TIME_LIT && ts[0].int_value == 10);
CHECK(ts[1].type == Tok::TIME_LIT && ts[1].int_value == 1250);
return true;
}
// ---- 3. 符号 ----
static bool test_symbols() {
using namespace compiler;
std::vector<Token> ts;
std::string err;
CHECK(lex("t.st", "a := b <> c <= d >= e < f > g + h - i * j / k ( ) , ; : .",
&ts, &err));
CHECK(ts[0].type == Tok::IDENT);
CHECK(ts[1].type == Tok::ASSIGN);
CHECK(ts[3].type == Tok::NE);
CHECK(ts[5].type == Tok::LE);
CHECK(ts[7].type == Tok::GE);
CHECK(ts[9].type == Tok::LT);
CHECK(ts[11].type == Tok::GT);
CHECK(ts[13].type == Tok::PLUS);
CHECK(ts[15].type == Tok::MINUS);
CHECK(ts[17].type == Tok::STAR);
CHECK(ts[19].type == Tok::SLASH);
CHECK(ts[21].type == Tok::LPAREN);
CHECK(ts[22].type == Tok::RPAREN);
CHECK(ts[23].type == Tok::COMMA);
CHECK(ts[24].type == Tok::SEMI);
CHECK(ts[25].type == Tok::COLON);
CHECK(ts[26].type == Tok::DOT);
return true;
}
// ---- 4. 注释 ----
static bool test_comments() {
using namespace compiler;
std::vector<Token> ts;
std::string err;
CHECK(lex("t.st", "(* 整行注释 *)\nPROGRAM (* 行尾注释 *) MAIN", &ts, &err));
CHECK(ts.size() == 3);
CHECK(ts[0].type == Tok::PROGRAM);
CHECK(ts[1].type == Tok::IDENT && ts[1].text == "main");
CHECK(ts[2].type == Tok::END);
return true;
}
// ---- 5. 行列号 ----
static bool test_pos() {
using namespace compiler;
std::vector<Token> ts;
std::string err;
CHECK(lex("f.st", "a\n b : INT;", &ts, &err));
CHECK(ts[0].type == Tok::IDENT && ts[0].line == 1 && ts[0].col == 1);
CHECK(ts[1].type == Tok::IDENT && ts[1].line == 2 && ts[1].col == 3);
CHECK(ts[2].type == Tok::COLON && ts[2].line == 2 && ts[2].col == 5);
CHECK(ts[3].type == Tok::INT && ts[3].line == 2 && ts[3].col == 7);
CHECK(ts[0].source_file == "f.st");
return true;
}
// ---- 6. 负例 ----
static bool expect_lex_err(const std::string& src, const char* prefix) {
std::vector<compiler::Token> ts;
std::string err;
if (compiler::lex("t.st", src, &ts, &err)) {
std::printf("FAIL: lexed ok: %s\n", src.c_str());
return false;
}
if (err.find(prefix) != 0) {
std::printf("FAIL: want '%s', got '%s'\n", prefix, err.c_str());
return false;
}
++g_checks;
return true;
}
static bool test_negative() {
if (!expect_lex_err("a @ b", "lex error")) return false;
if (!expect_lex_err("(* unclosed", "lex error")) return false;
if (!expect_lex_err("T#3q", "lex error")) return false;
if (!expect_lex_err("T#", "lex error")) return false;
if (!expect_lex_err("99999999999999999999", "lex error")) return false;
return true;
}
// ---- 7. line1 三个文件 ----
static bool test_line1() {
using namespace compiler;
const char* files[] = {"globals.st", "motor.st", "main.st"};
const int expect_count[] = {21, 34, 27};
for (int i = 0; i < 3; ++i) {
std::vector<Token> ts;
std::string err;
const std::string path =
std::string(REPO_ROOT) + "/examples/line1/" + files[i];
CHECK(lex_file(path, &ts, &err));
CHECK(ts.size() == static_cast<size_t>(expect_count[i]));
CHECK(ts.back().type == Tok::END);
}
// globals.st 局部抽查
std::vector<Token> ts;
std::string err;
CHECK(lex_file(std::string(REPO_ROOT) + "/examples/line1/globals.st", &ts, &err));
CHECK(ts[0].type == Tok::VAR_GLOBAL);
CHECK(ts[1].type == Tok::IDENT && ts[1].text == "emergencystop");
CHECK(ts[3].type == Tok::BOOL);
CHECK(ts[4].type == Tok::ASSIGN);
CHECK(ts[5].type == Tok::FALSE);
CHECK(ts[ts.size() - 2].type == Tok::END_VAR);
// main.st 抽查:FB 调用与字段访问
CHECK(lex_file(std::string(REPO_ROOT) + "/examples/line1/main.st", &ts, &err));
bool saw_fb_call = false;
for (size_t k = 0; k + 3 < ts.size(); ++k) {
if (ts[k].type == Tok::IDENT && ts[k].text == "starter" &&
ts[k + 1].type == Tok::LPAREN && ts[k + 2].type == Tok::IDENT) {
saw_fb_call = true;
}
}
CHECK(saw_fb_call);
bool saw_field = false;
for (size_t k = 0; k + 1 < ts.size(); ++k) {
if (ts[k].type == Tok::IDENT && ts[k].text == "starter" &&
ts[k + 1].type == Tok::DOT) {
saw_field = true;
}
}
CHECK(saw_field);
return true;
}
int main() {
if (!test_basic()) return 1;
if (!test_case()) return 1;
if (!test_symbols()) return 1;
if (!test_comments()) return 1;
if (!test_pos()) return 1;
if (!test_negative()) return 1;
if (!test_line1()) return 1;
std::printf("lexer_test: %d checks passed\n", g_checks);
return 0;
}