From 7ee395d918b97795f151c24ed877bfcc2edf602a Mon Sep 17 00:00:00 2001 From: Li Jin Date: Thu, 25 Dec 2025 00:00:08 +0800 Subject: Added named vararg support. --- doc/docs/doc/README.md | 49 ++++++++++++++++++++++++++ doc/docs/zh/doc/README.md | 49 ++++++++++++++++++++++++++ spec/inputs/vararg.yue | 44 +++++++++++++++++++++++ spec/outputs/codes_from_doc.lua | 60 +++++++++++++++++++++++++++++++ spec/outputs/codes_from_doc_zh.lua | 60 +++++++++++++++++++++++++++++++ spec/outputs/vararg.lua | 72 ++++++++++++++++++++++++++++++++++++++ src/yuescript/yue_ast.cpp | 3 ++ src/yuescript/yue_ast.h | 7 +++- src/yuescript/yue_compiler.cpp | 29 +++++++++++---- src/yuescript/yue_parser.cpp | 5 +-- src/yuescript/yue_parser.h | 1 + 11 files changed, 369 insertions(+), 10 deletions(-) diff --git a/doc/docs/doc/README.md b/doc/docs/doc/README.md index 1a46e59..d2f838d 100755 --- a/doc/docs/doc/README.md +++ b/doc/docs/doc/README.md @@ -1528,6 +1528,55 @@ print ok, count, first +### Named Varargs + +You can use the `(...t) ->` syntax to automatically store varargs into a named table. This table will contain all passed arguments (including `nil` values), and the `n` field of the table will store the actual number of arguments passed (including `nil` values). + +```moonscript +f = (...t) -> + print "argument count:", t.n + print "table length:", #t + for i = 1, t.n + print t[i] + +f 1, 2, 3 +f "a", "b", "c", "d" +f! + +-- Handling cases with nil values +process = (...args) -> + sum = 0 + for i = 1, args.n + if args[i] != nil and type(args[i]) == "number" + sum += args[i] + sum + +process 1, nil, 3, nil, 5 +``` + +
+f = (...t) ->
+  print "argument count:", t.n
+  print "table length:", #t
+  for i = 1, t.n
+    print t[i]
+
+f 1, 2, 3
+f "a", "b", "c", "d"
+f!
+
+-- Handling cases with nil values
+process = (...args) ->
+  sum = 0
+  for i = 1, args.n
+    if args[i] != nil and type(args[i]) == "number"
+      sum += args[i]
+  sum
+
+process 1, nil, 3, nil, 5
+
+
+ ## Whitespace YueScript is a whitespace significant language. You have to write some code block in the same indent with space **' '** or tab **'\t'** like function body, value list and some control blocks. And expressions containing different whitespaces might mean different things. Tab is treated like 4 space, but it's better not mix the use of spaces and tabs. diff --git a/doc/docs/zh/doc/README.md b/doc/docs/zh/doc/README.md index b348e06..34d577c 100755 --- a/doc/docs/zh/doc/README.md +++ b/doc/docs/zh/doc/README.md @@ -1526,6 +1526,55 @@ print ok, count, first +### 命名变长参数 + +你可以使用 `(...t) ->` 语法来将变长参数自动存储到一个命名表中。这个表会包含所有传入的参数(包括 `nil` 值),并且会在表的 `n` 字段中存储实际传入的参数个数(包括 `nil` 值在内的个数)。 + +```moonscript +f = (...t) -> + print "参数个数:", t.n + print "表长度:", #t + for i = 1, t.n + print t[i] + +f 1, 2, 3 +f "a", "b", "c", "d" +f! + +-- 处理包含 nil 的情况 +process = (...args) -> + sum = 0 + for i = 1, args.n + if args[i] != nil and type(args[i]) == "number" + sum += args[i] + sum + +process 1, nil, 3, nil, 5 +``` + +
+f = (...t) ->
+  print "参数个数:", t.n
+  print "表长度:", #t
+  for i = 1, t.n
+    print t[i]
+
+f 1, 2, 3
+f "a", "b", "c", "d"
+f!
+
+-- 处理包含 nil 的情况
+process = (...args) ->
+  sum = 0
+  for i = 1, args.n
+    if args[i] != nil and type(args[i]) == "number"
+      sum += args[i]
+  sum
+
+process 1, nil, 3, nil, 5
+
+
+ ## 空白 月之脚本是一个对空白敏感的语言。你必须在相同的缩进中使用空格 **' '** 或制表符 **'\t'** 来编写一些代码块,如函数体、值列表和一些控制块。包含不同空白的表达式可能意味着不同的事情。制表符被视为4个空格,但最好不要混合使用空格和制表符。 diff --git a/spec/inputs/vararg.yue b/spec/inputs/vararg.yue index 6100250..4f8a0d7 100644 --- a/spec/inputs/vararg.yue +++ b/spec/inputs/vararg.yue @@ -86,3 +86,47 @@ join = (...) -> print ... nil +do + f1 = (...t) -> + print t.n + print #t + for i = 1, t.n + print t[i] + + f1 1, 2, 3 + f1 "a", "b", "c", "d" + f1! + + f2 = (...args) -> + print "args count:", args.n + print "args length:", #args + for i = 1, args.n + if args[i] == nil + print "position", i, "is nil" + else + print "position", i, ":", args[i] + + f2 1, nil, 3, nil, 5 + + f3 = (prefix, ...items) -> + result = {} + for i = 1, items.n + result[i] = prefix .. tostring items[i] + result + + f3 "item_", 1, 2, 3 + + f4 = (...empty) -> + print "empty count:", empty.n + print "empty length:", #empty + + f4! + + process = (...data) -> + sum = 0 + for i = 1, data.n + if type(data[i]) == "number" + sum += data[i] + sum + + process 1, 2, 3, "skip", 5 diff --git a/spec/outputs/codes_from_doc.lua b/spec/outputs/codes_from_doc.lua index d6617a9..de5abdd 100644 --- a/spec/outputs/codes_from_doc.lua +++ b/spec/outputs/codes_from_doc.lua @@ -737,6 +737,36 @@ 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) @@ -3215,6 +3245,36 @@ 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) diff --git a/spec/outputs/codes_from_doc_zh.lua b/spec/outputs/codes_from_doc_zh.lua index ec84112..6a6c38c 100644 --- a/spec/outputs/codes_from_doc_zh.lua +++ b/spec/outputs/codes_from_doc_zh.lua @@ -737,6 +737,36 @@ 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) @@ -3209,6 +3239,36 @@ 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) diff --git a/spec/outputs/vararg.lua b/spec/outputs/vararg.lua index 254aa6a..9f97681 100644 --- a/spec/outputs/vararg.lua +++ b/spec/outputs/vararg.lua @@ -294,3 +294,75 @@ join = function(...) end return nil end +do + local f1 + f1 = function(...) + local t = { + n = select("#", ...), + ... + } + print(t.n) + print(#t) + for i = 1, t.n do + print(t[i]) + end + end + f1(1, 2, 3) + f1("a", "b", "c", "d") + f1() + local f2 + f2 = function(...) + local args = { + n = select("#", ...), + ... + } + print("args count:", args.n) + print("args length:", #args) + for i = 1, args.n do + if args[i] == nil then + print("position", i, "is nil") + else + print("position", i, ":", args[i]) + end + end + end + f2(1, nil, 3, nil, 5) + local f3 + f3 = function(prefix, ...) + local items = { + n = select("#", ...), + ... + } + local result = { } + for i = 1, items.n do + result[i] = prefix .. tostring(items[i]) + end + return result + end + f3("item_", 1, 2, 3) + local f4 + f4 = function(...) + local empty = { + n = select("#", ...), + ... + } + print("empty count:", empty.n) + return print("empty length:", #empty) + end + f4() + local process + process = function(...) + local data = { + n = select("#", ...), + ... + } + local sum = 0 + for i = 1, data.n do + if type(data[i]) == "number" then + sum = sum + data[i] + end + end + return sum + end + return process(1, 2, 3, "skip", 5) +end diff --git a/src/yuescript/yue_ast.cpp b/src/yuescript/yue_ast.cpp index be0ec45..55b5d03 100644 --- a/src/yuescript/yue_ast.cpp +++ b/src/yuescript/yue_ast.cpp @@ -82,6 +82,9 @@ std::string SelfClass_t::to_string(void*) const { std::string VarArg_t::to_string(void*) const { return "..."s; } +std::string VarArgDef_t::to_string(void* ud) const { + return "..."s + name->to_string(ud); +} std::string Seperator_t::to_string(void*) const { return {}; } diff --git a/src/yuescript/yue_ast.h b/src/yuescript/yue_ast.h index b7cd73e..885a038 100644 --- a/src/yuescript/yue_ast.h +++ b/src/yuescript/yue_ast.h @@ -134,6 +134,11 @@ AST_NODE(KeyName) AST_MEMBER(KeyName, &name) AST_END(KeyName) +AST_NODE(VarArgDef) + ast_ptr name; + AST_MEMBER(VarArgDef, &name) +AST_END(VarArgDef) + AST_LEAF(VarArg) AST_END(VarArg) @@ -790,7 +795,7 @@ AST_END(FnArgDef) AST_NODE(FnArgDefList) ast_ptr sep; ast_list definitions; - ast_ptr varArg; + ast_ptr varArg; ast_ptr label; AST_MEMBER(FnArgDefList, &sep, &definitions, &varArg, &label) AST_END(FnArgDefList) diff --git a/src/yuescript/yue_compiler.cpp b/src/yuescript/yue_compiler.cpp index 77643d3..cae01dd 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.3"sv; +const std::string_view version = "0.30.4"sv; const std::string_view extension = "yue"sv; class CompileError : public std::logic_error { @@ -5872,18 +5872,33 @@ private: temp.emplace_back(indent() + "local "s + join(names, ", "sv) + nl(def)); transformAssignment(arg.assignment, temp); } - if (varNames.empty()) + if (varNames.empty()) { varNames = arg.name; - else + } else { varNames.append(", "s + arg.name); + } } if (argDefList->varArg) { + std::string varStr; + if (auto varName = argDefList->varArg->name.get()) { + varStr = variableToString(varName); + int target = getLuaTarget(varName); + forceAddToScope(varStr); + if (target < 505) { + temp.push_back(indent() + "local "s + varStr + " = {"s + nl(varName)); + temp.push_back(indent(1) + "n = "s + globalVar("select", varName, AccessType::Read) + "(\"#\", ...),"s + nl(varName)); + temp.push_back(indent(1) + "..."s + nl(varName)); + temp.push_back(indent() + '}' + nl(varName)); + varStr.clear(); + } + } auto& arg = argItems.emplace_back(); arg.name = "..."sv; - if (varNames.empty()) - varNames = arg.name; - else - varNames.append(", "s + arg.name); + if (varNames.empty()) { + varNames = arg.name + varStr; + } else { + varNames.append(", "s + arg.name + varStr); + } _varArgs.top().hasVar = true; } if (assignSelf) { diff --git a/src/yuescript/yue_parser.cpp b/src/yuescript/yue_parser.cpp index 1999721..17b10c6 100644 --- a/src/yuescript/yue_parser.cpp +++ b/src/yuescript/yue_parser.cpp @@ -308,6 +308,7 @@ YueParser::YueParser() { SelfItem = SelfClassName | SelfClass | SelfName | Self; KeyName = SelfItem | Name | UnicodeName; + VarArgDef = "..." >> -(space >> Variable); VarArg = "..."; auto getIndent = [](const item_t& item) -> int { @@ -1032,8 +1033,8 @@ YueParser::YueParser() { check_vararg_position = and_(white >> (')' | key("using"))) | white >> -(',' >> white) >> vararg_position_error; var_arg_def = ( - VarArg | - +space_break >> push_indent_match >> ensure(space >> VarArg >> -(space >> '`' >> space >> Name), pop_indent) + VarArgDef | + +space_break >> push_indent_match >> ensure(space >> VarArgDef >> -(space >> '`' >> space >> Name), pop_indent) ) >> check_vararg_position; FnArgDefList = Seperator >> diff --git a/src/yuescript/yue_parser.h b/src/yuescript/yue_parser.h index b68742f..7deaa18 100644 --- a/src/yuescript/yue_parser.h +++ b/src/yuescript/yue_parser.h @@ -333,6 +333,7 @@ private: AST_RULE(SelfItem); AST_RULE(KeyName); AST_RULE(VarArg); + AST_RULE(VarArgDef); AST_RULE(Seperator); AST_RULE(NameList); AST_RULE(LocalFlag); -- cgit v1.2.3-55-g6feb