(377 lines)
1 --2 -- This file contains the source code of the Nomsu compiler.3 --4 unpack or= table.unpack5 {:match, :sub, :gsub, :format, :byte, :find} = string6 {:LuaCode, :Source} = require "code_obj"7 require "text"8 SyntaxTree = require "syntax_tree"9 Files = require "files"11 pretty_error = require("pretty_errors")12 fail_at = (source, msg)->13 local file14 if SyntaxTree\is_instance(source)15 file = source\get_source_file!16 source = source.source17 elseif type(source) == 'string'18 source = Source\from_string(source)19 elseif not Source\is_instance(source)20 -- debug.getinfo() output:21 assert(source.short_src and source.currentline)22 file = Files.read(source.short_src)23 assert file, "Could not find #{source.short_src}"24 lines = file\lines!25 start = 126 for i=1,source.currentline-127 start += #lines[i]28 stop = start + #lines[source.currentline]29 source = Source(source.short_src, start, stop)31 if source and not file32 file = Files.read(source.filename)33 if not file34 if NOMSU_PREFIX35 path = "#{NOMSU_PREFIX}/share/nomsu/#{table.concat NOMSU_VERSION, "."}/#{source.filename}"36 file = Files.read(path)37 if not file38 error("Can't find file: "..tostring(source.filename))40 title, err_msg, hint = msg\match("([^:]*):[ \n]+(.*)[ \n]+Hint: (.*)")41 if not err_msg42 err_msg, hint = msg\match("(.*)[ \n]+Hint:[ \n]+(.*)")43 title = "Failure"44 if not err_msg45 title, err_msg = msg\match("([^:]*):[ \n]+(.*)")46 if not err_msg47 err_msg = msg48 title = "Failure"50 err_str = pretty_error{51 title: title,52 error: err_msg, hint: hint, source: file,53 start:source.start, stop:source.stop, filename:source.filename,54 }55 error(err_str, 0)57 -- This is a bit of a hack, but this code handles arbitrarily complex58 -- math expressions like 2*x + 3^2 without having to define a single59 -- action for every possibility.60 re = require 're'61 math_expression = re.compile [[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]]63 MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value64 compile = (tree)=>65 if tree == nil66 error("No tree was passed in.")68 -- Automatically upgrade trees from older versions:69 if tree.version and tree.version < @NOMSU_VERSION\up_to(#tree.version) and @_1_upgraded_from_2_to70 tree = @._1_upgraded_from_2_to(tree, tree.version, @NOMSU_VERSION)72 switch tree.type73 when "Action"74 stub = tree.stub75 compile_action = @COMPILE_RULES[stub]76 if not compile_action and math_expression\match(stub)77 lua = LuaCode\from(tree.source)78 for i,tok in ipairs tree79 if type(tok) == 'string'80 lua\add tok81 else82 tok_lua = @compile(tok)83 -- TODO: this is overly eager, should be less aggressive84 tok_lua\parenthesize! if tok.type == "Action"85 lua\add tok_lua86 lua\add " " if i < #tree87 return lua89 if not compile_action90 seen_words = {}91 words = {}92 for word in stub\gmatch("[^0-9 ][^ ]*")93 unless seen_words[word]94 seen_words[word] = true95 table.insert words, word96 table.sort(words)97 stub2 = table.concat(words, " ")98 compile_action = @COMPILE_RULES[stub2]99 if compile_action100 if debug.getinfo(compile_action, 'u').isvararg101 stub = stub2102 else compile_action = nil104 if compile_action105 args = [arg for arg in *tree when type(arg) != "string"]106 ret = compile_action(@, tree, unpack(args))107 if ret == nil108 info = debug.getinfo(compile_action, "S")109 filename = Source\from_string(info.source).filename110 fail_at tree,111 ("Compile error: The compile-time action here (#{stub}) failed to return any value. "..112 "Hint: Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} and make sure it's returning something.")113 unless SyntaxTree\is_instance(ret)114 ret.source or= tree.source115 return ret116 if ret != tree117 return @compile(ret)119 lua = LuaCode\from(tree.source)120 lua\add((stub)\as_lua_id!,"(")121 for argnum, arg in ipairs tree\get_args!122 arg_lua = @compile(arg)123 if arg.type == "Block" and #arg > 1124 arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")125 if lua\trailing_line_len! + #arg_lua > MAX_LINE126 lua\add(argnum > 1 and ",\n " or "\n ")127 elseif argnum > 1128 lua\add ", "129 lua\add arg_lua130 lua\add ")"131 return lua133 when "MethodCall"134 stub = tree\get_stub!135 compile_action = @COMPILE_RULES[stub]136 if compile_action137 args = tree\get_args!138 ret = compile_action(@, tree, unpack(args))139 if ret == nil140 info = debug.getinfo(compile_action, "S")141 filename = Source\from_string(info.source).filename142 fail_at tree,143 ("Compile error: The compile-time method here (#{stub}) failed to return any value. "..144 "Hint: Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} "..145 "and make sure it's returning something.")146 unless SyntaxTree\is_instance(ret)147 ret.source or= tree.source148 return ret149 if ret != tree150 return @compile(ret)152 lua = LuaCode\from tree.source153 target_lua = @compile tree[1]154 target_text = target_lua\text!155 -- TODO: this parenthesizing is maybe overly conservative156 if not (target_text\match("^%(.*%)$") or target_text\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or157 tree[1].type == "IndexChain")158 target_lua\parenthesize!160 self_lua = #tree > 2 and "_self" or target_lua161 if #tree > 2162 lua\add "(function(", self_lua, ")\n "163 for i=2,#tree164 lua\add "\n " if i > 2165 if i > 2 and i == #tree166 lua\add "return "167 lua\add self_lua, ":"168 lua\add((tree[i].stub)\as_lua_id!,"(")169 for argnum, arg in ipairs tree[i]\get_args!170 arg_lua = @compile(arg)171 if arg.type == "Block" and #arg > 1172 arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")173 if lua\trailing_line_len! + #arg_lua > MAX_LINE174 lua\add(argnum > 1 and ",\n " or "\n ")175 elseif argnum > 1176 lua\add ", "177 lua\add arg_lua178 lua\add ")"179 if #tree > 2180 lua\add "\nend)(", target_lua, ")"181 return lua183 when "EscapedNomsu"184 lua = LuaCode\from tree.source, "SyntaxTree{"185 needs_comma, i = false, 1186 as_lua = (x)->187 if type(x) == 'number'188 tostring(x)189 elseif SyntaxTree\is_instance(x)190 @compile(x)191 elseif Source\is_instance(x)192 tostring(x)\as_lua!193 else x\as_lua!195 for k,v in pairs((SyntaxTree\is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1])196 entry_lua = LuaCode!197 if k == i198 i += 1199 elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*")200 entry_lua\add(k, "= ")201 else202 entry_lua\add("[", as_lua(k), "]= ")203 entry_lua\add as_lua(v)204 if needs_comma then lua\add ","205 if lua\trailing_line_len! + #(entry_lua\match("^[\n]*")) > MAX_LINE206 lua\add "\n "207 elseif needs_comma208 lua\add " "209 lua\add entry_lua210 needs_comma = true211 lua\add "}"212 return lua214 when "Block"215 lua = LuaCode\from(tree.source)216 for i, line in ipairs tree217 if line.type == "Error"218 return @compile(line)219 for i, line in ipairs tree220 if i > 1 then lua\add "\n"221 line_lua = @compile(line)222 lua\add line_lua223 unless line_lua\last(1) == ";" or line_lua\last(4)\match("[^_a-zA-Z0-9]end$")224 lua\add ";"225 return lua227 when "Text"228 if #tree == 0229 return LuaCode\from(tree.source, '""')230 if #tree == 1 and type(tree[1]) == 'string'231 return LuaCode\from(tree.source, tree[1]\as_lua!)232 lua = LuaCode\from(tree.source, "Text(")233 added = 0234 string_buffer = ""235 add_bit = (bit)->236 if added > 0237 if lua\trailing_line_len! + #bit > MAX_LINE238 lua\add ",\n "239 else240 lua\add ", "241 lua\add bit242 added += 1244 for i, bit in ipairs tree245 if type(bit) == "string"246 string_buffer ..= bit247 continue248 if string_buffer != ""249 for i=1,#string_buffer,MAX_LINE250 add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua!251 string_buffer = ""253 bit_lua = @compile(bit)254 if bit.type == "Block"255 bit_lua = LuaCode\from bit.source, "a_List(function(add)",256 "\n ", bit_lua,257 "\nend):joined()"258 add_bit bit_lua260 if string_buffer != ""261 for i=1,#string_buffer,MAX_LINE262 add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua!263 string_buffer = ""265 if added == 0266 return LuaCode\from(tree.source, '""')267 lua\add ")"268 return lua270 when "List", "Dict"271 typename = "a_"..tree.type272 if #tree == 0273 return LuaCode\from tree.source, typename, "{}"275 lua = LuaCode\from tree.source276 chunks = 0277 i = 1278 while tree[i]279 if tree[i].type == 'Block'280 lua\add " + " if chunks > 0281 lua\add typename, "(function(", (tree.type == 'List' and "add" or ("add, "..("add 1 =")\as_lua_id!)), ")"282 body = @compile(tree[i])283 body\declare_locals!284 lua\add "\n ", body, "\nend)"285 chunks += 1286 i += 1287 else288 lua\add " + " if chunks > 0289 sep = ''290 items_lua = LuaCode\from tree[i].source291 while tree[i]292 if tree[i].type == "Block"293 break294 item_lua = @compile tree[i]295 if item_lua\match("^%.[a-zA-Z_]")296 item_lua = item_lua\text!\sub(2)297 if tree.type == 'Dict' and tree[i].type == 'Index'298 item_lua = LuaCode\from tree[i].source, item_lua, "=true"299 items_lua\add sep, item_lua300 if tree[i].type == "Comment"301 items_lua\add "\n"302 sep = ''303 elseif items_lua\trailing_line_len! > MAX_LINE304 sep = items_lua\last(1) == ";" and "\n " or ",\n "305 else306 sep = items_lua\last(1) == ";" and " " or ", "307 i += 1308 if items_lua\is_multiline!309 lua\add LuaCode\from items_lua.source, typename, "{\n ", items_lua, "\n}"310 else311 lua\add LuaCode\from items_lua.source, typename, "{", items_lua, "}"312 chunks += 1314 return lua316 when "Index"317 key_lua = @compile(tree[1])318 key_str = key_lua\match('^"([a-zA-Z_][a-zA-Z0-9_]*)"$')319 return if key_str and key_str\is_lua_id!320 LuaCode\from tree.source, ".", key_str321 elseif key_lua\first(1) == "["322 -- NOTE: this *must* use a space after the [ to avoid freaking out323 -- Lua's parser if the inner expression is a long string. Lua324 -- parses x[[[y]]] as x("[y]"), not as x["y"]325 LuaCode\from tree.source, "[ ",key_lua,"]"326 else327 LuaCode\from tree.source, "[",key_lua,"]"329 when "DictEntry"330 key = tree[1]331 if key.type != "Index"332 key = SyntaxTree{type:"Index", source:key.source, key}333 return LuaCode\from tree.source, @compile(key),"=",(tree[2] and @compile(tree[2]) or "true")335 when "IndexChain"336 lua = @compile(tree[1])337 if lua\match("['\"}]$") or lua\match("]=*]$")338 lua\parenthesize!339 if lua\text! == "..."340 return LuaCode\from(tree.source, "select(", @compile(tree[2][1]), ", ...)")341 for i=2,#tree342 key = tree[i]343 -- TODO: remove this shim344 if key.type != "Index"345 key = SyntaxTree{type:"Index", source:key.source, key}346 lua\add @compile(key)347 return lua349 when "Number"350 number = tostring(tree[1])\gsub("_", "")351 return LuaCode\from(tree.source, number)353 when "Var"354 if tree[1].type == "MethodCall"355 return LuaCode\from(tree.source, @compile(tree[1][1]), ".", tree[1][2]\get_stub!\as_lua_id!)356 return LuaCode\from(tree.source, tree\as_var!\as_lua_id!)358 when "FileChunks"359 error("Can't convert FileChunks to a single block of lua, since each chunk's "..360 "compilation depends on the earlier chunks")362 when "Comment"363 return LuaCode\from(tree.source, "-- ", (tree[1]\gsub('\n', '\n-- ')))365 when "Error"366 err_msg = pretty_error{367 title:"Parse error"368 error:tree.error, hint:tree.hint, source:tree\get_source_file!369 start:tree.source.start, stop:tree.source.stop, filename:tree.source.filename370 }371 -- Coroutine yield here?372 error(err_msg)374 else375 error("Unknown type: #{tree.type}")377 return {:compile, :fail_at}