code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(464 lines)
1 local unpack = unpack or table.unpack
2 local match, sub, gsub, format, byte, find
3 do
4 local _obj_0 = string
5 match, sub, gsub, format, byte, find = _obj_0.match, _obj_0.sub, _obj_0.gsub, _obj_0.format, _obj_0.byte, _obj_0.find
6 end
7 local LuaCode, Source
8 do
9 local _obj_0 = require("code_obj")
10 LuaCode, Source = _obj_0.LuaCode, _obj_0.Source
11 end
12 require("text")
13 local SyntaxTree = require("syntax_tree")
14 local Files = require("files")
15 local pretty_error = require("pretty_errors")
16 local fail_at
17 fail_at = function(source, msg)
18 local file
19 if SyntaxTree:is_instance(source) then
20 file = source:get_source_file()
21 source = source.source
22 elseif type(source) == 'string' then
23 source = Source:from_string(source)
24 elseif not Source:is_instance(source) then
25 assert(source.short_src and source.currentline)
26 file = Files.read(source.short_src)
27 assert(file, "Could not find " .. tostring(source.short_src))
28 local lines = file:lines()
29 local start = 1
30 for i = 1, source.currentline - 1 do
31 start = start + #lines[i]
32 end
33 local stop = start + #lines[source.currentline]
34 source = Source(source.short_src, start, stop)
35 end
36 if source and not file then
37 file = Files.read(source.filename)
38 end
39 if not file then
40 if NOMSU_PREFIX then
41 local path = tostring(NOMSU_PREFIX) .. "/share/nomsu/" .. tostring(table.concat(NOMSU_VERSION, ".")) .. "/" .. tostring(source.filename)
42 file = Files.read(path)
43 end
44 end
45 if not file then
46 error("Can't find file: " .. tostring(source.filename))
47 end
48 local title, err_msg, hint = msg:match("([^:]*):[ \n]+(.*)[ \n]+Hint: (.*)")
49 if not err_msg then
50 err_msg, hint = msg:match("(.*)[ \n]+Hint:[ \n]+(.*)")
51 title = "Failure"
52 end
53 if not err_msg then
54 title, err_msg = msg:match("([^:]*):[ \n]+(.*)")
55 end
56 if not err_msg then
57 err_msg = msg
58 title = "Failure"
59 end
60 local err_str = pretty_error({
61 title = title,
62 error = err_msg,
63 hint = hint,
64 source = file,
65 start = source.start,
66 stop = source.stop,
67 filename = source.filename
68 })
69 return error(err_str, 0)
70 end
71 local re = require('re')
72 local math_expression = re.compile([[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]])
73 local MAX_LINE = 80
74 local compile
75 compile = function(self, tree)
76 if tree == nil then
77 error("No tree was passed in.")
78 end
79 if tree.version and tree.version < self.NOMSU_VERSION:up_to(#tree.version) and self._1_upgraded_from_2_to then
80 tree = self._1_upgraded_from_2_to(tree, tree.version, self.NOMSU_VERSION)
81 end
82 local _exp_0 = tree.type
83 if "Action" == _exp_0 then
84 local stub = tree.stub
85 local compile_action = self.COMPILE_RULES[stub]
86 if not compile_action and math_expression:match(stub) then
87 local lua = LuaCode:from(tree.source)
88 for i, tok in ipairs(tree) do
89 if type(tok) == 'string' then
90 lua:add(tok)
91 else
92 local tok_lua = self:compile(tok)
93 if tok.type == "Action" then
94 tok_lua:parenthesize()
95 end
96 lua:add(tok_lua)
97 end
98 if i < #tree then
99 lua:add(" ")
100 end
101 end
102 return lua
103 end
104 if not compile_action then
105 local seen_words = { }
106 local words = { }
107 for word in stub:gmatch("[^0-9 ][^ ]*") do
108 if not (seen_words[word]) then
109 seen_words[word] = true
110 table.insert(words, word)
111 end
112 end
113 table.sort(words)
114 local stub2 = table.concat(words, " ")
115 compile_action = self.COMPILE_RULES[stub2]
116 if compile_action then
117 if debug.getinfo(compile_action, 'u').isvararg then
118 stub = stub2
119 else
120 compile_action = nil
121 end
122 end
123 end
124 if compile_action then
125 local args
127 local _accum_0 = { }
128 local _len_0 = 1
129 for _index_0 = 1, #tree do
130 local arg = tree[_index_0]
131 if type(arg) ~= "string" then
132 _accum_0[_len_0] = arg
133 _len_0 = _len_0 + 1
134 end
135 end
136 args = _accum_0
137 end
138 local ret = compile_action(self, tree, unpack(args))
139 if ret == nil then
140 local info = debug.getinfo(compile_action, "S")
141 local filename = Source:from_string(info.source).filename
142 fail_at(tree, ("Compile error: The compile-time action here (" .. tostring(stub) .. ") failed to return any value. " .. "Hint: Look at the implementation of (" .. tostring(stub) .. ") in " .. tostring(filename) .. ":" .. tostring(info.linedefined) .. " and make sure it's returning something."))
143 end
144 if not (SyntaxTree:is_instance(ret)) then
145 ret.source = ret.source or tree.source
146 return ret
147 end
148 if ret ~= tree then
149 return self:compile(ret)
150 end
151 end
152 local lua = LuaCode:from(tree.source)
153 lua:add((stub):as_lua_id(), "(")
154 for argnum, arg in ipairs(tree:get_args()) do
155 local arg_lua = self:compile(arg)
156 if arg.type == "Block" and #arg > 1 then
157 arg_lua = LuaCode:from(arg.source, "(function()\n ", arg_lua, "\nend)()")
158 end
159 if lua:trailing_line_len() + #arg_lua > MAX_LINE then
160 lua:add(argnum > 1 and ",\n " or "\n ")
161 elseif argnum > 1 then
162 lua:add(", ")
163 end
164 lua:add(arg_lua)
165 end
166 lua:add(")")
167 return lua
168 elseif "MethodCall" == _exp_0 then
169 local stub = tree:get_stub()
170 local compile_action = self.COMPILE_RULES[stub]
171 if compile_action then
172 local args = tree:get_args()
173 local ret = compile_action(self, tree, unpack(args))
174 if ret == nil then
175 local info = debug.getinfo(compile_action, "S")
176 local filename = Source:from_string(info.source).filename
177 fail_at(tree, ("Compile error: The compile-time method here (" .. tostring(stub) .. ") failed to return any value. " .. "Hint: Look at the implementation of (" .. tostring(stub) .. ") in " .. tostring(filename) .. ":" .. tostring(info.linedefined) .. " " .. "and make sure it's returning something."))
178 end
179 if not (SyntaxTree:is_instance(ret)) then
180 ret.source = ret.source or tree.source
181 return ret
182 end
183 if ret ~= tree then
184 return self:compile(ret)
185 end
186 end
187 local lua = LuaCode:from(tree.source)
188 local target_lua = self:compile(tree[1])
189 local target_text = target_lua:text()
190 if not (target_text:match("^%(.*%)$") or target_text:match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or tree[1].type == "IndexChain") then
191 target_lua:parenthesize()
192 end
193 local self_lua = #tree > 2 and "_self" or target_lua
194 if #tree > 2 then
195 lua:add("(function(", self_lua, ")\n ")
196 end
197 for i = 2, #tree do
198 if i > 2 then
199 lua:add("\n ")
200 end
201 if i > 2 and i == #tree then
202 lua:add("return ")
203 end
204 lua:add(self_lua, ":")
205 lua:add((tree[i].stub):as_lua_id(), "(")
206 for argnum, arg in ipairs(tree[i]:get_args()) do
207 local arg_lua = self:compile(arg)
208 if arg.type == "Block" and #arg > 1 then
209 arg_lua = LuaCode:from(arg.source, "(function()\n ", arg_lua, "\nend)()")
210 end
211 if lua:trailing_line_len() + #arg_lua > MAX_LINE then
212 lua:add(argnum > 1 and ",\n " or "\n ")
213 elseif argnum > 1 then
214 lua:add(", ")
215 end
216 lua:add(arg_lua)
217 end
218 lua:add(")")
219 end
220 if #tree > 2 then
221 lua:add("\nend)(", target_lua, ")")
222 end
223 return lua
224 elseif "EscapedNomsu" == _exp_0 then
225 local lua = LuaCode:from(tree.source, "SyntaxTree{")
226 local needs_comma, i = false, 1
227 local as_lua
228 as_lua = function(x)
229 if type(x) == 'number' then
230 return tostring(x)
231 elseif SyntaxTree:is_instance(x) then
232 return self:compile(x)
233 elseif Source:is_instance(x) then
234 return tostring(x):as_lua()
235 else
236 return x:as_lua()
237 end
238 end
239 for k, v in pairs((SyntaxTree:is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) do
240 local entry_lua = LuaCode()
241 if k == i then
242 i = i + 1
243 elseif type(k) == 'string' and match(k, "[_a-zA-Z][_a-zA-Z0-9]*") then
244 entry_lua:add(k, "= ")
245 else
246 entry_lua:add("[", as_lua(k), "]= ")
247 end
248 entry_lua:add(as_lua(v))
249 if needs_comma then
250 lua:add(",")
251 end
252 if lua:trailing_line_len() + #(entry_lua:match("^[\n]*")) > MAX_LINE then
253 lua:add("\n ")
254 elseif needs_comma then
255 lua:add(" ")
256 end
257 lua:add(entry_lua)
258 needs_comma = true
259 end
260 lua:add("}")
261 return lua
262 elseif "Block" == _exp_0 then
263 local lua = LuaCode:from(tree.source)
264 for i, line in ipairs(tree) do
265 if line.type == "Error" then
266 return self:compile(line)
267 end
268 end
269 for i, line in ipairs(tree) do
270 if i > 1 then
271 lua:add("\n")
272 end
273 local line_lua = self:compile(line)
274 lua:add(line_lua)
275 if not (line_lua:last(1) == ";" or line_lua:last(4):match("[^_a-zA-Z0-9]end$")) then
276 lua:add(";")
277 end
278 end
279 return lua
280 elseif "Text" == _exp_0 then
281 if #tree == 0 then
282 return LuaCode:from(tree.source, '""')
283 end
284 if #tree == 1 and type(tree[1]) == 'string' then
285 return LuaCode:from(tree.source, tree[1]:as_lua())
286 end
287 local lua = LuaCode:from(tree.source, "Text(")
288 local added = 0
289 local string_buffer = ""
290 local add_bit
291 add_bit = function(bit)
292 if added > 0 then
293 if lua:trailing_line_len() + #bit > MAX_LINE then
294 lua:add(",\n ")
295 else
296 lua:add(", ")
297 end
298 end
299 lua:add(bit)
300 added = added + 1
301 end
302 for i, bit in ipairs(tree) do
303 local _continue_0 = false
304 repeat
305 if type(bit) == "string" then
306 string_buffer = string_buffer .. bit
307 _continue_0 = true
308 break
309 end
310 if string_buffer ~= "" then
311 for i = 1, #string_buffer, MAX_LINE do
312 add_bit(string_buffer:sub(i, i + MAX_LINE - 1):as_lua())
313 end
314 string_buffer = ""
315 end
316 local bit_lua = self:compile(bit)
317 if bit.type == "Block" then
318 bit_lua = LuaCode:from(bit.source, "a_List(function(add)", "\n ", bit_lua, "\nend):joined()")
319 end
320 add_bit(bit_lua)
321 _continue_0 = true
322 until true
323 if not _continue_0 then
324 break
325 end
326 end
327 if string_buffer ~= "" then
328 for i = 1, #string_buffer, MAX_LINE do
329 add_bit(string_buffer:sub(i, i + MAX_LINE - 1):as_lua())
330 end
331 string_buffer = ""
332 end
333 if added == 0 then
334 return LuaCode:from(tree.source, '""')
335 end
336 lua:add(")")
337 return lua
338 elseif "List" == _exp_0 or "Dict" == _exp_0 then
339 local typename = "a_" .. tree.type
340 if #tree == 0 then
341 return LuaCode:from(tree.source, typename, "{}")
342 end
343 local lua = LuaCode:from(tree.source)
344 local chunks = 0
345 local i = 1
346 while tree[i] do
347 if tree[i].type == 'Block' then
348 if chunks > 0 then
349 lua:add(" + ")
350 end
351 lua:add(typename, "(function(", (tree.type == 'List' and "add" or ("add, " .. ("add 1 ="):as_lua_id())), ")")
352 local body = self:compile(tree[i])
353 body:declare_locals()
354 lua:add("\n ", body, "\nend)")
355 chunks = chunks + 1
356 i = i + 1
357 else
358 if chunks > 0 then
359 lua:add(" + ")
360 end
361 local sep = ''
362 local items_lua = LuaCode:from(tree[i].source)
363 while tree[i] do
364 if tree[i].type == "Block" then
365 break
366 end
367 local item_lua = self:compile(tree[i])
368 if item_lua:match("^%.[a-zA-Z_]") then
369 item_lua = item_lua:text():sub(2)
370 end
371 if tree.type == 'Dict' and tree[i].type == 'Index' then
372 item_lua = LuaCode:from(tree[i].source, item_lua, "=true")
373 end
374 items_lua:add(sep, item_lua)
375 if tree[i].type == "Comment" then
376 items_lua:add("\n")
377 sep = ''
378 elseif items_lua:trailing_line_len() > MAX_LINE then
379 sep = items_lua:last(1) == ";" and "\n " or ",\n "
380 else
381 sep = items_lua:last(1) == ";" and " " or ", "
382 end
383 i = i + 1
384 end
385 if items_lua:is_multiline() then
386 lua:add(LuaCode:from(items_lua.source, typename, "{\n ", items_lua, "\n}"))
387 else
388 lua:add(LuaCode:from(items_lua.source, typename, "{", items_lua, "}"))
389 end
390 chunks = chunks + 1
391 end
392 end
393 return lua
394 elseif "Index" == _exp_0 then
395 local key_lua = self:compile(tree[1])
396 local key_str = key_lua:match('^"([a-zA-Z_][a-zA-Z0-9_]*)"$')
397 if key_str and key_str:is_lua_id() then
398 return LuaCode:from(tree.source, ".", key_str)
399 elseif key_lua:first(1) == "[" then
400 return LuaCode:from(tree.source, "[ ", key_lua, "]")
401 else
402 return LuaCode:from(tree.source, "[", key_lua, "]")
403 end
404 elseif "DictEntry" == _exp_0 then
405 local key = tree[1]
406 if key.type ~= "Index" then
407 key = SyntaxTree({
408 type = "Index",
409 source = key.source,
410 key
412 end
413 return LuaCode:from(tree.source, self:compile(key), "=", (tree[2] and self:compile(tree[2]) or "true"))
414 elseif "IndexChain" == _exp_0 then
415 local lua = self:compile(tree[1])
416 if lua:match("['\"}]$") or lua:match("]=*]$") then
417 lua:parenthesize()
418 end
419 if lua:text() == "..." then
420 return LuaCode:from(tree.source, "select(", self:compile(tree[2][1]), ", ...)")
421 end
422 for i = 2, #tree do
423 local key = tree[i]
424 if key.type ~= "Index" then
425 key = SyntaxTree({
426 type = "Index",
427 source = key.source,
428 key
430 end
431 lua:add(self:compile(key))
432 end
433 return lua
434 elseif "Number" == _exp_0 then
435 local number = tostring(tree[1]):gsub("_", "")
436 return LuaCode:from(tree.source, number)
437 elseif "Var" == _exp_0 then
438 if tree[1].type == "MethodCall" then
439 return LuaCode:from(tree.source, self:compile(tree[1][1]), ".", tree[1][2]:get_stub():as_lua_id())
440 end
441 return LuaCode:from(tree.source, tree:as_var():as_lua_id())
442 elseif "FileChunks" == _exp_0 then
443 return error("Can't convert FileChunks to a single block of lua, since each chunk's " .. "compilation depends on the earlier chunks")
444 elseif "Comment" == _exp_0 then
445 return LuaCode:from(tree.source, "-- ", (tree[1]:gsub('\n', '\n-- ')))
446 elseif "Error" == _exp_0 then
447 local err_msg = pretty_error({
448 title = "Parse error",
449 error = tree.error,
450 hint = tree.hint,
451 source = tree:get_source_file(),
452 start = tree.source.start,
453 stop = tree.source.stop,
454 filename = tree.source.filename
456 return error(err_msg)
457 else
458 return error("Unknown type: " .. tostring(tree.type))
459 end
460 end
461 return {
462 compile = compile,
463 fail_at = fail_at