code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(377 lines)
1 --
2 -- This file contains the source code of the Nomsu compiler.
3 --
4 unpack or= table.unpack
5 {:match, :sub, :gsub, :format, :byte, :find} = string
6 {: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 file
14 if SyntaxTree\is_instance(source)
15 file = source\get_source_file!
16 source = source.source
17 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 = 1
26 for i=1,source.currentline-1
27 start += #lines[i]
28 stop = start + #lines[source.currentline]
29 source = Source(source.short_src, start, stop)
31 if source and not file
32 file = Files.read(source.filename)
33 if not file
34 if NOMSU_PREFIX
35 path = "#{NOMSU_PREFIX}/share/nomsu/#{table.concat NOMSU_VERSION, "."}/#{source.filename}"
36 file = Files.read(path)
37 if not file
38 error("Can't find file: "..tostring(source.filename))
40 title, err_msg, hint = msg\match("([^:]*):[ \n]+(.*)[ \n]+Hint: (.*)")
41 if not err_msg
42 err_msg, hint = msg\match("(.*)[ \n]+Hint:[ \n]+(.*)")
43 title = "Failure"
44 if not err_msg
45 title, err_msg = msg\match("([^:]*):[ \n]+(.*)")
46 if not err_msg
47 err_msg = msg
48 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,
55 error(err_str, 0)
57 -- This is a bit of a hack, but this code handles arbitrarily complex
58 -- math expressions like 2*x + 3^2 without having to define a single
59 -- 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 value
64 compile = (tree)=>
65 if tree == nil
66 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_to
70 tree = @._1_upgraded_from_2_to(tree, tree.version, @NOMSU_VERSION)
72 switch tree.type
73 when "Action"
74 stub = tree.stub
75 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 tree
79 if type(tok) == 'string'
80 lua\add tok
81 else
82 tok_lua = @compile(tok)
83 -- TODO: this is overly eager, should be less aggressive
84 tok_lua\parenthesize! if tok.type == "Action"
85 lua\add tok_lua
86 lua\add " " if i < #tree
87 return lua
89 if not compile_action
90 seen_words = {}
91 words = {}
92 for word in stub\gmatch("[^0-9 ][^ ]*")
93 unless seen_words[word]
94 seen_words[word] = true
95 table.insert words, word
96 table.sort(words)
97 stub2 = table.concat(words, " ")
98 compile_action = @COMPILE_RULES[stub2]
99 if compile_action
100 if debug.getinfo(compile_action, 'u').isvararg
101 stub = stub2
102 else compile_action = nil
104 if compile_action
105 args = [arg for arg in *tree when type(arg) != "string"]
106 ret = compile_action(@, tree, unpack(args))
107 if ret == nil
108 info = debug.getinfo(compile_action, "S")
109 filename = Source\from_string(info.source).filename
110 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.source
115 return ret
116 if ret != tree
117 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 > 1
124 arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
125 if lua\trailing_line_len! + #arg_lua > MAX_LINE
126 lua\add(argnum > 1 and ",\n " or "\n ")
127 elseif argnum > 1
128 lua\add ", "
129 lua\add arg_lua
130 lua\add ")"
131 return lua
133 when "MethodCall"
134 stub = tree\get_stub!
135 compile_action = @COMPILE_RULES[stub]
136 if compile_action
137 args = tree\get_args!
138 ret = compile_action(@, tree, unpack(args))
139 if ret == nil
140 info = debug.getinfo(compile_action, "S")
141 filename = Source\from_string(info.source).filename
142 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.source
148 return ret
149 if ret != tree
150 return @compile(ret)
152 lua = LuaCode\from tree.source
153 target_lua = @compile tree[1]
154 target_text = target_lua\text!
155 -- TODO: this parenthesizing is maybe overly conservative
156 if not (target_text\match("^%(.*%)$") or target_text\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or
157 tree[1].type == "IndexChain")
158 target_lua\parenthesize!
160 self_lua = #tree > 2 and "_self" or target_lua
161 if #tree > 2
162 lua\add "(function(", self_lua, ")\n "
163 for i=2,#tree
164 lua\add "\n " if i > 2
165 if i > 2 and i == #tree
166 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 > 1
172 arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
173 if lua\trailing_line_len! + #arg_lua > MAX_LINE
174 lua\add(argnum > 1 and ",\n " or "\n ")
175 elseif argnum > 1
176 lua\add ", "
177 lua\add arg_lua
178 lua\add ")"
179 if #tree > 2
180 lua\add "\nend)(", target_lua, ")"
181 return lua
183 when "EscapedNomsu"
184 lua = LuaCode\from tree.source, "SyntaxTree{"
185 needs_comma, i = false, 1
186 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 == i
198 i += 1
199 elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*")
200 entry_lua\add(k, "= ")
201 else
202 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_LINE
206 lua\add "\n "
207 elseif needs_comma
208 lua\add " "
209 lua\add entry_lua
210 needs_comma = true
211 lua\add "}"
212 return lua
214 when "Block"
215 lua = LuaCode\from(tree.source)
216 for i, line in ipairs tree
217 if line.type == "Error"
218 return @compile(line)
219 for i, line in ipairs tree
220 if i > 1 then lua\add "\n"
221 line_lua = @compile(line)
222 lua\add line_lua
223 unless line_lua\last(1) == ";" or line_lua\last(4)\match("[^_a-zA-Z0-9]end$")
224 lua\add ";"
225 return lua
227 when "Text"
228 if #tree == 0
229 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 = 0
234 string_buffer = ""
235 add_bit = (bit)->
236 if added > 0
237 if lua\trailing_line_len! + #bit > MAX_LINE
238 lua\add ",\n "
239 else
240 lua\add ", "
241 lua\add bit
242 added += 1
244 for i, bit in ipairs tree
245 if type(bit) == "string"
246 string_buffer ..= bit
247 continue
248 if string_buffer != ""
249 for i=1,#string_buffer,MAX_LINE
250 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_lua
260 if string_buffer != ""
261 for i=1,#string_buffer,MAX_LINE
262 add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua!
263 string_buffer = ""
265 if added == 0
266 return LuaCode\from(tree.source, '""')
267 lua\add ")"
268 return lua
270 when "List", "Dict"
271 typename = "a_"..tree.type
272 if #tree == 0
273 return LuaCode\from tree.source, typename, "{}"
275 lua = LuaCode\from tree.source
276 chunks = 0
277 i = 1
278 while tree[i]
279 if tree[i].type == 'Block'
280 lua\add " + " if chunks > 0
281 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 += 1
286 i += 1
287 else
288 lua\add " + " if chunks > 0
289 sep = ''
290 items_lua = LuaCode\from tree[i].source
291 while tree[i]
292 if tree[i].type == "Block"
293 break
294 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_lua
300 if tree[i].type == "Comment"
301 items_lua\add "\n"
302 sep = ''
303 elseif items_lua\trailing_line_len! > MAX_LINE
304 sep = items_lua\last(1) == ";" and "\n " or ",\n "
305 else
306 sep = items_lua\last(1) == ";" and " " or ", "
307 i += 1
308 if items_lua\is_multiline!
309 lua\add LuaCode\from items_lua.source, typename, "{\n ", items_lua, "\n}"
310 else
311 lua\add LuaCode\from items_lua.source, typename, "{", items_lua, "}"
312 chunks += 1
314 return lua
316 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_str
321 elseif key_lua\first(1) == "["
322 -- NOTE: this *must* use a space after the [ to avoid freaking out
323 -- Lua's parser if the inner expression is a long string. Lua
324 -- parses x[[[y]]] as x("[y]"), not as x["y"]
325 LuaCode\from tree.source, "[ ",key_lua,"]"
326 else
327 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,#tree
342 key = tree[i]
343 -- TODO: remove this shim
344 if key.type != "Index"
345 key = SyntaxTree{type:"Index", source:key.source, key}
346 lua\add @compile(key)
347 return lua
349 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.filename
371 -- Coroutine yield here?
372 error(err_msg)
374 else
375 error("Unknown type: #{tree.type}")
377 return {:compile, :fail_at}