From fced0c4f4101ad7c8d81432a0e8c45d38b72616c Mon Sep 17 00:00:00 2001 From: Li Jin Date: Thu, 15 Jan 2026 17:07:11 +0800 Subject: Added `import global` syntax. --- doc/docs/doc/README.md | 37 ++++++++++ doc/docs/zh/doc/README.md | 37 ++++++++++ spec/inputs/import_global.yue | 86 ++++++++++++++++++++++ spec/outputs/codes_from_doc.lua | 142 +++++++++++++++++++++---------------- spec/outputs/codes_from_doc_zh.lua | 142 +++++++++++++++++++++---------------- spec/outputs/import_global.lua | 120 +++++++++++++++++++++++++++++++ src/yuescript/yue_ast.cpp | 3 + src/yuescript/yue_ast.h | 5 +- src/yuescript/yue_compiler.cpp | 78 ++++++++++++++++---- src/yuescript/yue_parser.cpp | 16 +++-- src/yuescript/yue_parser.h | 1 + 11 files changed, 527 insertions(+), 140 deletions(-) create mode 100644 spec/inputs/import_global.yue create mode 100644 spec/outputs/import_global.lua diff --git a/doc/docs/doc/README.md b/doc/docs/doc/README.md index 5308ebc..bda82a6 100755 --- a/doc/docs/doc/README.md +++ b/doc/docs/doc/README.md @@ -928,6 +928,43 @@ tb = The import statement is a syntax sugar for requiring a module or help extracting items from an imported module. The imported items are const by default. +#### Import Global + +You can place `import global` at the top of a block to automatically import all names that have not been explicitly declared or assigned in the current scope as globals. These implicit imports are treated as local consts that reference the corresponding globals at the position of the statement. + +Names that are explicitly declared as globals in the same scope will not be imported, so you can safely assign to them. + +```moonscript +do + import global + print "hello" + math.random 3 + -- print = nil -- error: imported globals are const + +do + -- explicit global variable will not be imported + import global + global FLAG + print FLAG + FLAG = 123 +``` + +
+do
+  import global
+  print "hello"
+  math.random 3
+  -- print = nil -- error: imported globals are const
+
+do
+  -- explicit global variable will not be imported
+  import global
+  global FLAG
+  print FLAG
+  FLAG = 123
+
+
+ ```moonscript -- used as table destructuring do diff --git a/doc/docs/zh/doc/README.md b/doc/docs/zh/doc/README.md index de5dff1..d316657 100755 --- a/doc/docs/zh/doc/README.md +++ b/doc/docs/zh/doc/README.md @@ -926,6 +926,43 @@ tb = 导入语句是一个语法糖,用于需要引入一个模块或者从已导入的模块中提取子项目。从模块导入的变量默认为不可修改的常量。 +#### 导入全局变量 + +在代码块顶部写 `import global`,会将当前作用域中尚未显式声明或赋值过的变量名,自动导入为本地常量,并在该语句的位置绑定到同名的全局变量。 + +但是在同一作用域中被显式声明为全局的变量不会被自动导入,因此可以继续进行赋值操作。 + +```moonscript +do + import global + print "hello" + math.random 3 + -- print = nil -- 报错:自动导入的全局变量为常量 + +do + -- 被显式声明为全局的变量不会被自动导入 + import global + global FLAG + print FLAG + FLAG = 123 +``` + +
+do
+  import global
+  print "hello"
+  math.random 3
+  -- print = nil -- 报错:自动导入的全局变量是常量
+
+do
+  -- 被显式声明为全局的变量不会被自动导入
+  import global
+  global FLAG
+  print FLAG
+  FLAG = 123
+
+
+ ```moonscript -- 用作表解构 do diff --git a/spec/inputs/import_global.yue b/spec/inputs/import_global.yue new file mode 100644 index 0000000..30a274e --- /dev/null +++ b/spec/inputs/import_global.yue @@ -0,0 +1,86 @@ + +do + import global + print "hello" + math.random 10 + +do + import global + value = 1 + value += 2 + print value + +do + local print = (msg) -> + return msg + do + import global + print "local" + math.random 1 + +do + import global + local tostring = (v) -> "local" + tostring "value" + print tostring 123 + +do + func = (x, y) -> + import global + return type x, tostring y, print + func 1, 2 + +do + import global + try + func "hello #{world}" + catch err + print err + +do + import global + global FLAG + print FLAG + FLAG = 123 + +do + import global + global Foo = 10 + print Foo + Foo += 2 + +do + import global + global Bar, Baz + Bar = 1 + Baz = 2 + print Bar, Baz + +do + import global + global * + x = 3434 + if y then + x = 10 + +do + import global + global ^ + foobar = "all #{lowercase}" + FooBar = "pascal case" + FOOBAR = "all #{Uppercase}" + +do + import global + global const class A + global const Flag = 1 + global const const, x, y = "const", 1, 2 + global const math, table + print math, table + +do + import global + with X + \func 1, 2, 3 + .tag = "abc" + diff --git a/spec/outputs/codes_from_doc.lua b/spec/outputs/codes_from_doc.lua index de5abdd..02f36d1 100644 --- a/spec/outputs/codes_from_doc.lua +++ b/spec/outputs/codes_from_doc.lua @@ -408,6 +408,17 @@ local tb = { } } } +do + local math = math + local print = print + print("hello") + math.random(3) +end +do + local print = print + print(FLAG) + FLAG = 123 +end do local insert, concat = table.insert, table.concat local C, Ct, Cmt @@ -737,36 +748,6 @@ end local first = select(1, ...) return print(ok, count, first) end)(fn(true)) -local f -f = function(...) - local t = { - n = select("#", ...), - ... - } - print("argument count:", t.n) - print("table length:", #t) - for i = 1, t.n do - print(t[i]) - end -end -f(1, 2, 3) -f("a", "b", "c", "d") -f() -local process -process = function(...) - local args = { - n = select("#", ...), - ... - } - local sum = 0 - for i = 1, args.n do - if args[i] ~= nil and type(args[i]) == "number" then - sum = sum + args[i] - end - end - return sum -end -process(1, nil, 3, nil, 5) Rx.Observable.fromRange(1, 8):filter(function(x) return x % 2 == 0 end):concat(Rx.Observable.of('who do we appreciate')):map(function(value) @@ -1000,6 +981,36 @@ local arg1 = { a = 0 } f2(arg1, arg2) +local f +f = function(...) + local t = { + n = select("#", ...), + ... + } + print("argument count:", t.n) + print("table length:", #t) + for i = 1, t.n do + print(t[i]) + end +end +f(1, 2, 3) +f("a", "b", "c", "d") +f() +local process +process = function(...) + local args = { + n = select("#", ...), + ... + } + local sum = 0 + for i = 1, args.n do + if args[i] ~= nil and type(args[i]) == "number" then + sum = sum + args[i] + end + end + return sum +end +process(1, nil, 3, nil, 5) f(function() return print("hello") end) @@ -2916,6 +2927,17 @@ local tb = { } } } +do + local math = math + local print = print + print("hello") + math.random(3) +end +do + local print = print + print(FLAG) + FLAG = 123 +end do local insert, concat = table.insert, table.concat local C, Ct, Cmt @@ -3245,36 +3267,6 @@ end local first = select(1, ...) return print(ok, count, first) end)(fn(true)) -local f -f = function(...) - local t = { - n = select("#", ...), - ... - } - print("argument count:", t.n) - print("table length:", #t) - for i = 1, t.n do - print(t[i]) - end -end -f(1, 2, 3) -f("a", "b", "c", "d") -f() -local process -process = function(...) - local args = { - n = select("#", ...), - ... - } - local sum = 0 - for i = 1, args.n do - if args[i] ~= nil and type(args[i]) == "number" then - sum = sum + args[i] - end - end - return sum -end -process(1, nil, 3, nil, 5) Rx.Observable.fromRange(1, 8):filter(function(x) return x % 2 == 0 end):concat(Rx.Observable.of('who do we appreciate')):map(function(value) @@ -3538,6 +3530,36 @@ findFirstEven = function(list) end return nil end +local f +f = function(...) + local t = { + n = select("#", ...), + ... + } + print("argument count:", t.n) + print("table length:", #t) + for i = 1, t.n do + print(t[i]) + end +end +f(1, 2, 3) +f("a", "b", "c", "d") +f() +local process +process = function(...) + local args = { + n = select("#", ...), + ... + } + local sum = 0 + for i = 1, args.n do + if args[i] ~= nil and type(args[i]) == "number" then + sum = sum + args[i] + end + end + return sum +end +process(1, nil, 3, nil, 5) f(function() return print("hello") end) diff --git a/spec/outputs/codes_from_doc_zh.lua b/spec/outputs/codes_from_doc_zh.lua index 6a6c38c..87944e0 100644 --- a/spec/outputs/codes_from_doc_zh.lua +++ b/spec/outputs/codes_from_doc_zh.lua @@ -408,6 +408,17 @@ local tb = { } } } +do + local math = math + local print = print + print("hello") + math.random(3) +end +do + local print = print + print(FLAG) + FLAG = 123 +end do local insert, concat = table.insert, table.concat local C, Ct, Cmt @@ -737,36 +748,6 @@ end local first = select(1, ...) return print(ok, count, first) end)(fn(true)) -local f -f = function(...) - local t = { - n = select("#", ...), - ... - } - print("参数个数:", t.n) - print("表长度:", #t) - for i = 1, t.n do - print(t[i]) - end -end -f(1, 2, 3) -f("a", "b", "c", "d") -f() -local process -process = function(...) - local args = { - n = select("#", ...), - ... - } - local sum = 0 - for i = 1, args.n do - if args[i] ~= nil and type(args[i]) == "number" then - sum = sum + args[i] - end - end - return sum -end -process(1, nil, 3, nil, 5) Rx.Observable.fromRange(1, 8):filter(function(x) return x % 2 == 0 end):concat(Rx.Observable.of('who do we appreciate')):map(function(value) @@ -994,6 +975,36 @@ local arg1 = { a = 0 } f2(arg1, arg2) +local f +f = function(...) + local t = { + n = select("#", ...), + ... + } + print("参数个数:", t.n) + print("表长度:", #t) + for i = 1, t.n do + print(t[i]) + end +end +f(1, 2, 3) +f("a", "b", "c", "d") +f() +local process +process = function(...) + local args = { + n = select("#", ...), + ... + } + local sum = 0 + for i = 1, args.n do + if args[i] ~= nil and type(args[i]) == "number" then + sum = sum + args[i] + end + end + return sum +end +process(1, nil, 3, nil, 5) f(function() return print("hello") end) @@ -2910,6 +2921,17 @@ local tb = { } } } +do + local math = math + local print = print + print("hello") + math.random(3) +end +do + local print = print + print(FLAG) + FLAG = 123 +end do local insert, concat = table.insert, table.concat local C, Ct, Cmt @@ -3239,36 +3261,6 @@ end local first = select(1, ...) return print(ok, count, first) end)(fn(true)) -local f -f = function(...) - local t = { - n = select("#", ...), - ... - } - print("参数个数:", t.n) - print("表长度:", #t) - for i = 1, t.n do - print(t[i]) - end -end -f(1, 2, 3) -f("a", "b", "c", "d") -f() -local process -process = function(...) - local args = { - n = select("#", ...), - ... - } - local sum = 0 - for i = 1, args.n do - if args[i] ~= nil and type(args[i]) == "number" then - sum = sum + args[i] - end - end - return sum -end -process(1, nil, 3, nil, 5) Rx.Observable.fromRange(1, 8):filter(function(x) return x % 2 == 0 end):concat(Rx.Observable.of('who do we appreciate')):map(function(value) @@ -3526,6 +3518,36 @@ findFirstEven = function(list) end return nil end +local f +f = function(...) + local t = { + n = select("#", ...), + ... + } + print("参数个数:", t.n) + print("表长度:", #t) + for i = 1, t.n do + print(t[i]) + end +end +f(1, 2, 3) +f("a", "b", "c", "d") +f() +local process +process = function(...) + local args = { + n = select("#", ...), + ... + } + local sum = 0 + for i = 1, args.n do + if args[i] ~= nil and type(args[i]) == "number" then + sum = sum + args[i] + end + end + return sum +end +process(1, nil, 3, nil, 5) f(function() return print("hello") end) diff --git a/spec/outputs/import_global.lua b/spec/outputs/import_global.lua new file mode 100644 index 0000000..08a90e2 --- /dev/null +++ b/spec/outputs/import_global.lua @@ -0,0 +1,120 @@ +do + local math = math + local print = print + print("hello") + math.random(10) +end +do + local print = print + local value = 1 + value = value + 2 + print(value) +end +do + local print + print = function(msg) + return msg + end + do + local math = math + print("local") + math.random(1) + end +end +do + local print = print + local tostring + tostring = function(v) + return "local" + end + tostring("value") + print(tostring(123)) +end +do + local func + func = function(x, y) + local tostring = tostring + local print = print + local type = type + return type(x, tostring(y, print)) + end + func(1, 2) +end +do + local print = print + local tostring = tostring + local func = func + local world = world + local xpcall = xpcall + xpcall(function() + return func("hello " .. tostring(world)) + end, function(err) + return print(err) + end) +end +do + local print = print + print(FLAG) + FLAG = 123 +end +do + local print = print + Foo = 10 + print(Foo) + Foo = Foo + 2 +end +do + local print = print + Bar = 1 + Baz = 2 + print(Bar, Baz) +end +do + local y = y + x = 3434 + if y then + x = 10 + end +end +do + local tostring = tostring + local Uppercase = Uppercase + local lowercase = lowercase + local foobar = "all " .. tostring(lowercase) + FooBar = "pascal case" + FOOBAR = "all " .. tostring(Uppercase) +end +do + local print = print + local setmetatable = setmetatable + do + local _class_0 + local _base_0 = { } + if _base_0.__index == nil then + _base_0.__index = _base_0 + end + _class_0 = setmetatable({ + __init = function() end, + __base = _base_0, + __name = "A" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({ }, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + A = _class_0 + end + Flag = 1 + const, x, y = "const", 1, 2 + print(math, table) +end +do + local X = X + X:func(1, 2, 3) + X.tag = "abc" + return X +end diff --git a/src/yuescript/yue_ast.cpp b/src/yuescript/yue_ast.cpp index 14d8db8..c9fd23f 100644 --- a/src/yuescript/yue_ast.cpp +++ b/src/yuescript/yue_ast.cpp @@ -326,6 +326,9 @@ std::string ImportGlobal_t::to_string(void* ud) const { } return item; } +std::string ImportAllGlobal_t::to_string(void*) const { + return "global"s; +} std::string Import_t::to_string(void* ud) const { if (ast_is(content)) { return content->to_string(ud); diff --git a/src/yuescript/yue_ast.h b/src/yuescript/yue_ast.h index ba4186d..af2355a 100644 --- a/src/yuescript/yue_ast.h +++ b/src/yuescript/yue_ast.h @@ -233,6 +233,9 @@ AST_NODE(ImportAs) AST_MEMBER(ImportAs, &literal, &target) AST_END(ImportAs) +AST_LEAF(ImportAllGlobal) +AST_END(ImportAllGlobal) + AST_NODE(ImportGlobal) ast_ptr sep; ast_list segs; @@ -241,7 +244,7 @@ AST_NODE(ImportGlobal) AST_END(ImportGlobal) AST_NODE(Import) - ast_sel content; + ast_sel content; AST_MEMBER(Import, &content) AST_END(Import) diff --git a/src/yuescript/yue_compiler.cpp b/src/yuescript/yue_compiler.cpp index cffa7a8..10f8719 100644 --- a/src/yuescript/yue_compiler.cpp +++ b/src/yuescript/yue_compiler.cpp @@ -78,7 +78,7 @@ static std::unordered_set Metamethods = { "close"s // Lua 5.4 }; -const std::string_view version = "0.30.4"sv; +const std::string_view version = "0.31.0"sv; const std::string_view extension = "yue"sv; class CompileError : public std::logic_error { @@ -434,6 +434,14 @@ private: Global = 2, GlobalConst = 3 }; + struct Scope; + struct ImportedGlobal { + std::string* globalCodeLine = nullptr; + Scope* importingScope = nullptr; + std::string indent; + std::string nl; + std::unordered_set globals; + }; struct Scope { GlobalMode mode = GlobalMode::None; bool lastStatement = false; @@ -442,8 +450,10 @@ private: #endif std::unique_ptr> vars; std::unique_ptr> allows; + std::unique_ptr importedGlobal; }; std::list _scopes; + ImportedGlobal* _importedGlobal = nullptr; static const std::string Empty; enum class MemType { @@ -1612,7 +1622,12 @@ private: std::string globalVar(std::string_view var, ast_node* x, AccessType accessType) { std::string str(var); - if (_config.lintGlobalVariable) { + if (_importedGlobal) { + if (_importedGlobal->globals.find(str) == _importedGlobal->globals.end() && !isSolidDefined(str)) { + _importedGlobal->globals.insert(str); + _importedGlobal->importingScope->vars->insert_or_assign(str, VarType::LocalConst); + } + } else if (_config.lintGlobalVariable) { if (!isLocal(str)) { auto key = str + ':' + std::to_string(x->m_begin.m_line) + ':' + std::to_string(x->m_begin.m_col); if (_globals.find(key) == _globals.end()) { @@ -4617,13 +4632,21 @@ private: switch (item->get_id()) { case id(): { transformVariable(static_cast(item), out); - if (_config.lintGlobalVariable && accessType != AccessType::None && !isLocal(out.back())) { - auto key = out.back() + ':' + std::to_string(item->m_begin.m_line) + ':' + std::to_string(item->m_begin.m_col); - if (_globals.find(key) == _globals.end()) { - if (accessType == AccessType::Read && _funcLevel > 1) { - accessType = AccessType::Capture; + if (accessType != AccessType::None) { + if (_importedGlobal) { + const auto& str = out.back(); + if (_importedGlobal->globals.find(str) == _importedGlobal->globals.end() && !isSolidDefined(str)) { + _importedGlobal->globals.insert(str); + _importedGlobal->importingScope->vars->insert_or_assign(str, VarType::LocalConst); + } + } else if (_config.lintGlobalVariable && !isLocal(out.back())) { + auto key = out.back() + ':' + std::to_string(item->m_begin.m_line) + ':' + std::to_string(item->m_begin.m_col); + if (_globals.find(key) == _globals.end()) { + if (accessType == AccessType::Read && _funcLevel > 1) { + accessType = AccessType::Capture; + } + _globals[key] = {out.back(), item->m_begin.m_line, item->m_begin.m_col, accessType, isSolidDefined(out.back())}; } - _globals[key] = {out.back(), item->m_begin.m_line, item->m_begin.m_col, accessType, isSolidDefined(out.back())}; } } break; @@ -5299,7 +5322,23 @@ private: } auto transformNode = [&]() { currentScope().lastStatement = (node == lastStmt) && currentScope().mode == GlobalMode::None; - transformStatement(static_cast(node), temp); + auto stmt = static_cast(node); + if (auto importNode = stmt->content.as(); + importNode && importNode->content.is()) { + if (_importedGlobal) { + throw CompileError("import global redeclared in same scope"sv, importNode); + } else { + auto& scope = currentScope(); + scope.importedGlobal = std::make_unique(); + _importedGlobal = scope.importedGlobal.get(); + _importedGlobal->importingScope = &scope; + _importedGlobal->indent = indent(); + _importedGlobal->nl = nl(stmt); + _importedGlobal->globalCodeLine = &temp.emplace_back(); + } + } else { + transformStatement(stmt, temp); + } if (isRoot && !_rootDefs.empty()) { auto last = std::move(temp.back()); temp.pop_back(); @@ -5338,6 +5377,14 @@ private: transformNode(); } } + if (auto importedGlobal = currentScope().importedGlobal.get()) { + str_list globalCodes; + for (const auto& global : importedGlobal->globals) { + globalCodes.emplace_back(importedGlobal->indent + "local "s + global + " = "s + global + importedGlobal->nl); + } + *importedGlobal->globalCodeLine = join(globalCodes); + _importedGlobal = nullptr; + } out.push_back(join(temp)); } else { out.push_back(Empty); @@ -9313,7 +9360,12 @@ private: } else { out.push_back(name + " = "s + name); } - if (_config.lintGlobalVariable && !isLocal(name)) { + if (_importedGlobal) { + if (_importedGlobal->globals.find(name) == _importedGlobal->globals.end() && !isSolidDefined(name)) { + _importedGlobal->globals.insert(name); + _importedGlobal->importingScope->vars->insert_or_assign(name, VarType::LocalConst); + } + } else if (_config.lintGlobalVariable && !isLocal(name)) { auto key = name + ':' + std::to_string(pair->name->m_begin.m_line) + ':' + std::to_string(pair->name->m_begin.m_col); if (_globals.find(key) == _globals.end()) { _globals[key] = {name, pair->name->m_begin.m_line, pair->name->m_begin.m_col, _funcLevel > 1 ? AccessType::Capture : AccessType::Read, isSolidDefined(name)}; @@ -10348,8 +10400,10 @@ private: } assignment->action.set(assign); transformAssignment(assignment, out); - for (const auto& name : varNames) { - markVarGlobalConst(name); + if (global->constAttrib) { + for (const auto& name : varNames) { + markVarGlobalConst(name); + } } } else { for (auto name : values->nameList->names.objects()) { diff --git a/src/yuescript/yue_parser.cpp b/src/yuescript/yue_parser.cpp index 1dfe978..6990e31 100644 --- a/src/yuescript/yue_parser.cpp +++ b/src/yuescript/yue_parser.cpp @@ -464,7 +464,9 @@ YueParser::YueParser() { ImportGlobal = Seperator >> UnicodeName >> *('.' >> UnicodeName) >> space >> not_(',' | key("from")) >> -(key("as") >> space >> must_variable); - Import = key("import") >> space >> (ImportGlobal | ImportAs | ImportFrom | invalid_import_syntax_error) | FromImport; + ImportAllGlobal = key("global"); + + Import = key("import") >> space >> (ImportAllGlobal | ImportGlobal | ImportAs | ImportFrom | invalid_import_syntax_error) | FromImport; Label = "::" >> (and_(LuaKeyword >> "::") >> keyword_as_label_error | UnicodeName >> "::"); @@ -982,7 +984,7 @@ YueParser::YueParser() { return true; }) | invalid_export_syntax_error - ) >> not_(space >> StatementAppendix); + ); VariablePair = ':' >> Variable; @@ -1125,14 +1127,14 @@ YueParser::YueParser() { StatementAppendix = (IfLine | WhileLine | CompFor) >> space; Statement = ( - Import | While | Repeat | For | - Return | Local | Global | Export | Macro | - MacroInPlace | BreakLoop | Label | Goto | ShortTabAppending | + Import | Export | Global | Macro | MacroInPlace | Label + ) | ( + Local | While | Repeat | For | Return | + BreakLoop | Goto | ShortTabAppending | LocalAttrib | Backcall | PipeBody | ExpListAssign | ChainAssign | StatementAppendix >> empty_block_error | and_(key("else") | key("elseif") | key("when")) >> dangling_clause_error - ) >> space >> - -StatementAppendix; + ) >> space >> -StatementAppendix; StatementSep = white >> (set("('\"") | "[[" | "[="); diff --git a/src/yuescript/yue_parser.h b/src/yuescript/yue_parser.h index acb56d0..c516ccd 100644 --- a/src/yuescript/yue_parser.h +++ b/src/yuescript/yue_parser.h @@ -352,6 +352,7 @@ private: AST_RULE(ImportTabLit); AST_RULE(ImportAs); AST_RULE(ImportGlobal); + AST_RULE(ImportAllGlobal); AST_RULE(Import); AST_RULE(Label); AST_RULE(Goto); -- cgit v1.2.3-55-g6feb