/** * @file lexer_test.cpp * @brief 词法测试:line1 token 流 + 负例 * @author * @date 2026-08-21 */ #include #include #include #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 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 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 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 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 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 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 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(expect_count[i])); CHECK(ts.back().type == Tok::END); } // globals.st 局部抽查 std::vector 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; }