Incremental checkin, currently not working, just saving progress.

This commit is contained in:
Bruce Hill 2018-09-21 00:30:28 -07:00
parent 79d4bd5125
commit f2048235f5
14 changed files with 1242 additions and 46 deletions

View File

@ -133,9 +133,11 @@ for i,entry in ipairs(Dict({x:99}))
do
{:reverse, :upper, :lower, :find, :byte, :match, :gmatch, :gsub, :sub, :format, :rep} = string
string2 = require 'string2'
{:lines, :line, :line_at, :as_lua_id} = string2
{:lines, :line, :line_at, :as_lua_id, :is_lua_id} = string2
text_methods =
formatted_with_1:format, byte_1:byte, position_of_1:find, position_of_1_after_2:find,
as_a_lua_identifier: as_lua_id, is_a_lua_identifier: is_lua_id,
as_a_lua_id: as_lua_id, is_a_lua_id: is_lua_id,
bytes_1_to_2: (start, stop)=> List{byte(tostring(@), start, stop)}
[as_lua_id "with 1 -> 2"]: gsub
bytes: => List{byte(tostring(@), 1, -1)},
@ -157,8 +159,10 @@ do
line_number_of_1: (i)=> select(2, line_at(@, i))
line_position_of_1: (i)=> select(3, line_at(@, i))
matches_1: (patt)=> match(@, patt) and true or false
matching_1: (patt)=> (match(@, patt))
matching_groups_1: (patt)=> {match(@, patt)}
[as_lua_id "* 1"]: (n)=> rep(@, n)
matching_1: (patt)=>
all_matches_of_1: (patt)=>
result = {}
stepper,x,i = gmatch(@, patt)
while true
@ -176,4 +180,6 @@ do
elseif type(i) == 'table' then return sub(@, i[1], i[2])
else return text_methods[i]
getmetatable("").__add = (x)=> tostring(@)..tostring(x)
return {:List, :Dict}

View File

@ -79,6 +79,10 @@ test:
%i += 1
unless (%i == 10): go to %loop
assume (%i == 10)
=== (Loop) ===
%i -= 1
unless (%i == 0): go to (Loop)
assume (%i == 0)
compile [=== %label ===, --- %label ---, *** %label ***] to (..)
Lua "::label_\(%label as lua identifier)::"
@ -240,10 +244,8 @@ test:
# For-each loop (lua's "ipairs()")
compile [for %var in %iterable %body] to:
# This uses Lua's approach of only allowing loop-scoped variables in a loop
unless (%var.type is "Var"):
compile error at %var "Expected a variable here, not a \(%var.type)."
define mangler
# This uses Lua's approach of only allowing loop-scoped variables in a loop
%lua = (..)
Lua "\
..for \(mangle "i"),\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do
@ -264,6 +266,29 @@ compile [for %var in %iterable %body] to:
return %lua
# TODO: reduce code duplication
compile [for %var in %iterable at %i %body] to:
# This uses Lua's approach of only allowing loop-scoped variables in a loop
%lua = (..)
Lua "\
..for \(%i as lua identifier),\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do
\(%body as lua statements)"
if (%body has subtree \(do next)):
%lua::append "\n ::continue::"
if (%body has subtree \(do next %var)):
%lua::append (Lua "\n\(compile as (===next %var ===))")
%lua::append "\nend --foreach-loop"
if (%body has subtree \(stop %var)):
%lua = (..)
Lua "\
..do -- scope for stopping for-loop
\%lua
\(compile as (===stop %var ===))
end -- end of scope for stopping for-loop"
return %lua
test:
%d = {a:10, b:20, c:30, d:40, e:50}
%result = []

View File

