实现 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:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user