Lots of overhaul, supporting a new Object Oriented approach (e.g.

%obj::action 1 2) and syntax.
This commit is contained in:
Bruce Hill 2018-08-28 15:08:00 -07:00
parent 930d522fbc
commit e44acbf338
14 changed files with 586 additions and 321 deletions

View File

@ -5,16 +5,16 @@ do
end
local repr
repr = require('utils').repr
local unpack = unpack or table.unpack
local LuaCode, NomsuCode, Source
do
local _class_0
local _base_0 = {
__tostring = function(self)
if self.stop then
return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. ":" .. tostring(self.stop) .. "]"
else
return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. "]"
end
return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. tostring(self.stop and ':' .. self.stop or '') .. "]"
end,
__repr = function(self)
return "Source(" .. tostring(repr(self.filename)) .. ", " .. tostring(self.start) .. tostring(self.stop and ', ' .. self.stop or '') .. ")"
end,
__eq = function(self, other)
return getmetatable(self) == getmetatable(other) and self.filename == other.filename and self.start == other.start and self.stop == other.stop
@ -108,9 +108,31 @@ do
end
return self.__str
end,
__repr = function(self)
return tostring(self.__class.__name) .. "(" .. tostring(concat({
repr(tostring(self.source)),
unpack((function()
local _accum_0 = { }
local _len_0 = 1
local _list_0 = self.bits
for _index_0 = 1, #_list_0 do
local b = _list_0[_index_0]
_accum_0[_len_0] = repr(b)
_len_0 = _len_0 + 1
end
return _accum_0
end)())
}, ", ")) .. ")"
end,
__len = function(self)
return #tostring(self)
end,
match = function(self, ...)
return tostring(self):match(...)
end,
gmatch = function(self, ...)
return tostring(self):gmatch(...)
end,
dirty = function(self)
self.__str = nil
self._trailing_line_len = nil
@ -192,8 +214,8 @@ do
if b.is_code then
b.dirty = error
end
local b_str = tostring(b)
local line = match(b_str, "\n([^\n]*)$")
b = tostring(b)
local line = match(b, "\n([^\n]*)$")
if line then
line_len = #line
else
@ -254,6 +276,7 @@ do
local _parent_0 = Code
local _base_0 = {
__tostring = Code.__tostring,
__repr = Code.__repr,
__len = Code.__len,
add_free_vars = function(self, vars)
if not (#vars > 0) then
@ -446,6 +469,7 @@ do
local _parent_0 = Code
local _base_0 = {
__tostring = Code.__tostring,
__repr = Code.__repr,
__len = Code.__len
}
_base_0.__index = _base_0

View File

@ -3,6 +3,7 @@
-- indentation levels.
{:insert, :remove, :concat} = table
{:repr} = require 'utils'
unpack or= table.unpack
local LuaCode, NomsuCode, Source
class Source
@ -16,11 +17,9 @@ class Source
@is_instance: (x)=> type(x) == 'table' and x.__class == @
__tostring: =>
if @stop
"@#{@filename}[#{@start}:#{@stop}]"
else
"@#{@filename}[#{@start}]"
__tostring: => "@#{@filename}[#{@start}#{@stop and ':'..@stop or ''}]"
__repr: => "Source(#{repr @filename}, #{@start}#{@stop and ', '..@stop or ''})"
__eq: (other)=>
getmetatable(@) == getmetatable(other) and @filename == other.filename and @start == other.start and @stop == other.stop
@ -68,8 +67,14 @@ class Code
@__str = concat(buff, "")
return @__str
__len: =>
#tostring(self)
__repr: =>
"#{@__class.__name}(#{concat {repr(tostring(@source)), unpack([repr(b) for b in *@bits])}, ", "})"
__len: => #tostring(@)
match: (...)=> tostring(@)\match(...)
gmatch: (...)=> tostring(@)\gmatch(...)
dirty: =>
@__str = nil
@ -126,8 +131,8 @@ class Code
bits[#bits+1] = joiner
bits[#bits+1] = b
b.dirty = error if b.is_code
b_str = tostring(b)
line = match(b_str, "\n([^\n]*)$")
b = tostring(b)
line = match(b, "\n([^\n]*)$")
if line
line_len = #line
else
@ -153,6 +158,7 @@ class Code
class LuaCode extends Code
__tostring: Code.__tostring
__repr: Code.__repr
__len: Code.__len
new: (...)=>
super ...
@ -247,6 +253,7 @@ class LuaCode extends Code
class NomsuCode extends Code
__tostring: Code.__tostring
__repr: Code.__repr
__len: Code.__len
return {:Code, :NomsuCode, :LuaCode, :Source}

View File

@ -7,11 +7,11 @@ use "core/metaprogramming.nom"
compile [barf] to (Lua "error(nil, 0);")
compile [barf %msg] to (Lua "error(\(%msg as lua expr), 0);")
compile [compile error at %source %msg] to (..)
Lua "_ENV:compile_error(\(%source as lua expr), \(%msg as lua expr))"
Lua "nomsu:compile_error(\(%source as lua expr), \(%msg as lua expr))"
compile [assume %condition] to:
lua> ".."
local \%assumption = 'Assumption failed: '..tostring(_ENV:tree_to_nomsu(\%condition))
local \%assumption = 'Assumption failed: '..tostring(nomsu:tree_to_nomsu(\%condition))
return (..)
Lua ".."
if not \(%condition as lua expr) then

View File

@ -5,12 +5,12 @@
lua> "NOMSU_CORE_VERSION = 5"
lua> ".."
COMPILE_ACTIONS["% -> %"] = function(nomsu, tree, \%args, \%body)
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(_ENV:compile(a)) or a 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 _ENV:compile(\%body):as_statements("return ") or \%body
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)")
@ -18,9 +18,9 @@ lua> ".."
end
lua> ".."
COMPILE_ACTIONS["compile as %"] = function(nomsu, tree, \%action)
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 _ENV:compile(a) end)
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, ", ")
@ -48,14 +48,14 @@ test:
asdf
assume (%tmp is (nil)) or barf "compile to is leaking variables"
lua> ".."
COMPILE_ACTIONS["compile % to %"] = function(nomsu, tree, \%actions, \%body)
local \%args = {"nomsu", "tree", unpack(table.map(\%actions[1]:get_args(), function(a) return tostring(_ENV:compile(\
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(_ENV:compile(\
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
@ -73,10 +73,10 @@ lua> ".."
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compile [call %fn with %args] to (..)
compile [call %fn with %args] to:
lua> ".."
local lua = LuaCode.Value(tree.source, _ENV:compile(\%fn), "(")
lua:concat_append(table.map(\%args, function(a) return _ENV:compile(a) end), ", ")
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
@ -92,17 +92,17 @@ test:
parse [baz %] as (foo %)
assume ((foo 1) == "outer")
compile [local action %actions %body] to (..)
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(_ENV:compile(a)) end)
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(_ENV:compile(a)) end)
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)
@ -151,24 +151,40 @@ compile [parse %actions as %body] to (..)
lua> ".."
local replacements = {}
for i,arg in ipairs(\%actions[1]:get_args()) do
replacements[arg[1]] = tostring(_ENV:compile(arg))
replacements[arg[1]] = tostring(nomsu:compile(arg))
end
local function make_tree(t)
if not AST.is_syntax_tree(t) then
return repr(t)
elseif t.type ~= 'Var' then
local args = {repr(tostring(t.source)), unpack(table.map(t, make_tree))}
return t.type.."("..table.concat(args, ", ")..")"
elseif replacements[t[1]] then
return replacements[t[1]]
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 t.type.."("..repr(tostring(t.source))..", "..repr(t[1].." \\0").."..string.format('%X', __MANGLE_INDEX))"
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 = _ENV:compile(tree); return lua")
"\\nlocal lua = nomsu:compile(tree)",
"\\nreturn lua")
local ret = \(compile as (compile %actions to %new_body))
return ret
@ -176,21 +192,21 @@ compile [parse %actions as %body] to (..)
action [%tree as lua expr]:
lua> ".."
\%tree_lua = _ENV:compile(\%tree)
\%tree_lua = nomsu:compile(\%tree)
if not \%tree_lua.is_value then
_ENV:compile_error(\%tree.source, "Could not convert %s to a Lua expression",
_ENV:tree_to_nomsu(\%tree))
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 "_ENV:compile(\(%tree as lua expr))")
compile [%tree as lua] to (Lua value "nomsu:compile(\(%tree as lua expr))")
compile [%tree as lua statements] to (..)
Lua value "_ENV:compile(\(%tree as lua expr)):as_statements()"
Lua value "nomsu:compile(\(%tree as lua expr)):as_statements()"
compile [%tree as lua return] to (..)
Lua value "_ENV:compile(\(%tree as lua expr)):as_statements('return ')"
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"
@ -199,10 +215,10 @@ test:
assume ("\(\(foo \%x) as nomsu)" == "foo %x") or barf ".."
action source code failed.
compile [%tree as nomsu] to (..)
Lua value "_ENV:tree_to_nomsu(\(%tree as lua expr))"
Lua value "nomsu:tree_to_nomsu(\(%tree as lua expr))"
compile [%tree as inline nomsu] to (..)
Lua value "_ENV:tree_to_nomsu(\(%tree as lua expr), true)"
Lua value "nomsu:tree_to_nomsu(\(%tree as lua expr), true)"
action [%var as lua identifier, %var as lua id] (..)
lua> ".."
@ -317,11 +333,13 @@ 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 "_ENV:parse(\(%text as lua expr))")
compile [parse %text] to (Lua value "nomsu:parse(\(%text as lua expr))")
compile [parse %text from %filename] to (..)
Lua value ".."
_ENV:parse(NomsuCode(Source(\(%filename as lua expr), 1, #\(%text as lua expr)), \(..)
nomsu:parse(NomsuCode(Source(\(%filename as lua expr), 1, #\(%text as lua expr)), \(..)
%text as lua expr
..))
@ -333,15 +351,15 @@ test:
assume %passed
compile [run %nomsu_code] to (..)
Lua value ".."
_ENV:run(\(%nomsu_code as lua expr), \(..)
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 _ENV:run(\%tree)")
action [run tree %tree, %tree as value] (lua> "return nomsu:run(\%tree)")
compile [compile %block, compiled %block, %block compiled] to (..)
Lua value "_ENV:compile(\(%block as lua))"
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.

View File

@ -238,6 +238,7 @@ compile [%x or %y] to (Lua value "(\(%x as lua expr) or \(%y as lua expr))")
# Bitwise Operators
# TODO: implement OR, XOR, AND for multiple operands?
test:
assume ((~5) == -6)
assume ((1 | 4) == 5)
assume ((1 ~ 3) == 2)
assume ((1 & 3) == 1)
@ -271,14 +272,9 @@ compile [%x LSHIFT %shift, %x << %shift] to (..)
(%use_bitops and "bit.lshift(\(%x as lua expr), \(%shift as lua expr))") or ".."
(\(%x as lua expr) << \(%shift as lua expr))
compile [%x RSHIFT %shift, %x >>> %shift] to (..)
compile [%x RSHIFT %shift, %x >> %shift] to (..)
Lua value (..)
(%use_bitops and "bit.rshift(\(%x as lua expr), \(%shift as lua expr))") or ".."
(\(%x as lua expr) >>> \(%shift as lua expr))
compile [%x ARSHIFT %shift, %x >> %shift] to (..)
Lua value (..)
(%use_bitops and "bit.arshift(\(%x as lua expr), \(%shift as lua expr))") or ".."
(\(%x as lua expr) >> \(%shift as lua expr))
# Unary operators

View File

@ -1,111 +1,112 @@
#!/usr/bin/env nomsu -V2.5.5.5
#!/usr/bin/env nomsu -V3
#
This file contains the implementation of an Object-Oriented programming system.
test:
object "Dog":
(class Dog).genus = "Canus"
method [initialize %] (%.barks or= 0)
method [bark, woof]:
%barks = ("Bark!" for % in 1 to (me).barks)
(Dog).genus = "Canus"
my action [set up]:
%me.barks or= 0
my action [bark, woof]:
%barks = ("Bark!" for % in 1 to %me.barks)
return (%barks joined with " ")
method [get pissed off] ((me).barks += 1)
my action [get pissed off]:
%me.barks += 1
%d = (new Dog {barks:2})
as %d:
assume ((me) == %d)
assume ((me).barks == 2)
assume ((bark) == "Bark! Bark!")
assume ((woof) == "Bark! Bark!")
get pissed off
assume ((me).barks == 3)
assume ((bark) == "Bark! Bark! Bark!")
assume ((me).genus == "Canus")
assume (%d.barks == 2)
assume ((%d::bark) == "Bark! Bark!")
assume ((%d::woof) == "Bark! Bark!")
%d::get pissed off
assume (%d.barks == 3)
assume ((%d::bark) == "Bark! Bark! Bark!")
assume (%d.genus == "Canus")
assume ("\(%d.class)" == "Dog")
assume (%d.genus == "Canus")
assume (%d.barks == 3)
as (new Dog) (assume ((me).barks == 0) or barf "Default initializer failed")
as (new Dog {barks:1}) (assume ((bark) == "Bark!"))
action [foo] (as (new Dog {barks:23}) (return (me).barks))
assume ((foo) == 23) or barf "Oops, \(foo) != 23"
as (new Dog {barks:101}):
try (as (new Dog {barks:8}) (barf)) and if it succeeds (barf)
assume ((me).barks == 101) or barf ".."
Error in nested 'as % %' failed to properly reset 'self'
%d2 = (new Dog)
assume (%d2.barks == 0) or barf "Default initializer failed"
with {%d:new Dog {barks:1}}: assume ((%d::bark) == "Bark!")
object "Corgi" extends (class Dog):
method [sploot] "splooted"
method [bark, woof]:
%barks = ("Yip!" for % in 1 to (me).barks)
object "Corgi" extends (Dog):
my action [sploot] "splooted"
my action [bark, woof]:
%barks = ("Yip!" for % in 1 to %me.barks)
return (%barks joined with " ")
%corg = (new Corgi)
assume (%corg.barks == 0)
as (new Corgi {barks:1}):
assume ((sploot) == "splooted") or barf "subclass method failed"
assume ((bark) == "Yip!") or barf "inheritance failed"
assume ((woof) == "Yip!")
with {%d:new Corgi {barks:1}}:
assume ((%d::sploot) == "splooted") or barf "subclass method failed"
assume ((%d::bark) == "Yip!") or barf "inheritance failed"
assume ((%d::woof) == "Yip!")
as (new Dog {barks:2}):
assume ((bark) == "Bark! Bark!")
with {%d:new Dog {barks:2}}:
assume ((%d::bark) == "Bark! Bark!")
compile [@, me] to (Lua value "self")
compile [method %actions %body] to:
%lua = (\(local action \[%actions.1] %body) as lua)
declare locals in %lua
for % in %actions:
to %lua write "\n\(\%class as lua id).\(% as lua id) = \(%actions.1 as lua id)"
%lua = (..)
Lua ".."
do -- Method: \(%actions.(1).stub)
\%lua
compile [my 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)
table.insert(\%args, \(\%me as lua id))
local lua = LuaCode(tree.source, "class.", fn_name, " = ", \(compile as (%args -> %body)))
for i=2,#\%actions do
local alias = \%actions[i]
local alias_name = "A"..string.as_lua_id(alias.stub)
local \%alias_args = table.map(alias:get_args(), function(a) return tostring(nomsu:compile(a)) end)
table.insert(\%alias_args, \(\%me as lua id))
lua:append("\\nclass.", alias_name, " = ")
if utils.equivalent(\%args, \%alias_args) then
lua:append("class.", fn_name)
else
lua:append("function(")
lua:concat_append(\%alias_args, ", ")
lua:append(")\\n return class.", fn_name, "(")
lua:concat_append(\%args, ", ")
lua:append(")\\nend")
end
return %lua
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
parse [as %instance %body] as (..)
result of:
%old_self = (me)
(me) = %instance
try %body and if it barfs %msg:
(me) = %old_self
barf %msg
..or if it succeeds: (me) = %old_self
end
return lua
compile [object %classname extends %parent %class_body] to:
%class = (\%class as lua id)
return (..)
Lua ".."
do
local \%class = {name=\(%classname as lua expr)}
setmetatable(\%class, {
local class = {name=\(%classname as lua expr)}
setmetatable(class, {
__index=\(%parent as lua expr),
__tostring=function(cls) return cls.name end,
__call=function(cls, inst)
inst = setmetatable(inst or {}, cls)
if cls.A_initialize_1 then
cls.A_initialize_1(inst)
if inst.A_set_up then
inst:A_set_up()
end
return inst
end,
})
_ENV["A"..string.as_lua_id("new "..\%class.name)] = \%class
_ENV["A"..string.as_lua_id("new "..\%class.name.." 1")] = \%class
_ENV["A"..string.as_lua_id("class "..\%class.name)] = function() return \%class end
\%class.__index = \%class
\%class.class = \%class
nomsu["A"..string.as_lua_id("new "..class.name)] = class
nomsu["A"..string.as_lua_id("new "..class.name.." 1")] = class
nomsu["A"..string.as_lua_id(class.name)] = function() return class end
class.__index = class
class.class = class
class.__tostring = function(inst)
return inst.name..getmetatable(dict{}).__tostring(inst)
end
\(%class_body as lua statements)
\%class.__tostring = \%class["A"..string.as_lua_id("as text")] or function(inst)
return inst.name..getmetatable(dict{}).__tostring(inst)
local metamethod_map = {["as text"]="__tostring", ["clean up"]="__gc",
["+ 1"]="__add", ["- 1"]="__sub", ["* 1"]="__mul", ["/ 1"]="__div",
["-"]="__unm", ["// 1"]="__idiv", ["mod 1"]="__mod", ["^ 1"]="__pow",
["& 1"]="__band", ["| 1"]="__bor", ["~ 1"]="__bxor", ["~"]="__bnot",
["<< 1"]="__bshl", [">> 1"]="__bshr", ["== 1"]="__eq", ["< 1"]="__lt",
["<= 1"]="__le", ["set 1 = 2"]="__newindex", ["length"]="__len",
["__ipairs"]="__ipairs", ["__pairs"]="__pairs",
}
for stub,metamethod in pairs(metamethod_map) do
class[metamethod] = class["A"..string.as_lua_id(stub)]
end
end
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
parse [object %classname %class_body] as (..)
object %classname extends (nil) %class_body

171
nomsu.3.peg Normal file
View File

@ -0,0 +1,171 @@
-- Nomsu version 3
file:
{:curr_indent: ' '* :}
(((action / expression / inline_block / indented_block) eol !.)
/ file_chunks / empty_block)
%ws* (!! .+ -> "Parse error" !!)?
shebang: {:shebang: "#!" (!"nomsu" [^%nl])* "nomsu" %ws+ "-V" %ws* {:version: [0-9.]+ :} [^%nl]* :}
file_chunks (FileChunks):
{:curr_indent: ' '* :}
shebang? comment? blank_lines?
(top_block (nl_nodent section_division top_block)*)
blank_lines?
top_block (Block):
{:curr_indent: ' '* :}
comment? blank_lines? statement (nl_nodent statement)*
empty_block (Block):
{:curr_indent: ' '* :}
comment? blank_lines?
nodent: =curr_indent !(" ")
indent: =curr_indent " "
blank_lines: %nl ((nodent comment / %ws*) %nl)*
eol: %ws* eol_comment? (!. / &%nl)
nl_nodent: blank_lines nodent
nl_indent: blank_lines {:curr_indent: indent :} (comment nl_nodent)?
comment:
"#" (({} {~ [^%nl]* (%nl+ (indent -> '') [^%nl]*)* ~} %userdata) => add_comment)
eol_comment:
"#" (({} {[^%nl]*} %userdata) => add_comment)
section_division: ("~")^+3 eol
inline_block:
"(" %ws* inline_block %ws* ")" / raw_inline_block
raw_inline_block (Block):
(!"::") ":" %ws* ((inline_statement (%ws* ";" %ws* inline_statement)*) / !(eol nl_indent))
indented_block (Block):
":" eol nl_indent statement (nl_nodent statement)* (%nl (%ws* %nl)* nodent comment)*
statement:
(action / expression) (eol / (!! [^%nl]+ -> "Unexpected code while parsing line" !!))
inline_statement: (inline_action / inline_expression)
noindex_inline_expression:
number / variable / inline_text / inline_list / inline_dict / inline_nomsu
/ ( "("
%ws* (inline_action / inline_expression) %ws*
(%ws* ',' %ws* (inline_action / inline_expression) %ws*)*
(")"
/ (!! eol -> 'Line ended without finding a closing )-parenthesis' !!)
/ (!! [^%nl]+ -> 'Unexpected code while parsing subexpression' !!)
)
)
inline_expression: index_chain / noindex_inline_expression
indented_expression:
indented_text / indented_nomsu / indented_list / indented_dict / ({|
"(..)" nl_indent
(action / expression) (nl_nodent comment)*
(eol / (!! [^%nl]+ -> "Unexpected code while parsing indented expression" !!))
|} -> unpack)
/ (nl_indent (!! [^%nl]* -> "Unexpected indentation. Perhaps you meant to put a ':' or '(..)' on the previous line?" !!) (nl_nodent [^%nl]*)*)
expression:
inline_expression / indented_expression
inline_nomsu (EscapedNomsu): "\" (inline_expression / inline_block)
indented_nomsu (EscapedNomsu):
"\" (noindex_inline_expression / inline_block / indented_expression / indented_block)
index_chain (IndexChain):
noindex_inline_expression ("." (text_word / noindex_inline_expression))+
-- Actions need either at least 1 word, or at least 2 tokens
inline_action (Action):
!section_division
({:target: inline_arg :} %ws* "::" %ws*)?
( (inline_arg (%ws* (inline_arg / word))+)
/ (word (%ws* (inline_arg / word))*))
(%ws* inline_block)?
inline_arg: inline_expression / inline_block
action (Action):
!section_division
({:target: arg :} (nl_nodent "..")? %ws* "::" (nl_nodent "..")? %ws*)?
( (arg ((nl_nodent "..")? %ws* (arg / word))+)
/ (word ((nl_nodent "..")? %ws* (arg / word))*))
arg: expression / inline_block / indented_block
word: !number { %operator_char+ / %ident_char+ }
text_word (Text): word
inline_text (Text):
!('".."' eol)
'"'
({~ (('\"' -> '"') / ('\\' -> '\') / %escaped_char / [^%nl\"])+ ~}
/ inline_text_interpolation)*
('"'
/ (!! eol -> 'Line ended before finding a closing double quotation mark' !!)
/ (!! [^%nl]+ -> 'Unexpected code while parsing Text' !!))
inline_text_interpolation:
"\" (
variable / inline_list / inline_dict / inline_text
/ ("("
%ws* (inline_action / inline_expression) %ws*
(%ws* ',' %ws* (inline_action / inline_expression) %ws*)*
(")"
/ (!! eol -> 'Line ended without finding a closing )-parenthesis' !!)
/ (!! [^%nl]+ -> 'Unexpected code while parsing Text interpolation' !!)))
)
indented_text (Text):
'".."' eol %nl {%nl*} {:curr_indent: indent :}
(indented_plain_text / text_interpolation / {~ %nl+ (=curr_indent -> "") ~})*
(!! [^%nl]+ -> "Unexpected code while parsing Text" !!)?
-- Tracking text-lines-within-indented-text as separate objects allows for better debugging line info
indented_plain_text (Text):
{~ (("\\" -> "\") / (("\" blank_lines =curr_indent "..") -> "") / (!text_interpolation "\") / [^%nl\]+)+
(%nl+ (=curr_indent -> ""))* ~}
text_interpolation:
inline_text_interpolation / ("\" indented_expression (blank_lines =curr_indent "..")?)
number (Number): (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / "0x" [0-9a-fA-F]+ / ([0-9]+)))-> tonumber)
-- Variables can be nameless (i.e. just %) and can only contain identifier chars.
-- This ensures you don't get weird parsings of `%x+%y` or `%'s thing`.
variable (Var): "%" {%ident_char*}
inline_list (List):
!('[..]')
"[" %ws*
(inline_list_item (%ws* ',' %ws* inline_list_item)* (%ws* ',')?)? %ws*
("]" / (","? (
(!! eol -> "Line ended before finding a closing ]-bracket" !!)
/(!! [^%nl]+ -> "Unexpected code while parsing List" !!)
)))
indented_list (List):
"[..]" eol nl_indent
list_line (nl_nodent list_line)* (nl_nodent comment)*
(","? (!! [^%nl]+ -> "Unexpected code while parsing List" !!))?
list_line:
(inline_list_item %ws* "," %ws*)+ eol
/ (inline_list_item %ws* "," %ws*)* (action / expression) eol
inline_list_item: inline_action / inline_expression
inline_dict (Dict):
!('{..}')
"{" %ws*
(inline_dict_entry (%ws* ',' %ws* inline_dict_entry)*)? %ws*
("}" / (","? (
(!! eol -> "Line ended before finding a closing }-brace" !!)
/ (!! [^%nl]* -> "Unexpected code while parsing Dictionary" !!)
)))
indented_dict (Dict):
"{..}" eol nl_indent
dict_line (nl_nodent dict_line)* (nl_nodent comment)*
(","? (!! [^%nl]+ -> "Unexpected code while parsing Dictionary" !!))?
dict_line:
(inline_dict_entry %ws* "," %ws*)+ eol
/ (inline_dict_entry %ws* "," %ws*)* dict_entry eol
dict_entry(DictEntry):
dict_key (%ws* ":" %ws* (action / expression))?
inline_dict_entry(DictEntry):
dict_key (%ws* ":" %ws* (inline_action / inline_expression)?)?
dict_key:
text_word / inline_expression

View File

@ -32,14 +32,10 @@ local AST = require("syntax_tree")
local Parser = require("parser")
SOURCE_MAP = { }
string.as_lua_id = function(str)
local argnum = 0
str = gsub(str, "x([0-9A-F][0-9A-F])", "x\0%1")
str = gsub(str, "%W", function(c)
if c == ' ' then
return '_'
elseif c == '%' then
argnum = argnum + 1
return tostring(argnum)
else
return format("x%02X", byte(c))
end
@ -60,6 +56,15 @@ table.fork = function(t, values)
__index = t
})
end
table.copy = function(t)
return setmetatable((function()
local _tbl_0 = { }
for k, v in pairs(t) do
_tbl_0[k] = v
end
return _tbl_0
end)(), getmetatable(t))
end
do
local STRING_METATABLE = getmetatable("")
STRING_METATABLE.__add = function(self, other)
@ -165,6 +170,7 @@ local NomsuCompiler = setmetatable({
do
NomsuCompiler.NOMSU_COMPILER_VERSION = 5
NomsuCompiler.NOMSU_SYNTAX_VERSION = Parser.version
NomsuCompiler.nomsu = NomsuCompiler
NomsuCompiler.parse = function(self, ...)
return Parser.parse(...)
end
@ -246,7 +252,7 @@ do
local err_msg = err_format_string:format(src, ...)
return error(tostring(source.filename) .. ":" .. tostring(line_no) .. ": " .. err_msg, 0)
end
local math_expression = re.compile([[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]])
local math_expression = re.compile([[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]])
local add_lua_bits
add_lua_bits = function(self, val_or_stmt, code)
local cls = val_or_stmt == "value" and LuaCode.Value or LuaCode
@ -279,7 +285,7 @@ do
end
local add_bit_lua
add_bit_lua = function(lua, bit_lua)
local bit_leading_len = #(tostring(bit_lua):match("^[^\n]*"))
local bit_leading_len = #(bit_lua:match("^[^\n]*"))
lua:append(lua:trailing_line_len() + bit_leading_len > MAX_LINE and ",\n " or ", ")
return lua:append(bit_lua)
end
@ -327,37 +333,37 @@ do
end
return lua
end,
["Lua %"] = function(self, tree, _code)
["Lua 1"] = function(self, tree, _code)
return add_lua_string_bits(self, 'statements', _code)
end,
["Lua value %"] = function(self, tree, _code)
["Lua value 1"] = function(self, tree, _code)
return add_lua_string_bits(self, 'value', _code)
end,
["lua > %"] = function(self, tree, _code)
["lua > 1"] = function(self, tree, _code)
if _code.type ~= "Text" then
return LuaCode(tree.source, "_ENV:run_lua(", self:compile(_code), ");")
return LuaCode(tree.source, "nomsu:run_lua(", self:compile(_code), ");")
end
return add_lua_bits(self, "statements", _code)
end,
["= lua %"] = function(self, tree, _code)
["= lua 1"] = function(self, tree, _code)
if _code.type ~= "Text" then
return LuaCode.Value(tree.source, "_ENV:run_lua(", self:compile(_code), ":as_statements('return '))")
return LuaCode.Value(tree.source, "nomsu:run_lua(", self:compile(_code), ":as_statements('return '))")
end
return add_lua_bits(self, "value", _code)
end,
["use %"] = function(self, tree, _path)
["use 1"] = function(self, tree, _path)
if _path.type == 'Text' and #_path == 1 and type(_path[1]) == 'string' then
local path = _path[1]
for _, f in Files.walk(path) do
self:run_file(f)
end
end
return LuaCode(tree.source, "for i,f in Files.walk(", self:compile(_path), ") do _ENV:run_file(f) end")
return LuaCode(tree.source, "for i,f in Files.walk(", self:compile(_path), ") do nomsu:run_file(f) end")
end,
["tests"] = function(self, tree)
return LuaCode.Value(tree.source, "TESTS")
end,
["test %"] = function(self, tree, _body)
["test 1"] = function(self, tree, _body)
local test_str = table.concat((function()
local _accum_0 = { }
local _len_0 = 1
@ -481,7 +487,7 @@ do
source = nil
end
local lua_string = tostring(lua)
local run_lua_fn, err = load(lua_string, tostring(source or lua.source), "t", self)
local run_lua_fn, err = load(lua_string, nil and tostring(source or lua.source), "t", self)
if not run_lua_fn then
local line_numbered_lua = concat((function()
local _accum_0 = { }
@ -502,7 +508,8 @@ do
if not file then
error("Failed to find file: " .. tostring(source.filename))
end
local nomsu_str = tostring(file:sub(source.start, source.stop))
local nomsu_str = file:sub(source.start, source.stop)
assert(type(nomsu_str) == 'string')
local lua_line = 1
local nomsu_line = Files.get_line_number(file, source.start)
local map_sources
@ -596,30 +603,34 @@ do
end
lua:concat_append(args, ", ")
lua:append(")")
if tree.target then
local target_lua = self:compile(tree.target)
lua:prepend(target_lua, ":")
end
return lua
elseif "EscapedNomsu" == _exp_0 then
local lua = LuaCode.Value(tree.source, tree[1].type, "(")
local bits
if tree[1].type == "EscapedNomsu" then
bits = {
self:compile(tree[1])
}
else
do
local _accum_0 = { }
local _len_0 = 1
local _list_0 = tree[1]
for _index_0 = 1, #_list_0 do
local bit = _list_0[_index_0]
_accum_0[_len_0] = AST.is_syntax_tree(bit) and self:compile(bit) or repr(bit)
_len_0 = _len_0 + 1
end
bits = _accum_0
local lua = LuaCode.Value(tree.source, tree[1].type, "{")
local needs_comma, i = false, 1
for k, v in pairs(AST.is_syntax_tree(tree[1], "EscapedNomsu") and tree or tree[1]) do
if needs_comma then
lua:append(", ")
else
needs_comma = true
end
if k == i then
i = i + 1
elseif type(k) == 'string' and match(k, "[_a-zA-Z][_a-zA-Z0-9]*") then
lua:append(k, "= ")
else
lua:append("[", (AST.is_syntax_tree(k) and self:compile(k) or repr(k)), "]= ")
end
if k == "source" then
lua:append(repr(tostring(v)))
else
lua:append(AST.is_syntax_tree(v) and self:compile(v) or repr(v))
end
end
insert(bits, 1, repr(tostring(tree[1].source)))
lua:concat_append(bits, ", ")
lua:append(")")
lua:append("}")
return lua
elseif "Block" == _exp_0 then
local lua = LuaCode(tree.source)
@ -719,7 +730,7 @@ do
if not (value_lua.is_value) then
self:compile_error(tree[2].source, "Cannot use:\n%s\nas a dict value, since it's not an expression.")
end
local key_str = match(tostring(key_lua), [=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=])
local key_str = match(tostring(key_lua), [=[^["']([a-zA-Z_][a-zA-Z0-9_]*)['"]$]=])
if key_str then
return LuaCode(tree.source, key_str, "=", value_lua)
elseif sub(tostring(key_lua), 1, 1) == "[" then
@ -790,6 +801,9 @@ do
return error("Cannot inline a FileChunks")
elseif "Action" == _exp_0 then
local nomsu = NomsuCode(tree.source)
if tree.target then
nomsu:append(self:tree_to_inline_nomsu(tree.target), "::")
end
for i, bit in ipairs(tree) do
if type(bit) == "string" then
local clump_words = (type(tree[i - 1]) == 'string' and Parser.is_operator(bit) ~= Parser.is_operator(tree[i - 1]))
@ -799,7 +813,7 @@ do
nomsu:append(bit)
else
local arg_nomsu = recurse(bit, nomsu, parenthesize_blocks or (i == 1 or i < #tree))
if not (tostring(arg_nomsu):match("^:") or i == 1) then
if not (arg_nomsu:match("^:") or i == 1) then
nomsu:append(" ")
end
if bit.type == "Action" then
@ -1056,13 +1070,13 @@ do
local should_clump
should_clump = function(prev_line, line)
if prev_line.type == "Action" and line.type == "Action" then
if prev_line.stub == "use %" then
return line.stub == "use %"
if prev_line.stub == "use 1" then
return line.stub == "use 1"
end
if prev_line.stub == "test %" then
if prev_line.stub == "test 1" then
return true
end
if line.stub == "test %" then
if line.stub == "test 1" then
return false
end
end
@ -1090,13 +1104,16 @@ do
end
end
nomsu:append(pop_comments(tree.source.stop, '\n'))
if not (tostring(nomsu):match("\n$")) then
if not (nomsu:match("\n$")) then
nomsu:append('\n')
end
return nomsu
elseif "Action" == _exp_0 then
local pos, next_space = tree.source.start, ''
local nomsu = NomsuCode(tree.source, pop_comments(pos))
if tree.target then
nomsu:append(self:tree_to_nomsu(tree.target), "::")
end
for i, bit in ipairs(tree) do
if next_space == "\n.." or (next_space == " " and nomsu:trailing_line_len() > MAX_LINE) then
nomsu:append("\n", pop_comments(pos), '..')
@ -1109,12 +1126,12 @@ do
nomsu:append(bit)
next_space = ' '
elseif bit.type == "Block" then
nomsu:append(recurse(bit, #tostring(nomsu):match('[^\n]*$')))
nomsu:append(recurse(bit, #nomsu:match('[^\n]*$')))
pos = bit.source.stop
next_space = inline and " " or "\n.."
else
nomsu:append(next_space)
local bit_nomsu = recurse(bit, #tostring(nomsu):match('[^\n]*$'))
local bit_nomsu = recurse(bit, #nomsu:match('[^\n]*$'))
if bit.type == "Action" and not bit_nomsu:is_multiline() then
bit_nomsu:parenthesize()
end
@ -1134,7 +1151,7 @@ do
local line_nomsu = recurse(line)
nomsu:append(line_nomsu)
if i < #tree then
nomsu:append(tostring(line_nomsu):match('\n[^\n]*\n') and "\n\n" or "\n")
nomsu:append(line_nomsu:match('\n[^\n]*\n') and "\n\n" or "\n")
end
end
nomsu:append(pop_comments(tree.source.stop, '\n'))
@ -1174,7 +1191,7 @@ do
add_text(nomsu, bit)
else
nomsu:append("\\")
local interp_nomsu = recurse(bit, #tostring(nomsu):match('[^\n]*$'))
local interp_nomsu = recurse(bit, #nomsu:match('[^\n]*$'))
if not (interp_nomsu:is_multiline()) then
if bit.type == "Var" then
if type(tree[i + 1]) == 'string' and not match(tree[i + 1], "^[ \n\t,.:;#(){}[%]]") then
@ -1193,7 +1210,7 @@ do
end
local nomsu = NomsuCode(tree.source)
add_text(nomsu, tree)
if nomsu:is_multiline() and tostring(nomsu):match("\n$") then
if nomsu:is_multiline() and nomsu:match("\n$") then
nomsu:append('\\("")')
end
return NomsuCode(tree.source, '".."\n ', nomsu)
@ -1205,7 +1222,7 @@ do
nomsu:append(pop_comments(item.source.start))
end
local inline_nomsu = self:tree_to_inline_nomsu(item)
local item_nomsu = #tostring(inline_nomsu) <= MAX_LINE and inline_nomsu or recurse(item, #tostring(nomsu):match('[^\n]*$'))
local item_nomsu = #tostring(inline_nomsu) <= MAX_LINE and inline_nomsu or recurse(item, #nomsu:match('[^\n]*$'))
nomsu:append(item_nomsu)
if i < #tree then
nomsu:append((item_nomsu:is_multiline() or nomsu:trailing_line_len() + #tostring(item_nomsu) >= MAX_LINE) and '\n' or ', ')

View File

@ -29,20 +29,17 @@ export SOURCE_MAP
SOURCE_MAP = {}
string.as_lua_id = (str)->
argnum = 0
-- Cut up escape-sequence-like chunks
str = gsub str, "x([0-9A-F][0-9A-F])", "x\0%1"
-- Alphanumeric unchanged, spaces to underscores, and everything else to hex escape sequences
str = gsub str, "%W", (c)->
if c == ' ' then '_'
elseif c == '%' then
argnum += 1
tostring(argnum)
else format("x%02X", byte(c))
return '_'..str
table.map = (fn)=> [fn(v) for _,v in ipairs(@)]
table.fork = (t, values)-> setmetatable(values or {}, {__index:t})
table.copy = (t)-> setmetatable({k,v for k,v in pairs(t)}, getmetatable(t))
-- TODO:
-- consider non-linear codegen, rather than doing thunks for things like comprehensions
@ -99,6 +96,7 @@ NomsuCompiler = setmetatable {name:"Nomsu"},
with NomsuCompiler
.NOMSU_COMPILER_VERSION = 5
.NOMSU_SYNTAX_VERSION = Parser.version
.nomsu = NomsuCompiler
.parse = (...)=> Parser.parse(...)
.can_optimize = -> false
@ -142,7 +140,7 @@ with NomsuCompiler
-- 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.
math_expression = re.compile [[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]]
math_expression = re.compile [[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]]
add_lua_bits = (val_or_stmt, code)=>
cls = val_or_stmt == "value" and LuaCode.Value or LuaCode
@ -167,7 +165,7 @@ with NomsuCompiler
if code.type != "Text"
return LuaCode.Value(code.source, cls_str, repr(tostring(code.source)), ", ", @compile(code), ")")
add_bit_lua = (lua, bit_lua)->
bit_leading_len = #(tostring(bit_lua)\match("^[^\n]*"))
bit_leading_len = #(bit_lua\match("^[^\n]*"))
lua\append(lua\trailing_line_len! + bit_leading_len > MAX_LINE and ",\n " or ", ")
lua\append(bit_lua)
operate_on_text = (text)->
@ -204,31 +202,31 @@ with NomsuCompiler
lua\append " "
return lua
["Lua %"]: (tree, _code)=>
["Lua 1"]: (tree, _code)=>
return add_lua_string_bits(@, 'statements', _code)
["Lua value %"]: (tree, _code)=>
["Lua value 1"]: (tree, _code)=>
return add_lua_string_bits(@, 'value', _code)
["lua > %"]: (tree, _code)=>
["lua > 1"]: (tree, _code)=>
if _code.type != "Text"
return LuaCode tree.source, "_ENV:run_lua(", @compile(_code), ");"
return LuaCode tree.source, "nomsu:run_lua(", @compile(_code), ");"
return add_lua_bits(@, "statements", _code)
["= lua %"]: (tree, _code)=>
["= lua 1"]: (tree, _code)=>
if _code.type != "Text"
return LuaCode.Value tree.source, "_ENV:run_lua(", @compile(_code), ":as_statements('return '))"
return LuaCode.Value tree.source, "nomsu:run_lua(", @compile(_code), ":as_statements('return '))"
return add_lua_bits(@, "value", _code)
["use %"]: (tree, _path)=>
["use 1"]: (tree, _path)=>
if _path.type == 'Text' and #_path == 1 and type(_path[1]) == 'string'
path = _path[1]
for _,f in Files.walk(path)
@run_file(f)
return LuaCode(tree.source, "for i,f in Files.walk(", @compile(_path), ") do _ENV:run_file(f) end")
return LuaCode(tree.source, "for i,f in Files.walk(", @compile(_path), ") do nomsu:run_file(f) end")
["tests"]: (tree)=> LuaCode.Value(tree.source, "TESTS")
["test %"]: (tree, _body)=>
["test 1"]: (tree, _body)=>
test_str = table.concat [tostring(@tree_to_nomsu(line)) for line in *_body], "\n"
LuaCode tree.source, "TESTS[#{repr(tostring(tree.source))}] = ", repr(test_str)
@ -304,7 +302,7 @@ with NomsuCompiler
.run_lua = (lua, source=nil)=>
lua_string = tostring(lua)
run_lua_fn, err = load(lua_string, tostring(source or lua.source), "t", self)
run_lua_fn, err = load(lua_string, nil and tostring(source or lua.source), "t", self)
if not run_lua_fn
line_numbered_lua = concat(
[format("%3d|%s",i,line) for i, line in ipairs Files.get_lines(lua_string)],
@ -317,7 +315,8 @@ with NomsuCompiler
file = Files.read(source.filename)
if not file
error "Failed to find file: #{source.filename}"
nomsu_str = tostring(file\sub(source.start, source.stop))
nomsu_str = file\sub(source.start, source.stop)
assert type(nomsu_str) == 'string'
lua_line = 1
nomsu_line = Files.get_line_number(file, source.start)
map_sources = (s)->
@ -373,15 +372,28 @@ with NomsuCompiler
insert args, arg_lua
lua\concat_append args, ", "
lua\append ")"
if tree.target
target_lua = @compile(tree.target)
lua\prepend(target_lua, ":")
return lua
when "EscapedNomsu"
lua = LuaCode.Value tree.source, tree[1].type, "("
bits = if tree[1].type == "EscapedNomsu" then {@compile(tree[1])}
else [AST.is_syntax_tree(bit) and @compile(bit) or repr(bit) for bit in *tree[1]]
insert bits, 1, repr(tostring tree[1].source)
lua\concat_append bits, ", "
lua\append ")"
lua = LuaCode.Value tree.source, tree[1].type, "{"
needs_comma, i = false, 1
for k,v in pairs(AST.is_syntax_tree(tree[1], "EscapedNomsu") and tree or tree[1])
if needs_comma then lua\append ", "
else needs_comma = true
if k == i
i += 1
elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*")
lua\append(k, "= ")
else
lua\append("[", (AST.is_syntax_tree(k) and @compile(k) or repr(k)), "]= ")
if k == "source"
lua\append repr(tostring(v))
else
lua\append(AST.is_syntax_tree(v) and @compile(v) or repr(v))
lua\append "}"
return lua
when "Block"
@ -442,7 +454,7 @@ with NomsuCompiler
@compile_error tree[2].source,
"Cannot use:\n%s\nas a dict value, since it's not an expression."
-- TODO: support arbitrary words here, like operators and unicode
key_str = match(tostring(key_lua), [=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=])
key_str = match(tostring(key_lua), [=[^["']([a-zA-Z_][a-zA-Z0-9_]*)['"]$]=])
return if key_str
LuaCode tree.source, key_str,"=",value_lua
elseif sub(tostring(key_lua),1,1) == "["
@ -502,6 +514,8 @@ with NomsuCompiler
when "Action"
nomsu = NomsuCode(tree.source)
if tree.target
nomsu\append @tree_to_inline_nomsu(tree.target), "::"
for i,bit in ipairs tree
if type(bit) == "string"
clump_words = (type(tree[i-1]) == 'string' and Parser.is_operator(bit) != Parser.is_operator(tree[i-1]))
@ -509,7 +523,7 @@ with NomsuCompiler
nomsu\append bit
else
arg_nomsu = recurse(bit, nomsu, parenthesize_blocks or (i == 1 or i < #tree))
nomsu\append " " unless tostring(arg_nomsu)\match("^:") or i == 1
nomsu\append " " unless arg_nomsu\match("^:") or i == 1
arg_nomsu\parenthesize! if bit.type == "Action"
nomsu\append arg_nomsu
check(len, nomsu, tree) if check
@ -656,9 +670,9 @@ with NomsuCompiler
nomsu = NomsuCode(tree.source, pop_comments(tree.source.start))
should_clump = (prev_line, line)->
if prev_line.type == "Action" and line.type == "Action"
if prev_line.stub == "use %" then return line.stub == "use %"
if prev_line.stub == "test %" then return true
if line.stub == "test %" then return false
if prev_line.stub == "use 1" then return line.stub == "use 1"
if prev_line.stub == "test 1" then return true
if line.stub == "test 1" then return false
return not recurse(prev_line)\is_multiline!
for chunk_no, chunk in ipairs tree
nomsu\append "\n\n#{("~")\rep(80)}\n\n" if chunk_no > 1
@ -675,12 +689,14 @@ with NomsuCompiler
else
nomsu\append recurse(chunk)
nomsu\append pop_comments(tree.source.stop, '\n')
nomsu\append('\n') unless tostring(nomsu)\match("\n$")
nomsu\append('\n') unless nomsu\match("\n$")
return nomsu
when "Action"
pos, next_space = tree.source.start, ''
nomsu = NomsuCode(tree.source, pop_comments(pos))
if tree.target
nomsu\append @tree_to_nomsu(tree.target), "::"
for i,bit in ipairs tree
if next_space == "\n.." or (next_space == " " and nomsu\trailing_line_len! > MAX_LINE)
nomsu\append "\n", pop_comments(pos), '..'
@ -692,12 +708,12 @@ with NomsuCompiler
nomsu\append bit
next_space = ' '
elseif bit.type == "Block"
nomsu\append(recurse(bit, #tostring(nomsu)\match('[^\n]*$')))
nomsu\append(recurse(bit, #nomsu\match('[^\n]*$')))
pos = bit.source.stop
next_space = inline and " " or "\n.."
else
nomsu\append next_space
bit_nomsu = recurse(bit, #tostring(nomsu)\match('[^\n]*$'))
bit_nomsu = recurse(bit, #nomsu\match('[^\n]*$'))
if bit.type == "Action" and not bit_nomsu\is_multiline!
bit_nomsu\parenthesize!
nomsu\append bit_nomsu
@ -717,7 +733,7 @@ with NomsuCompiler
line_nomsu = recurse(line)
nomsu\append line_nomsu
if i < #tree
nomsu\append(tostring(line_nomsu)\match('\n[^\n]*\n') and "\n\n" or "\n")
nomsu\append(line_nomsu\match('\n[^\n]*\n') and "\n\n" or "\n")
nomsu\append pop_comments(tree.source.stop, '\n')
return NomsuCode(tree.source, ":\n ", nomsu)
@ -749,7 +765,7 @@ with NomsuCompiler
add_text(nomsu, bit)
else
nomsu\append "\\"
interp_nomsu = recurse(bit, #tostring(nomsu)\match('[^\n]*$'))
interp_nomsu = recurse(bit, #nomsu\match('[^\n]*$'))
unless interp_nomsu\is_multiline!
if bit.type == "Var"
if type(tree[i+1]) == 'string' and not match(tree[i+1], "^[ \n\t,.:;#(){}[%]]")
@ -761,7 +777,7 @@ with NomsuCompiler
nomsu\append "\n.."
nomsu = NomsuCode(tree.source)
add_text(nomsu, tree)
if nomsu\is_multiline! and tostring(nomsu)\match("\n$")
if nomsu\is_multiline! and nomsu\match("\n$")
nomsu\append '\\("")' -- Need to specify where the text ends
return NomsuCode(tree.source, '".."\n ', nomsu)
@ -771,7 +787,7 @@ with NomsuCompiler
for i, item in ipairs tree
nomsu\append(pop_comments(item.source.start)) if nomsu\trailing_line_len! == 0
inline_nomsu = @tree_to_inline_nomsu(item)
item_nomsu = #tostring(inline_nomsu) <= MAX_LINE and inline_nomsu or recurse(item, #tostring(nomsu)\match('[^\n]*$'))
item_nomsu = #tostring(inline_nomsu) <= MAX_LINE and inline_nomsu or recurse(item, #nomsu\match('[^\n]*$'))
nomsu\append item_nomsu
if i < #tree
nomsu\append((item_nomsu\is_multiline! or nomsu\trailing_line_len! + #tostring(item_nomsu) >= MAX_LINE) and '\n' or ', ')

View File

@ -113,7 +113,7 @@ setmetatable(NOMSU_DEFS, {
end
})
local Parser = {
version = 2,
version = 3,
patterns = { }
}
do

View File

@ -74,7 +74,7 @@ setmetatable(NOMSU_DEFS, {__index:(key)=>
return make_node
})
Parser = {version:2, patterns:{}}
Parser = {version:3, patterns:{}}
do
-- Just for cleanliness, I put the language spec in its own file using a slightly modified
-- version of the lpeg.re syntax.

View File

@ -26,7 +26,8 @@ local types = {
"DictEntry",
"IndexChain",
"Action",
"FileChunks"
"FileChunks",
"Method"
}
for _index_0 = 1, #types do
local name = types[_index_0]
@ -40,20 +41,14 @@ for _index_0 = 1, #types do
return getmetatable(x) == self
end
cls.__tostring = function(self)
local args = {
tostring(self.source),
unpack(self)
}
return tostring(self.type) .. "(" .. tostring(concat((function()
local _accum_0 = { }
local _len_0 = 1
for _index_1 = 1, #args do
local v = args[_index_1]
_accum_0[_len_0] = repr(v)
_len_0 = _len_0 + 1
end
return _accum_0
end)(), ', ')) .. ")"
return tostring(self.type) .. tostring(repr(self, (function(x)
return Source:is_instance(x) and tostring(x) or nil
end)))
end
cls.__repr = function(self)
return tostring(self.type) .. tostring(repr(self, (function(x)
return Source:is_instance(x) and tostring(x) or nil
end)))
end
cls.map = function(self, fn)
local replacement = fn(self)
@ -61,14 +56,33 @@ for _index_0 = 1, #types do
return nil
end
if replacement then
replacement = (replacement.__class)(self.source, unpack(replacement))
if AST.is_syntax_tree(replacement) then
replacement = setmetatable((function()
local _tbl_0 = { }
for k, v in pairs(replacement) do
_tbl_0[k] = v
end
return _tbl_0
end)(), getmetatable(replacement))
replacement.source = self.source
if self.comments then
replacement.comments = {
unpack(self.comments)
}
end
end
else
local replacements = { }
replacement = {
source = self.source,
comments = self.comments and {
unpack(self.comments)
}
}
local changes = false
for i, v in ipairs(self) do
for k, v in pairs(self) do
local _continue_0 = false
repeat
replacements[#replacements + 1] = v
replacement[k] = v
if AST.is_syntax_tree(v) then
local r = v:map(fn)
if r == v or r == nil then
@ -76,7 +90,7 @@ for _index_0 = 1, #types do
break
end
changes = true
replacements[#replacements] = r
replacement[k] = r
end
_continue_0 = true
until true
@ -87,20 +101,7 @@ for _index_0 = 1, #types do
if not (changes) then
return self
end
replacement = (self.__class)(self.source, unpack(replacements))
end
if self.comments then
do
local _accum_0 = { }
local _len_0 = 1
local _list_0 = self.comments
for _index_1 = 1, #_list_0 do
local c = _list_0[_index_1]
_accum_0[_len_0] = c
_len_0 = _len_0 + 1
end
replacement.comments = _accum_0
end
replacement = setmetatable(replacement, getmetatable(self))
end
return replacement
end
@ -113,6 +114,9 @@ for _index_0 = 1, #types do
return false
end
end
if self.target ~= other.target then
return false
end
return true
end
end
@ -120,37 +124,31 @@ for _index_0 = 1, #types do
__tostring = function(self)
return self.__name
end,
__call = function(self, source, ...)
if type(source) == 'string' then
source = Source:from_string(source)
__call = function(self, t)
if type(t.source) == 'string' then
t.source = Source:from_string(t.source)
else
assert(Source:is_instance(t.source))
end
for i = 1, select('#', ...) do
assert(select(i, ...))
setmetatable(t, self)
if t.__init then
t:__init()
end
assert(Source:is_instance(source))
local inst = {
source = source,
...
}
setmetatable(inst, self)
if inst.__init then
inst:__init()
end
return inst
return t
end
})
end
AST.Action.__init = function(self)
local stub_bits
do
local _accum_0 = { }
local _len_0 = 1
for _index_0 = 1, #self do
local a = self[_index_0]
_accum_0[_len_0] = type(a) == 'string' and a or '%'
_len_0 = _len_0 + 1
local stub_bits = { }
local arg_i = 1
for _index_0 = 1, #self do
local a = self[_index_0]
if type(a) == 'string' then
stub_bits[#stub_bits + 1] = a
else
stub_bits[#stub_bits + 1] = tostring(arg_i)
arg_i = arg_i + 1
end
stub_bits = _accum_0
end
self.stub = concat(stub_bits, " ")
end

View File

@ -10,7 +10,7 @@ AST.is_syntax_tree = (n, t=nil)->
type(n) == 'table' and getmetatable(n) and AST[n.type] == getmetatable(n) and (t == nil or n.type == t)
types = {"Number", "Var", "Block", "EscapedNomsu", "Text", "List", "Dict", "DictEntry",
"IndexChain", "Action", "FileChunks"}
"IndexChain", "Action", "FileChunks", "Method"}
for name in *types
cls = {}
with cls
@ -19,49 +19,57 @@ for name in *types
.__name = name
.type = name
.is_instance = (x)=> getmetatable(x) == @
.__tostring = =>
args = {tostring(@source), unpack(@)}
"#{@type}(#{concat([repr(v) for v in *args], ', ')})"
.__tostring = => "#{@type}#{repr @, ((x)-> Source\is_instance(x) and tostring(x) or nil)}"
.__repr = => "#{@type}#{repr @, ((x)-> Source\is_instance(x) and tostring(x) or nil)}"
.map = (fn)=>
replacement = fn(@)
if replacement == false then return nil
if replacement
-- Clone the replacement, but give it a proper source
replacement = (replacement.__class)(@source, unpack(replacement))
-- Clone the replacement, so we can give it a proper source/comments
if AST.is_syntax_tree(replacement)
replacement = setmetatable {k,v for k,v in pairs replacement}, getmetatable(replacement)
replacement.source = @source
replacement.comments = {unpack(@comments)} if @comments
else
replacements = {}
replacement = {source:@source, comments:@comments and {unpack(@comments)}}
changes = false
for i,v in ipairs(@)
replacements[#replacements+1] = v
for k,v in pairs(@)
replacement[k] = v
if AST.is_syntax_tree(v)
r = v\map(fn)
continue if r == v or r == nil
changes = true
replacements[#replacements] = r
replacement[k] = r
return @ unless changes
replacement = (@__class)(@source, unpack(replacements))
replacement.comments = [c for c in *@comments] if @comments
replacement = setmetatable replacement, getmetatable(@)
return replacement
.__eq = (other)=>
return false if type(@) != type(other) or #@ != #other or getmetatable(@) != getmetatable(other)
for i=1,#@
return false if @[i] != other[i]
return false if @target != other.target
return true
AST[name] = setmetatable cls,
__tostring: => @__name
__call: (source, ...)=>
if type(source) == 'string'
source = Source\from_string(source)
for i=1,select('#', ...) do assert(select(i,...))
assert(Source\is_instance(source))
inst = {:source, ...}
setmetatable(inst, @)
if inst.__init then inst\__init!
return inst
__call: (t)=>
if type(t.source) == 'string'
t.source = Source\from_string(t.source)
else
assert(Source\is_instance(t.source))
setmetatable(t, @)
if t.__init then t\__init!
return t
AST.Action.__init = =>
stub_bits = [type(a) == 'string' and a or '%' for a in *@]
stub_bits = {}
arg_i = 1
for a in *@
if type(a) == 'string'
stub_bits[#stub_bits+1] = a
else
stub_bits[#stub_bits+1] = tostring(arg_i)
arg_i += 1
@stub = concat stub_bits, " "
AST.Action.get_args = =>

View File

@ -23,32 +23,34 @@ local function size(t)
return n
end
local function repr(x, depth)
local repr_behavior = function(x)
local mt = getmetatable(x)
if mt then
local fn = rawget(mt, "__repr")
if fn then return fn(x) end
end
end
local function repr(x, mt_behavior)
-- Create a string representation of the object that is close to the lua code that will
-- reproduce the object (similar to Python's "repr" function)
depth = depth or 10
if depth == 0 then return "..." end
depth = depth - 1
mt_behavior = mt_behavior or repr_behavior
local x_type = type(x)
if x_type == 'table' then
if getmetatable(x) then
-- If this object has a weird metatable, then don't pretend like it's a regular table
return tostring(x)
else
local ret = {}
local i = 1
for k, v in pairs(x) do
if k == i then
ret[#ret+1] = repr(x[i], depth)
i = i + 1
elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*") then
ret[#ret+1] = k.."= "..repr(v,depth)
else
ret[#ret+1] = "["..repr(k,depth).."]= "..repr(v,depth)
end
local ret = mt_behavior(x)
if ret then return ret end
local ret = {}
local i = 1
for k, v in pairs(x) do
if k == i then
ret[#ret+1] = repr(v, mt_behavior)
i = i + 1
elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*") then
ret[#ret+1] = k.."= "..repr(v, mt_behavior)
else
ret[#ret+1] = "["..repr(k, mt_behavior).."]= "..repr(v, mt_behavior)
end
return "{"..table.concat(ret, ", ").."}"
end
return "{"..table.concat(ret, ", ").."}"
elseif x_type == 'string' then
local escaped = gsub(x, "\\", "\\\\")
escaped = gsub(escaped, "\n", "\\n")
@ -60,11 +62,18 @@ local function repr(x, depth)
end
end
local stringify_behavior = function(x)
local mt = getmetatable(x)
if mt then
local fn = rawget(mt, "__tostring")
if fn then return fn(x) end
end
end
local function stringify(x)
if type(x) == 'string' then
return x
else
return repr(x)
return repr(x, stringify_behavior)
end
end