aboutsummaryrefslogtreecommitdiff
path: root/nomsu_compiler.lua
blob: d04c71f74d3720f924ac5b75d3eb5756d5c07678 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
local unpack = unpack or table.unpack
local match, sub, gsub, format, byte, find
do
  local _obj_0 = string
  match, sub, gsub, format, byte, find = _obj_0.match, _obj_0.sub, _obj_0.gsub, _obj_0.format, _obj_0.byte, _obj_0.find
end
local LuaCode, Source
do
  local _obj_0 = require("code_obj")
  LuaCode, Source = _obj_0.LuaCode, _obj_0.Source
end
local SyntaxTree = require("syntax_tree")
local Files = require("files")
local pretty_error = require("pretty_errors")
local fail_at
fail_at = function(source, msg)
  local file
  if SyntaxTree:is_instance(source) then
    file = source:get_source_file()
    source = source.source
  elseif type(source) == 'string' then
    source = Source:from_string(source)
  end
  if source and not file then
    file = Files.read(source.filename)
  end
  local title, err_msg, hint = msg:match("([^:]*):[ \n]+(.*)[ \n]+Hint: (.*)")
  if not err_msg then
    err_msg, hint = msg:match("*(.*)[ \n]+Hint:[ \n]+(.*)")
    title = "Error"
  end
  if not err_msg then
    title, err_msg = msg:match("([^:]*):[ \n]+(.*)")
  end
  if not err_msg then
    err_msg = msg
    title = "Error"
  end
  local err_str = pretty_error({
    title = title,
    error = err_msg,
    hint = hint,
    source = file,
    start = source.start,
    stop = source.stop,
    filename = source.filename
  })
  return error(err_str, 0)