@ -145,7 +145,7 @@ compile [action %actions %body] to (..)
test:
assume ((action (say %)) == (=lua "say_1"))
compile [action %action] to (Lua value (%action as lua id))
compile [action %action] to (Lua value (%action.stub as lua id))
test:
parse [swap %x and %y] as (..)
@ -233,7 +233,12 @@ action [%var as lua identifier, %var as lua id] (..)
lua> "\
..if type(\%var) == 'string' then return \%var:as_lua_id()
elseif AST.is_syntax_tree(\%var, 'Var') then return \%var[1]:as_lua_id()
elseif AST.is_syntax_tree(\%var, 'Action') then return \%var.stub:as_lua_id()
elseif AST.is_syntax_tree(\%var) then
local lua = \(%var as lua expr)
if not tostring(lua):match("^[_a-zA-Z][_a-zA-Z0-9]*$") then
\(compile error at %var "This is not a valid Lua identifier.")
end
return lua
else error("Unknown type: "..tostring(\%var))
end"
@ -317,8 +322,29 @@ test:
compile [quote %s] to (Lua value "tostring(\(%s as lua expr)):as_lua()")
test:
assume ((type of {}) == "table") or barf "type of failed."
compile [type of %obj] to (Lua value "type(\(%obj as lua expr))")
assume (lua type of {}) == "Lua table"
assume (type of {}) == "Dict"
assume ({} is a "Dict")
assume ("" is text)
assume ("" isn't a "Dict")
%dict_mt = (=lua "getmetatable(\{})")
%list_mt = (=lua "getmetatable(\[])")
action [% is text] (=lua "\(lua type of %) == 'string'")
action [% is not text, % isn't text] (=lua "\(lua type of %) ~= 'string'")
action [type of %]:
lua> "\
local lua_type = \(lua type of %)
if lua_type == 'string' then return 'Text'
elseif lua_type == 'table' then
local mt = getmetatable(\%)
if mt and mt.__type then return mt.__type end
return 'Lua table'
else return lua_type end"
parse [% is a %type, % is an %type] as ((type of %) == %type)
parse [% isn't a %type, % isn't an %type, % is not a %type, % is not an %type]
..as ((type of %) == %type)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -3,7 +3,7 @@
This file contains the implementation of an Object-Oriented programming system.
test:
object "Dog":
object (Dog):
(Dog).genus = "Canus"
my action [set up]: %me.barks or= 0
my action [bark, woof]:
@ -12,8 +12,10 @@ test:
my action [get pissed off]: %me.barks += 1
%d = (new Dog {barks:2})
assume (%d.barks == 2)
%d = (Dog {barks:2})
assume (type of %d) == "Dog"
assume (%d is a "Dog")
assume %d.barks == 2
assume ((%d::bark) == "Bark! Bark!")
assume ((%d::woof) == "Bark! Bark!")
%d::get pissed off
@ -23,24 +25,24 @@ test:
assume ("\(%d.class)" == "Dog")
assume (%d.genus == "Canus")
assume (%d.barks == 3)
%d2 = (new Dog)
%d2 = (Dog {})
assume (%d2.barks == 0) or barf "Default initializer failed"
with {%d:new Dog {barks:1}}:
with {%d:Dog {barks:1}}:
assume ((%d::bark) == "Bark!")
object "Corgi" extends (Dog):
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)
%corg = (Corgi {})
assume (%corg.barks == 0)
with {%d:new Corgi {barks:1}}:
with {%d:Corgi {barks:1}}:
assume ((%d::sploot) == "splooted") or barf "subclass method failed"
assume ((%d::bark) == "Yip!") or barf "inheritance failed"
assume ((%d::woof) == "Yip!")
with {%d:new Dog {barks:2}}:
with {%d:Dog {barks:2}}:
assume ((%d::bark) == "Bark! Bark!")
compile [my action %actions %body] to:
lua> "\
@ -69,12 +71,18 @@ compile [my action %actions %body] to:
return lua"
compile [object %classname extends %parent %class_body] to:
unless (%classname.type == "Action"):
compile error at %classname "Expected this to be an action, not a \(%classname.type)"
for % in %classname:
unless (% is text):
compile error at % "Class names should not have arguments."
return (..)
Lua "\
..do
local class = {name=\(%classname as lua expr)}
local class = {name=\(quote %classname.stub)}
setmetatable(class, {
__index=\(%parent as lua expr),
__type=class.name,
__tostring=function(cls) return cls.name end,
__call=function(cls, inst)
inst = setmetatable(inst or {}, cls)
@ -84,24 +92,21 @@ compile [object %classname extends %parent %class_body] to:
return inst
end,
})
nomsu.environment[("new "..class.name):as_lua_id()] = class
nomsu.environment[("new "..class.name.." 1"):as_lua_id()] = class
nomsu.environment[(class.name.." 1"):as_lua_id()] = class
nomsu.environment[class.name:as_lua_id()] = 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)
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",
["<= 1"]="__le", ["set 1 = 2"]="__newindex", ["size"]="__len",
["iterate"]="__ipairs", ["iterate all"]="__pairs",
}
for stub,metamethod in pairs(metamethod_map) do
class[metamethod] = class[stub:as_lua_id()]
@ -111,8 +116,3 @@ compile [object %classname extends %parent %class_body] to:
parse [object %classname %class_body] as (..)
object %classname extends (nil) %class_body
parse [%obj is a %class] as (..)
all of [..]
(type of %obj) == "table"
%obj's metatable
(%obj's metatable).__index == %class

88
nomnom/ast.nom Normal file
View File

@ -0,0 +1,88 @@
use "lib/object.nom"
#%types = [..]
"Number", "Var", "Block", "EscapedNomsu", "Text", "List", "Dict", "DictEntry",
"IndexChain", "Action", "FileChunks", "Error", "Comment"
object (Syntax Tree):
my action [set up]:
if (%me.type == "Action"):
%stub_bits = []
%argnum = 1
for %bit in %me:
if:
(%bit is text): %stub_bits::add %bit
(%bit.type != "Comment"):
%stub_bits::add "\%argnum"
%argnum += 1
%me.stub = (%stub_bits::joined with " ")
(Syntax Tree).source_code_for_tree = (..)
{} with fallback % -> (read file %.source.filename)
my action [children]:
%children = []
for % in %me:
if ((% is a "Syntax Tree") and (%.type != "Comment")):
%children::add %
if ((%me.type == "Action") and %me.target):
%children::add %me.target
return %children
my action [as lua] (..)
"Syntax_Tree(\(call ({}'s metatable).as_lua with [%me]))"
my action [as nomsu] (..)
"(Syntax Tree \(call ({}'s metatable).as_nomsu with [%me]))"
my action [as text] (..)
"(Syntax Tree \(call ({}'s metatable).__tostring with [%me]))"
my action [get source code] (..)
(Syntax Tree).source_code_for_tree.%me
my action [map %fn]:
%replacement = (call %fn with [%me])
if %replacement:
if (%replacement is a "Syntax Tree"):
%replacement = (%k = %v for %k = %v in %replacement)
%replacement.source = %me.source
return (Syntax Tree %replacement)
return %replacement
..else:
%replacement = {}
%changes = (no)
for %k = %v in %me:
%replacement.%k = %v
if (%v is a "Syntax Tree"):
%r = (%v::map %fn)
if ((%r == %v) or (%r == (nil))):
do next %k
%changes = (yes)
%replacement.%k = %r
unless %changes: return %me
return (Syntax Tree %replacement)
my action [== %other]:
unless (..)
all of [..]
(type of %me) == (type of %other)
(%me's metatable) == (%other's metatable)
(size of %me) == (size of %other)
%me.type == %other.type
..: return (no)
for %item in %me at %i:
if (%other.%i != %item): return (no)
if (%me.type == "Action"):
if (%me.target != %other.target): return (no)
return (yes)
my action [get args]:
assume (%me.type == "Action") or barf "Only actions have arguments, not \(%me.type)"
%args = []
for % in %me:
unless ((% is text) or (%.type == "Comment")):
%args::add %
return %args

180
nomnom/code_obj.nom Normal file
View File

@ -0,0 +1,180 @@
# This file contains objects that are used to track code positions and incrementally
build up generated code, while keeping track of where it came from, and managing
indentation levels.
use "lib/object.nom"
object (Code):
my action [set up]:
%old_bits = %me.bits
%me.bits = []
if (%me.source is text):
%me.source = (Source from text %me.source)
for % in %old_bits:
%me::add %
my action [as text]:
if (%me.__str == (nil)):
set {%buff:[], %indent:0}
for % in %me.bits:
if (% is text):
%spaces = (%::matching "\n([ ]*)[^\n]*$")
if %spaces.1: %indent = (size of %spaces)
..else:
% = "\%"
if (%indent > 0):
% = (%::with "\n" -> "\n\(" "::* %indent)")
%buff::add %
%me.__str = (%buff::joined)
return %me.__str
my action [as lua] (..)
"\(%me.class.name::as lua id)_1_2(\(%me.source::as lua), \(%me.bits::as lua))"
my action [as nomsu] (..)
"(\(%me.class.name) \(%me.source::as nomsu) \(%me.bits::as nomsu))"
my action [size] (size of "\%me")
my action [mark as dirty]:
%me.__str = (nil)
%me._trailing_line_len = (nil)
%me._num_lines = (nil)
my action [add %new_bits]:
unless (%new_bits is a "List"):
%new_bits = [%new_bits]
for % in %new_bits:
if (% == ""): do next %
if ((% isn't text) and (% isn't a (Code))):
% = (%::as lua)
%me.bits::add %
%me::mark as dirty
my action [trailing line length]:
if (%me._trailing_line_len == (nil)):
%me._trailing_line_len = (size of ("\%me"::matching "[^\n]*$"))
return %me._trailing_line_len
my action [number of lines]:
unless %me._num_lines:
%num_lines = 1
for % in %me:
if (% is text):
%num_lines += (size of (%::all matches of "\n"))
..else:
%num_lines += ((%::number of lines) - 1)
%me._num_lines = %num_lines
return %me._num_lines
my action [is multiline, is multi-line] ((%me::number of lines) > 1)
my action [is one line, is single line] ((%me::number of lines) == 1)
my action [add %values joined with %joiner]:
%me::add %values joined with %joiner or %joiner
my action [add %values joined with %joiner or %wrapping_joiner]:
%line_len = 0
%bits = %me.bits
for %i = % in %values:
if (%i > 1):
if (%line_len > 80):
%bits::add %wrapping_joiner
%line_len = 0
..else:
%bits::add %joiner
%bits::add %
%line = ("\%"::matching "\n([^\n]*)$")
if %line:
%line_len = (size of %line)
..else:
%line_len += (size of %)
%me::mark as dirty
my action [prepend %]:
if ((% isn't text) and (% isn't a %me.__type)):
% = (%::as lua)
%me.bits::add % at index 1
%me::mark as dirty
my action [parenthesize]:
%me.bits::add "(" at index 1
%me.bits::add ")"
%me::mark as dirty
object (Lua Code) extends (Code):
my action [add free vars %vars]:
if ((size of %vars) == 0): return
%seen = (%v = (yes) for %v in %me.free_vars)
for %var in %vars:
assume (%var is text)
unless %seen.%var:
%me.free_vars::add %var
%seen.%var = (yes)
%me::mark as dirty
my action [remove free vars %vars]:
if ((size of %vars) == 0): return
%removals = {}
for %var in %vars:
assume (%var is text)
%removals.%var = (yes)
%stack = [%me]
while ((size of %stack) > 0):
%lua = (%stack::pop)
for %i in (size of %lua.free_vars) to 1 by -1:
if %removals.(%lua.%free_vars.%i):
%lua.free_vars::remove index %i
for % in %lua.bits:
if (% is a "Lua Code"):
%stack::add %
%me::mark as dirty
my action [declare locals]:
set {%to_declare:[], %seen:{}}
for %lua in recursive %me:
for %var in %lua.free_vars:
unless %seen.%var:
%seen.%var = (yes)
%to_declare::add %var
for % in %lua.bits:
if (% is a "Lua Code"):
recurse %lua on %
return (%me::declare locals %to_declare)
my action [declare locals %to_declare]:
if ((size of %to_declare) > 0):
%me::remove free vars %to_declare
%me::prepend "local \(%to_declare::joined with ", ");\n"
return %to_declare
my action [as statements] (%me::as statements "" ";")
my action [as statements %prefix] (%me::as statements %prefix ";")
my action [as statements %prefix %suffix]:
unless %me.is_value:
return %me
%statements = (Lua Code %me.source [])
if (%prefix != ""):
%statements::add %prefix
%statements::add %me
if (%suffix != ""):
%statements::add %suffix
return %statements
action [Lua Code from %tree] (..)
Lua Code {source:%tree.source, bits:[], is_value:(no), free_vars:[]}
action [Lua Code from %tree %bits] (..)
Lua Code {source:%tree.source, bits:%bits, is_value:(no), free_vars:[]}
action [Lua Value from %tree] (..)
Lua Code {source:%tree.source, bits:[], is_value:(yes), free_vars:[]}
action [Lua Value from %tree %bits] (..)
Lua Code {source:%tree.source, bits:%bits, is_value:(yes), free_vars:[]}
object (Nomsu Code) extends (Code):
action [Nomsu Code from %tree] (..)
Nomsu Code {source:%tree.source, bits:[]}
action [Nomsu Code from %tree %bits] (..)
Nomsu Code {source:%tree.source, bits:%bits}

207
nomnom/compile.nom Normal file
View File

@ -0,0 +1,207 @@
# This file contains the code to convert syntax trees to Lua code
use "nomnom/code_obj.nom"
action [compile %tree using %compile_actions]:
assume (%tree is a "Syntax Tree")
if (..)
all of [..]
%tree.version, action (Nomsu version)
%tree.version != (Nomsu version)
action (1 upgraded from 2 to 3)
..: %tree = (upgrade %tree from %tree.version to (Nomsu version))
if %tree.type is:
"Action":
%stub = %tree.stub
%compile_action = %compile_actions.%stub
if %compile_action:
%args = [%tree, %compile_actions]
for % in (%tree::get args): %args::add %
%result = (call %compile_action with %args)
if (%result == (nil)):
compile error at %tree.source "\
..The compile-time action here (\(%tree.stub)) failed to return any value."
..hint "\
..Look at the implementation of (\(%tree.stub)) and make sure it's returning something."
if (%result is a "Syntax Tree"):
if (%result == %tree):
compile error at %tree.source "\
..The compile-time action here (\(%tree.stub)) is producing an endless loop."
..hint "\
..Look at the implementation of (\(%tree.stub)) and make sure it's not just returning the original tree."
return (compile %result using %compile_actions)
return %result
%lua = (new Lua Value from %tree)
if %tree.target: # Method call
%target_lua = (compile %tree.target using %compile_actions)
if (("\%target_lua"::matches "^%(.*%)$") or ("\%target_lua"::matches "^[_a-zA-Z][_a-zA-Z0-9]*$")):
%lua::add [%target_lua, ":"]
..else:
%lua::add ["(", %target_lua, "):"]
%lua:add [%stub as lua id, "("]
%args = []
for %tok in %tree:
if (%tok is text): do next %tok
# TODO: maybe translate Lua comments
if (%tok.type == "Comment"): do next %tok
if (%tok.type == "Block"):
%values = (..)
(compile %line using %compile_actions) for %line in %tok
..unless (%line.type == "Comment")
if (all of (%.is_value for % in %values)):
if ((size of %values) == 1):
%arg_lua = %values.1
..else:
%arg_lua = (new Lua Value from %tok ["("])
%arg_lua::add %values joined with " and nil or "
%arg_lua::add ")"
..else:
%arg_lua = (new Lua Value from %tok ["((function()"])
for %v in %values at %i:
if %v.is_value:
%v = (%v::as statements ("return " if (%i == (size of %values) else "")))
%arg_lua::add ["\n ", %v]
%arg_lua::add "\nend)())"
..else:
%arg_lua = (compile %tok using %compile_actions)
unless %arg_lua.is_value:
if (%tok.type == "Action"):
compile error at %tok "\
..Can't use this as an argument to (\%stub), since it's not \
..an expression, it produces: \%arg_lua"
..hint "\
..Check the implementation of (\(%tok.stub)) to see if it \
..is actually meant to produce an expression."
..else:
compile error at %tok "\
..Can't use this as an argument to (\%stub), since it's \
..not an expression, it produces: \%arg_lua"
%args::add %arg_lua
%lua::add %args joined with ", "
%lua::add ")"
return %lua
"EscapedNomsu":
return (new Lua Value from %tree ((%tree.1)::as nomsu))
"Block":
%lua = (new Lua Code from %tree)
%lua::add (..)
((compile %line using %compile_actions)::as statements)
..for %line in %tree
..joined with "\n"
return %lua
"Text":
%lua = (new Lua Code from %tree)
%lua_bits = []
%string_buffer = ""
for % in %tree:
if (% is text):
%string_buffer = "\%string_buffer\%"
do next %
if (%string_buffer != ""):
%lua_bits::add (%string_buffer::as lua)
%string_buffer = ""
%bit_lua = (compile %bit using %compile_actions)
unless %bit_lua.is_value:
compile error at %bit "\
..Can't use this as a string interpolation value, since it doesn't have a value."
if (%bit.type != "Text"):
%bit_lua = (new Lua Value from %bit ["tostring(",%bit_lua,")"])
%lua_bits::add %bit_lua
if ((%string_buffer != "") or ((size of %lua_bits) == 0)):
%lua_bits::add (%string_buffer::as lua)
%lua::add %lua_bits joined with ("..")
if ((size of %lua_bits) > 1):
%lua::parenthesize
return %lua
"List":
%lua = (new Lua Value from %tree ["_List{"])
%lua::add ((compile % using %compile_actions) for % in %tree) joined with ", " or ",\n "
%lua::add "}"
return %lua
"Dict":
%lua = (new Lua Value from %tree ["_Dict{"])
%lua::add ((compile % using %compile_actions) for % in %tree) joined with ", " or ",\n "
%lua::add "}"
return %lua
"DictEntry":
set {%key:%tree.1, %value:%tree.2}
%key_lua = (compile %key using %compile_actions)
unless %key_lua.is_value:
compile error at %tree.1 "\
..Can't use this as a dict key, since it's not an expression."
%value_lua = (..)
(compile %value using %compile_actions) if %value
..else (new Lua Value from %key ["true"]))
unless %value_lua.is_value:
compile error at %tree.2 "\
..Can't use this as a dict value, since it's not an expression."
%key_str = ("\%key_lua"::matching "^[\"']([a-zA-Z_][a-zA-Z0-9_]*)['\"]$")
if:
%key_str:
return (new Lua Code from %tree [%key_str, "=", %value_lua])
("\%key_lua".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"]
return (new Lua Code from %tree ["[ ", %key_lua, "]=", %value_lua])
else:
return (new Lua Code from %tree ["[", %key_lua, "]=", %value_lua])
"IndexChain":
%lua = (compile %tree.1 using %compile_actions)
unless %lua.is_value:
compile error at %tree.1 "\
..Can't index into this, since it's not an expression."
%first_char = "\%lua".1
if (any of [%first_char == "{", %first_char == "\"", %first_char == "["]):
%lua::parenthesize
for %i in 2 to (size of %tree):
%key = %tree.%i
%key_lua = (compile %key using %compile_actions)
unless %key_lua.is_value:
compile error at %key "\
..Can't use this as an index, since it's not an expression."
%key_lua_str = "\%key_lua"
%lua_id = (%key_lua_str::matching "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$")
if:
%lua_id:
%lua::add [".", %lua_id]
(%key_lua_str.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"]
%lua::add ["[ ", %key_lua, " ]"]
else:
%lua::add ["[", %key_lua, "]"]
return %lua
"Number":
return (new Lua Value from %tree ["\(%tree.1)"])
"Var":
return (new Lua Value from %tree [%tree.1::as lua id])
"FileChunks":
barf "\
..Can't convert FileChunks to a single block of lua, since each chunk's \
..compilation depends on the earlier chunks"
"Comment":
# TODO: implement?
return (new Lua Code from %tree)
"Error":
barf "Can't compile errors"
else:
barf "Unknown type: \(%tree.type)"

339
nomnom/decompile.nom Normal file
View File

@ -0,0 +1,339 @@
# This file contains the code to convert syntax trees to Nomsu code
use "nomnom/code_obj.nom"
use "nomnom/parser.nom"
# TODO: maybe re-implement the fancy coroutine checker that aborts early if nomsu gets too long
action [decompile %tree inline]:
assume (%tree is a "Syntax Tree")
if %tree.type is:
"Action":
%nomsu = (Nomsu Code from %tree)
if %tree.target:
%target_nomsu = (decompile %tree.target inline)
if %tree.target.type is:
("Action", "Block"):
%target_nomsu::parenthesize
%nomsu::add [%target_nomsu, "::"]
for %bit in %tree at %i:
if (%bit is text):
unless (..)
any of [..]
%i == 1,
%tree.(%i - 1) isn't text,
(%bit::is a nomsu operator) == (%tree.(%i - 1)::is a nomsu operator)
..: %nomsu::add " "
%nomsu::add %bit
..else:
%arg_nomsu = (decompile %bit inline)
unless ((%i == (size of %tree)) and (%bit.type == "Block")):
%nomsu::add " "
if ((%bit.type == "Action") or (%bit.type == "Block")):
%arg_nomsu::parenthesize
%nomsu::add %arg_nomsu
return %nomsu
"EscapedNomsu":
%inner_nomsu = (decompile %tree.1 inline)
unless (..)
any of [..]
%tree.1.type == "List", %tree.1.type == "Dict",
%tree.1.type == "Var"
..: %inner_nomsu::parenthesize
%nomsu = (Nomsu Code from %tree ["\\", %inner_nomsu])
return %nomsu
"Block":
%nomsu = (Nomsu Code from %tree [":"])
for i,line in ipairs tree
%nomsu::add(i == 1 and " " or "; ")
%nomsu::add recurse(line, nomsu, i == 1 or i < #tree)
return %nomsu
"Text":
%nomsu = (Nomsu Code from %tree ["\""])
for %text in recursive %tree:
for %bit in %text at %i:
if:
(%bit is text):
%nomsu::add %bit
if %bit.type is:
"Text":
recurse %text on %bit
"Var":
%interp_nomsu = (decompile %bit inline)
# Make sure "...\(%x)y..." isn't confused with "...\(%xy)..."
# TODO: make this more robust against "...\%x\("y").."
if (..)
(%tree.(%i + 1) is text) and (..)
not (%tree.(%i + 1)::matches "^[ \n\t,.:;#(){}%[%]]")
..: %interp_nomsu::parenthesize
%nomsu::add ["\\", %interp_nomsu]
("List", "Dict"):
%nomsu::add ["\\", decompile %bit inline]
else:
%nomsu::add ["\\(", decompile %bit inline, ")"]
return (Nomsu Code from %tree ["\"", %nomsu, "\""])
("List", "Dict"):
%nomsu = (Nomsu Code from %tree ["[" if (%tree.type == "List") else "{"])
for %item in %tree at %i:
if (%i > 1): %nomsu::add ", "
%nomsu::add (decompile %item inline)
%nomsu::add ("]" if (%tree.type == "List") else "}")
return %nomsu
"DictEntry":
set {%key:%tree.1, %value:%tree.2}
if (all of [%key.type == "Text", (size of %key) == 1, %key.1::is a nomsu identifier]):
%nomsu = (Nomsu Code from %key [key.1])
..else:
%nomsu = (decompile %key inline)
if (%key.type == "Action"):
%nomsu::parenthesize
%nomsu::add ":"
if %value:
%nomsu::add (decompile %value inline)
return %nomsu
"IndexChain":
%nomsu = (Nomsu Code from %tree)
for %bit in %tree at %i:
if (%i > 1): nomsu::add "."
if (..)
all of [..]
%i > 1, %bit.type == "Text", (size of %bit) == 1, %bit.1 is text,
%bit.1::is a nomsu identifier
%nomsu::add %bit.1
..else:
%bit_nomsu = (decompile %bit inline)
if (..)
any of [..]
%bit.type == "Action"
%bit.type == "Block"
%bit.type == "IndexChain"
(%bit.type == "Number") and (%i < (size of %tree))
..: %bit_nomsu::parenthesize
%nomsu::add %bit_nomsu
return %nomsu
"Number":
return (Nomsu Code from %tree [(%tree.1 as hex) if %tree.hex else "\(%tree.1)"])
"Var":
return (Nomsu Code from %tree ["%\(%tree.1)"])
"Comment":
return (nil)
"FileChunks":
barf "Can't inline a FileChunks"
"Error":
barf "Can't compile errors"
else:
barf "Unknown type: \(%tree.type)"
%MAX_LINE = 90
action [decompile %tree]:
%nomsu = (Nomsu Code from %tree)
# For concision:
local action [recurse on %t]:
%space = (%MAX_LINE - (%nomsu::trailing line length))
if (%space <= 0): go to (Indented)
for %subtree in recursive %tree:
if %subtree.type is:
"Block":
if ((size of %subtree) > 1):
go to (Use Indented)
if ((size of "\(decompile %subtree inline)") > 20):
go to (Use Indented)
for %k = %v in %subtree:
if (%v is a "Syntax Tree"):
recurse %subtree on %v
%inline_nomsu = (decompile %t inline)
if (%inline_nomsu and ((size of "\%inline_nomsu") <= %space)):
return %inline_nomsu
=== (Use Indented) ===
%indented = (decompile %t)
if (%t.type == "Action"):
%indented = (Nomsu Code from %t ["(..)\n ", %indented])
return %indented
if %tree.type is:
"FileChunks":
local action [%1 and %2 should clump]:
if ((%1.type == "Action") and (%2.type == "Action")):
if (%1.stub == "use 1"): return (%2.stub == "use 1")
if (%1.stub == "test 1"): return (yes)
if (%2.stub == "test 1"): return (no)
return (not ((recurse on %1)::is multi-line))
for %chunk in %tree at %chunk_no:
if (%chunk_no > 1):
%nomsu::add "\n\n\("~"::* 80)\n\n"
%nomsu::add (pop comments at %chunk.source.start)
if (%chunk.type == "Block"):
for %line in %chunk at %line_no:
if (%line_no > 1):
if (%chunk.(%line_no - 1) and %line should clump):
%nomsu::add ["\n", pop comments at %line.source.start "\n"]
..else:
%nomsu::add ["\n\n", pop comments at %line.source.start]
%nomsu::add (decompile %line %pop_comments)
%nomsu::add (pop comments at %chunk.source.stop "\n")
..else:
%nomsu::add (decompile %chunk %pop_comments)
%nomsu::add (pop comments at %tree.source.stop "\n")
unless ("\%nomsu"::matches "\n$"):
%nomsu::add "\n"
return %nomsu
"Action":
%pos = %tree.source.start
%next_space = ""
if %tree.target:
%target_nomsu = (recurse on %tree.target)
if ((%tree.target.type == "Action") and (%target_nomsu::is one line)):
%target_nomsu::parenthesize
%nomsu::add %target_nomsu
%pos = %tree.target.source.stop
%next_space = ("\n..::" if (%target_nomsu::is multi-line) else "::")
for %bit in %tree at %i:
if ((%next_space == " ") and ((%nomsu::trailing line length) > %MAX_LINE)):
%next_space = " \\\n"
%nomsu::add %next_space
if (%bit is text):
unless (..)
all of [..]
%tree.(%i - 1) is text
(%tree.(%i - 1)::is a nomsu operator) != (%bit::is a nomsu operator)
..: %nomsu::add %next_space
%nomsu::add %bit
%next_space = " "
do next %bit
%bit_nomsu = (recurse on %bit)
if (%bit.type == "Comment"):
%next_space = "\n"
..else:
%next_space = (" " if (%bit_nomsu::is one line) else "\n..")
if (%bit.type == "Action"):
%bit_nomsu::parenthesize
return %nomsu
"EscapedNomsu":
%nomsu::add "\\"
%val_nomsu = (recurse on %tree.1)
if ((%tree.(1).type == "Action") and (%val_nomsu::is one line)):
%val_nomsu::parenthesize
%nomsu::add %val_nomsu
return %nomsu
"Block":
for %line in %tree at %i:
if ((%i > 1) and (%line.type == "Comment")):
%nomsu::add "\n"
%line_nomsu = (recurse on %line)
%nomsu::add
if (%i < (size of %tree)):
if ((%line_nomsu::number of lines) > 2):
%nomsu::add "\n\n"
..else:
%nomsu::add "\n"
return (Nomsu Code from %tree [":\n ", %nomsu])
"Text":
# Multi-line text has more generous wrap margins
%max_line = ((1.5 * %MAX_LINE) rounded down)
%nomsu = (Nomsu Code from %tree)
local action [add text from %tree]:
for %bit in %tree at %i:
if (%bit is text):
# TODO: escape properly?
%bit = (escape text %bit)
for %line in (%bit::lines) at %j:
if:
(%j > 1): nomsu::add "\n"
(((size of %line) > 10) and ((%nomsu::trailing line length) > %max_line)):
%nomsu::add "\\\n.."
while ((size of %line) > 0):
%space = (%max_line - (%nomsu::trailing line length))
%split = (%line::position of "[%p%s]" after %space)
if ((not %split) or (%split > %space + 10)):
%split = (%space + 10)
if ((%line - %split) < 10):
%split = (size of %line)
set {%bite:%line.[1, %split], %line:%line.[%split + 1, -1]}
%nomsu::add %bite
if ((size of %line) > 0):
%nomsu::add "\\\n.."
if (%bit.type == "Text"):
add text from %bit
..else:
%nomsu::add "\\"
%interp_nomsu = (recurse on %bit)
unless (%interp_nomsu::is multi-line):
if %bit.type is:
"Var":
if ((%tree.(%i+1) is text) and (not (%tree.(%i+1)::matches "^[ \n\t,.:#(){}[%]]"))):
%interp_nomsu::parenthesize
("List", "Dict"):
%interp_nomsu::parenthesize
%nomsu::add %interp_nomsu
if (%interp_nomsu::is multi-line):
%nomsu::add "\n.."
add text from %tree
return (Nomsu Code from %tree ["\"\\\n ..", %nomsu, "\""])
("List", "Dict"):
if ((size of %tree) == 0):
%nomsu::add ("[]" if (%tree.type == "List") else "{}")
return %nomsu
for %item in %tree at %i:
%item_nomsu = (decompile %item inline)
if ((not %item_nomsu) or ((size of "\%item_nomsu") > %MAX_LINE)):
%item_nomsu = (recurse on %item_nomsu)
%nomsu::add %item_nomsu
if (%i < (size of %tree)):
if (..)
(%item_nomsu::is multi-line) or (..)
(%nomsu::trailing line length) + (size of "\%item_nomsu")) >= %MAX_LINE
..: %nomsu::add "\n"
..else: %nomsu::add ", "
return (..)
Nomsu Code from %tree [..]
"[..]\n " if tree.type == "List" else "{..}\n "
%nomsu
"DictEntry":
set {%key:%tree.1, %value:%tree.2}
if (all of [%key.type == "Text", (size of %key) == 1, %key.1::is a nomsu identifier]):
%nomsu::add %key.1
..else:
%nomsu::add (decompile %key inline)
if ((%key.type == "Action") or (%key.type == "Block")):
%nomsu::parenthesize
%nomsu::add [": ", recurse on %value]
return %nomsu
"Comment":
%nomsu::add ["#", %tree.1::with "\n" -> "\n "]
return %nomsu
("IndexChain", "Number", "Var"):
return (decompile %tree inline)
"Error":
barf "Cannot decompile an error"
else:
barf "Unknown type: \(%tree.type)"

123
nomnom/files.nom Normal file
View File

@ -0,0 +1,123 @@
# Some file utilities for searching for files recursively and using package.nomsupath
use "lib/os.nom"
%_SPOOFED_FILES = {}
%_FILE_CACHE = ({} with fallback %_SPOOFED_FILES)
%_BROWSE_CACHE = {}
# Create a fake file and put it in the cache
action [spoof file %filename %contents]:
%_SPOOFED_FILES.%filename = %contents
return %contents
# Read a file's contents
action [read file %filename]:
%contents = %_FILE_CACHE.%filename
if %contents: return %contents
if (%filename == "stdin"):
return (spoof file "stdin" (=lua "io.read('*a')"))
%file = (=lua "io.open(\%filename)")
unless %file: return (nil)
%contents = (call %file.read with [%file, "*a"])
%file::close
%_FILE_CACHE.%filename = %contents
return %contents
action [%path sanitized]:
%path = (%path::with "\\" -> "\\\\")
%path = (%path::with "`" -> "")
%path = (%path::with "\"" -> "\\\"")
%path = (%path::with "$" -> "")
%path = (%path::with "%.%." -> "\\..")
return %path
try:
%lfs = (=lua "require('lfs')")
..and if it succeeds:
local action [filesystem has %filename]:
%mode = (call %lfs.attributes with [%filename, "mode"])
if %mode is:
("file", "directory", "link", "char device"):
return (yes)
else: return (no)
action [file %path exists]:
if (..)
any of [..]
%_SPOOFED_FILES.%path
%path == "stdin"
filesystem has %path
..: return (yes)
for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
if (filesystem has "\%nomsupath/\%path"):
return (yes)
return (no)
action [files in %path]:
unless %_BROWSE_CACHE.%path:
if (%_SPOOFED_FILES.%path or (%filename == "stdin")):
%_BROWSE_CACHE.%path = [%path]
..else:
if (call %lfs.attributes with [%filename, "mode"]) is:
("file", "char device"):
%_BROWSE_CACHE.%path = [%filename]
("directory", "link"):
for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
%files = []
for %member in (call %lfs.dir with ["\%nomsupath/\%filename"]):
if ((%member == ".") or (%member == "..")):
do next %member
for % in (files in %member): %files::add %
if ((size of %files) > 0):
%_BROWSE_CACHE.%path = %files
go to (Found Files)
%_BROWSE_CACHE.%path = []
else:
%_BROWSE_CACHE.%path = []
=== (Found Files) ===
return %_BROWSE_CACHE.%filename
..or if it barfs:
# LFS not found! Fall back to shell commands, if available.
unless (sh> "find . -maxdepth 0"):
url = if jit
'https://github.com/spacewander/luafilesystem'
else
barf "\
..Could not find 'luafilesystem' module and couldn't run system command 'find' \
..(this might happen on Windows). Please install 'luafilesystem' (which can be \
..found at \(..)
"https://github.com/spacewander/luafilesystem"
..if %jit else "https://github.com/keplerproject/luafilesystem"
.. or obtained through `luarocks install luafilesystem`)"
action [file %path exists]:
if (..)
any of [..]
%_SPOOFED_FILES.%path
%path == "stdin"
sh> "ls \(%path sanitized)"
..: return (yes)
for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
if (sh> "ls \(%nomsupath)/\(%path)"):
return (yes)
return (no)
action [files in %path]:
unless %_BROWSE_CACHE.%path:
if %_SPOOFED_FILES.%path:
%_BROWSE_CACHE.%path = [%_SPOOFED_FILES.%path]
..else:
for %nomsupath in (%package.nomsupath::all matches of "[^;]+"):
%files = (sh> "find -L '\%path' -not -path '*/\\.*' -type f'")
if %files:
%_BROWSE_CACHE.%path = (%files::lines)
go to (Found Files)
%_BROWSE_CACHE.%path = []
=== (Found Files) ===
return %_BROWSE_CACHE.%path

81
nomnom/parser.nom Normal file
View File

@ -0,0 +1,81 @@
# This file contains the parser, which converts text into abstract syntax trees
use "nomonom/ast.nom"
%lpeg = (=lua "require('lpeg')")
%re = (=lua "require('re')")
call %lpeg.setmaxstack with [20_000]
set {..}
(action (P 1)): %lpeg.P, (action (R 1)): %lpeg.R, (action (Carg 1)): %lpeg.Carg,
(action (Cc 1)): %lpeg.Cc, (action (lpeg re pattern 1)): %re.compile,
(action (lpeg re pattern 1 using 2)): %re.compile
%source_code_for_tree = {}
%defs = (..)
{..}
nl: (P "\r")^(-1) * (P "\n")
tab: P "\t"
tonumber: %tonumber
tochar: %string.char
unpack: %unpack
nil: Cc (nil)
userdata: Carg 1
utf8_char: (..)
(R "\194\223")*(R "\128\191") +
(R "\224\239")*(R "\128\191")*(R "\128\191") +
(R "\240\244")*(R "\128\191")*(R "\128\191")*(R "\128\191")
Tree: [%t, %userdata] ->:
%source = (..)
Source {filename:%userdata.filename, start:%tree.start, stop:%tree.stop}
set {%t.start: nil, %t.stop: nil}
%t = (Syntax Tree %t)
(Syntax Tree).source_code_for_tree.%t = %userdata.source
return %t
..with fallback %key ->:
if:
(%key::matches "^ascii_(%d+)$"):
%i = (%key::matching "^ascii_(%d+)$")
return (call %string.char with [%i as a number])
(%key::matches "^number_(%d+)$"):
%i = (%key::matching "^number_(%d+)$")
return (Cc (%i as a number))
%id_patt = (((P "") - (R "09")) * ((%defs.utf8_char + (R "az") + (R "AZ") + (P "_") + (R "09"))^1 * -1))
%operator_patt = ((S "'`~!@$^&*+=|<>?/-")^1 * -1)
%text_methods = (""'s metatable).__index
%text_methods.(action (is a nomsu identifier)) = (..)
[%str] -> (call %id_patt.match with [%id_patt, %str])
%text_methods.(action (is a nomsu id)) = %text_methods.(action (is a nomsu identifier))
%text_methods.(action (is a nomsu operator)) = (..)
[%str] -> (call %operator_patt.match with [%operator_patt, %str])
%peg_tidier = (..)
lpeg re pattern "\
file <- %nl* {~ (def/comment) (%nl+ (def/comment))* %nl* ~}
def <- anon_def / captured_def
anon_def <-
({ident} (" "*) ":" {[^%nl]* (%nl+ " "+ [^%nl]*)*})
-> "%1 <- %2"
captured_def <-
({ident} (" "*) "(" {ident} ")" (" "*) ":" {[^%nl]* (%nl+ " "+ [^%nl]*)*})
-> "%1 <- ({| {:start:{}:} %3 {:stop:{}:} {:type: (''->'%2') :} |} %%userdata) -> Tree"
ident <- [a-zA-Z_][a-zA-Z0-9_]*
comment <- "--" [^%nl]*
"
action [make parser from %peg] (make parser from %peg using (nil))
action [make parser from %peg using %make_tree]:
%peg = (call %peg_tidier.match with [%peg_tidier, %peg])
%peg = (lpeg re pattern %peg using %defs)
local action [parse %input from %filename]:
%input = "\%input"
%tree_mt = {__index: {source:%input, filename:%filename}}
%userdata = {..}
make_tree: %make_tree or ([%]-> (: set %'s metatable to %tree_mt; return %))
filename:%filename, source:%input
%tree = (call %peg.match with [%peg, %input, (nil), %userdata])
assume %tree or barf "File \%filename failed to parse:\n\%input"
return %tree
return (action (parse 1 from 2))

75
nomnom/pretty_errors.nom Normal file
View File

@ -0,0 +1,75 @@
# This file has code for converting errors to user-friendly format, with colors,
line numbers, code excerpts, and so on.
local action [visible size of %text]:
return (size of (%text::with "\027%[[0-9;]*m" -> ""))
local action [boxed %text]:
%max_line = (..)
max of ((visible size of %line) for %line in (%text::lines))
%ret = (..)
"\n\%text"::with "\n([^\n]*)" as % -> (..)
"\n\%\(" "::* (%max_line - (visible size of %))) \027[0m"
return %ret.[2,-1]
action [%err as a pretty error]:
%context = 2
%err_code = (%err::get source code)
%err_line = (%err_code::line at %err.source.start)
%err_linenum = (%err_code::line number at %err.source.start)
%err_linepos = (%err_code::line position at %err.source.start)
# TODO: better handle multi-line errors
%err_size = (..)
min of [..]
%err.source.stop - %err.source.start
(size of %err_line) - %err_linepos + 1
%nl_indicator = (" " if (%err_linepos > (size of %err_line)) else "")
%fmt_str = " %\(size of "\(%err_linenum + %context)")d|"
local action [num %i] (%fmt_str::formatted with 0)
%linenum_size = (size of (num 0))
%pointer = "\(" "::* (%err_linepos + %linenum_size - 1))"
if (%err_size >= 2):
%pointer += "\(%pointer)╚\("═"::* (%err_size - 2))╝"
..else:
%pointer += "\(%pointer)⬆"
%err_msg = "\027[33;41;1m\(%err.title or "Error") at \(%err.source.filename or "???"):\(%err_linenum)\027[0m"
for %i in (%err_linenum - %context) to (%err_linenum - 1):
%line = (%err_code::line %i)
if %line:
%err_msg += "\n\027[2m\(num %i)\027[0m\(%line)\027[0m"
if %err_line:
%before = %err_line.[1, %err_linepos - 1]
%during = %err_line.[%err_linepos, %err_linepos + %err_size - 1]
%after = %err_line.[%err_linepos + %err_size, -1]
%err_line = "\027[0m\(%before)\027[41;30m\(%during)\(%nl_indicator)\027[0m\(%after)"
%err_msg += "\n\027[2m\(%fmt_str::formated with %err_linenum)\(%err_line)\027[0m"
%err_linenum_end = (%err_code::line number at %err.source.stop)
%err_linepos_end = (%err_code::line position at %err.source.stop)
%err_linenum_end or= %err_linenum
if (%err_linenum_end == %err_linenum):
%err_msg += "\n\(%pointer)"
..else:
for %i in (%err_linenum + 1) to %err_linenum_end:
%line = (%err_code::line %i)
if %line:
if (%i == %err_linenum_end):
%during = %line.[1, %err_linepos_end - 1]
%after = %line.[%err_linepos_end, -1]
%err_msg += "\n\027[2m\(num %i)\027[0;41;30m\(%during)\027[0m\(%after)"
..else:
%err_msg += "\n\027[2m\(num %i)\027[0;41;30m\(%line)\027[0m"
%box_width = 70
%err_text = "\
..\027[47;31;1m\((" \(%err.error)"::wrapped to %box_width)::with "\n" -> "\n\027[47;31;1m ")"
if %err.hint:
err_text += "\n\027[47;30m\((" Suggestion: \(%err.hint)"::wrapped to %box_width)::with "\n" -> "\n\027[47;30m ")"
%err_msg += "\n\027[33;1m \((%err_text boxed) with "\n" -> "\n ")"
for %i in (%err_linenum_end + 1) to (%err_linenum_end + %context):
%line = (%err_code::line %i)
if %line:
%err_msg += "\n\027[2m\(num %i)\027[0m\(%line)\027[0m"
return %err_msg

49
nomnom/source.nom Normal file
View File

@ -0,0 +1,49 @@
use "lib/object.nom"
object (Source):
action [Source from text %text]:
%match = (%text::matching groups "^@(.-)%[(%d+):(%d+)%]$")
set {%filename:%match.1, %start:%match.2, %stop:%match.3}
unless %filename:
%match = (%text::matching groups "^@(.-)%[(%d+)%]$")
set {%filename:%match.1, %start:%match.2}
return (Source {filename:%filename, start:(%start or 1) as number, stop: %stop as number})
my action [as text] "\
..@\(%me.filename)[\(%me.start)\(":\(%me.stop)" if %me.stop else "")]"
my action [as lua] "\
..Source{filename=\(%me.filename::as lua), start=\(%me.start)\
..\(", stop=\(%me.stop)" if %stop else "")}"
my action [as nomsu] "\
..(Source {filename:\(%me.filename::as nomsu), start:\(%me.start)\
..\(", stop:\(%me.stop)" if %stop else "")})"
my action [== %other] (..)
all of [..]
(%me's metatable) == (%other's metatable)
%me.filename == %other.filename
%me.start == %other.start
%me.stop == %other.stop
my action [< %other]:
assume %me.filename == %other.filename
if (%start == %other.start):
return ((%me.stop or %me.start) < (%other.stop or %other.start))
..else:
return (%me.start < %other.start)
my action [<= %other]:
assume %me.filename == %other.filename
if (%start == %other.start):
return ((%me.stop or %me.start) <= (%other.stop or %other.start))
..else:
return (%me.start <= %other.start)
my action [+ %offset]:
if ((type of %me) == "number"):
set {%me:%offset, %offset:%me}
..else:
assume (type of %offset) == "number"
return (Source {filename:%me.filename, start:%me.start + %offset, stop:%me.stop})

View File

@ -99,7 +99,7 @@ with NomsuCompiler
:next, :unpack, :setmetatable, :coroutine, :rawequal, :getmetatable, :pcall,
:error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall, :module,
:print, :loadfile, :rawset, :_VERSION, :collectgarbage, :rawget, :rawlen,
:table, :assert, :dofile, :loadstring, :type, :select, :math, :io, :load,
:table, :assert, :dofile, :loadstring, lua_type_of_1:type, :select, :math, :io, :load,
:pairs, :ipairs,
-- Nomsu types:
_List:List, _Dict:Dict,

View File

@ -13,8 +13,10 @@ isplit = (sep='%s+')=>
return step, {str:@, pos:1, :sep}, 0
lua_keywords = {
"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if",
"in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"
["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true, ["elseif"]=true, ["end"]=true,
["false"]=true, ["for"]=true, ["function"]=true, ["goto"]=true, ["if"]=true,
["in"]=true, ["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true, ["repeat"]=true,
["return"]=true, ["then"]=true, ["true"]=true, ["until"]=true, ["while"]=true
}
string2 = {
@ -76,24 +78,19 @@ string2 = {
str = gsub str, "%W", (c)->
if c == ' ' then '_'
else format("x%02X", byte(c))
-- Lua IDs can't start with numbers, so map "1" -> "_1", "_1" -> "__1", etc.
str = gsub str, "^_*%d", "_%1"
-- This pattern is guaranteed to match all keywords, but also matches some other stuff.
if match str, "^_*[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$"
for kw in *lua_keywords
if match str, ("^_*"..kw.."$")
unless string2.is_lua_id(str\match("^_*(.*)$"))
str = "_"..str
return str
is_lua_id: (str)->
match(str, "^[_a-zA-Z][_a-zA-Z0-9]*$") and not lua_keywords[str]
-- from_lua_id(as_lua_id(str)) == str, but behavior is unspecified for inputs that
-- did not come from as_lua_id()
from_lua_id: (str)->
-- This pattern is guaranteed to match all keywords, but also matches some other stuff.
if match str, "^_+[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$"
for kw in *lua_keywords
if match str, ("^_+"..kw.."$")
unless string2.is_lua_id("^_+(.*)$")
str = str\sub(2,-1)
str = gsub(str, "^_(_*%d.*)", "%1")
str = gsub(str, "_", " ")
str = gsub(str, "x([0-9A-F][0-9A-F])", (hex)-> char(tonumber(hex, 16)))
str = gsub(str, "^ ([ ]*)$", "%1")