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