end
local re = require('re')
local math_expression = re.compile([[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]])
local MAX_LINE = 80
local compile
compile = function(self, tree)
  local _exp_0 = tree.type
  if "Action" == _exp_0 then
    local stub = tree.stub
    local compile_action = self.COMPILE_RULES[stub]
    if not compile_action and math_expression:match(stub) then
      local lua = LuaCode:from(tree.source)
      for i, tok in ipairs(tree) do
        if type(tok) == 'string' then
          lua:add(tok)
        else
          local tok_lua = self:compile(tok)
          if tok.type == "Action" then
            tok_lua:parenthesize()
          end
          lua:add(tok_lua)
        end
        if i < #tree then
          lua:add(" ")
        end
      end
      return lua
    end
    if compile_action then
      local args
      do
        local _accum_0 = { }
        local _len_0 = 1
        for _index_0 = 1, #tree do
          local arg = tree[_index_0]
          if type(arg) ~= "string" then
            _accum_0[_len_0] = arg
            _len_0 = _len_0 + 1
          end
        end
        args = _accum_0
      end
      local ret = compile_action(self, tree, unpack(args))
      if ret == nil then
        local info = debug.getinfo(compile_action, "S")
        local filename = Source:from_string(info.source).filename
        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."))
      end
      if not (SyntaxTree:is_instance(ret)) then
        ret.source = ret.source or tree.source
        return ret
      end
      if ret ~= tree then
        return self:compile(ret)
      end
    end
    local lua = LuaCode:from(tree.source)
    lua:add((stub):as_lua_id(), "(")
    for argnum, arg in ipairs(tree:get_args()) do
      local arg_lua = self:compile(arg)
      if arg.type == "Block" then
        arg_lua = LuaCode:from(arg.source, "(function()\n    ", arg_lua, "\nend)()")
      end
      if lua:trailing_line_len() + #arg_lua:text() > MAX_LINE then
        lua:add(argnum > 1 and ",\n    " or "\n    ")
      elseif argnum > 1 then
        lua:add(", ")
      end
      lua:add(arg_lua)
    end
    lua:add(")")
    return lua
  elseif "MethodCall" == _exp_0 then
    local stub = tree:get_stub()
    local compile_action = self.COMPILE_RULES[stub]
    if compile_action then
      local args = tree:get_args()
      local ret = compile_action(self, tree, unpack(args))
      if ret == nil then
        local info = debug.getinfo(compile_action, "S")
        local filename = Source:from_string(info.source).filename
        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."))
      end
      if not (SyntaxTree:is_instance(ret)) then
        ret.source = ret.source or tree.source
        return ret
      end
      if ret ~= tree then
        return self:compile(ret)
      end
    end
    local lua = LuaCode:from(tree.source)
    local target_lua = self:compile(tree[1])
    local target_text = target_lua:text()
    if not (target_text:match("^%(.*%)$") or target_text:match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or tree[1].type == "IndexChain") then
      target_lua:parenthesize()
    end
    for i = 2, #tree do
      if i > 2 then
        lua:add("\n")
      end
      lua:add(target_lua, ":")
      lua:add((tree[i].stub):as_lua_id(), "(")
      for argnum, arg in ipairs(tree[i]:get_args()) do
        local arg_lua = self:compile(arg)
        if arg.type == "Block" then
          arg_lua = LuaCode:from(arg.source, "(function()\n    ", arg_lua, "\nend)()")
        end
        if lua:trailing_line_len() + #arg_lua:text() > MAX_LINE then
          lua:add(argnum > 1 and ",\n    " or "\n    ")
        elseif argnum > 1 then
          lua:add(", ")
        end
        lua:add(arg_lua)
      end
      lua:add(")")
    end
    return lua
  elseif "EscapedNomsu" == _exp_0 then
    local lua = LuaCode:from(tree.source, "SyntaxTree{")
    local needs_comma, i = false, 1
    local as_lua
    as_lua = function(x)
      if type(x) == 'number' then
        return tostring(x)
      elseif SyntaxTree:is_instance(x) then
        return self:compile(x)
      elseif Source:is_instance(x) then
        return tostring(x):as_lua()
      else
        return x:as_lua()
      end
    end
    for k, v in pairs((SyntaxTree:is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) do
      local entry_lua = LuaCode()
      if k == i then
        i = i + 1
      elseif type(k) == 'string' and match(k, "[_a-zA-Z][_a-zA-Z0-9]*") then
        entry_lua:add(k, "= ")
      else
        entry_lua:add("[", as_lua(k), "]= ")
      end
      entry_lua:add(as_lua(v))
      if needs_comma then
        lua:add(",")
      end
      if lua:trailing_line_len() + #(entry_lua:text():match("^[\n]*")) > MAX_LINE then
        lua:add("\n    ")
      elseif needs_comma then
        lua:add(" ")
      end
      lua:add(entry_lua)
      needs_comma = true
    end
    lua:add("}")
    return lua
  elseif "Block" == _exp_0 then
    local lua = LuaCode:from(tree.source)
    for i, line in ipairs(tree) do
      if i > 1 then
        lua:add("\n")
      end
      lua:add(self:compile(line))
    end
    return lua
  elseif "Text" == _exp_0 then
    if #tree == 0 then
      return LuaCode:from(tree.source, '""')
    end
    if #tree == 1 and type(tree[1]) == 'string' then
      return LuaCode:from(tree.source, tree[1]:as_lua())
    end
    local lua = LuaCode:from(tree.source, "Text(")
    local added = 0
    local string_buffer = ""
    local add_bit
    add_bit = function(bit)
      if added > 0 then
        if lua:trailing_line_len() + #bit > MAX_LINE then
          lua:add(",\n  ")
        else
          lua:add(", ")
        end
      end
      lua:add(bit)
      added = added + 1
    end
    for i, bit in ipairs(tree) do
      local _continue_0 = false
      repeat
        if type(bit) == "string" then
          string_buffer = string_buffer .. bit
          _continue_0 = true
          break
        end
        if string_buffer ~= "" then
          for i = 1, #string_buffer, MAX_LINE do
            add_bit(string_buffer:sub(i, i + MAX_LINE - 1):as_lua())
          end
          string_buffer = ""
        end
        local bit_lua = self:compile(bit)
        if bit.type == "Block" then
          bit_lua = LuaCode:from(bit.source, "a_List(function(add)", "\n    ", bit_lua, "\nend):joined()")
        end
        add_bit(bit_lua)
        _continue_0 = true
      until true
      if not _continue_0 then
        break
      end
    end
    if string_buffer ~= "" then
      for i = 1, #string_buffer, MAX_LINE do
        add_bit(string_buffer:sub(i, i + MAX_LINE - 1):as_lua())
      end
      string_buffer = ""
    end
    if added == 0 then
      return LuaCode:from(tree.source, '""')
    end
    lua:add(")")
    return lua
  elseif "List" == _exp_0 or "Dict" == _exp_0 then
    local typename = "a_" .. tree.type
    if #tree == 0 then
      return LuaCode:from(tree.source, typename, "{}")
    end
    local lua = LuaCode:from(tree.source)
    local chunks = 0
    local i = 1
    while tree[i] do
      if tree[i].type == 'Block' then
        if chunks > 0 then
          lua:add(" + ")
        end
        lua:add(typename, "(function(", (tree.type == 'List' and "add" or ("add, " .. ("add 1 ="):as_lua_id())), ")")
        lua:add("\n    ", self:compile(tree[i]), "\nend)")
        chunks = chunks + 1
        i = i + 1
      else
        if chunks > 0 then
          lua:add(" + ")
        end
        local sep = ''
        local items_lua = LuaCode:from(tree[i].source)
        while tree[i] do
          if tree[i].type == "Block" then
            break
          end
          local item_lua = self:compile(tree[i])
          if item_lua:text():match("^%.[a-zA-Z_]") then
            item_lua = item_lua:text():sub(2)
          end
          if tree.type == 'Dict' and tree[i].type == 'Index' then
            item_lua = LuaCode:from(tree[i].source, item_lua, "=true")
          end
          items_lua:add(sep, item_lua)
          if tree[i].type == "Comment" then
            items_lua:add("\n")
            sep = ''
          elseif items_lua:trailing_line_len() > MAX_LINE then
            sep = ',\n    '
          else
            sep = ', '
          end
          i = i + 1
        end
        if items_lua:is_multiline() then
          lua:add(LuaCode:from(items_lua.source, typename, "{\n    ", items_lua, "\n}"))
        else
          lua:add(LuaCode:from(items_lua.source, typename, "{", items_lua, "}"))
        end
        chunks = chunks + 1
      end
    end
    return lua
  elseif "Index" == _exp_0 then
    local key_lua = self:compile(tree[1])
    local key_str = match(key_lua:text(), '^"([a-zA-Z_][a-zA-Z0-9_]*)"$')
    if key_str and key_str:is_lua_id() then
      return LuaCode:from(tree.source, ".", key_str)
    elseif sub(key_lua:text(), 1, 1) == "[" then
      return LuaCode:from(tree.source, "[ ", key_lua, "]")
    else
      return LuaCode:from(tree.source, "[", key_lua, "]")
    end
  elseif "DictEntry" == _exp_0 then
    local key = tree[1]
    if key.type ~= "Index" then
      key = SyntaxTree({
        type = "Index",
        source = key.source,
        key
      })
    end
    return LuaCode:from(tree.source, self:compile(key), "=", (tree[2] and self:compile(tree[2]) or "true"))
  elseif "IndexChain" == _exp_0 then
    local lua = self:compile(tree[1])
    if lua:text():match("['\"}]$") or lua:text():match("]=*]$") then
      lua:parenthesize()
    end
    for i = 2, #tree do
      local key = tree[i]
      if key.type ~= "Index" then
        key = SyntaxTree({
          type = "Index",
          source = key.source,
          key
        })
      end
      lua:add(self:compile(key))
    end
    return lua
  elseif "Number" == _exp_0 then
    return LuaCode:from(tree.source, tostring(tree[1]))
  elseif "Var" == _exp_0 then
    return LuaCode:from(tree.source, tree:as_var():as_lua_id())
  elseif "FileChunks" == _exp_0 then
    return error("Can't convert FileChunks to a single block of lua, since each chunk's " .. "compilation depends on the earlier chunks")
  elseif "Comment" == _exp_0 then
    return LuaCode:from(tree.source, "-- ", (tree[1]:gsub('\n', '\n-- ')))
  elseif "Error" == _exp_0 then
    return error("Can't compile errors")
  else
    return error("Unknown type: " .. tostring(tree.type))
  end
end
return {
  compile = compile,
  fail_at = fail_at
}