From 5f98f5526dfd70f94fb8ab5a9536cca5b5681e96 Mon Sep 17 00:00:00 2001 From: esrrhs Date: Wed, 24 Jun 2026 18:41:07 +0800 Subject: [PATCH 1/3] feat: implement goto/label support - Semantic analysis: replace rejection with proper scope validation - Goto targets must exist in visible scope - Goto cannot jump over local variable declarations - Recursive validation for nested blocks (if/while/repeat/for) - CGen: add CompileStmtGoto and CompileStmtLabel - Labels emit C labels with flua_L_ prefix - Gotos emit C goto statements - SyntaxTreeGoto: add GetLabel() method - Remove old exception tests for unsupported goto/label - Add 4 new test cases: forward skip, backward loop, label-only, invalid goto --- src/compile/c_gen.cpp | 18 ++++++ src/compile/c_gen.h | 2 + src/compile/semantic_analysis.cpp | 88 +++++++++++++++++++++++++++++- src/compile/semantic_analysis.h | 2 + src/compile/syntax_tree.h | 4 ++ test/lua/jit/test_goto_basic.lua | 23 ++++++++ test/lua/jit/test_goto_invalid.lua | 7 +++ test/test_exception.cpp | 30 ---------- test/test_jitter.cpp | 40 ++++++++++++++ 9 files changed, 183 insertions(+), 31 deletions(-) create mode 100644 test/lua/jit/test_goto_basic.lua create mode 100644 test/lua/jit/test_goto_invalid.lua diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp index a5b35ae1..907b23e1 100644 --- a/src/compile/c_gen.cpp +++ b/src/compile/c_gen.cpp @@ -825,6 +825,12 @@ void CGen::CompileStmt(const SyntaxTreeInterfacePtr &stmt) { break; case SyntaxTreeType::Empty: break; + case SyntaxTreeType::Goto: + CompileStmtGoto(stmt); + break; + case SyntaxTreeType::Label: + CompileStmtLabel(stmt); + break; default: ThrowError(std::format("not support stmt type: {}", SyntaxTreeTypeToString(stmt->Type())), stmt); } @@ -1207,6 +1213,18 @@ void CGen::CompileStmtBreak(const SyntaxTreeInterfacePtr &stmt) { Out() << GenTab() << "break;\n"; } +void CGen::CompileStmtGoto(const SyntaxTreeInterfacePtr &stmt) { + DEBUG_ASSERT(stmt->Type() == SyntaxTreeType::Goto); + const auto goto_stmt = std::dynamic_pointer_cast(stmt); + Out() << GenTab() << "goto flua_L_" << goto_stmt->GetLabel() << ";\n"; +} + +void CGen::CompileStmtLabel(const SyntaxTreeInterfacePtr &stmt) { + DEBUG_ASSERT(stmt->Type() == SyntaxTreeType::Label); + const auto label_stmt = std::dynamic_pointer_cast(stmt); + Out() << "flua_L_" << label_stmt->GetName() << ": ;\n"; +} + void CGen::CompileStmtForLoop(const SyntaxTreeInterfacePtr &stmt) { DEBUG_ASSERT(stmt->Type() == SyntaxTreeType::ForLoop); const auto for_stmt = std::dynamic_pointer_cast(stmt); diff --git a/src/compile/c_gen.h b/src/compile/c_gen.h index bffef194..1467a2cf 100644 --- a/src/compile/c_gen.h +++ b/src/compile/c_gen.h @@ -153,6 +153,8 @@ class CGen { void CompileDynamicForLoop(const std::shared_ptr &for_stmt); // 编译泛型 for-in 遍历循环(如 ipairs/pairs 循环) void CompileStmtForIn(const SyntaxTreeInterfacePtr &stmt); + void CompileStmtGoto(const SyntaxTreeInterfacePtr &stmt); + void CompileStmtLabel(const SyntaxTreeInterfacePtr &stmt); // 编译具有独立原生强类型作用域的局部的花括号作用域块 void CompileScopedBlock(const SyntaxTreeInterfacePtr &block); // 编译条件布尔表达式(用于处理逻辑运算的短路特性与分支预测优化) diff --git a/src/compile/semantic_analysis.cpp b/src/compile/semantic_analysis.cpp index 0a750b20..ef8b8a64 100644 --- a/src/compile/semantic_analysis.cpp +++ b/src/compile/semantic_analysis.cpp @@ -267,6 +267,8 @@ void SemanticAnalysis::CheckUnsupportedSyntax(const SyntaxTreeInterfacePtr &chun } WalkSyntaxTree(chunk, [this, &ar](const SyntaxTreeInterfacePtr &node) { CheckNode(node, ar); }); + std::unordered_map visible_labels; + ValidateGotoInBlock(chunk, visible_labels); } void SemanticAnalysis::CheckNode(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar) { @@ -338,7 +340,91 @@ void SemanticAnalysis::CheckNode(const SyntaxTreeInterfacePtr &node, const Analy } void SemanticAnalysis::CheckGotoOrLabel(const SyntaxTreeInterfacePtr &node) { - ThrowError(node->Type() == SyntaxTreeType::Goto ? "goto is not supported" : "label is not supported", node); + // 不再直接拒绝,具体验证在 ValidateGotoInBlock 中完成 +} + +void SemanticAnalysis::CollectBlockLabels(const SyntaxTreeInterfacePtr &block, std::unordered_map &labels) { + const auto blk = std::dynamic_pointer_cast(block); + if (!blk) return; + for (const auto &stmt : blk->Stmts()) { + if (stmt->Type() == SyntaxTreeType::Label) { + const auto label = std::dynamic_pointer_cast(stmt); + labels[label->GetName()] = stmt; + } + } +} + +void SemanticAnalysis::ValidateGotoInBlock(const SyntaxTreeInterfacePtr &chunk, std::unordered_map &visible_labels) { + const auto blk = std::dynamic_pointer_cast(chunk); + if (!blk) return; + + // 收集当前 block 自身的 label 并加入可见集合 + CollectBlockLabels(chunk, visible_labels); + + // 收集当前 block 的局部变量声明位置 + std::vector local_positions; + for (size_t i = 0; i < blk->Stmts().size(); ++i) { + if (blk->Stmts()[i]->Type() == SyntaxTreeType::LocalVar) { + local_positions.push_back(i); + } + } + + // 检查 goto + for (size_t i = 0; i < blk->Stmts().size(); ++i) { + const auto &stmt = blk->Stmts()[i]; + if (stmt->Type() == SyntaxTreeType::Goto) { + const auto goto_stmt = std::dynamic_pointer_cast(stmt); + const auto &target_name = goto_stmt->GetLabel(); + auto it = visible_labels.find(target_name); + if (it == visible_labels.end()) { + ThrowError(std::format("goto target '{}' not found", target_name), stmt); + } + // 检查 label 是否在当前 block 内(若在,则检查是否跳过局部变量) + size_t label_pos = blk->Stmts().size(); + for (size_t j = 0; j < blk->Stmts().size(); ++j) { + if (blk->Stmts()[j].get() == it->second.get()) { + label_pos = j; + break; + } + } + if (i < label_pos && label_pos < blk->Stmts().size()) { + for (auto lp : local_positions) { + if (lp > i && lp <= label_pos) { + ThrowError(std::format("goto '{}' jumps over local variable declaration", target_name), stmt); + } + } + } + } + } + + // 递归检查嵌套 block,传递可见 label 集合 + for (const auto &stmt : blk->Stmts()) { + if (stmt->Type() == SyntaxTreeType::Block) { + ValidateGotoInBlock(stmt, visible_labels); + } else if (stmt->Type() == SyntaxTreeType::While) { + ValidateGotoInBlock(std::dynamic_pointer_cast(stmt)->Block(), visible_labels); + } else if (stmt->Type() == SyntaxTreeType::Repeat) { + ValidateGotoInBlock(std::dynamic_pointer_cast(stmt)->Block(), visible_labels); + } else if (stmt->Type() == SyntaxTreeType::If) { + const auto if_stmt = std::dynamic_pointer_cast(stmt); + ValidateGotoInBlock(if_stmt->Block(), visible_labels); + if (if_stmt->ElseBlock()) ValidateGotoInBlock(if_stmt->ElseBlock(), visible_labels); + } else if (stmt->Type() == SyntaxTreeType::ForLoop) { + ValidateGotoInBlock(std::dynamic_pointer_cast(stmt)->Block(), visible_labels); + } else if (stmt->Type() == SyntaxTreeType::ForIn) { + ValidateGotoInBlock(std::dynamic_pointer_cast(stmt)->Block(), visible_labels); + } else if (stmt->Type() == SyntaxTreeType::Function) { + std::unordered_map func_labels; + const auto fb = std::dynamic_pointer_cast( + std::dynamic_pointer_cast(stmt)->Funcbody()); + if (fb) ValidateGotoInBlock(fb->Block(), func_labels); + } else if (stmt->Type() == SyntaxTreeType::LocalFunction) { + std::unordered_map func_labels; + const auto fb = std::dynamic_pointer_cast( + std::dynamic_pointer_cast(stmt)->Funcbody()); + if (fb) ValidateGotoInBlock(fb->Block(), func_labels); + } + } } void SemanticAnalysis::CheckFunctionCall(const SyntaxTreeInterfacePtr &node) { diff --git a/src/compile/semantic_analysis.h b/src/compile/semantic_analysis.h index 352cae65..7962119f 100644 --- a/src/compile/semantic_analysis.h +++ b/src/compile/semantic_analysis.h @@ -23,6 +23,8 @@ class SemanticAnalysis { void CheckUnsupportedSyntax(const SyntaxTreeInterfacePtr &chunk, const AnalysisResult &ar); void CheckNode(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar); void CheckGotoOrLabel(const SyntaxTreeInterfacePtr &node); + void ValidateGotoInBlock(const SyntaxTreeInterfacePtr &chunk, std::unordered_map &visible_labels); + void CollectBlockLabels(const SyntaxTreeInterfacePtr &block, std::unordered_map &labels); void CheckFunctionCall(const SyntaxTreeInterfacePtr &node); void CheckParList(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar); void CheckLocalVar(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar); diff --git a/src/compile/syntax_tree.h b/src/compile/syntax_tree.h index 964a48d4..875c0114 100644 --- a/src/compile/syntax_tree.h +++ b/src/compile/syntax_tree.h @@ -722,6 +722,10 @@ class SyntaxTreeGoto final : public SyntaxTreeInterface { label_ = label; } + [[nodiscard]] const std::string &GetLabel() const { + return label_; + } + private: std::string label_; }; diff --git a/test/lua/jit/test_goto_basic.lua b/test/lua/jit/test_goto_basic.lua new file mode 100644 index 00000000..eefb325b --- /dev/null +++ b/test/lua/jit/test_goto_basic.lua @@ -0,0 +1,23 @@ +-- goto/label 合法用法 +function test_goto_forward() + local x = 100 + goto skip + x = 999 + ::skip:: + return x +end + +function test_goto_backward() + local i = 0 + ::loop:: + i = i + 1 + if i < 5 then + goto loop + end + return i +end + +function test_label_no_goto() + ::unused:: + return 42 +end diff --git a/test/lua/jit/test_goto_invalid.lua b/test/lua/jit/test_goto_invalid.lua new file mode 100644 index 00000000..5768336f --- /dev/null +++ b/test/lua/jit/test_goto_invalid.lua @@ -0,0 +1,7 @@ +-- goto 跳过局部变量声明,应该报错 +function test_invalid() + goto my_label + local x = 1 + ::my_label:: + return x +end diff --git a/test/test_exception.cpp b/test/test_exception.cpp index 7bda4776..5ab66f35 100644 --- a/test/test_exception.cpp +++ b/test/test_exception.cpp @@ -732,36 +732,6 @@ TEST(exception, test_const_unop_bitnot_error) { } -TEST(exception, stmt_support_error) { - FakeluaStateGuard sg; - auto s = sg.GetState(); - ASSERT_NE(s, nullptr); - SetDebugLogLevel(0); - - try { - CompileFile(s, "./exception/test_stmt_support_error.lua", {}); - ASSERT_TRUE(false); - } catch (const std::exception &e) { - std::cout << e.what() << std::endl; - ASSERT_TRUE(std::string(e.what()).find("goto is not supported") != std::string::npos); - } -} - -TEST(exception, label_support_error) { - FakeluaStateGuard sg; - auto s = sg.GetState(); - ASSERT_NE(s, nullptr); - SetDebugLogLevel(0); - - try { - CompileFile(s, "./exception/test_label_support_error.lua", {}); - ASSERT_TRUE(false); - } catch (const std::exception &e) { - std::cout << e.what() << std::endl; - ASSERT_TRUE(std::string(e.what()).find("label is not supported") != std::string::npos); - } -} - TEST(exception, const_func_call_error) { FakeluaStateGuard sg; auto s = sg.GetState(); diff --git a/test/test_jitter.cpp b/test/test_jitter.cpp index 71c2bf9d..65b12c1a 100644 --- a/test/test_jitter.cpp +++ b/test/test_jitter.cpp @@ -2885,3 +2885,43 @@ TEST(jitter, test_const_complex_expr) { ASSERT_EQ(t->Get(key_val).GetInt(), -20); }); } + +// ============================================================================ +// goto/label 测试 +// ============================================================================ + +TEST(jitter, goto_forward_skip) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "test_goto_forward", ret); + ASSERT_EQ(ret, 100); + }); +} + +TEST(jitter, goto_backward_loop) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "test_goto_backward", ret); + ASSERT_EQ(ret, 5); + }); +} + +TEST(jitter, goto_label_no_goto) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "test_label_no_goto", ret); + ASSERT_EQ(ret, 42); + }); +} + +TEST(jitter, goto_invalid_jumps_over_local) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + EXPECT_THROW( + CompileFile(s, "./jit/test_goto_invalid.lua", {.debug_mode = debug_mode}), + std::exception + ); + }); +} From 5ddabb0334728a1c9516b2d9875e43cd409ac42a Mon Sep 17 00:00:00 2001 From: esrrhs Date: Thu, 25 Jun 2026 10:19:08 +0800 Subject: [PATCH 2/3] refactor: split goto tests into individual files, add more scenarios Valid goto tests (test/lua/jit/): - test_goto_forward_skip: forward goto skipping a reassignment - test_goto_backward_loop: backward goto forming a loop - test_goto_label_only: label with no goto - test_goto_continue: goto simulating continue in for loop - test_goto_break_out: goto breaking out of nested for loops - test_goto_in_if_else: goto within if/else branches Invalid goto tests (test/lua/exception/): - test_goto_skip_single_local: goto jumps over single local - test_goto_skip_local: goto jumps over multiple locals - test_goto_nonexistent_label: goto to nonexistent label - test_goto_cross_function: goto targeting label in another function Removed old combined test files. --- .../exception/test_goto_cross_function.lua | 10 +++++ .../exception/test_goto_nonexistent_label.lua | 5 +++ test/lua/exception/test_goto_skip_local.lua | 8 ++++ .../exception/test_goto_skip_single_local.lua | 7 +++ test/lua/jit/test_goto_backward_loop.lua | 9 ++++ test/lua/jit/test_goto_basic.lua | 23 ---------- test/lua/jit/test_goto_break_out.lua | 14 ++++++ test/lua/jit/test_goto_continue.lua | 12 ++++++ test/lua/jit/test_goto_forward_skip.lua | 7 +++ test/lua/jit/test_goto_in_if_else.lua | 12 ++++++ test/lua/jit/test_goto_invalid.lua | 7 --- test/lua/jit/test_goto_label_only.lua | 4 ++ test/test_exception.cpp | 32 ++++++++++++++ test/test_jitter.cpp | 43 +++++++++++++------ 14 files changed, 151 insertions(+), 42 deletions(-) create mode 100644 test/lua/exception/test_goto_cross_function.lua create mode 100644 test/lua/exception/test_goto_nonexistent_label.lua create mode 100644 test/lua/exception/test_goto_skip_local.lua create mode 100644 test/lua/exception/test_goto_skip_single_local.lua create mode 100644 test/lua/jit/test_goto_backward_loop.lua delete mode 100644 test/lua/jit/test_goto_basic.lua create mode 100644 test/lua/jit/test_goto_break_out.lua create mode 100644 test/lua/jit/test_goto_continue.lua create mode 100644 test/lua/jit/test_goto_forward_skip.lua create mode 100644 test/lua/jit/test_goto_in_if_else.lua delete mode 100644 test/lua/jit/test_goto_invalid.lua create mode 100644 test/lua/jit/test_goto_label_only.lua diff --git a/test/lua/exception/test_goto_cross_function.lua b/test/lua/exception/test_goto_cross_function.lua new file mode 100644 index 00000000..184c58c0 --- /dev/null +++ b/test/lua/exception/test_goto_cross_function.lua @@ -0,0 +1,10 @@ +-- goto 跳转到另一个函数的 label(不可见) +function test_goto_cross_function_a() + goto cross_label + return 1 +end + +function test_goto_cross_function_b() + ::cross_label:: + return 2 +end diff --git a/test/lua/exception/test_goto_nonexistent_label.lua b/test/lua/exception/test_goto_nonexistent_label.lua new file mode 100644 index 00000000..49a814e2 --- /dev/null +++ b/test/lua/exception/test_goto_nonexistent_label.lua @@ -0,0 +1,5 @@ +-- goto 跳转到不存在的 label +function test_goto_nonexistent_label() + goto nonexistent + return 1 +end diff --git a/test/lua/exception/test_goto_skip_local.lua b/test/lua/exception/test_goto_skip_local.lua new file mode 100644 index 00000000..75ba7fba --- /dev/null +++ b/test/lua/exception/test_goto_skip_local.lua @@ -0,0 +1,8 @@ +-- goto 跳过多个局部变量 +function test_goto_skip_multiple_locals() + goto my_label + local a = 1 + local b = 2 + ::my_label:: + return a +end diff --git a/test/lua/exception/test_goto_skip_single_local.lua b/test/lua/exception/test_goto_skip_single_local.lua new file mode 100644 index 00000000..7a3ea1bd --- /dev/null +++ b/test/lua/exception/test_goto_skip_single_local.lua @@ -0,0 +1,7 @@ +-- goto 跳过局部变量声明(单个) +function test_goto_skip_single_local() + goto my_label + local x = 1 + ::my_label:: + return x +end diff --git a/test/lua/jit/test_goto_backward_loop.lua b/test/lua/jit/test_goto_backward_loop.lua new file mode 100644 index 00000000..b34bd0e7 --- /dev/null +++ b/test/lua/jit/test_goto_backward_loop.lua @@ -0,0 +1,9 @@ +function test_goto_backward_loop() + local i = 0 + ::loop:: + i = i + 1 + if i < 5 then + goto loop + end + return i +end diff --git a/test/lua/jit/test_goto_basic.lua b/test/lua/jit/test_goto_basic.lua deleted file mode 100644 index eefb325b..00000000 --- a/test/lua/jit/test_goto_basic.lua +++ /dev/null @@ -1,23 +0,0 @@ --- goto/label 合法用法 -function test_goto_forward() - local x = 100 - goto skip - x = 999 - ::skip:: - return x -end - -function test_goto_backward() - local i = 0 - ::loop:: - i = i + 1 - if i < 5 then - goto loop - end - return i -end - -function test_label_no_goto() - ::unused:: - return 42 -end diff --git a/test/lua/jit/test_goto_break_out.lua b/test/lua/jit/test_goto_break_out.lua new file mode 100644 index 00000000..b15d7129 --- /dev/null +++ b/test/lua/jit/test_goto_break_out.lua @@ -0,0 +1,14 @@ +-- goto 跳出多层嵌套循环 +function test_goto_break_out() + local result = 0 + for i = 1, 10 do + for j = 1, 10 do + if i * j > 20 then + result = i * j + goto done + end + end + end + ::done:: + return result +end diff --git a/test/lua/jit/test_goto_continue.lua b/test/lua/jit/test_goto_continue.lua new file mode 100644 index 00000000..bc011c2e --- /dev/null +++ b/test/lua/jit/test_goto_continue.lua @@ -0,0 +1,12 @@ +-- goto 模拟 continue:跳过循环体剩余部分 +function test_goto_continue() + local sum = 0 + for i = 1, 10 do + if i % 2 == 0 then + goto continue + end + sum = sum + i + ::continue:: + end + return sum +end diff --git a/test/lua/jit/test_goto_forward_skip.lua b/test/lua/jit/test_goto_forward_skip.lua new file mode 100644 index 00000000..9e170eb7 --- /dev/null +++ b/test/lua/jit/test_goto_forward_skip.lua @@ -0,0 +1,7 @@ +function test_goto_forward_skip() + local x = 100 + goto skip + x = 999 + ::skip:: + return x +end diff --git a/test/lua/jit/test_goto_in_if_else.lua b/test/lua/jit/test_goto_in_if_else.lua new file mode 100644 index 00000000..abf419af --- /dev/null +++ b/test/lua/jit/test_goto_in_if_else.lua @@ -0,0 +1,12 @@ +-- goto 在 if/else 分支中 +function test_goto_in_if_else() + local x = 0 + if x == 0 then + goto skip + x = 99 + else + x = 50 + end + ::skip:: + return x +end diff --git a/test/lua/jit/test_goto_invalid.lua b/test/lua/jit/test_goto_invalid.lua deleted file mode 100644 index 5768336f..00000000 --- a/test/lua/jit/test_goto_invalid.lua +++ /dev/null @@ -1,7 +0,0 @@ --- goto 跳过局部变量声明,应该报错 -function test_invalid() - goto my_label - local x = 1 - ::my_label:: - return x -end diff --git a/test/lua/jit/test_goto_label_only.lua b/test/lua/jit/test_goto_label_only.lua new file mode 100644 index 00000000..d6553363 --- /dev/null +++ b/test/lua/jit/test_goto_label_only.lua @@ -0,0 +1,4 @@ +function test_label_no_goto() + ::unused:: + return 42 +end diff --git a/test/test_exception.cpp b/test/test_exception.cpp index 5ab66f35..58536dac 100644 --- a/test/test_exception.cpp +++ b/test/test_exception.cpp @@ -732,6 +732,38 @@ TEST(exception, test_const_unop_bitnot_error) { } +TEST(exception, goto_skip_local) { + FakeluaStateGuard sg; + auto s = sg.GetState(); + ASSERT_NE(s, nullptr); + SetDebugLogLevel(0); + EXPECT_THROW(CompileFile(s, "./exception/test_goto_skip_single_local.lua", {}), std::exception); +} + +TEST(exception, goto_skip_multiple_locals) { + FakeluaStateGuard sg; + auto s = sg.GetState(); + ASSERT_NE(s, nullptr); + SetDebugLogLevel(0); + EXPECT_THROW(CompileFile(s, "./exception/test_goto_skip_local.lua", {}), std::exception); +} + +TEST(exception, goto_nonexistent_label) { + FakeluaStateGuard sg; + auto s = sg.GetState(); + ASSERT_NE(s, nullptr); + SetDebugLogLevel(0); + EXPECT_THROW(CompileFile(s, "./exception/test_goto_nonexistent_label.lua", {}), std::exception); +} + +TEST(exception, goto_cross_function) { + FakeluaStateGuard sg; + auto s = sg.GetState(); + ASSERT_NE(s, nullptr); + SetDebugLogLevel(0); + EXPECT_THROW(CompileFile(s, "./exception/test_goto_cross_function.lua", {}), std::exception); +} + TEST(exception, const_func_call_error) { FakeluaStateGuard sg; auto s = sg.GetState(); diff --git a/test/test_jitter.cpp b/test/test_jitter.cpp index 65b12c1a..64b248b0 100644 --- a/test/test_jitter.cpp +++ b/test/test_jitter.cpp @@ -2887,41 +2887,60 @@ TEST(jitter, test_const_complex_expr) { } // ============================================================================ -// goto/label 测试 +// goto/label 合法场景 // ============================================================================ TEST(jitter, goto_forward_skip) { JitterRunHelper([](State *s, JITType type, bool debug_mode) { - CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode}); + CompileFile(s, "./jit/test_goto_forward_skip.lua", {.debug_mode = debug_mode}); int64_t ret = 0; - Call(s, type, "test_goto_forward", ret); + Call(s, type, "test_goto_forward_skip", ret); ASSERT_EQ(ret, 100); }); } TEST(jitter, goto_backward_loop) { JitterRunHelper([](State *s, JITType type, bool debug_mode) { - CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode}); + CompileFile(s, "./jit/test_goto_backward_loop.lua", {.debug_mode = debug_mode}); int64_t ret = 0; - Call(s, type, "test_goto_backward", ret); + Call(s, type, "test_goto_backward_loop", ret); ASSERT_EQ(ret, 5); }); } -TEST(jitter, goto_label_no_goto) { +TEST(jitter, goto_label_only) { JitterRunHelper([](State *s, JITType type, bool debug_mode) { - CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode}); + CompileFile(s, "./jit/test_goto_label_only.lua", {.debug_mode = debug_mode}); int64_t ret = 0; Call(s, type, "test_label_no_goto", ret); ASSERT_EQ(ret, 42); }); } -TEST(jitter, goto_invalid_jumps_over_local) { +TEST(jitter, goto_continue_simulation) { JitterRunHelper([](State *s, JITType type, bool debug_mode) { - EXPECT_THROW( - CompileFile(s, "./jit/test_goto_invalid.lua", {.debug_mode = debug_mode}), - std::exception - ); + CompileFile(s, "./jit/test_goto_continue.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "test_goto_continue", ret); + // 奇数之和 1+3+5+7+9 = 25 + ASSERT_EQ(ret, 25); + }); +} + +TEST(jitter, goto_break_out_of_nested_loops) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_goto_break_out.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "test_goto_break_out", ret); + ASSERT_EQ(ret, 21); + }); +} + +TEST(jitter, goto_in_if_else) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_goto_in_if_else.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "test_goto_in_if_else", ret); + ASSERT_EQ(ret, 0); }); } From 44f546b26d7163437a6161d69957fcd4d09cb855 Mon Sep 17 00:00:00 2001 From: esrrhs Date: Thu, 25 Jun 2026 11:23:13 +0800 Subject: [PATCH 3/3] docs: remove goto/label from limitations list --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0d2be7d5..071a2ca2 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,6 @@ local z = (x + y) / 2.0 ## 当前已知限制 ### 语法限制 -- 不支持 `label` / `goto` - 泛型 `for in` 仅支持 `pairs()` / `ipairs()` - 脚本侧函数调用仅支持简单函数名调用(不支持复杂前缀表达式调用,如 `obj:method()` 需通过间接方式实现)