From 94edfbc8c7d62d700dfb59334a0ed3beedd49493 Mon Sep 17 00:00:00 2001 From: Li Jin Date: Mon, 5 Aug 2024 17:20:00 +0800 Subject: [PATCH] add macros generating macros feature. --- doc/docs/doc/README.md | 42 +++++++++++++++ doc/docs/zh/doc/README.md | 41 ++++++++++++++ spec/inputs/macro.yue | 33 ++++++++++++ spec/inputs/macro_export.yue | 18 +++++++ spec/outputs/codes_from_doc.lua | 2 + spec/outputs/codes_from_doc_zh.lua | 2 + spec/outputs/macro.lua | 11 +++- src/yuescript/yue_ast.cpp | 5 +- src/yuescript/yue_ast.h | 10 +++- src/yuescript/yue_compiler.cpp | 86 +++++++++++++++++++++--------- src/yuescript/yue_parser.cpp | 3 +- src/yuescript/yue_parser.h | 1 + 12 files changed, 225 insertions(+), 29 deletions(-) diff --git a/doc/docs/doc/README.md b/doc/docs/doc/README.md index 811497a..378e0f4 100755 --- a/doc/docs/doc/README.md +++ b/doc/docs/doc/README.md @@ -374,6 +374,48 @@ print $LINE -- get number 2 +### Generating Macros with Macros + +In Yuescript, macro functions allow you to generate code at compile time. By nesting macro functions, you can create more complex generation patterns. This feature enables you to define a macro function that generates another macro function, allowing for more dynamic code generation. + +```moonscript +macro Enum = (...) -> + items = {...} + itemSet = {item, true for item in *items} + (item) -> + error "got \"#{item}\", expecting one of #{table.concat items, ', '}" unless itemSet[item] + "\"#{item}\"" + +macro BodyType = $Enum( + Static + Dynamic + Kinematic +) + +print "Valid enum type:", $BodyType Static +-- print "Compilation error with enum type:", $BodyType Unknown +``` + + +
+macro Enum = (...) ->
+	items = {...}
+	itemSet = {item, true for item in *items}
+	(item) ->
+		error "got \"#{item}\", expecting one of #{table.concat items, ', '}" unless itemSet[item]
+		"\"#{item}\""
+
+macro BodyType = $Enum(
+	Static
+	Dynamic
+	Kinematic
+)
+
+print "Valid enum type:", $BodyType Static
+-- print "Compilation error with enum type:", $BodyType Unknown
+
+
+ ## Operator All of Lua's binary and unary operators are available. Additionally **!=** is as an alias for **~=**, and either **\\** or **::** can be used to write a chaining function call like `tb\func!` or `tb::func!`. And Yuescipt offers some other special operators to write more expressive codes. diff --git a/doc/docs/zh/doc/README.md b/doc/docs/zh/doc/README.md index 0257dee..90d1820 100755 --- a/doc/docs/zh/doc/README.md +++ b/doc/docs/zh/doc/README.md @@ -371,6 +371,47 @@ print $LINE -- 获取当前代码行数:2 +### 用宏生成宏 + +在月之脚本中,宏函数允许你在编译时生成代码。通过嵌套的宏函数,你可以创建更复杂的生成模式。这个特性允许你定义一个宏函数,用它来生成另一个宏函数,从而实现更加动态的代码生成。 + +```moonscript +macro Enum = (...) -> + items = {...} + itemSet = {item, true for item in *items} + (item) -> + error "got \"#{item}\", expecting one of #{table.concat items, ', '}" unless itemSet[item] + "\"#{item}\"" + +macro BodyType = $Enum( + Static + Dynamic + Kinematic +) + +print "有效的枚举类型:", $BodyType Static +-- print "编译报错的枚举类型:", $BodyType Unknown +``` + +
+macro Enum = (...) ->
+	items = {...}
+	itemSet = {item, true for item in *items}
+	(item) ->
+		error "got \"#{item}\", expecting one of #{table.concat items, ', '}" unless itemSet[item]
+		"\"#{item}\""
+
+macro BodyType = $Enum(
+	Static
+	Dynamic
+	Kinematic
+)
+
+print "有效的枚举类型:", $BodyType Static
+-- print "编译报错的枚举类型:", $BodyType Unknown
+
+
+ ## 操作符 Lua的所有二元和一元操作符在月之脚本中都是可用的。此外,**!=** 符号是 **~=** 的别名,而 **\\** 或 **::** 均可用于编写链式函数调用,如写作 `tb\func!` 或 `tb::func!`。此外月之脚本还提供了一些其他特殊的操作符,以编写更具表达力的代码。 diff --git a/spec/inputs/macro.yue b/spec/inputs/macro.yue index ae14f53..a2e1046 100644 --- a/spec/inputs/macro.yue +++ b/spec/inputs/macro.yue @@ -5,6 +5,39 @@ import "macro_export" as { import "macro_todo" as $ +macro WindowFlag = $enum( + NoNav + NoDecoration + NoTitleBar + NoResize + NoMove + NoScrollbar + NoScrollWithMouse + NoCollapse + AlwaysAutoResize + NoSavedSettings + NoInputs + MenuBar + HorizontalScrollbar + NoFocusOnAppearing + NoBringToFrontOnFocus + AlwaysVerticalScrollbar + AlwaysHorizontalScrollbar + NoNavInputs + NoNavFocus + UnsavedDocument +) + +print $WindowFlag AlwaysAutoResize +print $WindowFlag( + NoNav + NoDecoration + NoTitleBar + NoResize + NoMove + NoScrollbar +) + $asserts item == nil $myconfig false diff --git a/spec/inputs/macro_export.yue b/spec/inputs/macro_export.yue index eec5848..cc7d459 100644 --- a/spec/inputs/macro_export.yue +++ b/spec/inputs/macro_export.yue @@ -44,6 +44,24 @@ do _dst_.#{field} = _src_.#{field} "}" +export macro enum = (...) -> + items = {...} + items = [item\gsub('"', '') for item in *items] + itemSet = {item, true for item in *items} + (...) -> + count = select "#", ... + if 1 < count + result = "[" + for i = 1, count + item = select i, ... + error "got \"#{item}\", expecting one of #{table.concat items, ', '}" unless itemSet[item] + result ..= "\"#{item}\"," + result .. "]" + else + item = select 1, ... + error "got \"#{item}\", expecting one of #{table.concat items, ', '}" unless itemSet[item] + "\"#{item}\"" + $ -> global debugMode = true global debugMacro = true diff --git a/spec/outputs/codes_from_doc.lua b/spec/outputs/codes_from_doc.lua index 6f61569..4073056 100644 --- a/spec/outputs/codes_from_doc.lua +++ b/spec/outputs/codes_from_doc.lua @@ -71,6 +71,7 @@ if cond then end print("yuescript") print(3) +print("Valid enum type:", "Static") if tb ~= nil then tb:func() end @@ -2068,6 +2069,7 @@ if cond then end print("yuescript") print(3) +print("Valid enum type:", "Static") if tb ~= nil then tb:func() end diff --git a/spec/outputs/codes_from_doc_zh.lua b/spec/outputs/codes_from_doc_zh.lua index aa53926..f251450 100644 --- a/spec/outputs/codes_from_doc_zh.lua +++ b/spec/outputs/codes_from_doc_zh.lua @@ -71,6 +71,7 @@ if cond then end print("yuescript") print(3) +print("有效的枚举类型:", "Static") if tb ~= nil then tb:func() end @@ -2062,6 +2063,7 @@ if cond then end print("yuescript") print(3) +print("有效的枚举类型:", "Static") if tb ~= nil then tb:func() end diff --git a/spec/outputs/macro.lua b/spec/outputs/macro.lua index 2ee3e0a..953c260 100644 --- a/spec/outputs/macro.lua +++ b/spec/outputs/macro.lua @@ -1,3 +1,12 @@ +print("AlwaysAutoResize") +print({ + "NoNav", + "NoDecoration", + "NoTitleBar", + "NoResize", + "NoMove", + "NoScrollbar" +}) do assert(item == nil) end @@ -285,7 +294,7 @@ print((setmetatable({ return 998 end })) -print("current line: " .. tostring(268)) +print("current line: " .. tostring(301)) do -- TODO end diff --git a/src/yuescript/yue_ast.cpp b/src/yuescript/yue_ast.cpp index 3a22345..a454908 100644 --- a/src/yuescript/yue_ast.cpp +++ b/src/yuescript/yue_ast.cpp @@ -1362,8 +1362,11 @@ std::string MacroLit_t::to_string(void* ud) const { } return line; } +std::string MacroFunc_t::to_string(void* ud) const { + return name->to_string(ud) + invoke->to_string(ud); +} std::string Macro_t::to_string(void* ud) const { - return "macro "s + name->to_string(ud) + " = "s + macroLit->to_string(ud); + return "macro "s + name->to_string(ud) + " = "s + decl->to_string(ud); } std::string MacroInPlace_t::to_string(void* ud) const { auto info = reinterpret_cast(ud); diff --git a/src/yuescript/yue_ast.h b/src/yuescript/yue_ast.h index 2ea0a85..b287202 100644 --- a/src/yuescript/yue_ast.h +++ b/src/yuescript/yue_ast.h @@ -774,6 +774,12 @@ AST_NODE(MacroLit) AST_MEMBER(MacroLit, &argsDef, &body) AST_END(MacroLit, "macro_lit"sv) +AST_NODE(MacroFunc) + ast_ptr name; + ast_sel invoke; + AST_MEMBER(MacroFunc, &name, &invoke) +AST_END(MacroFunc, "macro_func"sv) + AST_NODE(MacroInPlace) ast_ptr body; AST_MEMBER(MacroInPlace, &body) @@ -781,8 +787,8 @@ AST_END(MacroInPlace, "macro_in_place"sv) AST_NODE(Macro) ast_ptr name; - ast_ptr macroLit; - AST_MEMBER(Macro, &name, ¯oLit) + ast_sel decl; + AST_MEMBER(Macro, &name, &decl) AST_END(Macro, "macro"sv) AST_NODE(NameOrDestructure) diff --git a/src/yuescript/yue_compiler.cpp b/src/yuescript/yue_compiler.cpp index 7b34d0e..994ab57 100644 --- a/src/yuescript/yue_compiler.cpp +++ b/src/yuescript/yue_compiler.cpp @@ -75,7 +75,7 @@ static std::unordered_set Metamethods = { "close"s // Lua 5.4 }; -const std::string_view version = "0.23.8"sv; +const std::string_view version = "0.23.9"sv; const std::string_view extension = "yue"sv; class CompileError : public std::logic_error { @@ -5065,7 +5065,36 @@ class YueCompilerImpl { throw CompileError("can not define macro outside the root block"sv, macro); } auto macroName = _parser.toString(macro->name); - auto argsDef = macro->macroLit->argsDef.get(); + if (auto macroFunc = macro->decl.as()) { + auto chainValue = macroFunc->new_ptr(); + auto callable = macroFunc->new_ptr(); + callable->item.set(macroFunc->name); + chainValue->items.push_back(callable); + chainValue->items.push_back(macroFunc->invoke); + pushCurrentModule(); // cur + int top = lua_gettop(L) - 1; + DEFER(lua_settop(L, top)); + if (auto builtinCode = expandMacroChain(chainValue)) { + throw CompileError("macro generating function must return a function"sv, chainValue); + } // cur res + if (lua_isfunction(L, -1) == 0) { + throw CompileError("macro generating function must return a function"sv, chainValue); + } // cur macro + if (exporting && _config.exporting && !_config.module.empty()) { + pushModuleTable(_config.module); // cur macro module + lua_pushlstring(L, macroName.c_str(), macroName.size()); // cur macro module name + lua_pushvalue(L, -3); // cur macro module name macro + lua_rawset(L, -3); // cur macro module + lua_pop(L, 1); + } // cur macro + lua_pushlstring(L, macroName.c_str(), macroName.size()); // cur macro name + lua_insert(L, -2); // cur name macro + lua_rawset(L, -3); // cur[name] = macro, cur + out.push_back(Empty); + return; + } + auto macroLit = macro->decl.to(); + auto argsDef = macroLit->argsDef.get(); str_list newArgs; if (argsDef) { for (auto def_ : argsDef->definitions.objects()) { @@ -5088,7 +5117,7 @@ class YueCompilerImpl { } } _buf << "("sv << join(newArgs, ","sv) << ")->"sv; - _buf << _parser.toString(macro->macroLit->body); + _buf << _parser.toString(macroLit->body); auto macroCodes = clearBuf(); _buf << "=(macro "sv << macroName << ")"; auto chunkName = clearBuf(); @@ -5101,22 +5130,22 @@ class YueCompilerImpl { pushOptions(macro->m_begin.m_line - 1); // cur loadstring codes chunk options if (lua_pcall(L, 3, 2, 0) != 0) { // loadstring(codes,chunk,options), cur f err std::string err = lua_tostring(L, -1); - throw CompileError("failed to load macro codes\n"s + err, macro->macroLit); + throw CompileError("failed to load macro codes\n"s + err, macroLit); } // cur f err if (lua_isnil(L, -2) != 0) { // f == nil, cur f err std::string err = lua_tostring(L, -1); - throw CompileError("failed to load macro codes, at (macro "s + macroName + "): "s + err, macro->macroLit); + throw CompileError("failed to load macro codes, at (macro "s + macroName + "): "s + err, macroLit); } lua_pop(L, 1); // cur f pushYue("pcall"sv); // cur f pcall lua_insert(L, -2); // cur pcall f if (lua_pcall(L, 1, 2, 0) != 0) { // f(), cur success macro std::string err = lua_tostring(L, -1); - throw CompileError("failed to generate macro function\n"s + err, macro->macroLit); + throw CompileError("failed to generate macro function\n"s + err, macroLit); } // cur success res if (lua_toboolean(L, -2) == 0) { std::string err = lua_tostring(L, -1); - throw CompileError("failed to generate macro function\n"s + err, macro->macroLit); + throw CompileError("failed to generate macro function\n"s + err, macroLit); } // cur true macro lua_remove(L, -2); // cur macro if (exporting && _config.exporting && !_config.module.empty()) { @@ -6180,23 +6209,20 @@ class YueCompilerImpl { return Empty; } - std::tuple expandMacroStr(ChainValue_t* chainValue) { + std::optional expandMacroChain(ChainValue_t* chainValue) { const auto& chainList = chainValue->items.objects(); auto x = ast_to(chainList.front())->item.to(); auto macroName = _parser.toString(x->name); if (!_useModule) { auto code = expandBuiltinMacro(macroName, x); - if (!code.empty()) return {Empty, code, {}}; + if (!code.empty()) return code; throw CompileError("can not resolve macro"sv, x); } - pushCurrentModule(); // cur - int top = lua_gettop(L) - 1; - DEFER(lua_settop(L, top)); lua_pushlstring(L, macroName.c_str(), macroName.size()); // cur macroName lua_rawget(L, -2); // cur[macroName], cur macroFunc if (lua_isfunction(L, -1) == 0) { auto code = expandBuiltinMacro(macroName, x); - if (!code.empty()) return {Empty, code, {}}; + if (!code.empty()) return code; throw CompileError("can not resolve macro"sv, x); } // cur macroFunc pushYue("pcall"sv); // cur macroFunc pcall @@ -6261,32 +6287,44 @@ class YueCompilerImpl { throw CompileError("failed to expand macro: "s + err, x); } lua_remove(L, -2); // cur res + return std::nullopt; + } + + std::tuple expandMacroStr(ChainValue_t* chainValue) { + auto x = chainValue->items.front(); + pushCurrentModule(); // cur + int top = lua_gettop(L) - 1; + DEFER(lua_settop(L, top)); + auto builtinCode = expandMacroChain(chainValue); + if (builtinCode) { + return {Empty, builtinCode.value(), {}}; + } // cur res if (lua_isstring(L, -1) == 0 && lua_istable(L, -1) == 0) { - throw CompileError("macro function must return string or table"sv, x); + throw CompileError("macro function must return a string or a table"sv, x); } // cur res std::string codes; std::string type; str_list localVars; - if (lua_istable(L, -1) != 0) { - lua_getfield(L, -1, "code"); // cur res code + if (lua_istable(L, -1) != 0) { // cur tab + lua_getfield(L, -1, "code"); // cur tab code if (lua_isstring(L, -1) != 0) { codes = lua_tostring(L, -1); } else { throw CompileError("macro table must contain field \"code\" of string"sv, x); } - lua_pop(L, 1); // cur res - lua_getfield(L, -1, "type"); // cur res type + lua_pop(L, 1); // cur tab + lua_getfield(L, -1, "type"); // cur tab type if (lua_isstring(L, -1) != 0) { type = lua_tostring(L, -1); } if (type != "lua"sv && type != "text"sv) { throw CompileError("macro table must contain field \"type\" of value \"lua\" or \"text\""sv, x); } - lua_pop(L, 1); // cur res - lua_getfield(L, -1, "locals"); // cur res locals + lua_pop(L, 1); // cur tab + lua_getfield(L, -1, "locals"); // cur tab locals if (lua_istable(L, -1) != 0) { for (int i = 0; i < static_cast(lua_objlen(L, -1)); i++) { - lua_rawgeti(L, -1, i + 1); // cur res locals item + lua_rawgeti(L, -1, i + 1); // cur tab locals item size_t len = 0; if (lua_isstring(L, -1) == 0) { throw CompileError("macro table field \"locals\" must be a table of strings"sv, x); @@ -6297,11 +6335,11 @@ class YueCompilerImpl { } else { throw CompileError("macro table field \"locals\" must contain names for local variables, got \""s + std::string(name, len) + '"', x); } - lua_pop(L, 1); + lua_pop(L, 1); // cur tab locals } } - lua_pop(L, 1); // cur res - } else { + lua_pop(L, 1); // cur tab + } else { // cur code codes = lua_tostring(L, -1); } Utils::trim(codes); diff --git a/src/yuescript/yue_parser.cpp b/src/yuescript/yue_parser.cpp index 3ffaf18..986f67a 100644 --- a/src/yuescript/yue_parser.cpp +++ b/src/yuescript/yue_parser.cpp @@ -870,7 +870,8 @@ YueParser::YueParser() { MacroName = '$' >> UnicodeName; macro_args_def = '(' >> white >> -FnArgDefList >> white >> ')'; MacroLit = -(macro_args_def >> space) >> "->" >> space >> Body; - Macro = key("macro") >> space >> UnicodeName >> space >> '=' >> space >> MacroLit; + MacroFunc = MacroName >> (Invoke | InvokeArgs); + Macro = key("macro") >> space >> UnicodeName >> space >> '=' >> space >> (MacroLit | MacroFunc); MacroInPlace = '$' >> space >> "->" >> space >> Body; NameList = Seperator >> Variable >> *(space >> ',' >> space >> Variable); diff --git a/src/yuescript/yue_parser.h b/src/yuescript/yue_parser.h index 05aa9e6..6623653 100644 --- a/src/yuescript/yue_parser.h +++ b/src/yuescript/yue_parser.h @@ -396,6 +396,7 @@ class YueParser { AST_RULE(FunLit); AST_RULE(MacroName); AST_RULE(MacroLit); + AST_RULE(MacroFunc); AST_RULE(Macro); AST_RULE(MacroInPlace); AST_RULE(NameOrDestructure);