From 2ff18b4fb66d25d22e5a25fb386fe171853e0b06 Mon Sep 17 00:00:00 2001 From: Li Jin <dragon-fly@qq.com> Date: Fri, 19 Nov 2021 13:23:11 +0800 Subject: try to fix issue #69 with new macro functions. add builtin macro $MODULE and $LINE. --- spec/inputs/macro-teal.yue | 20 +++--- spec/inputs/macro.yue | 38 +++++++--- spec/outputs/macro.lua | 31 ++++++++ src/yue.cpp | 6 +- src/yuescript/stacktraceplus.h | 2 +- src/yuescript/yue_compiler.cpp | 157 ++++++++++++++++++++++------------------- src/yuescript/yue_compiler.h | 2 + src/yuescript/yuescript.cpp | 6 ++ src/yuescript/yuescript.h | 3 +- 9 files changed, 171 insertions(+), 94 deletions(-) diff --git a/spec/inputs/macro-teal.yue b/spec/inputs/macro-teal.yue index 3a9bb2b..951e882 100644 --- a/spec/inputs/macro-teal.yue +++ b/spec/inputs/macro-teal.yue @@ -4,8 +4,8 @@ $ -> options.target_extension = "tl" package.path ..= "?.lua;./spec/lib/?.lua" -macro to_lua = (codes)-> - "require('yue').to_lua(#{codes}, reserve_line_number:false, same_module:true)" +macro to_lua = (code)-> + "require('yue').to_lua(#{code}, reserve_line_number:false, same_module:true)" macro trim = (name)-> "if result = #{name}\\match '[\\'\"](.*)[\\'\"]' then result else #{name}" @@ -16,12 +16,12 @@ export macro local = (decl, value = nil)-> if not (name and type) error "invalid local varaible declaration for \"#{decl}\"" value = $to_lua(value)\gsub "^return ", "" - codes = if tl_enabled + code = if tl_enabled "local #{name}:#{$trim type} = #{value}" else "local #{name} = #{value}" { - :codes + :code type: "text" locals: {name} } @@ -37,28 +37,28 @@ export macro function = (decl, value)-> _, node = tl.parse_program tokens,{},"macro-function" args = table.concat [arg.tk for arg in *node[1].args],", " value = "(#{args})#{value}" - codes = if tl_enabled + code = if tl_enabled value = $to_lua(value)\match "function%([^\n]*%)(.*)end" "local function #{name}#{type}\n#{value}\nend" else value = $to_lua(value)\gsub "^return ", "" "local #{name} = #{value}" { - :codes + :code type: "text" locals: {name} } export macro record = (name, decl)-> import "yue" as {options:{:tl_enabled}} - codes = if tl_enabled + code = if tl_enabled "local record #{name} #{decl} end" else "local #{name} = {}" { - :codes + :code type: "text" locals: {name} } @@ -74,14 +74,14 @@ export macro method = (decl, value)-> _, node = tl.parse_program tokens,{},"macro-function" args = table.concat [arg.tk for arg in *node[1].args],", " value = "(#{args})->#{value\match "[%-=]>(.*)"}" - codes = if tl_enabled + code = if tl_enabled value = $to_lua(value)\match "^return function%(.-%)\n(.*)end" "function #{tab}#{sym}#{func}#{type}\n#{value}\nend" else value = $to_lua(value)\gsub "^return ", "" "#{tab}.#{func} = #{value}" { - :codes + :code type: "text" } diff --git a/spec/inputs/macro.yue b/spec/inputs/macro.yue index 2dd15ac..366a3d9 100644 --- a/spec/inputs/macro.yue +++ b/spec/inputs/macro.yue @@ -69,9 +69,9 @@ $foreach $filter($map({1,2,3}, _ * 2), _ > 4), print _ val = $pipe( {1, 2, 3} - $map(_ * 2) - $filter(_ > 4) - $reduce(0, _1 + _2) + [[$map(_ * 2)]] + [[$filter(_ > 4)]] + [[$reduce(0, _1 + _2)]] ) macro plus = (a, b)-> "#{a} + #{b}" @@ -110,8 +110,8 @@ do a += $get_inner_hygienic! print a -macro lua = (codes)-> { - :codes +macro lua = (code)-> { + :code type: "lua" } @@ -136,7 +136,7 @@ macro def = (fname, ...)-> args = {...} last = table.remove args { - codes: $showMacro "def", "local function #{fname}(#{table.concat args, ', '}) + code: $showMacro "def", "local function #{fname}(#{table.concat args, ', '}) #{last} end" type: "lua" @@ -155,7 +155,7 @@ $def sel, a, b, c, [[ $def dummy,[[]] macro insertComment = (text)-> { - codes: "-- #{text\match '[\'"](.*)[\'"]'}" + code: "-- #{text\match '[\'"](.*)[\'"]'}" type: "lua" } @@ -239,7 +239,7 @@ macro chainC = (...)-> else callable = itemCodes { - codes: $showMacro "chainC", callable + code: $showMacro "chainC", callable type: "lua" } @@ -253,7 +253,27 @@ $chainC( Destroy! ) +macro tb = -> "{'abc', a:123, call#:=> 998}" +print $tb[1], $tb.a, ($tb)!, $tb! + +print "current line: #{ $LINE }" + +macro todoInner = (module, line, msg)-> + print "TODO#{msg and ': ' .. msg or ''} in file #{module}, at line #{line}" + { + code: "-- TODO#{msg and ': ' .. msg or ''}" + type: "lua" + } + +macro todo = (msg)-> + if msg + "$todoInner $MODULE, $LINE, #{msg}" + else + "$todoInner $MODULE, $LINE" + +$todo + macro implicitReturnMacroIsAllowed = -> "print 'abc'\n123" -$implicitReturnMacroIsAllowed! +$implicitReturnMacroIsAllowed diff --git a/spec/outputs/macro.lua b/spec/outputs/macro.lua index a2430a2..7812182 100644 --- a/spec/outputs/macro.lua +++ b/spec/outputs/macro.lua @@ -229,5 +229,36 @@ end origin.transform.root.gameObject:Parents():Descendants():SelectEnable():SelectVisible():TagEqual("fx"):Where(function(x) return x.name:EndsWith("(Clone)") end):Destroy() +print((setmetatable({ + 'abc', + a = 123, +}, { + __call = function(self) + return 998 + end +}))[1], (setmetatable({ + 'abc', + a = 123, +}, { + __call = function(self) + return 998 + end +})).a, (setmetatable({ + 'abc', + a = 123, +}, { + __call = function(self) + return 998 + end +}))(), setmetatable({ + 'abc', + a = 123, +}, { + __call = function(self) + return 998 + end +})) +print("current line: " .. tostring(259)) +-- TODO print('abc') return 123 diff --git a/src/yue.cpp b/src/yue.cpp index b04bd92..1704b57 100644 --- a/src/yue.cpp +++ b/src/yue.cpp @@ -429,9 +429,11 @@ int main(int narg, const char** args) { std::string s( (std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); + auto conf = config; + conf.module = file.first; if (dumpCompileTime) { auto start = std::chrono::high_resolution_clock::now(); - auto result = yue::YueCompiler{YUE_ARGS}.compile(s, config); + auto result = yue::YueCompiler{YUE_ARGS}.compile(s, conf); auto end = std::chrono::high_resolution_clock::now(); if (!result.codes.empty()) { std::chrono::duration<double> diff = end - start; @@ -451,7 +453,7 @@ int main(int narg, const char** args) { return std::tuple{1, file.first, buf.str()}; } } - auto result = yue::YueCompiler{YUE_ARGS}.compile(s, config); + auto result = yue::YueCompiler{YUE_ARGS}.compile(s, conf); if (result.error.empty()) { if (!writeToFile) { return std::tuple{0, file.first, result.codes + '\n'}; diff --git a/src/yuescript/stacktraceplus.h b/src/yuescript/stacktraceplus.h index ac61ba7..83460ef 100644 --- a/src/yuescript/stacktraceplus.h +++ b/src/yuescript/stacktraceplus.h @@ -335,7 +335,7 @@ local function getYueLineNumber(fname, line) end if file_exist then local codes = yue.read_file(file_path) - local yueFile = codes:match("^%s*--%s*%[yue%]:%s*([^\n]*)") + local yueFile = codes:match("^%s*--%s*%[.*%]:%s*([^\n]*)") if yueFile then fname = yueFile:gsub("^%s*(.-)%s*$", "%1") source = codes diff --git a/src/yuescript/yue_compiler.cpp b/src/yuescript/yue_compiler.cpp index e004821..14e33a5 100755 --- a/src/yuescript/yue_compiler.cpp +++ b/src/yuescript/yue_compiler.cpp @@ -60,7 +60,7 @@ using namespace parserlib; typedef std::list<std::string> str_list; -const std::string_view version = "0.8.5"sv; +const std::string_view version = "0.9.0"sv; const std::string_view extension = "yue"sv; class YueCompilerImpl { @@ -68,11 +68,9 @@ public: #ifndef YUE_NO_MACRO YueCompilerImpl(lua_State* sharedState, const std::function<void(void*)>& luaOpen, - bool sameModule, - std::string_view moduleName = {}): + bool sameModule): L(sharedState), - _luaOpen(luaOpen), - _moduleName(moduleName) { + _luaOpen(luaOpen) { BLOCK_START BREAK_IF(!sameModule); BREAK_IF(!L); @@ -210,7 +208,6 @@ private: std::ostringstream _buf; std::ostringstream _joinBuf; const std::string _newLine = "\n"; - std::string _moduleName; enum class LocalMode { None = 0, @@ -863,16 +860,10 @@ private: bool isMacroChain(ChainValue_t* chainValue) const { const auto& chainList = chainValue->items.objects(); - BLOCK_START auto callable = ast_cast<Callable_t>(chainList.front()); - BREAK_IF(!callable); - BREAK_IF(!callable->item.is<MacroName_t>()); - if (chainList.size() == 1 || - !ast_is<Invoke_t,InvokeArgs_t>(*(++chainList.begin()))) { - throw std::logic_error(_info.errorMessage("macro expression must be followed by arguments list"sv, callable)); + if (callable && callable->item.is<MacroName_t>()) { + return true; } - return true; - BLOCK_END return false; } @@ -1052,7 +1043,7 @@ private: } } if (auto chainValue = singleValue->item.as<ChainValue_t>()) { - if (isChainValueCall(chainValue)) { + if (isChainValueCall(chainValue) || isMacroChain(chainValue)) { transformChainValue(chainValue, out, ExpUsage::Common); break; } @@ -3139,8 +3130,8 @@ private: throw std::logic_error(_info.errorMessage("failed to generate macro function\n"s + err, macro->macroLit)); } // cur true macro lua_remove(L, -2); // cur macro - if (exporting && !_moduleName.empty()) { - pushModuleTable(_moduleName); // cur macro module + 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 @@ -3929,11 +3920,23 @@ private: } #ifndef YUE_NO_MACRO + std::string expandBuiltinMacro(const std::string& name, ast_node* x) { + if (name == "LINE"sv) { + return std::to_string(x->m_begin.m_line + _config.lineOffset); + } + if (name == "MODULE"sv) { + return _config.module.empty() ? "\"yuescript\""s : '"' + _config.module + '"'; + } + return Empty; + } + std::tuple<std::string,std::string,str_list> expandMacroStr(ChainValue_t* chainValue) { const auto& chainList = chainValue->items.objects(); auto x = ast_to<Callable_t>(chainList.front())->item.to<MacroName_t>(); auto macroName = x->name ? _parser.toString(x->name) : Empty; if (!macroName.empty() && !_useModule) { + auto code = expandBuiltinMacro(macroName, x); + if (!code.empty()) return {Empty, code, {}}; throw std::logic_error(_info.errorMessage("can not resolve macro"sv, x)); } pushCurrentModule(); // cur @@ -3941,8 +3944,8 @@ private: DEFER(lua_settop(L, top)); if (macroName.empty()) { lua_pop(L, 1); // empty - auto item = *(++chainList.begin()); const node_container* args = nullptr; + auto item = *(++chainList.begin()); if (auto invoke = ast_cast<Invoke_t>(item)) { args = &invoke->args.objects(); } else { @@ -3993,60 +3996,69 @@ private: 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, {}}; throw std::logic_error(_info.errorMessage("can not resolve macro"sv, x)); } // cur macroFunc pushYue("pcall"sv); // cur macroFunc pcall lua_insert(L, -2); // cur pcall macroFunc - auto item = *(++chainList.begin()); const node_container* args = nullptr; - if (auto invoke = ast_cast<Invoke_t>(item)) { - args = &invoke->args.objects(); - } else { - args = &ast_to<InvokeArgs_t>(item)->args.objects(); - } - for (auto arg : *args) { - std::string str; - // check whether arg is reassembled - // do some workaround for pipe expression - if (ast_is<Exp_t>(arg) && arg->m_begin.m_it == arg->m_end.m_it) { - auto exp = static_cast<Exp_t*>(arg); - BLOCK_START - BREAK_IF(!exp->opValues.empty()); - auto chainValue = exp->getByPath<unary_exp_t, Value_t, ChainValue_t>(); - BREAK_IF(!chainValue); - BREAK_IF(!isMacroChain(chainValue)); - BREAK_IF(chainValue->items.size() != 2); - str = std::get<1>(expandMacroStr(chainValue)); - BLOCK_END - if (str.empty()) { - // exp is reassembled due to pipe expressions - // in transform stage, toString(exp) won't be able - // to convert its whole text content - str = _parser.toString(exp->pipeExprs.front()); - } - } else if (auto lstr = ast_cast<LuaString_t>(arg)) { - str = _parser.toString(lstr->content); - } else { - bool multiLineStr = false; - BLOCK_START - auto exp = ast_cast<Exp_t>(arg); - BREAK_IF(!exp); - auto value = singleValueFrom(exp); - BREAK_IF(!value); - auto lstr = value->getByPath<String_t, LuaString_t>(); - BREAK_IF(!lstr); - str = _parser.toString(lstr->content); - multiLineStr = true; - BLOCK_END - if (!multiLineStr) { + if (chainList.size() > 1) { + auto item = *(++chainList.begin()); + if (auto invoke = ast_cast<Invoke_t>(item)) { + args = &invoke->args.objects(); + } else if (auto invoke = ast_cast<InvokeArgs_t>(item)){ + args = &invoke->args.objects(); + } + } + if (args) { + for (auto arg : *args) { + std::string str; + bool rawString = false; + if (auto lstr = ast_cast<LuaString_t>(arg)) { + str = _parser.toString(lstr->content); + rawString = true; + } else { + BLOCK_START + auto exp = ast_cast<Exp_t>(arg); + BREAK_IF(!exp); + auto value = singleValueFrom(exp); + BREAK_IF(!value); + auto lstr = value->getByPath<String_t, LuaString_t>(); + BREAK_IF(!lstr); + str = _parser.toString(lstr->content); + rawString = true; + BLOCK_END + } + if (!rawString && str.empty()) { + // check whether arg is reassembled + // do some workaround for pipe expression + if (ast_is<Exp_t>(arg)) { + auto exp = static_cast<Exp_t*>(arg); + BLOCK_START + BREAK_IF(!exp->opValues.empty()); + auto chainValue = exp->getByPath<unary_exp_t, Value_t, ChainValue_t>(); + BREAK_IF(!chainValue); + BREAK_IF(!isMacroChain(chainValue)); + str = std::get<1>(expandMacroStr(chainValue)); + BLOCK_END + if (str.empty() && arg->m_begin.m_it == arg->m_end.m_it) { + // exp is reassembled due to pipe expressions + // in transform stage, toString(exp) won't be able + // to convert its whole text content + str = _parser.toString(exp->pipeExprs.front()); + } + } + } + if (!rawString && str.empty()) { str = _parser.toString(arg); } - } - Utils::trim(str); - Utils::replace(str, "\r\n"sv, "\n"sv); - lua_pushlstring(L, str.c_str(), str.size()); - } // cur pcall macroFunc args... - bool success = lua_pcall(L, static_cast<int>(args->size()) + 1, 2, 0) == 0; + Utils::trim(str); + Utils::replace(str, "\r\n"sv, "\n"sv); + lua_pushlstring(L, str.c_str(), str.size()); + } // cur pcall macroFunc args... + } + bool success = lua_pcall(L, (args ? static_cast<int>(args->size()) : 0) + 1, 2, 0) == 0; if (!success) { // cur err std::string err = lua_tostring(L, -1); throw std::logic_error(_info.errorMessage("failed to expand macro: "s + err, x)); @@ -4063,11 +4075,11 @@ private: std::string type; str_list localVars; if (lua_istable(L, -1) != 0) { - lua_getfield(L, -1, "codes"); // cur res codes + lua_getfield(L, -1, "code"); // cur res code if (lua_isstring(L, -1) != 0) { codes = lua_tostring(L, -1); } else { - throw std::logic_error(_info.errorMessage("macro table must contain field \"codes\" of string"sv, x)); + throw std::logic_error(_info.errorMessage("macro table must contain field \"code\" of string"sv, x)); } lua_pop(L, 1); // cur res lua_getfield(L, -1, "type"); // cur res type @@ -4108,7 +4120,7 @@ private: std::string type, codes; str_list localVars; std::tie(type, codes, localVars) = expandMacroStr(chainValue); - bool isBlock = (usage == ExpUsage::Common) && (chainList.size() <= 2); + bool isBlock = (usage == ExpUsage::Common) && (chainList.size() < 2 || (chainList.size() == 2 && ast_is<Invoke_t, InvokeArgs_t>(chainList.back()))); ParseInfo info; if (type == "lua"sv) { if (!isBlock) { @@ -4161,7 +4173,7 @@ private: if (!isBlock) { ast_ptr<false, Exp_t> exp; exp.set(info.node); - if (!exp->opValues.empty() || chainList.size() > 2) { + if (!exp->opValues.empty() || (chainList.size() > 2 || (chainList.size() == 2 && !ast_is<Invoke_t, InvokeArgs_t>(chainList.back())))) { auto paren = x->new_ptr<Parens_t>(); paren->expr.set(exp); auto callable = x->new_ptr<Callable_t>(); @@ -4169,7 +4181,8 @@ private: auto newChain = x->new_ptr<ChainValue_t>(); newChain->items.push_back(callable); auto it = chainList.begin(); - it++; it++; + it++; + if (chainList.size() > 1 && ast_is<Invoke_t, InvokeArgs_t>(*it)) it++; for (; it != chainList.end(); ++it) { newChain->items.push_back(*it); } @@ -6146,12 +6159,14 @@ private: throw std::logic_error(_info.errorMessage("failed to get module text"sv, x)); } // cur text std::string text = lua_tostring(L, -1); - auto compiler = YueCompilerImpl(L, _luaOpen, false, moduleFullName); + auto compiler = YueCompilerImpl(L, _luaOpen, false); YueConfig config; config.lineOffset = 0; config.lintGlobalVariable = false; config.reserveLineNumber = false; config.implicitReturnRoot = _config.implicitReturnRoot; + config.module = moduleFullName; + config.exporting = true; auto result = compiler.compile(text, config); if (result.codes.empty() && !result.error.empty()) { throw std::logic_error(_info.errorMessage("failed to compile module '"s + moduleName + "\': "s + result.error, x)); diff --git a/src/yuescript/yue_compiler.h b/src/yuescript/yue_compiler.h index 2e8c86a..f15bcfb 100644 --- a/src/yuescript/yue_compiler.h +++ b/src/yuescript/yue_compiler.h @@ -28,7 +28,9 @@ struct YueConfig { bool implicitReturnRoot = true; bool reserveLineNumber = true; bool useSpaceOverTab = false; + bool exporting = false; int lineOffset = 0; + std::string module; Options options; }; diff --git a/src/yuescript/yuescript.cpp b/src/yuescript/yuescript.cpp index d534f3e..b83a454 100644 --- a/src/yuescript/yuescript.cpp +++ b/src/yuescript/yuescript.cpp @@ -85,6 +85,12 @@ static int yuetolua(lua_State* L) { config.lineOffset = static_cast<int>(lua_tonumber(L, -1)); } lua_pop(L, 1); + lua_pushliteral(L, "module"); + lua_gettable(L, -2); + if (lua_isstring(L, -1) != 0) { + config.module = lua_tostring(L, -1); + } + lua_pop(L, 1); } std::string s(input, size); auto result = yue::YueCompiler(L, nullptr, sameModule).compile(s, config); diff --git a/src/yuescript/yuescript.h b/src/yuescript/yuescript.h index 4428fbe..eac95da 100644 --- a/src/yuescript/yuescript.h +++ b/src/yuescript/yuescript.h @@ -103,6 +103,7 @@ end yue_loadstring = function(...) local options, str, chunk_name, mode, env = get_options(...) chunk_name = chunk_name or "=(yuescript.loadstring)" + options.module = chunk_name local code, err = yue.to_lua(str, options) if not code then return nil, err @@ -117,7 +118,7 @@ yue_loadstring = function(...) end local function yue_loadfile(fname, ...) local text = yue.read_file(fname) - return yue_loadstring(text, tostring(fname), ...) + return yue_loadstring(text, fname, ...) end local function yue_dofile(...) local f = assert(yue_loadfile(...)) -- cgit v1.2.3-55-g6feb