Download
519 lines 18.5 KiBNomsuModified 2019-03-27 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
#!/usr/bin/env nomsu -V7.0.0
###
    This File contains actions for making actions and compile-time actions and some helper
    functions to make that easier.
    
lua> ("
    do
        local mangle_index = 0
        function mangler()
            local my_mangle_index = mangle_index
            mangle_index = mangle_index + 1
            return function(varname)
                return (varname..(("\\3%X"):format(my_mangle_index))):as_lua_id()
            end
        end
    end
    COMPILE_RULES["define mangler"] = function(\(nomsu environment), _tree)
        return LuaCode("local mangle = mangler()")
    end
    COMPILE_RULES["this tree"] = function(\(nomsu environment), _tree)
        return LuaCode("_tree")
    end
")

lua> ("
    COMPILE_RULES["1 ->"] = function(\(nomsu environment), _tree, \$args, \$body)
        if not \$args and not \$body then \$args, \$body = {}, SyntaxTree{type='Action', "do", "nothing"}
        elseif \$args and not \$body then \$args, \$body = {}, \$args end
        local body_lua = SyntaxTree:is_instance(\$body) and \(nomsu environment):compile(\$body) or \$body
        if SyntaxTree:is_instance(\$body) and \$body.type ~= "Block" then body_lua:prepend("return ") end
        local lua = LuaCode("(function(")
        if SyntaxTree:is_instance(\$args) and (\$args.type == "Action" or \$args.type == "MethodCall") then
            \$args = \$args:get_args()
        elseif SyntaxTree:is_instance(\$args) and \$args.type == "Var" then \$args = {\$args} end
        for i, arg in ipairs(\$args) do
            local arg_lua = SyntaxTree:is_instance(arg) and \(nomsu environment):compile(arg):text() or arg
            if arg_lua == "..." then
                if i < #\$args then
                    at_1_fail(SyntaxTree:is_instance(arg) and arg or nil,
                        "Compile error: Extra arguments must come last. "..
                        "Hint: Try removing any arguments after (*extra arguments*)")
                end
            elseif not arg_lua:is_lua_id() then
                at_1_fail(SyntaxTree:is_instance(arg) and arg or nil,
                    "Compile error: This does not compile to a Lua identifier ("..arg_lua.."),"..
                    "so it can't be used as a function argument. "..
                    "Hint: This should probably be a Nomsu variable instead (like $x).")
            end
            lua:add(i > 1 and ", " or "", arg_lua)
            body_lua:remove_free_vars({arg_lua})
        end
        body_lua:declare_locals()
        lua:add(")\\n    ", body_lua, "\\nend)")
        return lua
    end
    COMPILE_RULES["->"] = COMPILE_RULES["1 ->"]
    COMPILE_RULES["for"] = COMPILE_RULES["1 ->"]
    
    COMPILE_RULES["\\\\"] = function(\(nomsu environment), _tree, escaped)
        local function escape(t)
            if t.type == "Action" and t:get_stub() == "\\\\" and #t == 2 then
                return \(nomsu environment):compile(t[2])
            else
                local bits = {}
                table.insert(bits, "type="..t.type:as_lua())
                if t.source then
                    table.insert(bits, "source="..t.source:as_lua())
                end
                for i,b in ipairs(t) do
                    table.insert(bits, lua_type_of(b) == 'string' and b:as_lua() or escape(b))
                end
                local lua = LuaCode:from(t.source, "SyntaxTree{")
                lua:concat_add(bits, ", ")
                lua:add("}")
                return lua
            end
        end
        return escape(escaped)
    end
")

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

test:
    (five) compiles to "5"

test:
    unless ((five) == 5):
        fail "Compile to expression failed."
    (loc x) compiles to "local x = 99;"

test:
    lua> "do"
    loc x
    unless ($x == 99):
        fail "Compile to statements with locals failed."
    lua> "end"
    unless ($x == (nil)):
        fail "Failed to properly localize a variable."
    
    (asdf) compiles to:
        $tmp = ""
        return (Lua $tmp)

test:
    asdf
    unless ($tmp == (nil)):
        fail "compile to is leaking variables"

lua> ("
    COMPILE_RULES["1 compiles to"] = function(\(nomsu environment), \(this tree), \$action, \$body)
        local \$args = a_List{"\(nomsu environment)", "\(this tree)"}
        if \$body.type == "Text" then
            \$body = SyntaxTree{source=\$body.source, type="Action", "Lua", \$body}
        end
        if not (\$action.type == "Action" or
                (\$action.type == "EscapedNomsu" and \$action[1].type == "Action") or
                \$action.type == "MethodCall") then
            at_1_fail(\$action.source, "Compile error: "..
                "This first argument to (* compiles to *) is neither an action nor an escaped \
    ..action (it's a "..\$action.type.."). "..
                "Hint: This should probably be an action like:\\n"
                .."(foo $x) compiles to \\"(\\\\($x as lua) + 1)\\"")
        end
        if \$action.type == "EscapedNomsu" then
            for _,a in ipairs(\$action[1]) do
                if a.type == "EscapedNomsu" then \$args:add(a[1]) end
            end
            return LuaCode("COMPILE_RULES[", \($action as lua), ":get_stub()] = ",
                \(\(\$args -> \$body) as lua))
        else
            for _,a in ipairs(\$action:get_args()) do \$args:add(a) end
            return LuaCode("COMPILE_RULES[", \$action:get_stub():as_lua(),
                "] = ", \(\(\$args -> \$body) as lua))
        end
    end
")

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

($actions all compile to $body) compiles to:
    lua> ("
        if \$actions.type ~= "List" then
            at_1_fail(\$actions, "Compile error: This should be a list of actions.")
        end
        local lua = \(\(\$actions.1 compiles to \$body) as lua)
        local \$args = a_List{"\(nomsu environment)", "\(this tree)", unpack(\$actions[1]:get_args())}
        local \$compiled_args = a_List{"\(nomsu environment)", "\(this tree)"};
        for i=3,#\$args do \$compiled_args[i] = \(nomsu environment):compile(\$args[i]) end
        for i=2,#\$actions do
            local alias = \$actions[i]
            local \$alias_args = a_List{"\(nomsu environment)", "\(this tree)", unpack(alias:get_args())}
            lua:add("\\nCOMPILE_RULES[", alias:get_stub():as_lua(), "] = ")
            if \$alias_args == \$args then
                lua:add("COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "]")
            else
                lua:add("function(")
                local \$compiled_alias_args = a_List{"\(nomsu environment)", "\(this tree)"};
                for i=3,#\$alias_args do \$compiled_alias_args[i] = \(nomsu environment):compile(\$alias_args[i]) end
                lua:concat_add(\$compiled_alias_args, ", ")
                lua:add(") return COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "](")
                lua:concat_add(\$compiled_args, ", ")
                lua:add(") end")
            end
        end
        return lua
    ")

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

test:
    (foo $x) means "outer"
    with [$(foo $)]:
        (foo $x) means:
            $y = ($x + 1)
            return $y
        
        unless ((foo 10) == 11):
            fail "Action didn't work."
        
        unless ($y == (nil)):
            fail "Action leaked a local into globals."
        
        (baz $) parses as (foo $)
    assume ((foo 1) == "outer")

($action means $body) compiles to:
    lua> ("
        local lua = LuaCode()
        if \$action.type == "MethodCall" then
            lua:add(\(nomsu environment):compile(\$action[1]), ".", \$action[2]:get_stub():as_lua_id())
        elseif \$action.type == "Action" then
            lua:add(\$action:get_stub():as_lua_id())
            lua:add_free_vars({\$action:get_stub():as_lua_id()})
        else
            at_1_fail(\$action, "Compile error: This is not an action or method call.")
        end
        lua:add(" = ", \(\(\$action -> \$body) as lua), ";")
        return lua
    ")

($actions all mean $body) compiles to:
    lua> ("
        local lua = \(\(\$actions.1 means \$body) as lua)
        local first_def = (\$actions[1].type == "MethodCall"
            and LuaCode(\(nomsu environment):compile(\$actions[1][1]), ".", \$actions[1][2]:get_\
        ..stub():as_lua_id())
            or LuaCode(\$actions[1]:get_stub():as_lua_id()))
        local \$args = a_List(\$actions[1]:get_args())
        for i=2,#\$actions do
            local alias = \$actions[i]
            local \$alias_args = a_List(alias:get_args())
            lua:add("\\n")
            if alias.type == "MethodCall" then
                lua:add(\(nomsu environment):compile(alias[1]), ".", alias[2]:get_stub():as_lua_id())
            else
                lua:add(alias:get_stub():as_lua_id())
                lua:add_free_vars({alias_name})
            end
            if \$args == \$alias_args then
                lua:add(" = ", first_def, ";")
            else
                lua:add(" = ", \(\(\$alias_args -> \$actions.1) as lua), ";")
            end
        end
        return lua
    ")

test:
    $loc = 99
    external ($glob = 99)

test:
    assume $loc == (nil)
    assume $glob == 99

(external $body) compiles to:
    lua> ("
        local lua = \($body as lua)
        lua:remove_free_vars()
        return lua
    ")

test:
    [$x, $y] = ["outer", "outer"]
    external:
        (set external x local y) means:
            with external [$x]:
                $x = "inner"
                $y = "inner"
    
    set external x local y
    unless (($x == "inner") and ($y == "outer")):
        fail "'with external' failed."

(with external $externals $body) compiles to:
    lua> ("
        local body_lua = \($body as lua)
        local varnames = {}
        for i,\$v in ipairs(\$externals) do
            varnames[i] = \($v as lua):text()
        end
        body_lua:remove_free_vars(varnames)
        return body_lua
    ")

test:
    (swap $x and $y) parses as
        do:
            $tmp = $x
            $x = $y
            $y = $tmp

test:
    [$1, $2] = [1, 2]
    swap $1 and $2
    unless (($1 == 2) and ($2 == 1)):
        fail "'parse $ as $' failed on 'swap $ and $'"
    [$tmp, $tmp2] = [1, 2]
    swap $tmp and $tmp2
    unless (($tmp == 2) and ($tmp2 == 1)):
        fail "'parse $ as $' variable mangling failed."

($actions all parse as $body) compiles to:
    lua> ("
        local replacements = {}
        if \$actions.type ~= "List" then
            at_1_fail(\$actions, "Compile error: This should be a list.")
        end
        for i,arg in ipairs(\$actions[1]:get_args()) do
            replacements[arg[1]] = \(nomsu environment):compile(arg):text()
        end
        local function make_tree(t)
            if SyntaxTree:is_instance(t) and t.type == "Var" then
                if replacements[t:as_var()] then
                    return replacements[t:as_var()]
                else
                    return "SyntaxTree{mangle("..t:as_var():as_lua().."), type="..t.type:as_lua(\
        ..)..", source=".._1_as_text(t.source):as_lua().."}"
                end
            elseif SyntaxTree:is_instance(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.."= ".._1_as_text(v):as_lua()
                    elseif lua_type_of(k) == 'string' and k:is_a_lua_id() then
                        ret[#ret+1] = k.."= "..make_tree(v)
                    else
                        ret[#ret+1] = "["..make_tree(k).."]= "..make_tree(v)
                    end
                end
                return "SyntaxTree{"..table.concat(ret, ", ").."}"
            elseif lua_type_of(t) == 'number' then
                return _1_as_text(t)
            else
                return t:as_lua()
            end
        end
        local \$new_body = LuaCode:from(\$body.source,
            "local mangle = mangler()",
            "\\nreturn ", make_tree(\$body))
        return \(\(\$actions all compile to \$new_body) as lua)
    ")

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

[$action parses as $body] all parse as ([$action] all parse as $body)
(nomsu environment, $tree as lua expr) means:
    lua> ("
        local tree_lua = \(nomsu environment):compile(\$tree)
        if \$tree.type == 'Block' and #\$tree > 1 then
            tree_lua = LuaCode:from(\$tree.source, '(function()\\n    ', tree_lua, '\\nend)()')
        end
        return tree_lua
    ")

### Need to make sure the proper environment is used for compilation (i.e. the caller's environment)
($tree as lua expr) compiles to \(nomsu environment, \$tree as lua expr)

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

external:
    [$var as lua identifier, $var as lua id] all mean:
        lua> ("
            local lua = \($var as lua)
            if not lua:text():is_a_lua_id() then
                at_1_fail(\$var, "Compile error: "..
                    "This is supposed to be something that compiles to a valid Lua identifier. "..
                    "Hint: This should probably be a variable.")
            end
            return lua
        ")

test:
    (num args (*extra arguments*)) means #(*extra arguments*)
    assume (num args 1 2 3) == 3
    (extra args (*extra arguments*)) means [*extra arguments*]
    assume (extra args 1 2 3) == [1, 2, 3]
    (third arg (*extra arguments*)) means (*extra arguments*).3
    assume (third arg 5 6 7 8) == 7

(*extra arguments*) compiles to "..."
($ is syntax tree) compiles to "SyntaxTree:is_instance(\($ as lua expr))"
external:
    ($ is $kind syntax tree) means
        =lua "SyntaxTree:is_instance(\$) and \$.type == \$kind"

external:
    (match $tree with $patt) means:
        lua> ("
            if \$patt.type == "Var" then return a_Dict{[\$patt:as_var()]=\$tree} end
            if \$patt.type == "Action" and \$patt:get_stub() ~= \$tree:get_stub() then return nil end
            if #\$patt ~= #\$tree then return nil end
            local matches = a_Dict{}
            for \($i)=1,#\$patt do
                if SyntaxTree:is_instance(\$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
        ")

test:
    assume
        (
            quote ("
                one
                "two"
            ")
        ) == "\"one\\n\\\"two\\\"\""

external:
    (quote $) means ($ as text, as lua)

test:
    assume (lua type of {}) == "table"
    assume (type of {}) == "a Dict"
    assume ((type of 5) == "a Number")
    assume ({} is "a Dict")
    assume ("" is text)
    assume ("" is "Text")
    assume (5 is "a Number")
    assume ((->) is "an Action")
    assume ((yes) is "a Boolean")
    assume ("" isn't "a Dict")

external:
    ($ is text) means (=lua "\(lua type of $) == 'string'")
    [$ is not text, $ isn't text] all mean (=lua "\(lua type of $) ~= 'string'")
    (type of $) means:
        lua> ("
            local mt = getmetatable(\$)
            if mt and mt.__type then return mt.__type end
            if \$ == nil then return 'nil' end
            local lua_type = \(lua type of $)
            return 'a '..lua_type:capitalized()
        ")
    
    ($ is $type) means:
        lua> ("
            local class = getmetatable(\$)
            ::check_parent::
            if not class or not class.__type then return 'a '..\(lua type of $):capitalized() == \$type end
            if class.__type == \$type then return true end
            local class_mt = getmetatable(class)
            if class_mt.__index and class_mt.__index ~= class then
                class = class_mt.__index
                goto check_parent
            end
            return false
        ")

[$ isn't $type, $ is not $type] all parse as (not ($ is $type))

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

test:
    assume ("Action" tree with "foo" ("Var" tree with "x")) == \(foo $x)

external:
    ($type tree with (*extra arguments*)) means
        SyntaxTree (=lua "{type=\$type, ...}")
    
    ($type tree from $source) means
        SyntaxTree (=lua "{type=\$type, source=\$source}")
    
    ($type tree from $source with (*extra arguments*)) means
        SyntaxTree (=lua "{type=\$type, source=\$source, ...}")

test:
    (foo) means:
        return 100 200 300
    assume (select 2 (foo)) == 200

### 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.
(return (*extra arguments*)) compiles to:
    lua> ("
        local lua = \(Lua "do return ")
        for i=1,select('#',...) do
            if i > 1 then lua:add(", ") end
            lua:add(\(nomsu environment):compile((select(i, ...))))
        end
        lua:add(" end")
        return lua
    ")

### Convenience helper:
(return Lua (*extra arguments*)) compiles to \(return (Lua \(*extra arguments*)))

### Literals
(yes) compiles to "(true)"
(no) compiles to "(false)"
[nothing, nil, null] all compile to "(nil)"
(command line args) compiles to "COMMAND_LINE_ARGS"

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

(at compilation $expr) compiles to:
    lua> ("
        local value = \(nomsu environment):run(\(\(return \$expr)))
        if lua_type_of(value) == 'table' or lua_type_of(value) == 'string' and value.as_lua then
            return LuaCode(value:as_lua())
        else
            return LuaCode(tostring(value))
        end
    ")

test:
    using compile rules:
        (yes) compiles to "3"
        assume $(COMPILE RULES).yes
    ..do:
        assume (yes) == 3
    assume (yes) == (=lua "true")

(using compile rules $rules do $body) compiles to:
    lua> ("
        local env = \(new environment)
        env:run(\$rules)
        local lua = env:compile(\$body)
        return lua
    ")

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

### TODO: Remove shim
($tree with $t -> $replacement) parses as ($tree, with ($t -> $replacement))
[tree $tree with vars $replacements, $tree with vars $replacements] all parse as
    $tree, with $replacements
($tree has subtree $match_tree) parses as ($tree, contains $match_tree)