local lpeg = require('lpeg')
local R, P, S
R, P, S = lpeg.R, lpeg.P, lpeg.S
local re = require('re')
local List, Dict, Text
do
  local _obj_0 = require('containers')
  List, Dict, Text = _obj_0.List, _obj_0.Dict, _obj_0.Text
end
local insert, remove, concat
do
  local _obj_0 = table
  insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
end
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 Importer, import_to_1_from, _1_forked
do
  local _obj_0 = require('importer')
  Importer, import_to_1_from, _1_forked = _obj_0.Importer, _obj_0.import_to_1_from, _obj_0._1_forked
end
local Files = require("files")
table.map = function(t, fn)
  return setmetatable((function()
    local _accum_0 = { }
    local _len_0 = 1
    for _, v in ipairs(t) do
      _accum_0[_len_0] = fn(v)
      _len_0 = _len_0 + 1
    end
    return _accum_0
  end)(), getmetatable(t))
end
local pretty_error = require("pretty_errors")
local compile_error
compile_error = function(source, err_msg, hint)
  if hint == nil then
    hint = nil
  end
  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 err_str = pretty_error({
    title = "Compile error",
    error = err_msg,
    hint = hint,
    source = file,
    start = source.start,
    stop = source.stop,
    filename = source.filename
  })
  return error(err_str, 0)
