Added metatables for bool, number, function, coroutine. Added
run-time check to make sure precompiled code used the same version of Lua. Methods can now be used in (* compiles to *), etc.
This commit is contained in:
parent
1713a0e38f
commit
a1b559a3a2
6
Makefile
6
Makefile
@ -13,10 +13,12 @@ UNINSTALL_VERSION=
|
||||
|
||||
MOON_FILES= code_obj.moon error_handling.moon files.moon nomsu.moon nomsu_compiler.moon \
|
||||
syntax_tree.moon containers.moon bitops.moon parser.moon pretty_errors.moon \
|
||||
text.moon nomsu_decompiler.moon nomsu_environment.moon bootstrap.moon
|
||||
text.moon nomsu_decompiler.moon nomsu_environment.moon bootstrap.moon \
|
||||
builtin_metatables.moon
|
||||
LUA_FILES= code_obj.lua error_handling.lua files.lua nomsu.lua nomsu_compiler.lua \
|
||||
syntax_tree.lua containers.lua bitops.lua parser.lua pretty_errors.lua \
|
||||
text.lua nomsu_decompiler.lua nomsu_environment.lua bootstrap.lua
|
||||
text.lua nomsu_decompiler.lua nomsu_environment.lua bootstrap.lua \
|
||||
builtin_metatables.lua
|
||||
CORE_NOM_FILES=$(shell cat lib/core/init.nom | sed -n 's;export "\(.*\)";lib/\1.nom;p') lib/core/init.nom
|
||||
CORE_LUA_FILES= $(patsubst %.nom,%.lua, $(CORE_NOM_FILES))
|
||||
COMPAT_NOM_FILES=$(wildcard lib/compatibility/*.nom)
|
||||
|
@ -124,9 +124,6 @@ local compile_actions = {
|
||||
["is jit"] = function(self, _t, code)
|
||||
return LuaCode("jit")
|
||||
end,
|
||||
["Lua version"] = function(self, _t, code)
|
||||
return LuaCode("_VERSION")
|
||||
end,
|
||||
["nomsu environment"] = function(self, _t)
|
||||
return LuaCode("_ENV")
|
||||
end,
|
||||
|
@ -98,7 +98,6 @@ compile_actions = {
|
||||
return LuaCode "TESTS[#{tostring(body.source)\as_lua!}] = ", test_text
|
||||
|
||||
["is jit"]: (_t, code)=> LuaCode("jit")
|
||||
["Lua version"]: (_t, code)=> LuaCode("_VERSION")
|
||||
["nomsu environment"]: (_t)=> LuaCode("_ENV")
|
||||
["nomsu environment name"]: (_t)=> LuaCode('"_ENV"')
|
||||
["this file was run directly"]: (_t)=> LuaCode('WAS_RUN_DIRECTLY')
|
||||
|
65
builtin_metatables.lua
Normal file
65
builtin_metatables.lua
Normal file
@ -0,0 +1,65 @@
|
||||
require("text")
|
||||
local number_mt = {
|
||||
__type = "a Number",
|
||||
as_lua = tostring,
|
||||
as_nomsu = tostring,
|
||||
as_text = tostring,
|
||||
as_a_number = function(self)
|
||||
return self
|
||||
end,
|
||||
rounded = function(self)
|
||||
return math.floor(self + .5)
|
||||
end,
|
||||
rounded_down = math.floor,
|
||||
rounded_up = math.ceil,
|
||||
to_the_nearest = function(self, rounder)
|
||||
return rounder * math.floor(self / rounder + 0.5)
|
||||
end,
|
||||
base16 = function(self)
|
||||
return ("%X"):format(self)
|
||||
end
|
||||
}
|
||||
number_mt.__index = number_mt
|
||||
debug.setmetatable(0, number_mt)
|
||||
local bool_mt = {
|
||||
__type = "a Boolean",
|
||||
as_lua = tostring,
|
||||
as_nomsu = function(self)
|
||||
return self and "yes" or "no"
|
||||
end,
|
||||
as_text = function(self)
|
||||
return self and "yes" or "no"
|
||||
end
|
||||
}
|
||||
bool_mt.__index = bool_mt
|
||||
debug.setmetatable(true, bool_mt)
|
||||
local fn_mt = {
|
||||
__type = "an Action",
|
||||
as_text = function(self)
|
||||
return (tostring(self):gsub("function", "Action"))
|
||||
end
|
||||
}
|
||||
fn_mt.__index = fn_mt
|
||||
debug.setmetatable((function() end), fn_mt)
|
||||
local co_mt = {
|
||||
__type = "a Coroutine",
|
||||
as_text = function(self)
|
||||
return (tostring(self):gsub("thread", "Coroutine"))
|
||||
end
|
||||
}
|
||||
co_mt.__index = co_mt
|
||||
debug.setmetatable(coroutine.create(function() end), co_mt)
|
||||
local nil_mt = {
|
||||
__type = "Nil",
|
||||
as_lua = function(self)
|
||||
return "nil"
|
||||
end,
|
||||
as_nomsu = function(self)
|
||||
return "nil"
|
||||
end,
|
||||
as_text = function(self)
|
||||
return "nil"
|
||||
end
|
||||
}
|
||||
nil_mt.__index = nil_mt
|
||||
return debug.setmetatable(nil, nil_mt)
|
44
builtin_metatables.moon
Normal file
44
builtin_metatables.moon
Normal file
@ -0,0 +1,44 @@
|
||||
-- This file defines some methods on Lua numbers
|
||||
require "text"
|
||||
|
||||
number_mt =
|
||||
__type: "a Number"
|
||||
as_lua: tostring
|
||||
as_nomsu: tostring
|
||||
as_text: tostring
|
||||
as_a_number: => @
|
||||
rounded: => math.floor(@ + .5)
|
||||
rounded_down: math.floor
|
||||
rounded_up: math.ceil
|
||||
to_the_nearest: (rounder)=> rounder * math.floor(@/rounder + 0.5)
|
||||
base16: => ("%X")\format(@)
|
||||
number_mt.__index = number_mt
|
||||
debug.setmetatable 0, number_mt
|
||||
|
||||
bool_mt =
|
||||
__type: "a Boolean"
|
||||
as_lua: tostring
|
||||
as_nomsu: => @ and "yes" or "no"
|
||||
as_text: => @ and "yes" or "no"
|
||||
bool_mt.__index = bool_mt
|
||||
debug.setmetatable true, bool_mt
|
||||
|
||||
fn_mt =
|
||||
__type: "an Action"
|
||||
as_text: => (tostring(@)\gsub("function", "Action"))
|
||||
fn_mt.__index = fn_mt
|
||||
debug.setmetatable (->), fn_mt
|
||||
|
||||
co_mt =
|
||||
__type: "a Coroutine"
|
||||
as_text: => (tostring(@)\gsub("thread", "Coroutine"))
|
||||
co_mt.__index = co_mt
|
||||
debug.setmetatable(coroutine.create(->), co_mt)
|
||||
|
||||
nil_mt =
|
||||
__type: "Nil"
|
||||
as_lua: => "nil"
|
||||
as_nomsu: => "nil"
|
||||
as_text: => "nil"
|
||||
nil_mt.__index = nil_mt
|
||||
debug.setmetatable nil, nil_mt
|
@ -4,7 +4,7 @@
|
||||
functions to make that easier.
|
||||
|
||||
lua> "NOMSU_CORE_VERSION = 15"
|
||||
lua> "NOMSU_LIB_VERSION = 8"
|
||||
lua> "NOMSU_LIB_VERSION = 9"
|
||||
lua> ("
|
||||
do
|
||||
local mangle_index = 0
|
||||
@ -93,8 +93,9 @@ lua> ("
|
||||
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")) then
|
||||
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 is neither an action nor an escaped action. "..
|
||||
"Hint: This should probably be an action like:\\n"
|
||||
@ -182,7 +183,7 @@ test:
|
||||
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]:get_stub():as_lua_id())
|
||||
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
|
||||
@ -190,7 +191,7 @@ test:
|
||||
local \$alias_args = a_List(alias:get_args())
|
||||
lua:add("\\n")
|
||||
if alias.type == "MethodCall" then
|
||||
lua:add(\(nomsu environment):compile(alias[1]), ".", alias:get_stub():as_lua_id())
|
||||
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})
|
||||
@ -481,9 +482,9 @@ test:
|
||||
")
|
||||
|
||||
# Literals
|
||||
(yes) compiles to "true"
|
||||
(no) compiles to "false"
|
||||
[nothing, nil, null] all compile to "nil"
|
||||
(yes) compiles to "(true)"
|
||||
(no) compiles to "(false)"
|
||||
[nothing, nil, null] all compile to "(nil)"
|
||||
(Nomsu syntax version) compiles to "NOMSU_SYNTAX_VERSION"
|
||||
(Nomsu compiler version) compiles to "NOMSU_COMPILER_VERSION"
|
||||
(core version) compiles to "NOMSU_CORE_VERSION"
|
||||
@ -492,6 +493,16 @@ test:
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
(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"
|
||||
|
@ -159,6 +159,18 @@ test:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
# Bitwise Operators
|
||||
# These can break if running the precompiled code from another Lua version:
|
||||
lua> ("
|
||||
if \(at compilation $(LUA VERSION)) ~= \$(LUA VERSION) then
|
||||
\(
|
||||
fail ("
|
||||
This code was precompiled with \(at compilation $(LUA VERSION)), but it is being run with \$(LUA VERSION)\
|
||||
... This will cause problems with bitwise operations.
|
||||
")
|
||||
)
|
||||
end
|
||||
")
|
||||
|
||||
# TODO: implement OR, XOR, AND for multiple operands?
|
||||
test:
|
||||
assume ((~ (~ 5)) == 5)
|
||||
@ -170,22 +182,22 @@ test:
|
||||
|
||||
# Lua 5.3 introduced bit operators like | and &. Use them when possible, otherwise
|
||||
fall back to bit.bor(), bit.band(), etc.
|
||||
lua> "if \((is jit) or ((Lua version) == "Lua 5.2")) then"
|
||||
[NOT $, ~ $] all compile to "bit.bnot(\($ as lua expr))"
|
||||
lua> "if \((is jit) or ($(LUA API) == "Lua 5.2")) then"
|
||||
[NOT $, ~ $] all compile to "Bit.bnot(\($ as lua expr))"
|
||||
[$x OR $y, $x | $y] all compile to
|
||||
"bit.bor(\($x as lua expr), \($y as lua expr))"
|
||||
"Bit.bor(\($x as lua expr), \($y as lua expr))"
|
||||
|
||||
[$x XOR $y, $x ~ $y] all compile to
|
||||
"bit.bxor(\($x as lua expr), \($y as lua expr))"
|
||||
"Bit.bxor(\($x as lua expr), \($y as lua expr))"
|
||||
|
||||
[$x AND $y, $x & $y] all compile to
|
||||
"bit.band(\($x as lua expr), \($y as lua expr))"
|
||||
"Bit.band(\($x as lua expr), \($y as lua expr))"
|
||||
|
||||
[$x LSHIFT $shift, $x << $shift] all compile to
|
||||
"bit.lshift(\($x as lua expr), \($shift as lua expr))"
|
||||
"Bit.lshift(\($x as lua expr), \($shift as lua expr))"
|
||||
|
||||
[$x RSHIFT $shift, $x >> $shift] all compile to
|
||||
"bit.rshift(\($x as lua expr), \($shift as lua expr))"
|
||||
"Bit.rshift(\($x as lua expr), \($shift as lua expr))"
|
||||
|
||||
lua> "else"
|
||||
[NOT $, ~ $] all compile to "~(\($ as lua expr))"
|
||||
|
@ -41,16 +41,19 @@ command line program with $args:
|
||||
if $args.v:
|
||||
say " \(yellow ($.test, with "\n" -> "\n "))"
|
||||
|
||||
try:
|
||||
if $args.e:
|
||||
$(test environment), run $.test
|
||||
..if it fails with $msg:
|
||||
$src = ($Source, from string $.source)
|
||||
$l1 = ($file, line number at $src.start)
|
||||
$l2 = ($file, line number at $src.stop)
|
||||
$failures, add ("
|
||||
\(yellow "\($src.filename):\($l1)-\$l2:")
|
||||
\(bright (red ($msg, indented)))
|
||||
")
|
||||
..else:
|
||||
try:
|
||||
$(test environment), run $.test
|
||||
..if it fails with $msg:
|
||||
$src = ($Source, from string $.source)
|
||||
$l1 = ($file, line number at $src.start)
|
||||
$l2 = ($file, line number at $src.stop)
|
||||
$failures, add ("
|
||||
\(yellow "\($src.filename):\($l1)-\$l2:")
|
||||
\(bright (red ($msg, indented)))
|
||||
")
|
||||
|
||||
if ($failures is empty):
|
||||
if $args.v:
|
||||
|
@ -45,6 +45,7 @@ do
|
||||
List, Dict = _obj_0.List, _obj_0.Dict
|
||||
end
|
||||
local Text = require('text')
|
||||
require('builtin_metatables')
|
||||
local sep = "\3"
|
||||
local parser = re.compile([[ args <- {| (flag %sep)*
|
||||
{:files: {|
|
||||
|
@ -43,6 +43,7 @@ Files = require "files"
|
||||
{:NomsuCode, :LuaCode, :Source} = require "code_obj"
|
||||
{:List, :Dict} = require 'containers'
|
||||
Text = require 'text'
|
||||
require 'builtin_metatables'
|
||||
|
||||
sep = "\3"
|
||||
parser = re.compile([[
|
||||
|
@ -52,6 +52,14 @@ local math_expression = re.compile([[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]])
|
||||
local MAX_LINE = 80
|
||||
local compile
|
||||
compile = function(self, tree)
|
||||
if not (SyntaxTree:is_instance(tree)) then
|
||||
do
|
||||
local as_lua = tree.as_lua
|
||||
if as_lua then
|
||||
return as_lua(tree)
|
||||
end
|
||||
end
|
||||
end
|
||||
local _exp_0 = tree.type
|
||||
if "Action" == _exp_0 then
|
||||
local stub = tree.stub
|
||||
@ -119,6 +127,24 @@ compile = function(self, tree)
|
||||
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()
|
||||
|
@ -62,8 +62,6 @@ compile = (tree)=>
|
||||
|
||||
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(@, tree, unpack(args))
|
||||
if ret == nil
|
||||
info = debug.getinfo(compile_action, "S")
|
||||
@ -92,6 +90,24 @@ compile = (tree)=>
|
||||
return lua
|
||||
|
||||
when "MethodCall"
|
||||
stub = tree\get_stub!
|
||||
compile_action = @COMPILE_RULES[stub]
|
||||
if compile_action
|
||||
args = tree\get_args!
|
||||
ret = compile_action(@, tree, unpack(args))
|
||||
if ret == nil
|
||||
info = debug.getinfo(compile_action, "S")
|
||||
filename = Source\from_string(info.source).filename
|
||||
fail_at tree,
|
||||
("Compile error: The compile-time method here (#{stub}) failed to return any value. "..
|
||||
"Hint: 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
|
||||
target_lua = @compile tree[1]
|
||||
target_text = target_lua\text!
|
||||
|
@ -131,7 +131,9 @@ nomsu_environment = Importer({
|
||||
ipairs = ipairs,
|
||||
jit = jit,
|
||||
_VERSION = _VERSION,
|
||||
bit = (jit or _VERSION == "Lua 5.2") and require('bitops') or nil,
|
||||
LUA_VERSION = (jit and jit.version or _VERSION),
|
||||
LUA_API = _VERSION,
|
||||
Bit = (jit or _VERSION == "Lua 5.2") and require('bitops') or nil,
|
||||
a_List = List,
|
||||
a_Dict = Dict,
|
||||
Text = Text,
|
||||
|
@ -58,8 +58,8 @@ nomsu_environment = Importer{
|
||||
:error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall,
|
||||
:print, :loadfile, :rawset, :_VERSION, :collectgarbage, :rawget, :rawlen,
|
||||
:table, :assert, :dofile, :loadstring, lua_type_of:type, :select, :math, :io, :load,
|
||||
:pairs, :ipairs, :jit, :_VERSION
|
||||
bit: (jit or _VERSION == "Lua 5.2") and require('bitops') or nil
|
||||
:pairs, :ipairs, :jit, :_VERSION, LUA_VERSION: (jit and jit.version or _VERSION),
|
||||
LUA_API: _VERSION, Bit: (jit or _VERSION == "Lua 5.2") and require('bitops') or nil
|
||||
-- Nomsu types:
|
||||
a_List:List, a_Dict:Dict, Text:Text,
|
||||
-- Utilities and misc.
|
||||
|
@ -147,13 +147,14 @@ do
|
||||
assert(self.type == "Action" or self.type == "MethodCall", "Only actions and method calls have arguments")
|
||||
local args = { }
|
||||
if self.type == "MethodCall" then
|
||||
assert(#self == 2, "Can't get arguments for multiple method calls at once.")
|
||||
args[1] = self[1]
|
||||
local _list_0 = self[2]
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local tok = _list_0[_index_0]
|
||||
if type(tok) ~= 'string' then
|
||||
args[#args + 1] = tok
|
||||
for i = 2, #self do
|
||||
local _list_0 = self[i]
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local tok = _list_0[_index_0]
|
||||
if type(tok) ~= 'string' then
|
||||
args[#args + 1] = tok
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -168,8 +169,15 @@ do
|
||||
end,
|
||||
get_stub = function(self)
|
||||
if self.type == "MethodCall" then
|
||||
assert(#self == 2, "Can't get the stubs of multiple method calls at once.")
|
||||
return self[2]:get_stub()
|
||||
return "0, " .. table.concat((function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for i = 2, #self do
|
||||
_accum_0[_len_0] = self[i]:get_stub()
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
return _accum_0
|
||||
end)(), "; ")
|
||||
end
|
||||
local stub_bits = { }
|
||||
local arg_i = 1
|
||||
|
@ -72,10 +72,10 @@ class SyntaxTree
|
||||
assert(@type == "Action" or @type == "MethodCall", "Only actions and method calls have arguments")
|
||||
args = {}
|
||||
if @type == "MethodCall"
|
||||
assert(#@ == 2, "Can't get arguments for multiple method calls at once.")
|
||||
args[1] = @[1]
|
||||
for tok in *@[2]
|
||||
if type(tok) != 'string' then args[#args+1] = tok
|
||||
for i=2,#@
|
||||
for tok in *@[i]
|
||||
if type(tok) != 'string' then args[#args+1] = tok
|
||||
else
|
||||
for tok in *@
|
||||
if type(tok) != 'string' then args[#args+1] = tok
|
||||
@ -83,8 +83,7 @@ class SyntaxTree
|
||||
|
||||
get_stub: =>
|
||||
if @type == "MethodCall"
|
||||
assert(#@ == 2, "Can't get the stubs of multiple method calls at once.")
|
||||
return @[2]\get_stub!
|
||||
return "0, "..table.concat([@[i]\get_stub! for i=2,#@], "; ")
|
||||
stub_bits = {}
|
||||
arg_i = 1
|
||||
for a in *@
|
||||
|
Loading…
Reference in New Issue
Block a user