aboutsummaryrefslogtreecommitdiff
path: root/nomsu_compiler.moon
diff options
context:
space:
mode:
Diffstat (limited to 'nomsu_compiler.moon')
-rw-r--r--nomsu_compiler.moon545
1 files changed, 220 insertions, 325 deletions
diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon
index 7ab2494..f3530e9 100644
--- a/nomsu_compiler.moon
+++ b/nomsu_compiler.moon
@@ -1,28 +1,13 @@
+--
-- This file contains the source code of the Nomsu compiler.
--- Nomsu is a programming language that cross-compiles to Lua. It was designed to be good
--- at natural-language-like code that is highly self-modifying and flexible.
--- The only dependency is LPEG, which can be installed using "luarocks install lpeg"
--- File usage:
--- Either, in a lua/moonscript file:
--- Nomsu = require "nomsu"
--- nomsu = Nomsu()
--- nomsu:run(your_nomsu_code)
--- Or from the command line:
--- lua nomsu.lua your_file.nom
-lpeg = require 'lpeg'
-{:R,:P,:S} = lpeg
-re = require 're'
+--
{:List, :Dict, :Text} = require 'containers'
-{:insert, :remove, :concat} = table
unpack or= table.unpack
{:match, :sub, :gsub, :format, :byte, :find} = string
{:LuaCode, :Source} = require "code_obj"
SyntaxTree = require "syntax_tree"
-{:Importer, :import_to_1_from, :_1_forked} = require 'importer'
Files = require "files"
-table.map = (t, fn)-> setmetatable([fn(v) for _,v in ipairs(t)], getmetatable(t))
-
-- TODO: de-duplicate this
pretty_error = require("pretty_errors")
compile_error = (source, err_msg, hint=nil)->
@@ -45,137 +30,74 @@ compile_error = (source, err_msg, hint=nil)->
-- This is a bit of a hack, but this code handles arbitrarily complex
-- math expressions like 2*x + 3^2 without having to define a single
-- action for every possibility.
+re = require 're'
math_expression = re.compile [[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]]
MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value
-compile = setmetatable({
- action: Importer{
- [""]: (compile, fn, ...)->
- lua = LuaCode!
- fn_lua = compile(fn)
- lua\add fn_lua
- unless fn_lua\text!\match("^%(.*%)$") or fn_lua\text!\match("^[_a-zA-Z][_a-zA-Z0-9.]*$")
- lua\parenthesize!
- lua\add "("
- for i=1,select('#',...)
- lua\add(", ") if i > 1
- lua\add compile((select(i, ...)))
- lua\add ")"
- return lua
-
- ["Lua"]: (compile, code)->
- if not code
- return LuaCode("LuaCode()")
- if code.type != "Text"
- return LuaCode("LuaCode:from(", tostring(code.source)\as_lua!, ", ", compile(code), ")")
-
- operate_on_text = (text)->
- lua = LuaCode\from(text.source, "LuaCode:from(", tostring(text.source)\as_lua!)
- for bit in *text
- local bit_lua
- if type(bit) == "string"
- bit_lua = bit\as_lua!
- elseif bit.type == "Text"
- bit_lua = operate_on_text(bit)
- elseif bit.type == "Block"
- 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)
-
- 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)
- lua\add ")"
- return lua
-
- return operate_on_text code
-
- ["lua >"]: (compile, code)->
- if code.type != "Text"
- return code
- operate_on_text = (text)->
- lua = LuaCode\from(text.source)
- for bit in *text
- if type(bit) == "string"
- lua\add bit
- elseif bit.type == "Text"
- lua\add(operate_on_text(bit))
+compile = (tree)=>
+ switch tree.type
+ when "Action"
+ stub = tree.stub
+ compile_action = @COMPILE_RULES[stub]
+ if not compile_action and math_expression\match(stub)
+ lua = LuaCode\from(tree.source)
+ for i,tok in ipairs tree
+ if type(tok) == 'string'
+ lua\add tok
else
- lua\add compile(bit)
+ tok_lua = @compile(tok)
+ -- TODO: this is overly eager, should be less aggressive
+ tok_lua\parenthesize! if tok.type == "Action"
+ lua\add tok_lua
+ lua\add " " if i < #tree
return lua
- return operate_on_text code
-
- ["= lua"]: (compile, code)-> compile.action["lua >"](compile, code)
-
- ["use"]: (compile, path)-> LuaCode("run_file_1_in(#{compile(path)}, _ENV, OPTIMIZATION)")
- ["use 1 with prefix"]: (compile, path, prefix)->
- LuaCode("run_file_1_in(#{compile(path)}, _ENV, OPTIMIZATION, ", compile(prefix), ")")
-
- ["test"]: (compile, body)->
- unless body.type == 'Block'
- compile_error(body, "This should be a Block")
- test_nomsu = body\get_source_code!\match(":[ ]*(.*)")
- if indent = test_nomsu\match("\n([ ]*)")
- test_nomsu = test_nomsu\gsub("\n"..indent, "\n")
- test_text = compile(SyntaxTree{type:"Text", source:body.source, test_nomsu})
- return LuaCode "TESTS[#{tostring(body.source)\as_lua!}] = ", test_text
-
- ["is jit"]: (compile, code)-> LuaCode("jit")
- ["Lua version"]: (compile, code)-> LuaCode("_VERSION")
- ["nomsu environment"]: (compile)-> LuaCode("_ENV")
- }
-}, {
- __import: import_to_1_from
- __call: (compile, tree)->
- switch tree.type
- when "Action"
- stub = tree.stub
- compile_action = compile.action[stub]
- if not compile_action and math_expression\match(stub)
- lua = LuaCode\from(tree.source)
- for i,tok in ipairs tree
- if type(tok) == 'string'
- lua\add tok
- else
- tok_lua = compile(tok)
- -- TODO: this is overly eager, should be less aggressive
- tok_lua\parenthesize! if tok.type == "Action"
- lua\add tok_lua
- lua\add " " if i < #tree
- return lua
-
- if compile_action
- args = [arg for arg in *tree when type(arg) != "string"]
- -- Force Lua to avoid tail call optimization for debugging purposes
- -- TODO: use tail call?
- ret = compile_action(compile, unpack(args))
- if ret == nil
- info = debug.getinfo(compile_action, "S")
- filename = Source\from_string(info.source).filename
- compile_error tree,
- "The compile-time action here (#{stub}) failed to return any value.",
- "Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} and make sure it's returning something."
- unless SyntaxTree\is_instance(ret)
- ret.source or= tree.source
- return ret
- if ret != tree
- return compile(ret)
+ if compile_action
+ args = [arg for arg in *tree when type(arg) != "string"]
+ -- Force Lua to avoid tail call optimization for debugging purposes
+ -- TODO: use tail call?
+ ret = compile_action(@, unpack(args))
+ if ret == nil
+ info = debug.getinfo(compile_action, "S")
+ filename = Source\from_string(info.source).filename
+ compile_error tree,
+ "The compile-time action here (#{stub}) failed to return any value.",
+ "Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} and make sure it's returning something."
+ unless SyntaxTree\is_instance(ret)
+ ret.source or= tree.source
+ return ret
+ if ret != tree
+ return @compile(ret)
+
+ lua = LuaCode\from(tree.source)
+ lua\add((stub)\as_lua_id!,"(")
+ for argnum, arg in ipairs tree\get_args!
+ arg_lua = @compile(arg)
+ if arg.type == "Block"
+ arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
+ if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE
+ lua\add(argnum > 1 and ",\n " or "\n ")
+ elseif argnum > 1
+ lua\add ", "
+ lua\add arg_lua
+ lua\add ")"
+ return lua
- lua = LuaCode\from(tree.source)
- lua\add((stub)\as_lua_id!,"(")
- for argnum, arg in ipairs tree\get_args!
- arg_lua = compile(arg)
+ when "MethodCall"
+ lua = LuaCode\from tree.source
+ target_lua = @compile tree[1]
+ target_text = target_lua\text!
+ -- TODO: this parenthesizing is maybe overly conservative
+ if not (target_text\match("^%(.*%)$") or target_text\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or
+ tree[1].type == "IndexChain")
+ target_lua\parenthesize!
+
+ for i=2,#tree
+ lua\add "\n" if i > 2
+ lua\add target_lua, ":"
+ lua\add((tree[i].stub)\as_lua_id!,"(")
+ for argnum, arg in ipairs tree[i]\get_args!
+ arg_lua = @compile(arg)
if arg.type == "Block"
arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE
@@ -184,207 +106,180 @@ compile = setmetatable({
lua\add ", "
lua\add arg_lua
lua\add ")"
- return lua
-
- when "MethodCall"
- lua = LuaCode\from tree.source
- target_lua = compile tree[1]
- target_text = target_lua\text!
- -- TODO: this parenthesizing is maybe overly conservative
- if not (target_text\match("^%(.*%)$") or target_text\match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or
- tree[1].type == "IndexChain")
- target_lua\parenthesize!
-
- for i=2,#tree
- lua\add "\n" if i > 2
- lua\add target_lua, ":"
- lua\add((tree[i].stub)\as_lua_id!,"(")
- for argnum, arg in ipairs tree[i]\get_args!
- arg_lua = compile(arg)
- if arg.type == "Block"
- arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
- if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE
- lua\add(argnum > 1 and ",\n " or "\n ")
- elseif argnum > 1
- lua\add ", "
- lua\add arg_lua
- lua\add ")"
- return lua
-
- when "EscapedNomsu"
- lua = LuaCode\from tree.source, "SyntaxTree{"
- needs_comma, i = false, 1
- as_lua = (x)->
- if type(x) == 'number'
- tostring(x)
- elseif SyntaxTree\is_instance(x)
- compile(x)
- elseif Source\is_instance(x)
- tostring(x)\as_lua!
- else x\as_lua!
-
- for k,v in pairs((SyntaxTree\is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1])
- entry_lua = LuaCode!
- if k == i
- i += 1
- elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*")
- entry_lua\add(k, "= ")
- else
- entry_lua\add("[", as_lua(k), "]= ")
- entry_lua\add as_lua(v)
- if needs_comma then lua\add ","
- if lua\trailing_line_len! + #(entry_lua\text!\match("^[\n]*")) > MAX_LINE
- lua\add "\n "
- elseif needs_comma
- lua\add " "
- lua\add entry_lua
- needs_comma = true
- lua\add "}"
- return lua
-
- when "Block"
- lua = LuaCode\from(tree.source)
- for i, line in ipairs tree
- if i > 1 then lua\add "\n"
- lua\add compile(line)
- return lua
-
- when "Text"
- lua = LuaCode\from(tree.source)
- added = 0
- string_buffer = ""
- add_bit = (bit)->
- if added > 0
- if lua\trailing_line_len! + #bit > MAX_LINE
- lua\add "\n "
- lua\add ".."
- lua\add bit
- added += 1
-
- for i, bit in ipairs tree
- if type(bit) == "string"
- string_buffer ..= bit
- continue
- if string_buffer != ""
- for i=1,#string_buffer,MAX_LINE
- add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua!
- string_buffer = ""
+ return lua
- bit_lua = compile(bit)
- if bit.type == "Block" and #bit == 1
- bit = bit[1]
- if bit.type == "Block"
- bit_lua = LuaCode\from bit.source, "List(function(add)",
- "\n ", bit_lua,
- "\nend):joined()"
- elseif bit.type != "Text"
- bit_lua = LuaCode\from(bit.source, "tostring(",bit_lua,")")
- add_bit bit_lua
+ when "EscapedNomsu"
+ lua = LuaCode\from tree.source, "SyntaxTree{"
+ needs_comma, i = false, 1
+ as_lua = (x)->
+ if type(x) == 'number'
+ tostring(x)
+ elseif SyntaxTree\is_instance(x)
+ @compile(x)
+ elseif Source\is_instance(x)
+ tostring(x)\as_lua!
+ else x\as_lua!
+
+ for k,v in pairs((SyntaxTree\is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1])
+ entry_lua = LuaCode!
+ if k == i
+ i += 1
+ elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*")
+ entry_lua\add(k, "= ")
+ else
+ entry_lua\add("[", as_lua(k), "]= ")
+ entry_lua\add as_lua(v)
+ if needs_comma then lua\add ","
+ if lua\trailing_line_len! + #(entry_lua\text!\match("^[\n]*")) > MAX_LINE
+ lua\add "\n "
+ elseif needs_comma
+ lua\add " "
+ lua\add entry_lua
+ needs_comma = true
+ lua\add "}"
+ return lua
+
+ when "Block"
+ lua = LuaCode\from(tree.source)
+ for i, line in ipairs tree
+ if i > 1 then lua\add "\n"
+ lua\add @compile(line)
+ return lua
+ when "Text"
+ lua = LuaCode\from(tree.source)
+ added = 0
+ string_buffer = ""
+ add_bit = (bit)->
+ if added > 0
+ if lua\trailing_line_len! + #bit > MAX_LINE
+ lua\add "\n "
+ lua\add ".."
+ lua\add bit
+ added += 1
+
+ for i, bit in ipairs tree
+ if type(bit) == "string"
+ string_buffer ..= bit
+ continue
if string_buffer != ""
for i=1,#string_buffer,MAX_LINE
add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua!
string_buffer = ""
- if added == 0
- add_bit '""'
- if added > 1
- lua\parenthesize!
- return lua
+ bit_lua = @compile(bit)
+ if bit.type == "Block" and #bit == 1
+ bit = bit[1]
+ if bit.type == "Block"
+ bit_lua = LuaCode\from bit.source, "List(function(add)",
+ "\n ", bit_lua,
+ "\nend):joined()"
+ elseif bit.type != "Text"
+ bit_lua = LuaCode\from(bit.source, "tostring(",bit_lua,")")
+ add_bit bit_lua
+
+ if string_buffer != ""
+ for i=1,#string_buffer,MAX_LINE
+ add_bit string_buffer\sub(i, i+MAX_LINE-1)\as_lua!
+ string_buffer = ""
- when "List", "Dict"
- if #tree == 0
- return LuaCode\from tree.source, tree.type, "{}"
+ if added == 0
+ add_bit '""'
+ if added > 1
+ lua\parenthesize!
+ return lua
- lua = LuaCode\from tree.source
- chunks = 0
- i = 1
- while tree[i]
- if tree[i].type == 'Block'
- lua\add " + " if chunks > 0
- 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 += 1
+ when "List", "Dict"
+ if #tree == 0
+ return LuaCode\from tree.source, tree.type, "{}"
+
+ lua = LuaCode\from tree.source
+ chunks = 0
+ i = 1
+ while tree[i]
+ if tree[i].type == 'Block'
+ lua\add " + " if chunks > 0
+ 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 += 1
+ i += 1
+ else
+ lua\add " + " if chunks > 0
+ sep = ''
+ items_lua = LuaCode\from tree[i].source
+ while tree[i]
+ if tree[i].type == "Block"
+ break
+ item_lua = @compile tree[i]
+ if item_lua\text!\match("^%.[a-zA-Z_]")
+ item_lua = item_lua\text!\sub(2)
+ if tree.type == 'Dict' and tree[i].type == 'Index'
+ item_lua = LuaCode\from tree[i].source, item_lua, "=true"
+ items_lua\add sep, item_lua
+ if tree[i].type == "Comment"
+ items_lua\add "\n"
+ sep = ''
+ elseif items_lua\trailing_line_len! > MAX_LINE
+ sep = ',\n '
+ else
+ sep = ', '
i += 1
+ if items_lua\is_multiline!
+ lua\add LuaCode\from items_lua.source, tree.type, "{\n ", items_lua, "\n}"
else
- lua\add " + " if chunks > 0
- sep = ''
- items_lua = LuaCode\from tree[i].source
- while tree[i]
- if tree[i].type == "Block"
- break
- item_lua = compile tree[i]
- if item_lua\text!\match("^%.[a-zA-Z_]")
- item_lua = item_lua\text!\sub(2)
- if tree.type == 'Dict' and tree[i].type == 'Index'
- item_lua = LuaCode\from tree[i].source, item_lua, "=true"
- items_lua\add sep, item_lua
- if tree[i].type == "Comment"
- items_lua\add "\n"
- sep = ''
- elseif items_lua\trailing_line_len! > MAX_LINE
- sep = ',\n '
- else
- sep = ', '
- i += 1
- if items_lua\is_multiline!
- 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, "}"
- chunks += 1
-
- return lua
-
- when "Index"
- key_lua = compile(tree[1])
- key_str = match(key_lua\text!, '^"([a-zA-Z_][a-zA-Z0-9_]*)"$')
- return if key_str and key_str\is_lua_id!
- LuaCode\from tree.source, ".", key_str
- elseif sub(key_lua\text!,1,1) == "["
- -- NOTE: this *must* use a space after the [ to avoid freaking out
- -- Lua's parser if the inner expression is a long string. Lua
- -- parses x[[[y]]] as x("[y]"), not as x["y"]
- LuaCode\from tree.source, "[ ",key_lua,"]"
- else
- LuaCode\from tree.source, "[",key_lua,"]"
+ lua\add LuaCode\from items_lua.source, tree.type, "{", items_lua, "}"
+ chunks += 1
+
+ return lua
- when "DictEntry"
- key = tree[1]
+ when "Index"
+ key_lua = @compile(tree[1])
+ key_str = match(key_lua\text!, '^"([a-zA-Z_][a-zA-Z0-9_]*)"$')
+ return if key_str and key_str\is_lua_id!
+ LuaCode\from tree.source, ".", key_str
+ elseif sub(key_lua\text!,1,1) == "["
+ -- NOTE: this *must* use a space after the [ to avoid freaking out
+ -- Lua's parser if the inner expression is a long string. Lua
+ -- parses x[[[y]]] as x("[y]"), not as x["y"]
+ LuaCode\from tree.source, "[ ",key_lua,"]"
+ else
+ LuaCode\from tree.source, "[",key_lua,"]"
+
+ when "DictEntry"
+ key = tree[1]
+ if key.type != "Index"
+ key = SyntaxTree{type:"Index", source:key.source, key}
+ return LuaCode\from tree.source, @compile(key),"=",(tree[2] and @compile(tree[2]) or "true")
+
+ when "IndexChain"
+ lua = @compile(tree[1])
+ if lua\text!\match("['\"}]$") or lua\text!\match("]=*]$")
+ lua\parenthesize!
+ for i=2,#tree
+ key = tree[i]
+ -- TODO: remove this shim
if key.type != "Index"
key = SyntaxTree{type:"Index", source:key.source, key}
- return LuaCode\from tree.source, compile(key),"=",(tree[2] and compile(tree[2]) or "true")
-
- when "IndexChain"
- lua = compile(tree[1])
- if lua\text!\match("['\"}]$") or lua\text!\match("]=*]$")
- lua\parenthesize!
- for i=2,#tree
- key = tree[i]
- -- TODO: remove this shim
- if key.type != "Index"
- key = SyntaxTree{type:"Index", source:key.source, key}
- lua\add compile(key)
- return lua
-
- when "Number"
- return LuaCode\from(tree.source, tostring(tree[1]))
+ lua\add @compile(key)
+ return lua
- when "Var"
- return LuaCode\from(tree.source, tree\as_var!\as_lua_id!)
+ when "Number"
+ return LuaCode\from(tree.source, tostring(tree[1]))
- when "FileChunks"
- error("Can't convert FileChunks to a single block of lua, since each chunk's "..
- "compilation depends on the earlier chunks")
-
- when "Comment"
- return LuaCode\from(tree.source, "-- ", (tree[1]\gsub('\n', '\n-- ')))
-
- when "Error"
- error("Can't compile errors")
+ when "Var"
+ return LuaCode\from(tree.source, tree\as_var!\as_lua_id!)
- else
- error("Unknown type: #{tree.type}")
+ when "FileChunks"
+ error("Can't convert FileChunks to a single block of lua, since each chunk's "..
+ "compilation depends on the earlier chunks")
+
+ when "Comment"
+ return LuaCode\from(tree.source, "-- ", (tree[1]\gsub('\n', '\n-- ')))
+
+ when "Error"
+ error("Can't compile errors")
-})
+ else
+ error("Unknown type: #{tree.type}")
return {:compile, :compile_error}