end
local math_expression = re.compile([[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]])
local MAX_LINE = 80
local compile = setmetatable({
  action = Importer({
    [""] = function(compile, fn, ...)
      local lua = LuaCode()
      local fn_lua = compile(fn)
      lua:add(fn_lua)
      if not (fn_lua:text():match("^%(.*%)$") or fn_lua:text():match("^[_a-zA-Z][_a-zA-Z0-9.]*$")) then
        lua:parenthesize()
      end
      lua:add("(")
      for i = 1, select('#', ...) do
        if i > 1 then
          lua:add(", ")
        end
        lua:add(compile((select(i, ...))))
      end
      lua:add(")")
      return lua
    end,
    ["Lua"] = function(compile, code)
      if not code then
        return LuaCode("LuaCode()")
      end
      if code.type ~= "Text" then
        return LuaCode("LuaCode:from(", tostring(code.source):as_lua(), ", ", compile(code), ")")
      end
      local operate_on_text
      operate_on_text = function(text)
        local lua = LuaCode:from(text.source, "LuaCode:from(", tostring(text.source):as_lua())
        for _index_0 = 1, #text do
          local bit = text[_index_0]
          local bit_lua
          if type(bit) == "string" then
            bit_lua = bit:as_lua()
          elseif bit.type == "Text" then
            bit_lua = operate_on_text(bit)
          elseif bit.type == "Block" then
            bit_lua = LuaCode:from(bit.source, "(function()", "\n    local _lua = LuaCode:from(", tostring(bit.source):as_lua(), ")", "\n    local function add(...) _lua:add(...) end", "\n    local function join_with(glue)", "\n        local old_bits = _lua.bits", "\n        _lua = LuaCode:from(_lua.source)", "\n        _lua:concat_add(old_bits, glue)", "\n    end", "\n    ", compile(bit), "\n    return _lua", "\nend)()")
          else
            bit_lua = compile(bit)
          end
          local bit_leading_len = #(bit_lua:match("^[^\n]*"))
          lua:add(lua:trailing_line_len() + bit_leading_len > MAX_LINE and ",\n    " or ", ")
          lua:add(bit_lua)
        end
        lua:add(")")
        return lua
      end
      return operate_on_text(code)
    end,
    ["lua >"] = function(compile, code)
      if code.type ~= "Text" then
        return code
      end
      local operate_on_text
      operate_on_text = function(text)
        local lua = LuaCode:from(text.source)
        for _index_0 = 1, #text do
          local bit = text[_index_0]
          if type(bit) == "string" then
            lua:add(bit)
          elseif bit.type == "Text" then
            lua:add(operate_on_text(bit))
          else
            lua:add(compile(bit))
          end
        end
        return lua
      end
      return operate_on_text(code)
    end,
    ["= lua"] = function(compile, code)
      return compile.action["lua >"](compile, code)
    end,
    ["use"] = function(compile, path)
      return LuaCode("run_file_1_in(" .. tostring(compile(path)) .. ", _ENV, OPTIMIZATION)")
    end,
    ["use 1 with prefix"] = function(compile, path, prefix)
      return LuaCode("run_file_1_in(" .. tostring(compile(path)) .. ", _ENV, OPTIMIZATION, ", compile(prefix), ")")
    end,
    ["test"] = function(compile, body)
      if not (body.type == 'Block') then
        compile_error(body, "This should be a Block")
      end
      local test_nomsu = body:get_source_code():match(":[ ]*(.*)")
      do
        local indent = test_nomsu:match("\n([ ]*)")
        if indent then
          test_nomsu = test_nomsu:gsub("\n" .. indent, "\n")
        end
      end
      local test_text = compile(SyntaxTree({
        type = "Text",
        source = body.source,
        test_nomsu
      }))
      return LuaCode("TESTS[" .. tostring(tostring(body.source):as_lua()) .. "] = ", test_text)
    end,
    ["is jit"] = function(compile, code)
      return LuaCode("jit")
    end,
    ["Lua version"] = function(compile, code)
      return LuaCode("_VERSION")
    end,
    ["nomsu environment"] = function(compile)
      return LuaCode("_ENV")
    end
  })
}, {
  __import = import_to_1_from,
  __call = function(compile, tree)
    local _exp_0 = tree.type
    if "Action" == _exp_0 then
      local stub = tree.stub
      local compile_action = compile.action[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 = 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(compile, unpack(args))
        if ret == nil then
          local info = debug.getinfo(compile_action, "S")
          local filename = Source:from_string(info.source).filename
          compile_error(tree, "The compile-time action here (" .. tostring(stub) .. ") failed to return any value.", "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 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 = 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 lua = LuaCode:from(tree.source)
      local target_lua = 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 = 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 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(compile(line))
      end
      return lua
    elseif "Text" == _exp_0 then
      local lua = LuaCode:from(tree.source)
      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  ")
          end
          lua:add("..")
        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 = compile(bit)
          if bit.type == "Block" and #bit == 1 then
            bit = bit[1]
          end
          if bit.type == "Block" then
            bit_lua = LuaCode:from(bit.source, "List(function(add)", "\n    ", bit_lua, "\nend):joined()")
          elseif bit.type ~= "Text" and bit.type ~= "Number" then
            bit_lua = LuaCode:from(bit.source, "tostring(", bit_lua, ")")
          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
        add_bit('""')
      end
      if added > 1 then
        lua:parenthesize()
      end
      return lua
    elseif "List" == _exp_0 or "Dict" == _exp_0 then
      if #tree == 0 then
        return LuaCode:from(tree.source, tree.type, "{}")
      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(tree.type, "(function(", (tree.type == 'List' and "add" or ("add, " .. ("add 1 ="):as_lua_id())), ")")
          lua:add("\n    ", 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 = 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, tree.type, "{\n    ", items_lua, "\n}"))
          else
            lua:add(LuaCode:from(items_lua.source, tree.type, "{", items_lua, "}"))
          end
          chunks = chunks + 1
        end
      end
      return lua
    elseif "Index" == _exp_0 then
      local key_lua = 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, compile(key), "=", (tree[2] and compile(tree[2]) or "true"))
    elseif "IndexChain" == _exp_0 then
      local lua = 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(compile(key))
      end
      return lua
    elseif "Number" == _exp_0 then
      return LuaCode:from(tree.source, tostring(tree[1]))
    elseif "Var" == _exp_0 then
      if type(tree[1]) == 'string' then
        return LuaCode:from(tree.source, (concat(tree, " ")):as_lua_id())
      else
        assert(tree[1].type == 'Action')
        return LuaCode:from(tree.source, tree[1]:get_stub():as_lua_id())
      end
    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,
  compile_error = compile_error
}