#!/usr/bin/env nomsu -V3.6.5.6
#
    This File contains actions for making actions and compile-time actions and some helper
    functions to make that easier.

lua> "NOMSU_CORE_VERSION = 6"
lua> ".."
    COMPILE_ACTIONS["1 -> 2"] = function(nomsu, tree, \%args, \%body)
        local lua = LuaCode.Value(tree.source, "(function(")
        if AST.is_syntax_tree(\%args, "Action") then \%args = \%args:get_args() end
        local lua_args = table.map(\%args, function(a) return AST.is_syntax_tree(a) and tostring(nomsu:compile(a)) or a end)
        lua:concat_append(lua_args, ", ")
        local body_lua = AST.is_syntax_tree(\%body) and nomsu:compile(\%body):as_statements("return ") or \%body
        body_lua:remove_free_vars(lua_args)
        body_lua:declare_locals()
        lua:append(")\\n    ", body_lua, "\\nend)")
        return lua
    end

lua> ".."
    COMPILE_ACTIONS["compile as 1"] = function(nomsu, tree, \%action)
        local lua = LuaCode.Value(tree.source, "COMPILE_ACTIONS[", repr(\%action.stub), "](")
        local lua_args = table.map(\%action:get_args(), function(a) return nomsu:compile(a) end)
        table.insert(lua_args, 1, "nomsu")
        table.insert(lua_args, 2, "tree")
        lua:concat_append(lua_args, ", ")
        lua:append(")")
        return lua
    end

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

test:
    compile [five] to (Lua value "5")
test:
    assume ((five) == 5) or barf "Compile to expression failed."
    compile [loc x] to (Lua "local _x = 99;")
test:
    lua> "do"
    loc x
    assume (%x is 99) or barf "Compile to statements with locals failed."
    lua> "end"
    assume (%x is (nil)) or barf "Failed to properly localize a variable."
    compile [asdf] to:
        %tmp = ""
        return (Lua %tmp)
test:
    asdf
    assume (%tmp is (nil)) or barf "compile to is leaking variables"
lua> ".."
    COMPILE_ACTIONS["compile 1 to 2"] = function(nomsu, tree, \%actions, \%body)
        local \%args = {"nomsu", "tree", unpack(table.map(\%actions[1]:get_args(), function(a) return tostring(nomsu:compile(\
    ..a)) end))}
        local lua = LuaCode(tree.source, "COMPILE_ACTIONS[", repr(\%actions[1].stub),
            "] = ", \(compile as (%args -> %body)))
        for i=2,#\%actions do
            local alias = \%actions[i]
            local \%alias_args = {"nomsu", "tree", unpack(table.map(alias:get_args(), function(a) return tostring(nomsu:compile(\
    ..a)) end))}
            lua:append("\\nCOMPILE_ACTIONS[", repr(alias.stub), "] = ")
            if utils.equivalent(\%args, \%alias_args) then
                lua:append("COMPILE_ACTIONS[", repr(\%actions[1].stub), "]")
            else
                lua:append("function(")
                lua:concat_append(\%alias_args, ", ")
                lua:append(")\\n    return COMPILE_ACTIONS[", repr(\%actions[1].stub), "](")
                lua:concat_append(\%args, ", ")
                lua:append(")\\nend")
            end
        end
        return lua
    end

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

compile [call %fn with %args] to:
    lua> ".."
        local lua = LuaCode.Value(tree.source, nomsu:compile(\%fn), "(")
        lua:concat_append(table.map(\%args, function(a) return nomsu:compile(a) end), ", ")
        lua:append(")")
        return lua

test:
    local action [foo %x]: return "outer"
    with local [action (foo %)]:
        local action [foo %x]:
            %y = (%x + 1)
            return %y
        
        assume ((foo 10) == 11) or barf "Action didn't work."
        assume (%y is (nil)) or barf "Action leaked a local into globals."
        parse [baz %] as (foo %)
    
    assume ((foo 1) == "outer")
compile [local action %actions %body] to:
    lua> ".."
        local fn_name = "A_"..string.as_lua_id(\%actions[1].stub)
        local \%args = table.map(\%actions[1]:get_args(), function(a) return tostring(nomsu:compile(a)) end)
        local lua = LuaCode(tree.source, fn_name, " = ", \(compile as (%args -> %body)))
        lua:add_free_vars({fn_name})
        for i=2,#\%actions do
            local alias = \%actions[i]
            local alias_name = "A_"..string.as_lua_id(alias.stub)
            lua:add_free_vars({alias_name})
            local \%alias_args = table.map(alias:get_args(), function(a) return tostring(nomsu:compile(a)) end)
            lua:append("\\n", alias_name, " = ")
            if utils.equivalent(\%args, \%alias_args) then
                lua:append(fn_name)
            else
                lua:append("function(")
                lua:concat_append(\%alias_args, ", ")
                lua:append(")\\n    return ", fn_name, "(")
                lua:concat_append(\%args, ", ")
                lua:append(")\\nend")
            end
        end
        return lua

