Bunch of changes:

- Added shebangs to generated code output
- SyntaxTree:map() -> SyntaxTree:with(), and corresponding changes to
metaprogramming API
- Added (return Lua 1) shorthand for (return (Lua 1))
- (1 and 2 and 3) compile rule mapping to -> (1 and (*extra arguments*))
- Don't scan for errors, just report them when compiling
- Syntax changes:
    - Added prefix actions (e.g. #$foo)
    - Operator chars now include utf8 chars
    - Ditch "escaped nomsu" type (use (\ 1) compile action instead)
This commit is contained in:
Bruce Hill 2019-02-05 15:45:27 -08:00
parent 0ff3219f35
commit 72d699fe86
20 changed files with 780 additions and 392 deletions

View File

@ -64,8 +64,7 @@ test:
# If $when_true_expr is guaranteed to be truthy, we can use Lua's idiomatic
equivalent of a conditional expression: (cond and if_true or if_false)
if {.Text, .List, .Dict, .Number}.($when_true_expr.type):
return
Lua ("
return Lua ("
(\($condition as lua expr) and \($when_true_expr as lua expr) or \
..\($when_false_expr as lua expr))
")
@ -73,8 +72,7 @@ test:
# Otherwise, need to do an anonymous inline function (yuck, too bad lua
doesn't have a proper ternary operator!)
To see why this is necessary consider: (random()<.5 and false or 99)
return
Lua ("
return Lua ("
((function()
if \($condition as lua expr) then
return \($when_true_expr as lua expr)
@ -117,15 +115,15 @@ test:
# Basic loop control
(stop $var) compiles to:
if $var:
return (Lua "goto stop_\($var as lua identifier)")
return Lua "goto stop_\($var as lua identifier)"
..else:
return (Lua "break")
return Lua "break"
(do next $var) compiles to:
if $var:
return (Lua "goto continue_\($var as lua identifier)")
return Lua "goto continue_\($var as lua identifier)"
..else:
return (Lua "goto continue")
return Lua "goto continue"
(---stop $var ---) compiles to "::stop_\($var as lua identifier)::"
(---next $var ---) compiles to "::continue_\($var as lua identifier)::"
@ -162,18 +160,75 @@ test:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# For-each loop (lua's "ipairs()")
(for $var in $iterable at $i $body) compiles to:
# This uses Lua's approach of only allowing loop-scoped variables in a loop
if (($iterable.type == "Action") and (($iterable, get stub) == "1 to")):
[$start, $stop] = [$iterable.1, $iterable.3]
$loop =
Lua ("
local _start = \($start as lua expr)
for \($var as lua identifier)=_start,\($stop as lua expr) do
\($i as lua identifier) = \($var as lua identifier) - _start + 1
")
if (($iterable.type == "Action") and (($iterable, get stub) == "1 to 2 by")):
[$start, $stop, $step] = [$iterable.1, $iterable.3, $iterable.5]
$loop =
Lua ("
local _start, _step = \($start as lua expr), \($step as lua expr)
for \($var as lua identifier)=_start,\($stop as lua expr),_step do
\($i as lua identifier) = (\($var as lua identifier) - _start)/_step + 1
")
unless $loop:
$loop =
Lua ("
local _iterating = _1_as_list(\($iterable as lua expr))
for \($i as lua identifier)=1,#_iterating do
\($var as lua identifier) = _iterating[\($i as lua identifier)]
")
$lua =
Lua ("
do -- for-loop
\$loop
\;
")
$lua, add ($body as lua)
if ($body has subtree \(do next)):
$lua, add "\n ::continue::"
if ($body has subtree \(do next $var)):
$lua, add "\n " (\(---next $var ---) as lua)
$lua, add "\n end"
if ($body has subtree \(stop $var)):
$lua, add "\n " (\(---stop $var ---) as lua)
$lua, add "\nend -- for-loop"
return $lua
(for $var in $iterable $body) parses as
for $var in $iterable at (=lua "_i") $body
test:
$d = {.a = 10, .b = 20, .c = 30, .d = 40, .e = 50}
$result = []
for $k = $v in $d:
if ($k == "a"):
do next $k
if ($v == 20):
do next $v
$result, add "\$k = \$v"
assume (($result sorted) == ["c = 30", "d = 40", "e = 50"])
# Numeric range for loops
test:
assume ([: for $ in (1 to 5): add $] == [1, 2, 3, 4, 5])
assume ([: for $ in (1 to 5 by 2): add $] == [1, 3, 5])
assume ([: for $ in (5 to 1): add $] == [])
$nums = []
for $x in 1 to 5:
$nums, add $x
assume ($nums == [1, 2, 3, 4, 5])
$nums = []
for $x in 1 to 5 via 2:
$nums, add $x
assume ($nums == [1, 3, 5])
$nums = []
for $outer in 1 to 100:
for $inner in $outer to ($outer + 2):
for $outer in (1 to 100):
for $inner in ($outer to ($outer + 2)):
if ($inner == 2):
$nums, add -2
do next $inner
@ -182,47 +237,25 @@ test:
stop $outer
assume ($nums == [1, -2, 3, -2, 3, 4, 3, 4, 5])
# Numeric range for loops
# These are shims, and should be phased out:
[
for $var in $start to $stop by $step $body
for $var in $start to $stop via $step $body
] all compile to:
# This uses Lua's approach of only allowing loop-scoped variables in a loop
$lua =
Lua ("
for \($var as lua identifier)=\($start as lua expr),\($stop as lua expr),\
..\($step as lua expr) do
")
$lua, add "\n " ($body as lua)
if ($body has subtree \(do next)):
$lua, add "\n ::continue::"
if ($body has subtree \(do next $var)):
$lua, add "\n " (\(---next $var ---) as lua)
$lua, add "\nend -- numeric for " ($var as lua identifier) " loop"
if ($body has subtree \(stop $var)):
$lua =
Lua ("
do -- scope for (stop \($var as lua identifier))
\$lua
\(\(---stop $var ---) as lua)
end -- scope for (stop \($var as lua identifier))
")
return $lua
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
] all parse as (for $var in ($start to $stop by $step) $body)
(for $var in $start to $stop $body) parses as
for $var in $start to $stop via 1 $body
for $var in ($start to $stop) $body
# repeat $n times is a shorthand:
test:
$x = 0
repeat 5 times:
$x += 1
assume $x == 5
(repeat $n times $body) parses as (for (=lua "_XXX_") in 1 to $n $body)
(repeat $n times $body) parses as (for (=lua "_XXX_") in (1 to $n by 1) $body)
# Dict iteration (lua's "pairs()")
test:
$a = [10, 20, 30, 40, 50]
$b = []
@ -239,48 +272,9 @@ test:
$b, add $x
assume ($b == [20, 30, 40])
# Small memory footprint:
assume (1 to (1 << 31))
# For-each loop (lua's "ipairs()")
(for $var in $iterable at $i $body) compiles 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
\;
")
$lua, add ($body as lua)
if ($body has subtree \(do next)):
$lua, add "\n ::continue::"
if ($body has subtree \(do next $var)):
$lua, add "\n " (\(---next $var ---) as lua)
$lua, add "\nend --for \($var as lua identifier) loop"
if ($body has subtree \(stop $var)):
$inner_lua = $lua
$lua = (Lua "do -- scope for stopping for-loop\n ")
$lua, add $inner_lua "\n "
$lua, add (\(---stop $var ---) as lua)
$lua, add "\nend -- end of scope for stopping for-loop"
return $lua
(for $var in $iterable $body) parses as
for $var in $iterable at (=lua "__") $body
test:
$d = {.a = 10, .b = 20, .c = 30, .d = 40, .e = 50}
$result = []
for $k = $v in $d:
if ($k == "a"):
do next $k
if ($v == 20):
do next $v
$result, add "\$k = \$v"
assume (($result sorted) == ["c = 30", "d = 40", "e = 50"])
# Dict iteration (lua's "pairs()")
[for $key = $value in $iterable $body, for $key $value in $iterable $body]
..all compile to:
$lua =
@ -453,8 +447,7 @@ test:
")
$code, add "\nend --when"
return
Lua ("
return Lua ("
do --if $ is...
local \(mangle "branch value") = \($branch_value as lua expr)
\$code

View File

@ -18,9 +18,8 @@ use "core/control_flow"
if ($condition.type == "Action"):
when $condition.stub is:
"1 ==":
return
LuaCode ("
do
return Lua ("
do -- Assumption:
local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
if _a ~= _b then
_a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
@ -30,10 +29,10 @@ use "core/control_flow"
end
end
")
"1 !=":
return
LuaCode ("
do
return Lua ("
do -- Assumption:
local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
if _a == _b then
_a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
@ -42,32 +41,48 @@ use "core/control_flow"
end
end
")
"1 >" "1 <" "1 >=" "1 <=":
return
LuaCode ("
do
return Lua ("
do -- Assumption:
local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
if _a ~= _b then
_a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
_b = type_of(_b) == 'Text' and _b:as_lua() or _1_as_text(_b)
at_1_fail(\(quote "\($condition.1.source)"),
"Assumption failed: This value was ".._a..", but it was expected to be \($condition.3)".._b..".")
"Assumption failed: This value was ".._a..", but it was expected to be \
..\($condition.3)".._b..".")
end
end
")
"1 is":
return
LuaCode ("
do
local _ta, _tb = type_of(\($condition.1 as lua expr)), \($condition.3 as lua expr)
if _ta ~= _tb then
return Lua ("
do -- Assumption:
local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
if not _1_is(_a, _b) then
_a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
at_1_fail(\(quote "\($condition.1.source)"),
"Assumption failed: This value was ".._ta.." but it was expected to be ".._tb..".")
"Assumption failed: This value (".._a..") was expected to be "..\
.._b..", but wasn't.")
end
end
")
return
LuaCode ("
"1 isn ' t" "1 is not":
return Lua ("
do -- Assumption:
local _a, _b = \($condition.1 as lua expr), \($condition.(#$condition) as lua expr)
if _1_is(_a, _b) then
_a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
at_1_fail(\(quote "\($condition.1.source)"),
"Assumption failed: This value (".._a..") was expected to not be \
..".._b..", but it was.")
end
end
")
return Lua ("
if not \($condition as lua expr) then
at_1_fail(\(quote "\($condition.source)"), "Assumption failed: This assumption did not hold.")
end
@ -76,7 +91,6 @@ use "core/control_flow"
(assume $a == $b) parses as (assume ($a == $b))
(assume $a != $b) parses as (assume ($a != $b))
(test that $condition) parses as (assume $condition)
test:
try: fail
$worked = (no)
@ -109,8 +123,7 @@ test:
if ($msg_lua, text, is lua id):
$fallback_lua, add free vars [($msg_lua, text)]
$fallback_lua, prepend "-- Failure:\n"
return
Lua ("
return Lua ("
do
local _fell_through = false
local _result = {xpcall(function()

View File

@ -44,8 +44,8 @@ lua> ("
end
elseif not arg_lua:is_lua_id() then
at_1_fail(SyntaxTree:is_instance(arg) and arg or nil,
"Compile error: This does not compile to a Lua identifier, so it "..
"can't be used as a function argument. "..
"Compile error: This does not compile to a Lua identifier ("..arg_lua.."),"..
"so it can't be used as a function argument. "..
"Hint: This should probably be a Nomsu variable instead (like $x).")
end
lua:add(i > 1 and ", " or "", arg_lua)
@ -97,7 +97,7 @@ lua> ("
(\$action.type == "EscapedNomsu" and \$action[1].type == "Action") or
\$action.type == "MethodCall") then
at_1_fail(\$action.source, "Compile error: "..
"This is neither an action nor an escaped action. "..
"This first argument to (* compiles to *) is neither an action nor an escaped action (it's a "..\$action.type.."). "..
"Hint: This should probably be an action like:\\n"
.."(foo $x) compiles to \\"(\\\\($x as lua) + 1)\\"")
end
@ -117,6 +117,8 @@ lua> ("
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(` $) compiles to (Lua (=lua "\$ or SyntaxTree{type='Action'}", as lua))
($actions all compile to $body) compiles to:
lua> ("
if \$actions.type ~= "List" then
@ -308,22 +310,19 @@ test:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[$action parses as $body] all parse as ([$action] all parse as $body)
external:
(in (nomsu environment) $tree as lua expr) means:
((nomsu environment), $tree as lua expr) means:
lua> ("
local tree_lua = \(nomsu environment):compile(\$tree)
if \$tree.type == 'Block' then
if \$tree.type == 'Block' and #\$tree > 1 then
tree_lua = LuaCode:from(\$tree.source, '(function()\\n ', tree_lua, '\\nend)()')
elseif \$tree.type == 'MethodCall' and #\$tree > 2 then
at_1_fail(\$tree, "Compile error: This must be a single value instead of "..
(#\$tree - 1).." method calls. Hint: Replace this with a single method call.")
end
return tree_lua
")
# Need to make sure the proper environment is used for compilation (i.e. the caller's environment)
($tree as lua expr) compiles to
\(in \(nomsu environment) $tree as lua expr) as lua
=lua "SyntaxTree{type='MethodCall', \(\(nomsu environment)), \(\($tree as lua expr))}"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -340,11 +339,11 @@ external:
")
test:
(num args (*extra arguments*)) means (select "#" (*extra arguments*))
(num args (*extra arguments*)) means (#(*extra arguments*))
assume (num args 1 2 3) == 3
(extra args (*extra arguments*)) means [*extra arguments*]
assume (extra args 1 2 3) == [1, 2, 3]
(third arg (*extra arguments*)) means (select 3 (*extra arguments*))
(third arg (*extra arguments*)) means ((*extra arguments*).3)
assume (third arg 5 6 7 8) == 7
(*extra arguments*) compiles to "..."
@ -353,44 +352,6 @@ external:
($ is $kind syntax tree) means
=lua "SyntaxTree:is_instance(\$) and \$.type == \$kind"
($tree with $t -> $replacement) compiles to ("
\($tree as lua expr):map(function(\($t as lua expr))
\(
=lua ("
\$replacement.type == 'Block' and \($replacement as lua) or 'return '..\
..\($replacement as lua expr):text()
")
)
end)
")
external:
($tree with vars $replacements) means
=lua ("
\$tree:map(function(\$t)
if \$t.type == "Var" then
return \$replacements[\$t:as_var()]
end
end)
")
(tree $tree with vars $replacements) compiles to ("
\(=lua "(\$tree):as_lua()"):map(function(t)
if t.type == "Var" then
return \($replacements as lua expr)[t:as_var()]
end
end)
")
($tree has subtree $match_tree) compiles to ("
(function()
local match_tree = \($match_tree as lua expr)
for subtree in coroutine_wrap(function() \($tree as lua expr):map(yield) end) do
if subtree == match_tree then return true end
end
end)()
")
external:
(match $tree with $patt) means:
lua> ("
@ -444,12 +405,24 @@ external:
if mt and mt.__type then return mt.__type end
if \$ == nil then return 'nil' end
local lua_type = \(lua type of $)
if lua_type == 'function' then return "an Action" end
return 'a '..lua_type:capitalized()
")
($ is $type) parses as ((type of $) == $type)
[$ isn't $type, $ is not $type] all parse as ((type of $) != $type)
($ is $type) means:
lua> ("
local class = getmetatable(\$)
::check_parent::
if not class or not class.__type then return 'a '..lua_type:capitalized() == \$type end
if class.__type == \$type then return true end
local class_mt = getmetatable(class)
if class_mt.__index and class_mt.__index ~= class then
class = class_mt.__index
goto check_parent
end
return false
")
[$ isn't $type, $ is not $type] all parse as (not ($ is $type))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -482,6 +455,9 @@ test:
return lua
")
# Convenience helper:
(return Lua (*extra arguments*)) compiles to \(return \(Lua (*extra arguments*)))
# Literals
(yes) compiles to "(true)"
(no) compiles to "(false)"
@ -525,3 +501,14 @@ external:
return ("
\(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version)
")
~~~~
# TODO: Remove shim
($tree with $t -> $replacement) parses as
$tree, with ($t -> $replacement)
[tree $tree with vars $replacements, $tree with vars $replacements] all parse as
($tree, with $replacements)
($tree has subtree $match_tree) parses as
$tree, contains $match_tree

View File

@ -43,8 +43,7 @@ test:
($expr for $match in $text matching $patt) compiles to:
define mangler
return
Lua ("
return Lua ("
(function()
local \(mangle "comprehension") = a_List{}
for \($match as lua expr) in (\($text as lua expr)):gmatch(\($patt as lua expr)) do

View File

@ -37,6 +37,8 @@ test:
($self, number of commas) means ((#$bits) - 1)
$csv = (a Comma Buffer)
assume $csv.is_a_buffer
assume ($csv is "a Comma Buffer")
assume ($csv is "a Buffer")
assume "\$csv" == ""
$csv, add "x"
$csv, add "y"
@ -96,17 +98,20 @@ external:
Compile error: This is not a list of variables.
")
$class_id = ($classname.stub, as lua id)
if $class_body:
$class_body =
$class_body with vars {
: for $v in $vars:
add ($v as lua expr, text) =
$class_body and=
$class_body, with
$t ->:
for $v in $vars:
if ($t == $v):
return
"IndexChain" tree with ("Var" tree with "self")
"Index" tree with ("Text" tree with $v.1)
}
if ($parent.type == "Action"):
$parent = ("Var" tree with $parent)
$lua =
Lua ("
\$class_id = _1_class_named(\($parent as lua), \(quote $classname.stub)\(
\$class_id = _1_class_named(\($parent as lua id), \(quote $classname.stub)\(
(
Lua ("
, function(\$class_id)

View File

@ -70,6 +70,13 @@ command line program with $args:
unless $tree:
do next
if ($tree.type == "Comment"):
say (dim "Comment:\($tree.1)")
do next
if ($tree.type != "FileChunks"):
$tree = [$tree]
for $chunk in $tree:
try:
$lua = ($chunk as lua)

View File

@ -107,7 +107,7 @@ command line program with $args:
$matched = {}
$user_answers = {}
($tree with replacements) means
$tree, map
$tree, with
for $t:
$values = ($t matches $pattern_tree with {})
if $values:

273
nomsu.7.peg Normal file
View File

@ -0,0 +1,273 @@
-- Nomsu version 7
file <-
{:curr_indent: ' '* :}
(((comment / methodchain / action / expression / inline_block) eol !.)
/ file_chunks / comment? blank_lines?)
{:curr_indent: %nil :}
!.
shebang <- "#!" [^%nl]*
file_chunks (FileChunks) <-
{:shebang: shebang :}?
(top_block (nl_nodent section_division top_block)*)
blank_lines?
unexpected_indent? unexpected_chunk?
section_division <- ("~")^+3 eol
eof <- !.
eol <- ws* (&%nl / !.)
nodent <- (unexpected_indent [^%nl]* / =curr_indent)
indent <- { =curr_indent " " }
blank_lines <- %nl ((tab_error / nodent comment / ws*) %nl)*
nl_nodent <- blank_lines nodent
nl_indent <- blank_lines {:curr_indent: indent :} (comment nl_nodent)*
comment (Comment) <-
"###" {~ [^%nl]* (%nl+ (indent -> '') [^%nl]*)* (%nl &%nl)* ~}
top_block (Block) <-
((blank_lines nodent) / (comment nl_nodent))? statement (nl_nodent statement)*
inline_block (Block) <-
":" ws* (inline_statement (ws* ";" ws* inline_statement)*)?
(&eol !nl_indent / &(ws* ([)},;] / "]")))
indented_block (Block) <-
":" eol nl_indent statement (nl_nodent statement)*
(%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
{:curr_indent: %nil :}
statement_block (Block) <-
":" ws* (methodchain / action / expression)
statement <-
(methodchain / action / expression / statement_block) (eol / unexpected_code)
inline_statement <-
inline_methodchain / inline_action / inline_expression
noindex_inline_expression <-
number / variable / inline_text / inline_list / inline_dict / inline_unary_action /
"("
ws* (inline_block / inline_methodchain / inline_action / inline_expression) ws*
(")" / eof / missing_paren_err / unexpected_code)
inline_expression <- inline_index_chain / noindex_inline_expression / inline_index / inline_block
indented_expression <-
indented_text / indented_list / indented_dict / indented_block /
indented_parens / unary_action
indented_parens <-
"(" indented_naked_expression (nl_nodent ")" / missing_paren_err / unexpected_code)
indented_naked_expression <-
({| nl_indent
(methodchain / action / expression) (eol / unexpected_code)
(%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
{:curr_indent: %nil :}
|} -> unpack)
expression <-
inline_expression / indented_index_chain / indented_expression / inline_index / indented_index
inline_index (Index) <-
"." (hex_integer / integer / text_word / noindex_inline_expression)
_inline_index (IndexChain) <- inline_index
inline_index_chain <-
(noindex_inline_expression _inline_index+) -> foldr
indented_index (Index) <- "." indented_expression
_indented_index (IndexChain) <- indented_index
indented_index_chain <-
(noindex_inline_expression inline_index* (inline_index / _indented_index)) -> foldr
-- Actions need 1 argument and either another argument or a word.
inline_action (Action) <-
!section_division
( inline_expression ((ws* inline_arg)+ / "(" ws* ")")
/ word (ws* inline_arg)*)
inline_arg <- inline_expression / word
inline_unary_action (Action) <-
!section_division %at_break operator !":" inline_expression
action (Action) <-
!section_division
( !statement_block (expression / indented_naked_expression) (((linesplit / ws*) arg)+ / "(" ws* ")")
/ word ((linesplit / ws*) arg)*)
arg <- expression / indented_naked_expression / word
linesplit <- eol nl_nodent ".." ws*
unary_action (Action) <-
!section_division %at_break operator !":" !eol (expression / indented_naked_expression)
inline_methodsuffix (MethodCall) <-
inline_action
/ "(" ws* inline_action (ws* ";" ws* inline_action)* ")"
inline_methodchain <-
((inline_action / inline_expression) (ws* "," ws* inline_methodsuffix)+) -> foldr
methodsuffix (MethodCall) <-
action
/ "(" ws* inline_action (ws* ";" ws* inline_action)* ws* ")"
/ eol ({| nl_indent
(action eol) (nl_nodent action eol)*
(%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
{:curr_indent: %nil :}
|} -> unpack)
methodchain <-
((unary_action / inline_action / expression) ((linesplit / ws*) "," ws* methodsuffix)+) -> foldr
word <- !number { %operator_char+ / ident_char+ }
operator <- !"###" {%operator_char+}
text_word (Text) <- word
inline_text (Text) <-
'"' !eol _inline_text* ('"' / eof / missing_quote_err / unexpected_code)
_inline_text <-
{~ (('\"' -> '"') / ('\\' -> '\') / escaped_char / text_char+)+ ~}
/ inline_text_interpolation / illegal_char
inline_text_interpolation <-
"\" (
variable / inline_list / inline_dict
/ ("("
ws* ((inline_methodchain / inline_action / inline_expression) ws*)?
(")" / eof / missing_paren_err / unexpected_code))
)
text_char <- %utf8_char / !["\] %print / %tab
indented_text (Text) <-
'("' %nl {%nl*} ({|
{:curr_indent: indent :}
(indented_plain_text / text_interpolation / illegal_char / blank_text_lines)*
{:curr_indent: %nil :}
|} -> unpack)
(nl_nodent '")' / eof / missing_indented_quote_err)
-- 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 ((!("\n") escaped_char) / '\'))
/ ('"' / text_char)+)+
blank_text_lines?
~}
blank_text_lines <-
{~ (%nl ((ws* -> '') (&%nl / !.) / (=curr_indent -> '') &[^%nl]))+ ~}
text_interpolation <-
({|
-- %indentation will backtrack and match the actual indentation of the current line
"\" {:curr_indent: %indentation :}
(indented_block (blank_lines =curr_indent "..")? / indented_expression)
|} -> unpack)
/ inline_text_interpolation
number <-
hex_integer / real_number / integer
integer (Number) <-
(((%at_break "-")? [0-9]+)-> tonumber)
hex_integer (Number) <-
(((%at_break "-")? "0x" [0-9a-fA-F]+)-> tonumber)
{:hex: '' -> 'yes' :}
real_number (Number) <-
(((%at_break "-")? [0-9]+ "." [0-9]+)-> tonumber)
variable (Var) <- "$" ({ident_char+} / "(" ws* (inline_action / variable) ws* ")" / {''})
inline_list (List) <-
"[" ws* !eol
(inline_list_item (ws* ',' ws* inline_list_item)* (ws* ',')?)? ws*
("]" / eof / (","? (missing_bracket_error / unexpected_code)))
inline_list_item <- inline_action / inline_expression
indented_list (List) <-
({|
"[" eol nl_indent
list_line (nl_nodent list_line)*
{:curr_indent: %nil :}
|} -> unpack)
(nl_nodent "]" / eof / missing_bracket_error / unexpected_code)
list_line <-
(inline_list_item ws* "," ws*)+ eol
/ (inline_list_item ws* "," ws*)* (action / statement_block / expression) eol
inline_dict (Dict) <-
"{" ws* !eol
((inline_action / inline_expression) (ws* ',' ws* (inline_action / inline_expression))*)? ws*
("}" / eof / (","? (missing_brace_error / unexpected_code)))
indented_dict (Dict) <-
({|
"{" eol nl_indent
dict_line (nl_nodent dict_line)*
(%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
{:curr_indent: %nil :}
|} -> unpack)
(nl_nodent "}" / eof / missing_brace_error / unexpected_code)
dict_line <-
((inline_action / inline_expression) ws* "," ws*)+ eol
/ ((inline_action / inline_expression) ws* "," ws*)* (action / statement_block / expression) eol
ident_char <- [a-zA-Z0-9_] / (!%operator_char %utf8_char)
ws <- " "
escaped_char <-
("\"->'') (
(([xX]->'') ((({[0-9a-fA-F]^2} %number_16) -> tonumber) -> tochar))
/ ((([0-9] [0-9]^-2) -> tonumber) -> tochar)
/ ("a"->ascii_7) / ("b"->ascii_8) / ("t"->ascii_9) / ("n"->ascii_10)
/ ("v"->ascii_11) / ("f"->ascii_12) / ("r"->ascii_13)
)
-- Errors
unexpected_code <- ws* _unexpected_code
_unexpected_code (Error) <-
{:error: {~ [^%nl]+ -> "Couldn't parse this code." ~} :}
unexpected_chunk (Error) <-
{:error: {~ .+ -> "Couldn't parse this chunk of code." ~} :}
unexpected_indent (Error) <-
{:error: {~ (=curr_indent ws+) -> "This indentation is messed up." ~} :}
{:hint: {~ '' -> 'This line should either have the same indentation as the line above it, or exactly 4 spaces more.' ~} :}
missing_paren_err (Error) <-
{:error: {~ eol -> 'This expression is missing a closing )-parenthesis.' ~} :}
{:hint: {~ '' -> 'Put a ")" here' ~} :}
missing_quote_err (Error) <-
{:error: {~ eol -> "This text is missing a closing quotation mark." ~} :}
{:hint: {~ "" -> "Put a quotation mark here." ~} :}
missing_indented_quote_err (Error) <-
{:error: {~ '' -> 'This text is missing a closing ")-quotation mark.' ~} :}
{:hint: {~ "" -> 'Put a ") after this line, at the same level of indentation as the opening (".' ~} :}
missing_bracket_error (Error) <-
{:error: {~ eol -> "This list is missing a closing ]-bracket" ~} :}
{:hint: {~ '' -> 'Put a "]" here' ~} :}
missing_brace_error (Error) <-
{:error: {~ eol -> "This dict is missing a closing }-brace" ~} :}
{:hint: {~ '' -> 'Put a "}" here' ~} :}
tab_error <- ws* _tab_error [^%nl]*
_tab_error (Error) <-
{:error: {~ %tab+ -> 'Tabs are not allowed for indentation.' ~} :}
{:hint: {~ '' -> 'Use 4-space indentation instead of tabs.' ~} :}
illegal_char (Error) <-
{:error: {~ (!(%nl / %tab / %print) .) -> "Illegal unprintable character here (it may not be visible, but it's there)" ~} :}
{:hint: {~ '' -> "This sort of thing can happen when copying and pasting code. Try deleting and retyping the code." ~} :}

View File

@ -199,6 +199,9 @@ run = function()
local env = nomsu_environment.new_environment()
env.MODULE_NAME = filename
local tree = env._1_parsed(code)
if tree.shebang then
output:write(tree.shebang)
end
if not (tree.type == 'FileChunks') then
tree = {
tree

View File

@ -162,6 +162,8 @@ run = ->
env = nomsu_environment.new_environment!
env.MODULE_NAME = filename
tree = env._1_parsed(code)
if tree.shebang
output\write tree.shebang
tree = {tree} unless tree.type == 'FileChunks'
for chunk_no, chunk in ipairs tree
lua = env\compile(chunk)

View File

@ -21,7 +21,7 @@ fail_at = function(source, msg)
source = source.source
elseif type(source) == 'string' then
source = Source:from_string(source)
else
elseif not Source:is_instance(source) then
assert(source.short_src and source.currentline)
file = Files.read(source.short_src)
assert(file, "Could not find " .. tostring(source.short_src))
@ -86,6 +86,26 @@ compile = function(self, tree)
end
return lua
end
if not compile_action then
local seen_words = { }
local words = { }
for word in stub:gmatch("[^0-9 ][^ ]*") do
if not (seen_words[word]) then
seen_words[word] = true
table.insert(words, word)
end
end
table.sort(words)
local stub2 = table.concat(words, " ")
compile_action = self.COMPILE_RULES[stub2]
if compile_action then
if debug.getinfo(compile_action, 'u').isvararg then
stub = stub2
else
compile_action = nil
end
end
end
if compile_action then
local args
do
@ -118,7 +138,7 @@ compile = function(self, tree)
lua:add((stub):as_lua_id(), "(")
for argnum, arg in ipairs(tree:get_args()) do
local arg_lua = self:compile(arg)
if arg.type == "Block" then
if arg.type == "Block" and #arg > 1 then
arg_lua = LuaCode:from(arg.source, "(function()\n ", arg_lua, "\nend)()")
end
if lua:trailing_line_len() + #arg_lua:text() > MAX_LINE then
@ -155,15 +175,22 @@ compile = function(self, tree)
if not (target_text:match("^%(.*%)$") or target_text:match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or tree[1].type == "IndexChain") then
target_lua:parenthesize()
end
local self_lua = #tree > 2 and "_self" or target_lua
if #tree > 2 then
lua:add("(function(", self_lua, ")\n ")
end
for i = 2, #tree do
if i > 2 then
lua:add("\n")
lua:add("\n ")
end
lua:add(target_lua, ":")
if i > 2 and i == #tree then
lua:add("return ")
end
lua:add(self_lua, ":")
lua:add((tree[i].stub):as_lua_id(), "(")
for argnum, arg in ipairs(tree[i]:get_args()) do
local arg_lua = self:compile(arg)
if arg.type == "Block" then
if arg.type == "Block" and #arg > 1 then
arg_lua = LuaCode:from(arg.source, "(function()\n ", arg_lua, "\nend)()")
end
if lua:trailing_line_len() + #arg_lua:text() > MAX_LINE then
@ -175,6 +202,9 @@ compile = function(self, tree)
end
lua:add(")")
end
if #tree > 2 then
lua:add("\nend)(", target_lua, ")")
end
return lua
elseif "EscapedNomsu" == _exp_0 then
local lua = LuaCode:from(tree.source, "SyntaxTree{")
@ -320,9 +350,9 @@ compile = function(self, tree)
items_lua:add("\n")
sep = ''
elseif items_lua:trailing_line_len() > MAX_LINE then
sep = ',\n '
sep = items_lua:text():sub(-1) == ";" and "\n " or ",\n "
else
sep = ', '
sep = items_lua:text():sub(-1) == ";" and " " or ", "
end
i = i + 1
end
@ -360,6 +390,9 @@ compile = function(self, tree)
if lua:text():match("['\"}]$") or lua:text():match("]=*]$") then
lua:parenthesize()
end
if lua:text() == "..." then
return LuaCode:from(tree.source, "select(", self:compile(tree[2][1]), ", ...)")
end
for i = 2, #tree do
local key = tree[i]
if key.type ~= "Index" then
@ -381,7 +414,16 @@ compile = function(self, tree)
elseif "Comment" == _exp_0 then
return LuaCode:from(tree.source, "-- ", (tree[1]:gsub('\n', '\n-- ')))
elseif "Error" == _exp_0 then
return error("Can't compile errors")
local err_msg = pretty_error({
title = "Parse error",
error = tree.error,
hint = tree.hint,
source = tree:get_source_file(),
start = tree.source.start,
stop = tree.source.stop,
filename = tree.source.filename
})
return error(err_msg)
else
return error("Unknown type: " .. tostring(tree.type))
end

View File

@ -16,7 +16,7 @@ fail_at = (source, msg)->
source = source.source
elseif type(source) == 'string'
source = Source\from_string(source)
else
elseif not Source\is_instance(source)
-- debug.getinfo() output:
assert(source.short_src and source.currentline)
file = Files.read(source.short_src)
@ -73,6 +73,21 @@ compile = (tree)=>
lua\add " " if i < #tree
return lua
if not compile_action
seen_words = {}
words = {}
for word in stub\gmatch("[^0-9 ][^ ]*")
unless seen_words[word]
seen_words[word] = true
table.insert words, word
table.sort(words)
stub2 = table.concat(words, " ")
compile_action = @COMPILE_RULES[stub2]
if compile_action
if debug.getinfo(compile_action, 'u').isvararg
stub = stub2
else compile_action = nil
if compile_action
args = [arg for arg in *tree when type(arg) != "string"]
ret = compile_action(@, tree, unpack(args))
@ -92,7 +107,7 @@ compile = (tree)=>
lua\add((stub)\as_lua_id!,"(")
for argnum, arg in ipairs tree\get_args!
arg_lua = @compile(arg)
if arg.type == "Block"
if arg.type == "Block" and #arg > 1
arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE
lua\add(argnum > 1 and ",\n " or "\n ")
@ -129,13 +144,18 @@ compile = (tree)=>
tree[1].type == "IndexChain")
target_lua\parenthesize!
self_lua = #tree > 2 and "_self" or target_lua
if #tree > 2
lua\add "(function(", self_lua, ")\n "
for i=2,#tree
lua\add "\n" if i > 2
lua\add target_lua, ":"
lua\add "\n " if i > 2
if i > 2 and i == #tree
lua\add "return "
lua\add self_lua, ":"
lua\add((tree[i].stub)\as_lua_id!,"(")
for argnum, arg in ipairs tree[i]\get_args!
arg_lua = @compile(arg)
if arg.type == "Block"
if arg.type == "Block" and #arg > 1
arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE
lua\add(argnum > 1 and ",\n " or "\n ")
@ -143,6 +163,8 @@ compile = (tree)=>
lua\add ", "
lua\add arg_lua
lua\add ")"
if #tree > 2
lua\add "\nend)(", target_lua, ")"
return lua
when "EscapedNomsu"
@ -258,9 +280,9 @@ compile = (tree)=>
items_lua\add "\n"
sep = ''
elseif items_lua\trailing_line_len! > MAX_LINE
sep = ',\n '
sep = items_lua\text!\sub(-1) == ";" and "\n " or ",\n "
else
sep = ', '
sep = items_lua\text!\sub(-1) == ";" and " " or ", "
i += 1
if items_lua\is_multiline!
lua\add LuaCode\from items_lua.source, typename, "{\n ", items_lua, "\n}"
@ -293,6 +315,8 @@ compile = (tree)=>
lua = @compile(tree[1])
if lua\text!\match("['\"}]$") or lua\text!\match("]=*]$")
lua\parenthesize!
if lua\text! == "..."
return LuaCode\from(tree.source, "select(", @compile(tree[2][1]), ", ...)")
for i=2,#tree
key = tree[i]
-- TODO: remove this shim
@ -315,7 +339,13 @@ compile = (tree)=>
return LuaCode\from(tree.source, "-- ", (tree[1]\gsub('\n', '\n-- ')))
when "Error"
error("Can't compile errors")
err_msg = pretty_error{
title:"Parse error"
error:tree.error, hint:tree.hint, source:tree\get_source_file!
start:tree.source.start, stop:tree.source.stop, filename:tree.source.filename
}
-- Coroutine yield here?
error(err_msg)
else
error("Unknown type: #{tree.type}")

View File

@ -14,15 +14,20 @@ local re = require('re')
local MAX_LINE = 80
local GOLDEN_RATIO = ((math.sqrt(5) - 1) / 2)
local utf8_char_patt = (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"))
local operator_patt = S("'`~!@%#^&*+=|<>?/-") ^ 1 * -1
local identifier_patt = (R("az", "AZ", "09") + P("_") + utf8_char_patt) ^ 1 * -1
local operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B") + R("\xA8\xAB")) * R("\128\191"))
local operator_patt = operator_char ^ 1 * -1
local identifier_patt = (R("az", "AZ", "09") + P("_") + (-operator_char * utf8_char_patt)) ^ 1 * -1
local is_operator
is_operator = function(s)
return not not operator_patt:match(s)
return type(s) == 'string' and operator_patt:match(s)
end
local is_identifier
is_identifier = function(s)
return not not identifier_patt:match(s)
return type(s) == 'string' and identifier_patt:match(s)
end
local can_be_unary
can_be_unary = function(t)
return t.type == "Action" and #t == 2 and is_operator(t[1]) and type(t[2]) ~= 'string' and t[2].type ~= "Block"
end
local inline_escaper = re.compile("{~ (%utf8_char / ('\"' -> '\\\"') / ('\n' -> '\\n') / ('\t' -> '\\t') / ('\b' -> '\\b') / ('\a' -> '\\a') / ('\v' -> '\\v') / ('\f' -> '\\f') / ('\r' -> '\\r') / ('\\' -> '\\\\') / ([^ -~] -> escape) / .)* ~}", {
utf8_char = utf8_char_patt,
@ -49,6 +54,15 @@ tree_to_inline_nomsu = function(tree)
local _exp_0 = tree.type
if "Action" == _exp_0 then
local nomsu = NomsuCode:from(tree.source)
if can_be_unary(tree) then
nomsu:add(tree[1])
local arg_nomsu = tree_to_inline_nomsu(tree[2])
if tree[2].type == "MethodCall" or tree[2].type == "Action" then
arg_nomsu:parenthesize()
end
nomsu:add(arg_nomsu)
return nomsu
end
local num_args, num_words = 0, 0
for i, bit in ipairs(tree) do
if type(bit) == "string" then
@ -67,17 +81,19 @@ tree_to_inline_nomsu = function(tree)
num_args = num_args + 1
local arg_nomsu = tree_to_inline_nomsu(bit)
if bit.type == "Block" then
if i > 1 and i < #tree then
if i ~= #tree then
if i > 1 then
nomsu:add(" ")
end
if not (i == #tree) then
arg_nomsu:parenthesize()
end
else
if i > 1 and tree[i - 1] ~= "#" then
if i > 1 then
nomsu:add(" ")
end
if bit.type == "Action" or bit.type == "MethodCall" then
if bit.type == "MethodCall" then
arg_nomsu:parenthesize()
elseif bit.type == "Action" and not can_be_unary(bit) then
arg_nomsu:parenthesize()
end
end
@ -153,7 +169,7 @@ tree_to_inline_nomsu = function(tree)
nomsu:add(", ")
end
local item_nomsu = tree_to_inline_nomsu(item, true)
if item.type == "MethodCall" then
if item.type == "MethodCall" or (item.type == "Block" and i < #tree) then
item_nomsu:parenthesize()
end
nomsu:add(item_nomsu)
@ -256,7 +272,7 @@ tree_to_nomsu = function(tree)
local space = MAX_LINE - nomsu:trailing_line_len()
local try_inline = true
for subtree in coroutine.wrap(function()
return (t:map(coroutine.yield) and nil)
return (t:with(coroutine.yield) and nil)
end) do
local _exp_0 = subtree.type
if "Comment" == _exp_0 then
@ -294,7 +310,9 @@ tree_to_nomsu = function(tree)
local inline_nomsu
if try_inline then
inline_nomsu = tree_to_inline_nomsu(t)
if (t.type == "Action" or t.type == "MethodCall") then
if t.type == "MethodCall" then
inline_nomsu:parenthesize()
elseif t.type == "Action" and not can_be_unary(t) then
inline_nomsu:parenthesize()
end
if #inline_nomsu:text() <= space or #inline_nomsu:text() <= 8 then
@ -306,11 +324,13 @@ tree_to_nomsu = function(tree)
local indented = tree_to_nomsu(t)
if t.type == "Action" or t.type == "MethodCall" then
if indented:is_multiline() then
if not (indented:text():match("\n%S[^\n ]*$")) then
if argnum == nil or argnum == 1 then
return NomsuCode:from(t.source, "(\n ", indented, "\n)")
else
return NomsuCode:from(t.source, "\n ", indented)
end
end
elseif argnum and argnum > 1 then
return NomsuCode:from(t.source, "\n ", indented)
else
@ -346,7 +366,7 @@ tree_to_nomsu = function(tree)
local _exp_0 = tree.type
if "FileChunks" == _exp_0 then
if tree.shebang then
nomsu:add(tree.shebang)
nomsu:add(tree.shebang, "\n")
end
for chunk_no, chunk in ipairs(tree) do
if chunk_no > 1 then
@ -360,6 +380,11 @@ tree_to_nomsu = function(tree)
end
return nomsu
elseif "Action" == _exp_0 then
if can_be_unary(tree) and not can_be_unary(tree[2]) then
nomsu:add(tree[1])
nomsu:add(recurse(tree[2]))
return nomsu
end
local next_space = ""
local word_buffer = { }
local num_args, num_words = 0, 0
@ -636,7 +661,7 @@ tree_to_nomsu = function(tree)
end
return nomsu
elseif "Comment" == _exp_0 then
nomsu:add("#", (tree[1]:gsub("\n", "\n ")))
nomsu:add("###", (tree[1]:gsub("\n", "\n ")))
return nomsu
elseif "IndexChain" == _exp_0 or "Index" == _exp_0 or "Number" == _exp_0 or "Var" == _exp_0 or "Comment" == _exp_0 or "Error" == _exp_0 then
return tree_to_inline_nomsu(tree)

View File

@ -11,14 +11,18 @@ utf8_char_patt = (
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"))
operator_patt = S("'`~!@%#^&*+=|<>?/-")^1 * -1
identifier_patt = (R("az","AZ","09") + P("_") + utf8_char_patt)^1 * -1
operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B")+R("\xA8\xAB")) * R("\128\191"))
operator_patt = operator_char^1 * -1
identifier_patt = (R("az","AZ","09") + P("_") + (-operator_char*utf8_char_patt))^1 * -1
is_operator = (s)->
return not not operator_patt\match(s)
return type(s) == 'string' and operator_patt\match(s)
is_identifier = (s)->
return not not identifier_patt\match(s)
return type(s) == 'string' and identifier_patt\match(s)
can_be_unary = (t)->
t.type == "Action" and #t == 2 and is_operator(t[1]) and type(t[2]) != 'string' and t[2].type != "Block"
inline_escaper = re.compile("{~ (%utf8_char / ('\"' -> '\\\"') / ('\n' -> '\\n') / ('\t' -> '\\t') / ('\b' -> '\\b') / ('\a' -> '\\a') / ('\v' -> '\\v') / ('\f' -> '\\f') / ('\r' -> '\\r') / ('\\' -> '\\\\') / ([^ -~] -> escape) / .)* ~}", {utf8_char: utf8_char_patt, escape:(=> ("\\%03d")\format(@byte!))})
inline_escape = (s)->
@ -33,6 +37,14 @@ tree_to_inline_nomsu = (tree)->
switch tree.type
when "Action"
nomsu = NomsuCode\from(tree.source)
if can_be_unary(tree)
nomsu\add tree[1]
arg_nomsu = tree_to_inline_nomsu(tree[2])
if tree[2].type == "MethodCall" or tree[2].type == "Action"
arg_nomsu\parenthesize!
nomsu\add arg_nomsu
return nomsu
num_args, num_words = 0, 0
for i,bit in ipairs tree
if type(bit) == "string"
@ -46,13 +58,14 @@ tree_to_inline_nomsu = (tree)->
num_args += 1
arg_nomsu = tree_to_inline_nomsu(bit)
if bit.type == "Block"
if i > 1 and i < #tree
nomsu\add " "
unless i == #tree
if i != #tree
nomsu\add " " if i > 1
arg_nomsu\parenthesize!
else
nomsu\add " " if i > 1 and tree[i-1] != "#"
if bit.type == "Action" or bit.type == "MethodCall"
nomsu\add " " if i > 1
if bit.type == "MethodCall"
arg_nomsu\parenthesize!
elseif bit.type == "Action" and not can_be_unary(bit)
arg_nomsu\parenthesize!
nomsu\add arg_nomsu
if num_args == 1 and num_words == 0
@ -112,7 +125,7 @@ tree_to_inline_nomsu = (tree)->
item_nomsu = tree_to_inline_nomsu(item, true)
--if item.type == "Block" or item.type == "Action" or item.type == "MethodCall"
-- item_nomsu\parenthesize!
if item.type == "MethodCall"
if item.type == "MethodCall" or (item.type == "Block" and i < #tree)
item_nomsu\parenthesize!
nomsu\add item_nomsu
nomsu\add(tree.type == "List" and "]" or "}")
@ -196,7 +209,7 @@ tree_to_nomsu = (tree)->
recurse = (t, argnum=nil)->
space = MAX_LINE - nomsu\trailing_line_len!
try_inline = true
for subtree in coroutine.wrap(-> (t\map(coroutine.yield) and nil))
for subtree in coroutine.wrap(-> (t\with(coroutine.yield) and nil))
switch subtree.type
when "Comment"
try_inline = false
@ -215,7 +228,9 @@ tree_to_nomsu = (tree)->
local inline_nomsu
if try_inline
inline_nomsu = tree_to_inline_nomsu(t)
if (t.type == "Action" or t.type == "MethodCall")
if t.type == "MethodCall"
inline_nomsu\parenthesize!
elseif t.type == "Action" and not can_be_unary(t)
inline_nomsu\parenthesize!
if #inline_nomsu\text! <= space or #inline_nomsu\text! <= 8
if t.type != "Text"
@ -223,6 +238,7 @@ tree_to_nomsu = (tree)->
indented = tree_to_nomsu(t)
if t.type == "Action" or t.type == "MethodCall"
if indented\is_multiline!
unless indented\text!\match("\n%S[^\n ]*$")
if argnum == nil or argnum == 1
return NomsuCode\from(t.source, "(\n ", indented, "\n)")
else
@ -244,7 +260,7 @@ tree_to_nomsu = (tree)->
switch tree.type
when "FileChunks"
if tree.shebang
nomsu\add tree.shebang
nomsu\add tree.shebang, "\n"
for chunk_no, chunk in ipairs tree
nomsu\add "\n\n#{("~")\rep(80)}\n\n" if chunk_no > 1
@ -256,6 +272,11 @@ tree_to_nomsu = (tree)->
return nomsu
when "Action"
if can_be_unary(tree) and not can_be_unary(tree[2])
nomsu\add tree[1]
nomsu\add recurse(tree[2])
return nomsu
next_space = ""
word_buffer = {}
num_args, num_words = 0, 0
@ -429,10 +450,6 @@ tree_to_nomsu = (tree)->
nomsu\add "\\;"
return NomsuCode\from(tree.source, '("\n ', nomsu, '\n")')
when "List", "Dict"
if #tree == 0
nomsu\add(tree.type == "List" and "[]" or "{}")
@ -490,7 +507,7 @@ tree_to_nomsu = (tree)->
return nomsu
when "Comment"
nomsu\add "#", (tree[1]\gsub("\n", "\n "))
nomsu\add "###", (tree[1]\gsub("\n", "\n "))
return nomsu
when "IndexChain", "Index", "Number", "Var", "Comment", "Error"

View File

@ -22,17 +22,6 @@ make_tree = function(tree, userdata)
tree = SyntaxTree(tree)
return tree
end
table.map = function(t, fn)
return setmetatable((function()
local _accum_0 = { }
local _len_0 = 1
for _, v in ipairs(t) do
_accum_0[_len_0] = fn(v)
_len_0 = _len_0 + 1
end
return _accum_0
end)(), getmetatable(t))
end
local Parsers = { }
local max_parser_version = 0
for version = 1, 999 do
@ -88,6 +77,14 @@ _1_as_text = function(x)
end
return tostring(x)
end
local _1_as_list
_1_as_list = function(x)
local mt = getmetatable(x)
if mt.as_list then
return mt.as_list(x)
end
return x
end
local nomsu_environment
nomsu_environment = Importer({
NOMSU_COMPILER_VERSION = 13,
@ -160,6 +157,7 @@ nomsu_environment = Importer({
compile = compile,
at_1_fail = fail_at,
_1_as_text = _1_as_text,
_1_as_list = _1_as_list,
exit = os.exit,
quit = os.exit,
_1_parsed = function(nomsu_code, syntax_version)
@ -179,56 +177,6 @@ nomsu_environment = Importer({
if tree.shebang then
tree.version = tree.version or tree.shebang:match("nomsu %-V[ ]*([%d.]*)")
end
local errs = { }
local find_errors
find_errors = function(t)
if t.type == "Error" then
errs[#errs + 1] = t
else
for k, v in pairs(t) do
local _continue_0 = false
repeat
if not (SyntaxTree:is_instance(v)) then
_continue_0 = true
break
end
find_errors(v)
_continue_0 = true
until true
if not _continue_0 then
break
end
end
end
end
find_errors(tree)
local num_errs = #errs
if num_errs > 0 then
local err_strings
do
local _accum_0 = { }
local _len_0 = 1
for i, e in ipairs(errs) do
if i <= 3 then
_accum_0[_len_0] = pretty_error({
title = "Parse error",
error = e.error,
hint = e.hint,
source = e:get_source_file(),
start = e.source.start,
stop = e.source.stop,
filename = e.source.filename
})
_len_0 = _len_0 + 1
end
end
err_strings = _accum_0
end
if num_errs > #err_strings then
table.insert(err_strings, C("bright red", " +" .. tostring(num_errs - #err_strings) .. " additional errors...\n"))
end
error(table.concat(err_strings, '\n\n'), 0)
end
return tree
end,
Module = function(self, package_name)
@ -361,6 +309,7 @@ nomsu_environment = Importer({
elseif LuaCode:is_instance(to_run) then
local source = to_run.source
local lua_string = to_run:text()
lua_string = lua_string:gsub("^#![^\n]*\n", "")
local run_lua_fn, err = load(lua_string, tostring(source), "t", self)
if not run_lua_fn then
local lines

View File

@ -16,8 +16,6 @@ make_tree = (tree, userdata)->
tree = SyntaxTree(tree)
return tree
table.map = (t, fn)-> setmetatable([fn(v) for _,v in ipairs(t)], getmetatable(t))
Parsers = {}
max_parser_version = 0
for version=1,999
@ -48,6 +46,11 @@ _1_as_text = (x)->
if x == false then return "no"
return tostring(x)
_1_as_list = (x)->
mt = getmetatable(x)
if mt.as_list then return mt.as_list(x)
return x
local nomsu_environment
nomsu_environment = Importer{
NOMSU_COMPILER_VERSION: 13, NOMSU_SYNTAX_VERSION: max_parser_version
@ -74,7 +77,7 @@ nomsu_environment = Importer{
-- Nomsu functions:
_1_as_nomsu:tree_to_nomsu, _1_as_inline_nomsu:tree_to_inline_nomsu,
compile: compile, at_1_fail:fail_at, _1_as_text:_1_as_text,
compile: compile, at_1_fail:fail_at, :_1_as_text, :_1_as_list,
exit:os.exit, quit:os.exit,
_1_parsed: (nomsu_code, syntax_version)->
@ -91,26 +94,6 @@ nomsu_environment = Importer{
tree = parse(nomsu_code, source.filename)
if tree.shebang
tree.version or= tree.shebang\match("nomsu %-V[ ]*([%d.]*)")
errs = {}
find_errors = (t)->
if t.type == "Error"
errs[#errs+1] = t
else
for k,v in pairs(t)
continue unless SyntaxTree\is_instance(v)
find_errors(v)
find_errors(tree)
num_errs = #errs
if num_errs > 0
err_strings = [pretty_error{
title:"Parse error"
error:e.error, hint:e.hint, source:e\get_source_file!
start:e.source.start, stop:e.source.stop, filename:e.source.filename
} for i, e in ipairs(errs) when i <= 3]
if num_errs > #err_strings
table.insert(err_strings, C("bright red", " +#{num_errs-#err_strings} additional errors...\n"))
error(table.concat(err_strings, '\n\n'), 0)
return tree
Module: (package_name)=>
@ -218,6 +201,8 @@ nomsu_environment = Importer{
elseif LuaCode\is_instance(to_run)
source = to_run.source
lua_string = to_run\text!
-- For some reason, Lua doesn't strip shebangs from Lua files
lua_string = lua_string\gsub("^#![^\n]*\n","")
-- If you replace tostring(source) with "nil", source mapping won't happen
run_lua_fn, err = load(lua_string, tostring(source), "t", @)
if not run_lua_fn

View File

@ -21,6 +21,7 @@ do
local _with_0 = { }
_with_0.nl = P("\r") ^ -1 * P("\n")
_with_0.tab = P("\t")
_with_0.at_break = lpeg.B(lpeg.S(";,. \r\n\t({[")) + -lpeg.B(1)
_with_0.tonumber = tonumber
_with_0.tochar = string.char
_with_0.unpack = unpack or table.unpack
@ -34,6 +35,7 @@ do
return true, (s:match("^ *", i))
end)
_with_0.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"))
_with_0.operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B") + R("\xA8\xAB")) * R("\128\191"))
_with_0.Tree = function(t, userdata)
return userdata.make_tree(t, userdata)
end

View File

@ -20,6 +20,7 @@ DEFS = with {}
-- Newline supports either windows-style CR+LF or unix-style LF
.nl = P("\r")^-1 * P("\n")
.tab = P("\t")
.at_break = lpeg.B(lpeg.S(";,. \r\n\t({[")) + -lpeg.B(1)
.tonumber = tonumber
.tochar = string.char
.unpack = unpack or table.unpack
@ -36,6 +37,7 @@ DEFS = with {}
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"))
.operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B")+R("\xA8\xAB")) * R("\128\191"))
.Tree = (t, userdata)-> userdata.make_tree(t, userdata)
.foldr = foldr

View File

@ -55,6 +55,9 @@ do
if type(self) ~= type(other) or #self ~= #other or getmetatable(self) ~= getmetatable(other) then
return false
end
if self.type ~= other.type then
return false
end
for i = 1, #self do
if self[i] ~= other[i] then
return false
@ -87,7 +90,24 @@ do
get_source_code = function(self)
return self.__class.source_code_for_tree[self]:sub(self.source.start, self.source.stop - 1)
end,
map = function(self, fn)
add = function(self, ...)
local n = #self
for i = 1, select('#', ...) do
self[n + i] = select(i, ...)
end
self.stub = nil
end,
with = function(self, fn)
if type(fn) == 'table' then
local replacements = fn
fn = function(t)
for k, v in pairs(replacements) do
if k == t then
return v
end
end
end
end
local replacement = fn(self)
if replacement == false then
return nil
@ -122,7 +142,7 @@ do
repeat
replacement[k] = v
if SyntaxTree:is_instance(v) then
local r = v:map(fn)
local r = v:with(fn)
if r == v or r == nil then
_continue_0 = true
break
@ -143,6 +163,19 @@ do
end
return replacement
end,
contains = function(self, subtree)
if subtree == self then
return true
end
for k, v in pairs(self) do
if SyntaxTree:is_instance(v) then
if v:contains(subtree) then
return true
end
end
end
return false
end,
get_args = function(self)
assert(self.type == "Action" or self.type == "MethodCall", "Only actions and method calls have arguments")
local args = { }

View File

@ -24,6 +24,7 @@ class SyntaxTree
__eq: (other)=>
return false if type(@) != type(other) or #@ != #other or getmetatable(@) != getmetatable(other)
return false if @type != other.type
for i=1,#@
return false if @[i] != other[i]
return true
@ -44,7 +45,20 @@ class SyntaxTree
})
get_source_file: => @@source_code_for_tree[@]
get_source_code: => @@source_code_for_tree[@]\sub(@source.start, @source.stop-1)
map: (fn)=>
add: (...)=>
n = #@
for i=1,select('#', ...)
@[n+i] = select(i, ...)
@stub = nil
with: (fn)=>
if type(fn) == 'table'
replacements = fn
fn = (t)->
for k,v in pairs(replacements)
if k == t then return v
replacement = fn(@)
if replacement == false then return nil
if replacement
@ -60,7 +74,7 @@ class SyntaxTree
for k,v in pairs(@)
replacement[k] = v
if SyntaxTree\is_instance(v)
r = v\map(fn)
r = v\with(fn)
continue if r == v or r == nil
changes = true
replacement[k] = r
@ -68,6 +82,13 @@ class SyntaxTree
replacement = SyntaxTree(replacement)
return replacement
contains: (subtree)=>
if subtree == @ then return true
for k,v in pairs(@)
if SyntaxTree\is_instance(v)
return true if v\contains(subtree)
return false
get_args: =>
assert(@type == "Action" or @type == "MethodCall", "Only actions and method calls have arguments")
args = {}