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()` 需通过间接方式实现) 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/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_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_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 7bda4776..58536dac 100644 --- a/test/test_exception.cpp +++ b/test/test_exception.cpp @@ -732,34 +732,36 @@ TEST(exception, test_const_unop_bitnot_error) { } -TEST(exception, stmt_support_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); +} - 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, 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, label_support_error) { +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); +} - 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, 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) { diff --git a/test/test_jitter.cpp b/test/test_jitter.cpp index 71c2bf9d..64b248b0 100644 --- a/test/test_jitter.cpp +++ b/test/test_jitter.cpp @@ -2885,3 +2885,62 @@ 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_forward_skip.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + 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_backward_loop.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "test_goto_backward_loop", ret); + ASSERT_EQ(ret, 5); + }); +} + +TEST(jitter, goto_label_only) { + JitterRunHelper([](State *s, JITType type, bool 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_continue_simulation) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + 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); + }); +}