test:
    action [baz1]: return "baz1"
    action [baz2] "baz2"
test:
    assume ((baz1) == "baz1")
    assume ((baz2) == "baz2")
compile [action %actions %body] to (..)
    lua> ".."
        local lua = \(compile as (local action %actions %body))
        lua:remove_free_vars(table.map(\%actions, function(a) return "A_"..string.as_lua_id(a.stub) end))
        return lua

test:
    assume ((action (say %)) == (=lua "A_say_1"))
compile [action %action] to (Lua value (%action as lua id))

test:
    parse [swap %x and %y] as (..)
        do:
            %tmp = %x
            %x = %y
            %y = %tmp
test:
    set {%1:1, %2:2}
    swap %1 and %2
    assume ((%1 == 2) and (%2 == 1)) or barf ".."
        'parse % as %' failed on 'swap % and %'
    set {%tmp:1, %tmp2:2}
    swap %tmp and %tmp2
    assume ((%tmp == 2) and (%tmp2 == 1)) or barf ".."
        'parse % as %' variable mangling failed.
compile [parse %actions as %body] to (..)
    lua> ".."
        local replacements = {}
        for i,arg in ipairs(\%actions[1]:get_args()) do
            replacements[arg[1]] = tostring(nomsu:compile(arg))
        end
        local function make_tree(t)
            if AST.is_syntax_tree(t, "Var") then
                if replacements[t[1]] then
                    return replacements[t[1]]
                else
                    return t.type.."{"..repr(t[1].." \\0").."..('%X'):format(__MANGLE_INDEX), source="..repr(tostring(t.source)).."}"
                end
            elseif AST.is_syntax_tree(t) then
                local ret = {}
                local i = 1
                for k, v in pairs(t) do
                    if k == i then
                        ret[#ret+1] = make_tree(t[i])
                        i = i + 1
                    elseif k == "source" then
                        ret[#ret+1] = k.."= "..repr(tostring(v))
                    elseif type(k) == 'string' and k:match("[_a-zA-Z][_a-zA-Z0-9]*") then
                        ret[#ret+1] = k.."= "..make_tree(v)
                    else
                        ret[#ret+1] = "["..make_tree(k).."]= "..make_tree(v)
                    end
                end
                return t.type.."{"..table.concat(ret, ", ").."}"
            else
                return repr(t)
            end
        end
        local \%new_body = LuaCode(\%body.source,
            "__MANGLE_INDEX = (__MANGLE_INDEX or 0) + 1",
            "\\nlocal tree = ", make_tree(\%body),
            "\\nlocal lua = nomsu:compile(tree)",
            "\\nreturn lua")
        local ret = \(compile as (compile %actions to %new_body))
        return ret

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

action [%tree as lua expr]:
    lua> ".."
        \%tree_lua = nomsu:compile(\%tree)
        if not \%tree_lua.is_value then
            nomsu:compile_error(\%tree.source, "Could not convert %s to a Lua expression",
                nomsu:tree_to_nomsu(\%tree))
        end
        return \%tree_lua

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

compile [%tree as lua] to (Lua value "nomsu:compile(\(%tree as lua expr))")
compile [%tree as lua statements] to (..)
    Lua value "nomsu:compile(\(%tree as lua expr)):as_statements()"

compile [%tree as lua return] to (..)
    Lua value "nomsu:compile(\(%tree as lua expr)):as_statements('return ')"

compile [remove action %action] to (..)
    Lua "A_\(=lua "string.as_lua_id(\(%action.stub))") = nil"

test:
    assume ("\(\(foo \%x) as nomsu)" == "foo %x") or barf ".."
        action source code failed.
compile [%tree as nomsu] to (..)
    Lua value "nomsu:tree_to_nomsu(\(%tree as lua expr))"

compile [%tree as inline nomsu] to (..)
    Lua value "nomsu:tree_to_nomsu(\(%tree as lua expr), true)"

action [%var as lua identifier, %var as lua id] (..)
    lua> ".."
        if type(\%var) == 'string' then return string.as_lua_id(\%var)
        elseif \%var.type == 'Var' then return "_"..string.as_lua_id(\%var[1])
        elseif \%var.type == 'Action' then return "A_"..string.as_lua_id(\%var.stub)
        end

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

compile [% is syntax tree] to (Lua value "AST.is_syntax_tree(\(% as lua expr))")
compile [% is %kind syntax tree] to (..)
    Lua value "AST.is_syntax_tree(\(% as lua expr), \(%kind as lua expr))"

compile [%tree with %t -> %replacement] to (..)
    Lua value ".."
        \(%tree as lua expr):map(function(\(%t as lua expr))
            \(%replacement as lua return)
        end)

action [%tree with vars %replacements] (..)
    =lua ".."
        \%tree:map(function(\%t)
            if \%t.type == "Var" then
                return \%replacements[\%t[1]]
            end
        end)

compile [tree %tree with vars %replacements] to (..)
    Lua value ".."
        \(=lua "repr(\%tree)"):map(function(t)
            if t.type == "Var" then
                return \(%replacements as lua expr)[t[1]]
            end
        end)

compile [%tree has subtree %match_tree] to (..)
    Lua value ".."
        (function()
            local match_tree = \(%match_tree as lua expr)
            for subtree in coroutine.wrap(function() \(%tree as lua expr):map(coroutine.yield) end) do
                if subtree == match_tree then return true end
            end
        end)()

action [match %tree with %patt]:
    lua> ".."
        if \%patt.type == "Var" then return dict{[\%patt[1]]=\%tree} end
        if \%patt.type == "Action" and \%patt.stub ~= \%tree.stub then return nil end
        if #\%patt ~= #\%tree then return nil end
        local matches = dict{}
        for \%i=1,#\%patt do
            if AST.is_syntax_tree(\%tree[\%i]) then
                local submatch = \(match %tree.%i with %patt.%i)
                if not submatch then return nil end
                for k,v in pairs(submatch) do
                    if matches[k] and matches[k] ~= v then return nil end
                    matches[k] = v
                end
            end
        end
        return matches

action [%tree with %patt ~> %replacement]:
    lua> ".."
        return \%tree:map(function(\%t)
            local \%vars = \(match %t with %patt)
            if not \%vars then return nil end
            for \%k,\%v in pairs(\%vars) do
                \%vars[\%k] = \(%v with %patt ~> %replacement)
            end
            return \%replacement:map(function(\%t)
                if \%t.type == "Var" then
                    return \%vars[\%t[1]]
                end
            end)
        end)

test:
    assume (..)
        (..)
            quote ".."
                one
                "two"
        ..== "\"one\\n\\\"two\\\"\""
compile [quote %s] to (Lua value "repr(\(%s as lua expr))")

test:
    assume ((type of {}) == "table") or barf "type of failed."
compile [type of %obj] to (Lua value "type(\(%obj as lua expr))")

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

test:
    assume ((parse "foo %") == \(foo \%))
    %a = (parse "\\1")
    %b = \(\(1))
    assume ((parse "\\1") == \(\(1)))
compile [parse %text] to (Lua value "nomsu:parse(\(%text as lua expr))")
compile [parse %text from %filename] to (..)
    Lua value ".."
        nomsu:parse(NomsuCode(Source(\(%filename as lua expr), 1, #\(%text as lua expr)), \(..)
            %text as lua expr
        ..))

test:
    assume ((run "return (2 + 99)") == 101)
    external %passed = (no)
    run \:
        \(external \%passed = \(yes))
    assume %passed
compile [run %nomsu_code] to (..)
    Lua value ".."
        nomsu:run(\(%nomsu_code as lua expr), \(..)
            =lua "repr(tostring(\(%nomsu_code.source)))"
        ..)

test:
    assume ((\(\(5) + \(5)) as value) == 10) or barf "%tree as value failed."
action [run tree %tree, %tree as value] (lua> "return nomsu:run(\%tree)")
compile [compile %block, compiled %block, %block compiled] to (..)
    Lua value "nomsu:compile(\(%block as lua))"

# Return statement is wrapped in a do..end block because Lua is unhappy if you
    put code after a return statement, unless you wrap it in a block.
compile [return] to (Lua "do return; end")
compile [return %return_value] to (..)
    Lua "do return \(%return_value as lua expr) end"

# Literals
compile [yes] to (Lua value "true")
compile [no] to (Lua value "false")
compile [nothing, nil, null] to (Lua value "nil")
compile [Nomsu syntax version] to (Lua value "NOMSU_SYNTAX_VERSION")
compile [Nomsu compiler version] to (Lua value "NOMSU_COMPILER_VERSION")
compile [core version] to (Lua value "NOMSU_CORE_VERSION")
compile [lib version] to (Lua value "NOMSU_LIB_VERSION")
compile [command line args] to (Lua value "arg")

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

compile [with local compile actions %body] to (..)
    Lua ".."
        do
            local nomsu = table.fork(nomsu, {COMPILE_ACTIONS=table.fork(COMPILE_ACTIONS)})
            \(%body as lua statements)
        end

action [Nomsu version]:
    use "lib/version.nom"
    return ".."
        \(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version)