aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2019-01-14 15:42:48 -0800
committerBruce Hill <bruce@bruce-hill.com>2019-01-14 15:43:24 -0800
commitc1c32688a4afc43f6addb99b8b5fa878944a70e3 (patch)
treec886f21b5b08a9053aa74fcba4b241dae5ede76d /lib
parent2309b696fc34b24f05f6658b94f9105ca8ee76e4 (diff)
Overhaul in progress, mostly working. Moved all the nomsu packages into
lib/, including core/*. Changes to how nomsu environments and importing work.
Diffstat (limited to 'lib')
-rw-r--r--lib/base64/init.nom (renamed from lib/base64.nom)0
-rw-r--r--lib/commandline/init.nom11
-rw-r--r--lib/consolecolor/init.nom (renamed from lib/consolecolor.nom)0
-rw-r--r--lib/core/collections.nom145
-rw-r--r--lib/core/control_flow.nom489
-rw-r--r--lib/core/coroutines.nom40
-rw-r--r--lib/core/errors.nom131
-rw-r--r--lib/core/id.nom66
-rw-r--r--lib/core/init.nom11
-rw-r--r--lib/core/io.nom29
-rw-r--r--lib/core/math.nom212
-rw-r--r--lib/core/metaprogramming.nom459
-rw-r--r--lib/core/operators.nom263
-rw-r--r--lib/core/text.nom78
-rw-r--r--lib/file_hash/init.nom (renamed from lib/file_hash.nom)4
-rw-r--r--lib/filesystem/init.nom (renamed from lib/os.nom)15
-rw-r--r--lib/progressbar/init.nom16
-rw-r--r--lib/shell/init.nom12
-rw-r--r--lib/things.nom2
-rwxr-xr-xlib/tools/find.nom95
-rwxr-xr-xlib/tools/format.nom45
-rwxr-xr-xlib/tools/parse.nom47
-rwxr-xr-xlib/tools/repl.nom86
-rwxr-xr-xlib/tools/replace.nom147
-rwxr-xr-xlib/tools/test.nom65
-rwxr-xr-xlib/tools/upgrade.nom43
26 files changed, 2494 insertions, 17 deletions
diff --git a/lib/base64.nom b/lib/base64/init.nom
index 54f785f..54f785f 100644
--- a/lib/base64.nom
+++ b/lib/base64/init.nom
diff --git a/lib/commandline/init.nom b/lib/commandline/init.nom
new file mode 100644
index 0000000..f28e02b
--- /dev/null
+++ b/lib/commandline/init.nom
@@ -0,0 +1,11 @@
+#
+ A library defining some command line program functionality
+
+(command line program with $args $body) parses as:
+ externally (run with $args) means $body
+ if (this file was run directly):
+ run with (the command line arguments)
+
+externally (usage $) means:
+ say "Usage: \$"
+ exit 1
diff --git a/lib/consolecolor.nom b/lib/consolecolor/init.nom
index d1da247..d1da247 100644
--- a/lib/consolecolor.nom
+++ b/lib/consolecolor/init.nom
diff --git a/lib/core/collections.nom b/lib/core/collections.nom
new file mode 100644
index 0000000..4cf54cd
--- /dev/null
+++ b/lib/core/collections.nom
@@ -0,0 +1,145 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file contains code that supports manipulating and using collections like lists
+ and dictionaries.
+
+use "core/metaprogramming"
+use "core/control_flow"
+use "core/operators"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# List functionality:
+test:
+ $list = [1, 2, 3, 4, 5]
+ $visited = {}
+ for $i = $x in $list:
+ $visited.$i = (yes)
+ assume ($visited == {.1, .2, .3, .4, .5})
+ $visited = {}
+ for $x in $list:
+ $visited.$x = (yes)
+ assume ($visited == {.1, .2, .3, .4, .5})
+ assume (($list, 2 nd to last) == 4)
+ assume (($list, first) == 1)
+ assume ($list, has 3)
+ assume (($list, index of 3) == 3)
+ assume ((size of $list) == 5)
+ $list, add 6
+ assume (($list, last) == 6)
+ $list, pop
+ assume (($list, last) == 5)
+ $list, remove index 1
+ assume (($list, first) == 2)
+ assume (([1, 2] + [3, 4]) == [1, 2, 3, 4])
+
+# Dict functionality
+test:
+ $dict = {.x = 1, .y = 2, .z = 3}
+ assume (size of $dict) == 3
+ assume [: for $k = $v in {.x = 1}: add {.key = $k, .value = $v}] ==
+ [{.key = "x", .value = 1}]
+ assume ({.x = 1, .y = 1} + {.y = 10, .z = 10}) == {.x = 1, .y = 11, .z = 10}
+ assume ({.x = 1, .y = 1} | {.y = 10, .z = 10}) == {.x = 1, .y = 1, .z = 10}
+ assume ({.x = 1, .y = 1} & {.y = 10, .z = 10}) == {.y = 1}
+ assume ({.x = 1, .y = 1} ~ {.y = 10, .z = 10}) == {.x = 1, .z = 10}
+
+test:
+ assume (([[1, 2], [3, 4]] flattened) == [1, 2, 3, 4])
+
+externally ($lists flattened) means:
+ $flat = []
+ for $item in recursive $lists:
+ if ($item is a "List"):
+ for $ in $item:
+ recurse $item on $
+ ..else:
+ $flat, add $item
+ return $flat
+
+test:
+ assume ((entries in {.x = 1}) == [{.key = "x", .value = 1}])
+
+(entries in $dict) parses as [
+ : for $k = $v in $dict:
+ add {.key = $k, .value = $v}
+]
+
+test:
+ assume ((keys in {.x = 1}) == ["x"])
+
+[keys in $dict, keys of $dict] all parse as [: for $k = $v in $dict: add $k]
+test:
+ assume ((values in {.x = 1}) == [1])
+[values in $dict, values of $dict] all parse as [: for $k = $v in $dict: add $v]
+
+# Metatable stuff
+test:
+ $t = {}
+ set $t's metatable to {.__tostring = ($ -> "XXX")}
+ assume ("\$t" == "XXX")
+
+(set $dict's metatable to $metatable) compiles to
+ "setmetatable(\($dict as lua expr), \($metatable as lua expr));"
+
+[$'s metatable, $'metatable] all compile to "getmetatable(\($ as lua expr))"
+test:
+ assume (({} with fallback $ -> ($ + 1)).10 == 11)
+
+($dict with fallback $key -> $value) compiles to ("
+ (function(d)
+ local mt = {}
+ for k,v in pairs(getmetatable(d) or {}) do mt[k] = v end
+ mt.__index = function(self, \($key as lua expr))
+ local value = \($value as lua expr)
+ self[\($key as lua expr)] = value
+ return value
+ end
+ return setmetatable(d, mt)
+ end)(\($dict as lua expr))
+")
+
+# Sorting
+test:
+ $x = [3, 1, 2]
+ sort $x
+ assume ($x == [1, 2, 3])
+ sort $x by $ = (- $)
+ assume ($x == [3, 2, 1])
+ $keys = {.1 = 999, .2 = 0, .3 = 50}
+ sort $x by $ = $keys.$
+ assume ($x == [2, 3, 1])
+(sort $items) compiles to "table.sort(\($items as lua expr));"
+[sort $items by $item = $key_expr, sort $items by $item -> $key_expr]
+..all parse as
+ do:
+ $keys = ({} with fallback $item -> $key_expr)
+ lua> "table.sort(\$items, function(x,y) return \$keys[x] < \$keys[y] end)"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ assume ((sorted [3, 1, 2]) == [1, 2, 3])
+
+externally [$items sorted, sorted $items] all mean:
+ $copy = [: for $ in $items: add $]
+ sort $copy
+ return $copy
+
+[$items sorted by $item = $key, $items sorted by $item -> $key] all parse as
+ result of:
+ $copy = [: for $ in $items: add $]
+ sort $copy by $item = $key
+ return $copy
+
+test:
+ assume ((unique [1, 2, 1, 3, 2, 3]) == [1, 2, 3])
+
+externally (unique $items) means:
+ $unique = []
+ $seen = {}
+ for $ in $items:
+ unless $seen.$:
+ $unique, add $
+ $seen.$ = (yes)
+ return $unique
diff --git a/lib/core/control_flow.nom b/lib/core/control_flow.nom
new file mode 100644
index 0000000..b0c4f27
--- /dev/null
+++ b/lib/core/control_flow.nom
@@ -0,0 +1,489 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file contains compile-time actions that define basic control flow structures
+ like "if" statements and loops.
+
+use "core/metaprogramming"
+use "core/operators"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# No-Op
+test:
+ do nothing
+(do nothing) compiles to ""
+
+# Conditionals
+test:
+ if (no):
+ fail "conditional fail"
+
+(if $condition $if_body) compiles to ("
+ if \($condition as lua expr) then
+ \($if_body as lua)
+ end
+")
+
+test:
+ unless (yes):
+ fail "conditional fail"
+
+(unless $condition $unless_body) parses as (if (not $condition) $unless_body)
+[
+ if $condition $if_body else $else_body, unless $condition $else_body else $if_body
+] all compile to ("
+ if \($condition as lua expr) then
+ \($if_body as lua)
+ else
+ \($else_body as lua)
+ end
+")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# Conditional expression (ternary operator)
+# Note: this uses a function instead of "(condition and if_expr or else_expr)"
+ because that breaks if $if_expr is falsey, e.g. "x < 5 and false or 99"
+test:
+ assume ((1 if (yes) else 2) == 1)
+ assume ((1 if (no) else 2) == 2)
+
+[
+ $when_true_expr if $condition else $when_false_expr
+ $when_true_expr if $condition otherwise $when_false_expr
+ $when_false_expr unless $condition else $when_true_expr
+ $when_false_expr unless $condition then $when_true_expr
+] all compile to:
+ # 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 ("
+ (\($condition as lua expr) and \($when_true_expr as lua expr) or \
+ ..\($when_false_expr as lua expr))
+ ")
+ ..else:
+ # 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 ("
+ ((function()
+ if \($condition as lua expr) then
+ return \($when_true_expr as lua expr)
+ else
+ return \($when_false_expr as lua expr)
+ end
+ end)())
+ ")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# GOTOs
+test:
+ $i = 0
+ --- $loop ---
+ $i += 1
+ unless ($i == 10):
+ go to $loop
+ assume ($i == 10)
+ --- (Loop) ---
+ $i -= 1
+ unless ($i == 0):
+ go to (Loop)
+ assume ($i == 0)
+
+(--- $label ---) compiles to ("
+ ::label_\(
+ ($label.stub, as lua id) if ($label.type == "Action") else
+ $label as lua identifier
+ )::
+")
+
+(go to $label) compiles to ("
+ goto label_\(
+ ($label.stub, as lua id) if ($label.type == "Action") else
+ $label as lua identifier
+ )
+")
+
+# Basic loop control
+(stop $var) compiles to:
+ if $var:
+ return (Lua "goto stop_\($var as lua identifier)")
+ ..else:
+ return (Lua "break")
+
+(do next $var) compiles to:
+ if $var:
+ return (Lua "goto continue_\($var as lua identifier)")
+ ..else:
+ return (Lua "goto continue")
+
+(---stop $var ---) compiles to "::stop_\($var as lua identifier)::"
+(---next $var ---) compiles to "::continue_\($var as lua identifier)::"
+
+# While loops
+test:
+ $x = 0
+ repeat while ($x < 10): $x += 1
+ assume ($x == 10)
+ repeat while ($x < 20): stop
+ assume ($x == 10)
+ repeat while ($x < 20):
+ $x += 1
+ if (yes):
+ do next
+ fail "Failed to 'do next'"
+ assume ($x == 20)
+
+(repeat while $condition $body) compiles to:
+ $lua =
+ Lua ("
+ while \($condition as lua expr) do
+ \($body as lua)
+ ")
+
+ if ($body has subtree \(do next)):
+ $lua, add "\n ::continue::"
+
+ $lua, add "\nend --while-loop"
+ return $lua
+
+(repeat $body) parses as (repeat while (yes) $body)
+(repeat until $condition $body) parses as (repeat while (not $condition) $body)
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ $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):
+ if ($inner == 2):
+ $nums, add -2
+ do next $inner
+ $nums, add $inner
+ if ($inner == 5):
+ stop $outer
+ assume ($nums == [1, -2, 3, -2, 3, 4, 3, 4, 5])
+
+# Numeric range for loops
+[
+ 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
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(for $var in $start to $stop $body) parses as
+ for $var in $start to $stop via 1 $body
+
+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)
+test:
+ $a = [10, 20, 30, 40, 50]
+ $b = []
+ for $x in $a:
+ $b, add $x
+ assume ($a == $b)
+ $b = []
+ for $x in $a:
+ if ($x == 10):
+ do next $x
+
+ if ($x == 50):
+ stop $x
+
+ $b, add $x
+ assume ($b == [20, 30, 40])
+
+# 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 =
+ Lua ("
+ for \($key as lua identifier),\($value as lua identifier) in pairs(\
+ ..\($iterable 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 $key)):
+ $lua, add "\n " (\(---next $key ---) as lua)
+
+ if ($body has subtree \(do next $value)):
+ $lua, add "\n " (\(---next $value ---) as lua)
+
+ $lua, add "\nend --foreach-loop"
+ $stop_labels = (Lua "")
+ if ($body has subtree \(stop $key)):
+ $stop_labels, add "\n" (\(---stop $key ---) as lua)
+
+ if ($body has subtree \(stop $value)):
+ $stop_labels, add "\n" (\(---stop $value ---) as lua)
+
+ if ((size of "\$stop_labels") > 0):
+ $inner_lua = $lua
+ $lua = (Lua "do -- scope for stopping for $ = $ loop\n ")
+ $lua, add $inner_lua $stop_labels "\nend"
+
+ return $lua
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ when:
+ (1 == 2) (100 < 0):
+ fail "bad conditional"
+ (1 == 0) (1 == 1) $not_a_variable.x: do nothing
+ (1 == 1):
+ fail "bad conditional"
+
+ (1 == 2):
+ fail "bad conditional"
+
+ else:
+ fail "bad conditional"
+
+# Multi-branch conditional (if..elseif..else)
+(when $body) compiles to:
+ $code = (Lua "")
+ $clause = "if"
+ $else_allowed = (yes)
+ unless ($body.type is "Block"):
+ compile error at $body "'if' expected a Block, but got a \($body.type)."
+ "Perhaps you forgot to put a ':' after 'if'?"
+
+ for $line in $body:
+ unless
+ (($line.type is "Action") and ((size of $line) >= 2)) and
+ $line.(size of $line) is "Block" syntax tree
+ ..:
+ compile error at $line "Invalid line for the body of an 'if' block." ("
+ Each line should contain one or more conditional expressions followed by a block, or "else"\
+ .. followed by a block.
+ ")
+ $action = $line.(size of $line)
+ if (($line.1 is "else") and ((size of $line) == 2)):
+ unless $else_allowed:
+ compile error at $line "You can't have two 'else' blocks."
+ "Merge all of the 'else' blocks together."
+
+ unless ((size of "\$code") > 0):
+ compile error at $line
+ .."You can't have an 'else' block without a preceding condition" ("
+ If you want the code in this block to always execute, you don't need a conditional block \
+ ..around it. Otherwise, make sure the 'else' block comes last.
+ ")
+
+ $code, add "\nelse\n " ($action as lua)
+ $else_allowed = (no)
+ ..else:
+ $code, add $clause " "
+ for $i in 1 to ((size of $line) - 1):
+ if ($i > 1):
+ $code, add " or "
+ $code, add ($line.$i as lua expr)
+ $code, add " then\n " ($action as lua)
+ $clause = "\nelseif"
+
+ if ((size of "\$code") == 0):
+ compile error at $body "'if' block has an empty body."
+ "This means nothing would happen, so the 'if' block should be deleted."
+
+ $code, add "\nend --when"
+ return $code
+
+test:
+ if 5 is:
+ 1 2 3:
+ fail "bad switch statement"
+
+ 4 5:
+ do nothing
+
+ 5 6:
+ fail "bad switch statement"
+
+ else:
+ fail "bad switch statement"
+
+# Switch statement
+[if $branch_value is $body, when $branch_value is $body] all compile to:
+ $code = (Lua "")
+ $clause = "if"
+ $else_allowed = (yes)
+ define mangler
+ unless ($body.type is "Block"):
+ compile error at $body "'if' expected a Block, but got a \($body.type)"
+ "Perhaps you forgot to put a ':' after the 'is'?"
+
+ for $line in $body:
+ unless
+ (($line.type is "Action") and ((size of $line) >= 2)) and
+ $line.(size of $line) is "Block" syntax tree
+ ..:
+ compile error at $line "Invalid line for 'if' block." ("
+ Each line should contain expressions followed by a block, or "else" followed by a block
+ ")
+ $action = $line.(size of $line)
+ if (($line.1 is "else") and ((size of $line) == 2)):
+ unless $else_allowed:
+ compile error at $line "You can't have two 'else' blocks."
+ "Merge all of the 'else' blocks together."
+
+ unless ((size of "\$code") > 0):
+ compile error at $line
+ .."You can't have an 'else' block without a preceding condition" ("
+ If you want the code in this block to always execute, you don't need a conditional block \
+ ..around it. Otherwise, make sure the 'else' block comes last.
+ ")
+
+ $code, add "\nelse\n " ($action as lua)
+ $else_allowed = (no)
+ ..else:
+ $code, add $clause " "
+ for $i in 1 to ((size of $line) - 1):
+ if ($i > 1):
+ $code, add " or "
+ $code, add "\(mangle "branch value") == " ($line.$i as lua expr)
+ $code, add " then\n " ($action as lua)
+ $clause = "\nelseif"
+
+ if ((size of "\$code") == 0):
+ compile error at $body "'if' block has an empty body."
+ "This means nothing would happen, so the 'if' block should be deleted."
+
+ $code, add "\nend --when"
+ return
+ Lua ("
+ do --if $ is...
+ local \(mangle "branch value") = \($branch_value as lua expr)
+ \$code
+ end -- if $ is...
+ ")
+
+# Do/finally
+(do $action) compiles to ("
+ do
+ \($action as lua)
+ end -- do
+")
+
+test:
+ assume ((result of: return 99) == 99)
+
+# Inline thunk:
+(result of $body) compiles to "\(\(-> $body) as lua)()"
+test:
+ $t = [1, [2, [[3], 4], 5, [[[6]]]]]
+ $flat = []
+ for $ in recursive $t:
+ if ((lua type of $) is "table"):
+ for $2 in $:
+ recurse $ on $2
+ ..else:
+ $flat, add $
+ assume (sorted $flat) == [1, 2, 3, 4, 5, 6]
+
+# Recurion control flow
+(recurse $v on $x) compiles to
+ Lua "table.insert(_stack_\($v as lua expr), \($x as lua expr))"
+(for $var in recursive $structure $body) compiles to:
+ $lua =
+ Lua ("
+ do
+ local _stack_\($var as lua expr) = List{\($structure as lua expr)}
+ while #_stack_\($var as lua expr) > 0 do
+ \($var as lua expr) = table.remove(_stack_\($var as lua expr), 1)
+ \($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 -- Recursive loop"
+ if ($body has subtree \(stop $var)):
+ $lua, add "\n \(\(---stop $var ---) as lua)"
+ $lua, add "\nend -- Recursive scope"
+ return $lua
diff --git a/lib/core/coroutines.nom b/lib/core/coroutines.nom
new file mode 100644
index 0000000..6a99f7e
--- /dev/null
+++ b/lib/core/coroutines.nom
@@ -0,0 +1,40 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file defines the code that creates and manipulates coroutines
+
+use "core/metaprogramming"
+use "core/operators"
+use "core/control_flow"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ $co =
+ ->:
+ yield 4
+ yield 5
+ repeat 3 times:
+ yield 6
+ $nums = []
+ for $ in coroutine $co:
+ $nums, add $
+
+ unless ($nums == [4, 5, 6, 6, 6]):
+ fail "Coroutine iteration failed"
+
+ $d = {.x = 0}
+ $co2 =
+ coroutine:
+ $d.x += 1
+ yield 1
+ $d.x += 1
+ yield
+ $d.x += 1
+ repeat while ((coroutine status of $co2) != "dead"): resume $co2
+ assume $d.x == 3
+(coroutine $body) parses as (coroutine from (-> $body))
+(for $ in coroutine $co $body) compiles to ("
+ for \($ as lua expr) in coroutine_wrap(\($co as lua expr)) do
+ \($body as lua)
+ end
+")
diff --git a/lib/core/errors.nom b/lib/core/errors.nom
new file mode 100644
index 0000000..c878bb7
--- /dev/null
+++ b/lib/core/errors.nom
@@ -0,0 +1,131 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file contains basic error reporting code
+
+use "core/metaprogramming"
+use "core/operators"
+use "core/control_flow"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(fail $msg) compiles to "error(\(($msg as lua expr) if $msg else "nil"), 0);"
+(assume $condition) compiles to:
+ lua> ("
+ local \$assumption = 'Assumption failed: '..tostring((\$condition):get_source_code())
+ ")
+
+ return
+ Lua ("
+ if not \($condition as lua expr) then
+ error(\(quote "\$assumption"), 0)
+ end
+ ")
+
+(assume $a == $b) compiles to:
+ lua> "local \$assumption = 'Assumption failed: '..tostring(\(\($a == $b) as nomsu))"
+
+ define mangler
+
+ return
+ Lua ("
+ do
+ local \(mangle "a"), \(mangle "b") = \($a as lua expr), \($b as lua expr)
+ if \(mangle "a") ~= \(mangle "b") then
+ error(\(quote "\$assumption").."\\n"..tostring(\(mangle "a")).." != "..tostring(\
+ ..\(mangle "b")), 0)
+ end
+ end
+ ")
+
+test:
+ try: fail
+ $worked = (no)
+ try:
+ fail "xx"
+ ..if it fails with $failure:
+ $worked = (yes)
+ ..if it succeeds:
+ fail "'try' incorrectly ran success case."
+ assume $failure == "xx"
+ unless $worked:
+ fail "'try' failed to recover from failure"
+
+# Try/except
+[
+ try $action if it succeeds $success if it fails with $msg $fallback
+ try $action if it fails with $msg $fallback if it succeeds $success
+] all compile to:
+ $success_lua = ($success as lua)
+ if ((#"\$success_lua") > 0):
+ $success_lua, add "\n"
+ $success_lua, prepend "-- Success:\n"
+ $success_lua,
+ add "if not _fell_through then return table.unpack(_result, 2) end"
+ $fallback_lua = ($fallback as lua)
+ if ((#"\$fallback_lua") > 0):
+ $msg_lua = ($msg as lua expr)
+ if ((#"\$msg_lua") > 0):
+ $fallback_lua, prepend "\n\$msg_lua = _result[2]\n"
+ if ($msg_lua, text, is lua id):
+ $fallback_lua, add free vars [($msg_lua, text)]
+ $fallback_lua, prepend "-- Failure:"
+ return
+ Lua ("
+ do
+ local _fell_through = false
+ local _result = {pcall(function()
+ \($action as lua)
+ _fell_through = true
+ end)}
+ if _result[1] then
+ \$success_lua
+ else
+ \$fallback_lua
+ end
+ end
+ ")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(try $action) parses as
+ try $action if it succeeds (do nothing) if it fails (do nothing)
+
+(try $action if it fails $fallback) parses as
+ try $action if it succeeds (do nothing) if it fails $fallback
+
+(try $action if it fails with $msg $fallback) parses as
+ try $action if it succeeds (do nothing) if it fails with $msg $fallback
+
+(try $action if it succeeds $success) parses as
+ try $action if it succeeds $success if it fails (do nothing)
+
+(try $action if it fails $fallback if it succeeds $success) parses as
+ try $action if it fails with (=lua "") $fallback if it succeeds $success
+
+(try $action if it succeeds $success if it fails $fallback) parses as
+ try $action if it succeeds $success if it fails with (=lua "") $fallback
+
+test:
+ $success = (no)
+ try:
+ do: fail
+ ..then always:
+ $success = (yes)
+ ..if it succeeds:
+ fail "'try ... then always ...' didn't propagate failure"
+
+ unless $success:
+ fail "'try ... then always ...' didn't execute the 'always' code"
+
+(do $action then always $final_action) compiles to ("
+ do -- do/then always
+ local _fell_through = false
+ local _results = {pcall(function()
+ \($action as lua)
+ _fell_through = true
+ end)}
+ \($final_action as lua)
+ if not _results[1] then error(_results[2], 0) end
+ if not _fell_through then return table.unpack(_results, 2) end
+ end
+")
diff --git a/lib/core/id.nom b/lib/core/id.nom
new file mode 100644
index 0000000..d2427b5
--- /dev/null
+++ b/lib/core/id.nom
@@ -0,0 +1,66 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ A simple UUID function based on RFC 4122: http://www.ietf.org/rfc/rfc4122.txt
+
+use "core/metaprogramming"
+use "core/operators"
+use "core/math"
+use "core/collections"
+use "core/control_flow"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+$NaN_surrogate = {}
+$nil_surrogate = {}
+$obj_by_id = {}
+set $obj_by_id's metatable to {.__mode = "v"}
+$id_by_obj = {}
+set $id_by_obj's metatable to {
+ .__mode = "k"
+ .__index =
+ for ($self $key):
+ if ($key == (nil)):
+ return $self.$nil_surrogate
+
+ if ($key != $key):
+ return $self.$NaN_surrogate
+
+ --- (retry) ---
+ $id = (uuid)
+ if ($obj_by_id.$id != (nil)): go to (retry)
+ $self.$key = $id
+ $obj_by_id.$id = $key
+ return $id
+}
+
+externally (uuid) means:
+ # Set all the other bits to randomly (or pseudo-randomly) chosen values.
+ $bytes = [
+ # time-low, time-mid, time-high-and-version
+ randint (2 ^ (4 * 8)), randint (2 ^ (2 * 8)), randint (2 ^ (2 * 8 - 4))
+ # clock-seq-and-reserved, clock-seq-low
+ randint (2 ^ (1 * 8 - 2)), randint (2 ^ (1 * 8)), randint (2 ^ (3 * 8))
+ # node
+ randint (2 ^ (3 * 8))
+ ]
+
+ # Set the four most significant bits (bits 12 through 15) of the
+ # time_hi_and_version field to the 4-bit version number from
+ # Section 4.1.3.
+ $bytes.3 += 0x4000
+
+ # Set the two most significant bits (bits 6 and 7) of the
+ # clock_seq_hi_and_reserved to zero and one, respectively.
+ $bytes.4 += 0xC0
+ return (=lua "('%08x-%04x-%04x-%02x%02x-%6x%6x'):format(unpack(\$bytes))")
+
+# For strict identity checking, use ($x's id) == ($y's id)
+test:
+ assume (([] == []) and ((id of []) != (id of [])))
+ seed random with 0
+ $x = []
+ assume ((id of $x) == (id of $x))
+ seed random with 0
+ assume ((id of $x) != (id of []))
+ seed random
+externally [id of $, $'s id, $'id] all mean $id_by_obj.$
diff --git a/lib/core/init.nom b/lib/core/init.nom
new file mode 100644
index 0000000..0c8051d
--- /dev/null
+++ b/lib/core/init.nom
@@ -0,0 +1,11 @@
+# Export everything
+export "core/metaprogramming"
+export "core/operators"
+export "core/control_flow"
+export "core/errors"
+export "core/collections"
+export "core/coroutines"
+export "core/math"
+export "core/id"
+export "core/io"
+export "core/text"
diff --git a/lib/core/io.nom b/lib/core/io.nom
new file mode 100644
index 0000000..7afe889
--- /dev/null
+++ b/lib/core/io.nom
@@ -0,0 +1,29 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file contains basic input/output code
+
+use "core/metaprogramming"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(say $message) compiles to:
+ lua> ("
+ if \$message.type == "Text" then
+ return LuaCode("say(", \($message as lua expr), ");");
+ else
+ return LuaCode("say(tostring(", \($message as lua expr), "));");
+ end
+ ")
+
+(say $message inline) compiles to:
+ lua> ("
+ if \$message.type == "Text" then
+ return LuaCode("io.write(", \($message as lua expr), ")");
+ else
+ return LuaCode("io.write(tostring(", \($message as lua expr), "))");
+ end
+ ")
+
+externally (ask $prompt) means:
+ $io.write $prompt
+ return ($io.read())
diff --git a/lib/core/math.nom b/lib/core/math.nom
new file mode 100644
index 0000000..685ab1e
--- /dev/null
+++ b/lib/core/math.nom
@@ -0,0 +1,212 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file defines some common math literals and functions
+
+use "core/metaprogramming"
+use "core/text"
+use "core/operators"
+use "core/control_flow"
+use "core/collections"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# Literals:
+test:
+ unless (all of [inf, NaN, pi, tau, golden ratio, e]):
+ fail "math constants failed"
+ $nan = (NaN)
+ unless ($nan != $nan):
+ fail "NaN failed"
+[infinity, inf] all compile to "math.huge"
+[not a number, NaN, nan] all compile to "(0/0)"
+[pi, Pi, PI] all compile to "math.pi"
+[tau, Tau, TAU] all compile to "(2*math.pi)"
+(golden ratio) compiles to "((1+math.sqrt(5))/2)"
+(e) compiles to "math.exp(1)"
+
+# Functions:
+test:
+ assume (("5" as a number) == 5)
+external $($ as a number) = $(tonumber $)
+external $($ as number) = $(tonumber $)
+test:
+ unless
+ all of [
+ abs 5, | 5 |, sqrt 5, √ 5, sine 5, cosine 5, tangent 5, arc sine 5, arc cosine 5
+ arc tangent 5, arc tangent 5 / 10, hyperbolic sine 5, hyperbolic cosine 5
+ hyperbolic tangent 5, e^ 5, ln 5, log 5 base 2, floor 5, ceiling 5, round 5
+ ]
+ ..:
+ fail "math functions failed"
+external [$(absolute value $), $(absolute value of $), $(| $ |), $(abs $)] =
+ [$math.abs, $math.abs, $math.abs, $math.abs]
+external [$(square root $), $(square root of $), $(√ $), $(sqrt $)] =
+ [$math.sqrt, $math.sqrt, $math.sqrt, $math.sqrt]
+external [$(sine $), $(sin $)] = [$math.sin, $math.sin]
+external [$(cosine $), $(cos $)] = [$math.cos, $math.cos]
+external [$(tangent $), $(tan $)] = [$math.tan, $math.tan]
+external [$(arc sine $), $(asin $)] = [$math.asin, $math.asin]
+external [$(arc cosine $), $(acos $)] = [$math.acos, $math.acos]
+external [$(arc tangent $), $(atan $)] = [$math.atan, $math.atan]
+external [$(arc tangent $y / $x), $(atan2 $y $x)] = [$math.atan2, $math.atan2]
+external [$(hyperbolic sine $), $(sinh $)] = [$math.sinh, $math.sinh]
+external [$(hyperbolic cosine $), $(cosh $)] = [$math.cosh, $math.cosh]
+external [$(hyperbolic tangent $), $(tanh $)] = [$math.tanh, $math.tanh]
+external [$(e^ $), $(exp $)] = [$math.exp, $math.exp]
+external [$(natural log $), $(ln $), $(log $), $(log $ base $)] = [$math.log, $math.log, $math.log, $math.log]
+external $(floor $) = $math.floor
+external [$(ceiling $), $(ceil $)] = [$math.ceil, $math.ceil]
+externally [round $, $ rounded] all mean
+ floor ($ + 0.5)
+test:
+ unless ((463 to the nearest 100) == 500): fail "rounding failed"
+ unless ((2.6 to the nearest 0.25) == 2.5): fail "rounding failed"
+
+externally ($n to the nearest $rounder) means
+ $rounder * (floor ($n / $rounder + 0.5))
+
+# Any/all
+externally [all of $items, all $items] all mean:
+ for $ in $items:
+ unless $:
+ return (no)
+ return (yes)
+[not all of $items, not all $items] all parse as (not (all of $items))
+externally [any of $items, any $items] all mean:
+ for $ in $items:
+ if $:
+ return (yes)
+ return (no)
+[none of $items, none $items] all parse as (not (any of $items))
+
+# Sum/product
+externally [sum of $items, sum $items] all mean:
+ $total = 0
+ for $ in $items:
+ $total += $
+ return $total
+
+externally [product of $items, product $items] all mean:
+ $prod = 1
+ for $ in $items:
+ $prod *= $
+ return $prod
+
+externally [avg of $items, average of $items] all mean
+ (sum of $items) / (size of $items)
+
+# Min/max
+externally [min of $items, smallest of $items, lowest of $items] all mean:
+ $best = (nil)
+ for $ in $items:
+ if (($best == (nil)) or ($ < $best)): $best = $
+ return $best
+
+externally [
+ max of $items, biggest of $items, largest of $items, highest of $items
+] all mean:
+ $best = (nil)
+ for $ in $items:
+ if (($best == (nil)) or ($ > $best)): $best = $
+ return $best
+
+test:
+ assume ((min of [3, -4, 1, 2] by $ = ($ * $)) == 1)
+ assume ((max of [3, -4, 1, 2] by $ = ($ * $)) == -4)
+
+(min of $items by $item = $value_expr) parses as
+ result of:
+ $best = (nil)
+ $best_key = (nil)
+ for $item in $items:
+ $key = $value_expr
+ if (($best == (nil)) or ($key < $best_key)):
+ $best = $item
+ $best_key = $key
+ return $best
+
+(max of $items by $item = $value_expr) parses as
+ result of:
+ $best = (nil)
+ $best_key = (nil)
+ for $item in $items:
+ $key = $value_expr
+ if (($best == (nil)) or ($key > $best_key)):
+ $best = $item
+ $best_key = $key
+ return $best
+
+test:
+ assume (100 clamped between 0 and 10) == 10
+
+externally ($ clamped between $min and $max) means:
+ when:
+ ($ < $min):
+ return $min
+
+ ($ > $max):
+ return $max
+
+ else:
+ return $
+
+test:
+ assume (-0.1 smoothed by 2.7) == 0
+ assume (0 smoothed by 2.7) == 0
+ assume (0.5 smoothed by 2.7) == 0.5
+ assume (1 smoothed by 2.7) == 1
+ assume (1.1 smoothed by 2.7) == 1
+
+externally ($ smoothed by $smoothness) means:
+ $ = ($ clamped between 0 and 1)
+ if ($smoothness == 0): return $
+ $k = (2 ^ $smoothness)
+ if ($ < 0.5):
+ return (0.5 * (2 * $) ^ $k)
+ ..else:
+ return (1 - 0.5 * (2 - 2 * $) ^ $k)
+
+test:
+ assume (5 to 7 mixed by -1.0) == 5
+ assume (5 to 7 mixed by 0.0) == 5
+ assume (5 to 7 mixed by 0.5) == 6
+ assume (5 to 7 mixed by 1.0) == 7
+ assume (5 to 7 mixed by 2.0) == 7
+
+externally ($lo to $hi mixed by $amount) means:
+ $ = ($amount clamped between 0 and 1)
+ return ((1 - $) * $lo + $ * $hi)
+
+test:
+ assume ([0, 1, 11] mixed by 0.0) == 0
+ assume ([0, 1, 11] mixed by 0.25) == 0.5
+ assume ([0, 1, 11] mixed by 0.5) == 1
+ assume ([0, 1, 11] mixed by 0.75) == 6
+ assume ([0, 1, 11] mixed by 1.0) == 11
+ assume ([99] mixed by 0.5) == 99
+
+externally ($nums mixed by $amount) means:
+ $ = ($amount clamped between 0 and 1)
+ $i = (1 + ($ * ((#$nums) - 1)))
+ if ((floor $i) == (#$nums)):
+ return $nums.(floor $i)
+ [$lo, $hi] = [$nums.(floor $i), $nums.(floor ($i + 1))]
+ return ($lo to $hi mixed by ($i mod 1))
+
+# Random functions
+externally (seed random with $) means:
+ lua> ("
+ math.randomseed(\$);
+ for i=1,20 do math.random(); end
+ ")
+(seed random) parses as (seed random with (=lua "os.time()"))
+[random number, random, rand] all compile to "math.random()"
+[random int $n, random integer $n, randint $n] all compile to
+ "math.random(\($n as lua expr))"
+
+[random from $low to $high, random number from $low to $high, rand $low $high]
+..all compile to "math.random(\($low as lua expr), \($high as lua expr))"
+
+externally [
+ random choice from $elements, random choice $elements, random $elements
+] all mean (=lua "\$elements[math.random(#\$elements)]")
diff --git a/lib/core/metaprogramming.nom b/lib/core/metaprogramming.nom
new file mode 100644
index 0000000..936f9bd
--- /dev/null
+++ b/lib/core/metaprogramming.nom
@@ -0,0 +1,459 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This File contains actions for making actions and compile-time actions and some helper
+ functions to make that easier.
+
+lua> "NOMSU_CORE_VERSION = 14"
+lua> "NOMSU_LIB_VERSION = 8"
+lua> ("
+ do
+ local mangle_index = 0
+ function mangler()
+ local my_mangle_index = mangle_index
+ mangle_index = mangle_index + 1
+ return function(varname)
+ return (varname..(("\\3%X"):format(my_mangle_index))):as_lua_id()
+ end
+ end
+ end
+ COMPILE_RULES["define mangler"] = function(\(nomsu environment))
+ return LuaCode("local mangle = mangler()")
+ end
+")
+
+lua> ("
+ COMPILE_RULES["1 ->"] = function(\(nomsu environment), \$args, \$body)
+ if \$args and not \$body then \$args, \$body = {}, \$args end
+ local body_lua = SyntaxTree:is_instance(\$body) and \(nomsu environment):compile(\$body) or \$body
+ if SyntaxTree:is_instance(\$body) and \$body.type ~= "Block" then body_lua:prepend("\
+ ..return ") end
+ local lua = LuaCode("(function(")
+ if SyntaxTree:is_instance(\$args) and (\$args.type == "Action" or \$args.type == "MethodCall") then
+ \$args = \$args:get_args()
+ elseif SyntaxTree:is_instance(\$args) and \$args.type == "Var" then \$args = {\$args} end
+ for i, arg in ipairs(\$args) do
+ local arg_lua = SyntaxTree:is_instance(arg) and \(nomsu environment):compile(arg):text() or arg
+ if arg_lua == "..." then
+ if i < #\$args then
+ compile_error_at(SyntaxTree:is_instance(arg) and arg or nil,
+ "Extra arguments must come last.", "Try removing any arguments after \
+ ..(*extra arguments*)")
+ end
+ elseif not arg_lua:is_lua_id() then
+ compile_error_at(SyntaxTree:is_instance(arg) and arg or nil,
+ "This does not compile to a Lua identifier, so it can't be used as a function \
+ ..argument.",
+ "This should probably be a Nomsu variable instead (like $x).")
+ end
+ lua:add(i > 1 and ", " or "", arg_lua)
+ body_lua:remove_free_vars({arg_lua})
+ end
+ body_lua:declare_locals()
+ lua:add(")\\n ", body_lua, "\\nend)")
+ return lua
+ end
+ COMPILE_RULES["->"] = COMPILE_RULES["1 ->"]
+ COMPILE_RULES["for"] = COMPILE_RULES["1 ->"]
+")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ (five) compiles to "5"
+
+test:
+ unless ((five) == 5):
+ fail "Compile to expression failed."
+ (loc x) compiles to "local x = 99;"
+
+test:
+ lua> "do"
+ loc x
+ unless ($x is 99):
+ fail "Compile to statements with locals failed."
+ lua> "end"
+ unless ($x is (nil)):
+ fail "Failed to properly localize a variable."
+
+ (asdf) compiles to:
+ $tmp = ""
+ return (Lua $tmp)
+
+test:
+ asdf
+ unless ($tmp is (nil)):
+ fail "compile to is leaking variables"
+
+lua> ("
+ COMPILE_RULES["1 compiles to"] = function(\(nomsu environment), \$action, \$body)
+ local \$args = List{"\(nomsu environment)", unpack(\$action:get_args())}
+ if \$body.type == "Text" then
+ \$body = SyntaxTree{source=\$body.source, type="Action", "Lua", \$body}
+ end
+ return LuaCode("COMPILE_RULES[", \$action:get_stub():as_lua(),
+ "] = ", \(\($args -> $body) as lua))
+ end
+")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+($actions all compile to $body) compiles to:
+ lua> ("
+ if \$actions.type ~= "List" then
+ compile_error(\$actions, "This should be a list of actions.")
+ end
+ local lua = \(\($actions.1 compiles to $body) as lua)
+ local \$args = List{"\(nomsu environment)", unpack(\$actions[1]:get_args())}
+ local \$compiled_args = List{"\(nomsu environment)"};
+ for i=2,#\$args do \$compiled_args[i] = \(nomsu environment):compile(\$args[i]) end
+ for i=2,#\$actions do
+ local alias = \$actions[i]
+ local \$alias_args = List{"\(nomsu environment)", unpack(alias:get_args())}
+ lua:add("\\nCOMPILE_RULES[", alias:get_stub():as_lua(), "] = ")
+ if \$alias_args == \$args then
+ lua:add("COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "]")
+ else
+ lua:add("function(")
+ local \$compiled_alias_args = List{"\(nomsu environment)"};
+ for i=2,#\$alias_args do \$compiled_alias_args[i] = \(nomsu environment):compile(\$alias_args[i]) end
+ lua:concat_add(\$compiled_alias_args, ", ")
+ lua:add(") return COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "](")
+ lua:concat_add(\$compiled_args, ", ")
+ lua:add(") end")
+ end
+ end
+ return lua
+ ")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ (foo $x) means "outer"
+ with [$(foo $)]:
+ (foo $x) means:
+ $y = ($x + 1)
+ return $y
+
+ unless ((foo 10) == 11):
+ fail "Action didn't work."
+
+ unless ($y is (nil)):
+ fail "Action leaked a local into globals."
+
+ (baz $) parses as (foo $)
+ assume ((foo 1) == "outer")
+
+($action means $body) compiles to:
+ lua> ("
+
+ local lua = LuaCode()
+ if \$action.type == "MethodCall" then
+ lua:add(\(nomsu environment):compile(\$action[1]), ".", \$action[2]:get_stub():as_lua_id())
+ elseif \$action.type == "Action" then
+ lua:add(\$action:get_stub():as_lua_id())
+ lua:add_free_vars({\$action:get_stub():as_lua_id()})
+ else
+ compile_error_at(\$action, "Expected an action or method call here")
+ end
+ lua:add(" = ", \(\($action -> $body) as lua), ";")
+ return lua
+ ")
+
+($actions all mean $body) compiles to:
+ lua> ("
+ local lua = \(\($actions.1 means $body) as lua)
+ local first_def = (\$actions[1].type == "MethodCall"
+ and LuaCode(\(nomsu environment):compile(\$actions[1][1]), ".", \$actions[1]:get_stub():as_lua_id())
+ or LuaCode(\$actions[1]:get_stub():as_lua_id()))
+ local \$args = List(\$actions[1]:get_args())
+ for i=2,#\$actions do
+ local alias = \$actions[i]
+ local \$alias_args = List(alias:get_args())
+ lua:add("\\n")
+ if alias.type == "MethodCall" then
+ lua:add(\(nomsu environment):compile(alias[1]), ".", alias:get_stub():as_lua_id())
+ else
+ lua:add(alias:get_stub():as_lua_id())
+ lua:add_free_vars({alias_name})
+ end
+ if \$args == \$alias_args then
+ lua:add(" = ", first_def, ";")
+ else
+ lua:add(" = ", \(\($alias_args -> $actions.1) as lua), ";")
+ end
+ end
+ return lua
+ ")
+
+test:
+ externally (baz1) means:
+ return "baz1"
+ externally (baz2) means "baz2"
+
+test:
+ assume ((baz1) == "baz1")
+ assume ((baz2) == "baz2")
+
+(externally $action means $body) compiles to:
+ lua> ("
+ local lua = \(\($action means $body) as lua)
+ lua:remove_free_vars({\$action:get_stub():as_lua_id()})
+ return lua
+ ")
+
+(externally $actions all mean $body) compiles to:
+ lua> ("
+ local lua = \(\($actions all mean $body) as lua)
+ lua:remove_free_vars(table.map(\$actions, function(a) return a:get_stub():as_lua_id() end))
+ return lua
+ ")
+
+test:
+ (swap $x and $y) parses as
+ do:
+ $tmp = $x
+ $x = $y
+ $y = $tmp
+
+test:
+ [$1, $2] = [1, 2]
+ swap $1 and $2
+ unless (($1 == 2) and ($2 == 1)):
+ fail "'parse $ as $' failed on 'swap $ and $'"
+ [$tmp, $tmp2] = [1, 2]
+ swap $tmp and $tmp2
+ unless (($tmp == 2) and ($tmp2 == 1)):
+ fail "'parse $ as $' variable mangling failed."
+
+($actions all parse as $body) compiles to:
+ lua> ("
+ local replacements = {}
+ if \$actions.type ~= "List" then
+ compile_error(\$actions, "This should be a list.")
+ end
+ for i,arg in ipairs(\$actions[1]:get_args()) do
+ replacements[arg[1]] = \(nomsu environment):compile(arg):text()
+ end
+ local function make_tree(t)
+ if SyntaxTree:is_instance(t) and t.type == "Var" then
+ if replacements[t:as_var()] then
+ return replacements[t:as_var()]
+ else
+ return "SyntaxTree{mangle("..t:as_var():as_lua().."), type="..t.type:as_lua()..", \
+ ..source="..tostring(t.source):as_lua().."}"
+ end
+ elseif SyntaxTree:is_instance(t) then
+ local ret = {}
+ local i = 1
+ for k, v in pairs(t) do
+ if k == i then
+ ret[#ret+1] = make_tree(t[i])
+ i = i + 1
+ elseif k == "source" then
+ ret[#ret+1] = k.."= "..tostring(v):as_lua()
+ elseif lua_type_of(k) == 'string' and k:is_a_lua_id() then
+ ret[#ret+1] = k.."= "..make_tree(v)
+ else
+ ret[#ret+1] = "["..make_tree(k).."]= "..make_tree(v)
+ end
+ end
+ return "SyntaxTree{"..table.concat(ret, ", ").."}"
+ elseif lua_type_of(t) == 'number' then
+ return tostring(t)
+ else
+ return t:as_lua()
+ end
+ end
+ local \$new_body = LuaCode:from(\$body.source,
+ "local mangle = mangler()",
+ "\\nreturn ", make_tree(\$body))
+ local ret = \(\($actions all compile to $new_body) as lua)
+ return ret
+ ")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+[$action parses as $body] all parse as ([$action] all parse as $body)
+externally (in (nomsu environment) $tree as lua expr) means:
+ lua> ("
+ local tree_lua = \(nomsu environment):compile(\$tree)
+ if \$tree.type == 'Block' then
+ tree_lua = LuaCode:from(\$tree.source, '(function()\\n ', tree_lua, '\\nend)()')
+ elseif \$tree.type == 'MethodCall' and #\$tree > 2 then
+ compile_error_at(\$tree, "This must be a single value instead of "..(#\$tree - 1).."\
+ .. method calls.",
+ "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)
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+externally [$var as lua identifier, $var as lua id] all mean:
+ lua> ("
+ local lua = \($var as lua)
+ if not lua:text():is_a_lua_id() then
+ compile_error(\$var,
+ "This is supposed to be something that compiles to a valid Lua identifier.",
+ "This should probably be a variable.")
+ end
+ return lua
+ ")
+
+test:
+ (num args (*extra arguments*)) means (select "#" (*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*))
+ assume (third arg 5 6 7 8) == 7
+
+(*extra arguments*) compiles to "..."
+
+($ is syntax tree) compiles to "SyntaxTree:is_instance(\($ as lua expr))"
+
+externally ($ 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)
+")
+
+externally ($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)()
+")
+
+externally (match $tree with $patt) means:
+ lua> ("
+ if \$patt.type == "Var" then return Dict{[\$patt:as_var()]=\$tree} end
+ if \$patt.type == "Action" and \$patt:get_stub() ~= \$tree:get_stub() then return nil end
+ if #\$patt ~= #\$tree then return nil end
+ local matches = Dict{}
+ for \($i)=1,#\$patt do
+ if SyntaxTree:is_instance(\$tree[\$i]) then
+ local submatch = \(match $tree.$i with $patt.$i)
+ if not submatch then return nil end
+ for k,v in pairs(submatch) do
+ if matches[k] and matches[k] ~= v then return nil end
+ matches[k] = v
+ end
+ end
+ end
+ return matches
+ ")
+
+test:
+ assume
+ (
+ quote ("
+ one
+ "two"
+ ")
+ ) == "\"one\\n\\\"two\\\"\""
+
+(quote $s) compiles to "tostring(\($s as lua expr)):as_lua()"
+test:
+ assume (lua type of {}) == "table"
+ assume (type of {}) == "Dict"
+ assume ({} is a "Dict")
+ assume ("" is text)
+ assume ("" isn't a "Dict")
+externally ($ is text) means (=lua "\(lua type of $) == 'string'")
+externally [$ is not text, $ isn't text] all mean
+ =lua "\(lua type of $) ~= 'string'"
+
+externally (type of $) means:
+ lua> ("
+ local lua_type = \(lua type of $)
+ if lua_type == 'string' then return 'Text'
+ elseif lua_type == 'table' or lua_type == 'userdata' then
+ local mt = getmetatable(\$)
+ if mt and mt.__type then return mt.__type end
+ end
+ return lua_type
+ ")
+
+[$ is a $type, $ is an $type] all parse as ((type of $) == $type)
+[$ isn't a $type, $ isn't an $type, $ is not a $type, $ is not an $type]
+..all parse as ((type of $) != $type)
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ (foo) means:
+ return 100 200 300
+ assume (select 2 (foo)) == 200
+
+# Return statement is wrapped in a do..end block because Lua is unhappy if you
+ put code after a return statement, unless you wrap it in a block.
+(return (*extra arguments*)) compiles to:
+ lua> ("
+ local lua = \(Lua "do return ")
+ for i=1,select('#',...) do
+ if i > 1 then lua:add(", ") end
+ lua:add(\(nomsu environment):compile((select(i, ...))))
+ end
+ lua:add(" end")
+ return lua
+ ")
+
+# Literals
+(yes) compiles to "true"
+(no) compiles to "false"
+[nothing, nil, null] all compile to "nil"
+(Nomsu syntax version) compiles to "NOMSU_SYNTAX_VERSION"
+(Nomsu compiler version) compiles to "NOMSU_COMPILER_VERSION"
+(core version) compiles to "NOMSU_CORE_VERSION"
+(lib version) compiles to "NOMSU_LIB_VERSION"
+(command line args) compiles to "COMMAND_LINE_ARGS"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#
+ (with local compile actions $body) compiles to ("
+ do
+ local OLD_RULES = COMPILE_RULES
+ local OLD_ENV = \(nomsu environment)
+ local \(nomsu environment) = setmetatable({
+ COMPILE_RULES=setmetatable({}, {__index=OLD_RULES})
+ }, {__index=OLD_ENV})
+ \($body as lua)
+ end
+ ")
+
+externally (Nomsu version) means:
+ return ("
+ \(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version)
+ ")
diff --git a/lib/core/operators.nom b/lib/core/operators.nom
new file mode 100644
index 0000000..dee76b6
--- /dev/null
+++ b/lib/core/operators.nom
@@ -0,0 +1,263 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file contains definitions of operators like "+" and "and".
+
+use "core/metaprogramming"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ assume (all [1 < 2, 2 > 1, 1 <= 2, 2 >= 1, 1 == 1, 1 != 2])
+
+# Comparison Operators
+($x < $y) compiles to "(\($x as lua expr) < \($y as lua expr))"
+($x > $y) compiles to "(\($x as lua expr) > \($y as lua expr))"
+($x <= $y) compiles to "(\($x as lua expr) <= \($y as lua expr))"
+($x >= $y) compiles to "(\($x as lua expr) >= \($y as lua expr))"
+[$a is $b, $a == $b] all compile to "(\($a as lua expr) == \($b as lua expr))"
+[$a isn't $b, $a is not $b, $a not= $b, $a != $b] all compile to
+ "(\($a as lua expr) ~= \($b as lua expr))"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ $x = 10
+ assume ($x == 10)
+ [$x, $y] = [10, 20]
+ unless (($x == 10) and ($y == 20)):
+ fail "mutli-assignment failed."
+ [$x, $y] = [$y, $x]
+ unless (($y == 10) and ($x == 20)):
+ fail "swapping vars failed."
+ $vals = [4, 5]
+ [$x, $y] = (unpack $vals)
+ unless (($x == 4) and ($y == 5)):
+ fail "unpacking failed"
+
+# Variable assignment operator
+($var = $value) compiles to:
+ lua> ("
+ local lua = LuaCode()
+ if \$var.type == "List" then
+ for i, \$assignment in ipairs(\$var) do
+ if i > 1 then lua:add(", ") end
+ local assignment_lua = \($assignment as lua expr)
+ lua:add(assignment_lua)
+ if \$assignment.type == 'Var' then
+ lua:add_free_vars({assignment_lua:text()})
+ end
+ end
+ lua:add(' = ')
+ if \$value.type == "List" then
+ if #\$value ~= #\$var then
+ compile_error_at(\$value,
+ "This assignment has too "..(#\$value > #\$var and "many" or "few").." values.",
+ "Make sure it has the same number of values on the left and right hand side \
+ ..of the '=' operator.")
+ end
+ for i, \$val in ipairs(\$value) do
+ if i > 1 then lua:add(", ") end
+ local val_lua = \($val as lua expr)
+ lua:add(val_lua)
+ end
+ lua:add(";")
+ else
+ lua:add(\($value as lua expr), ';')
+ end
+ else
+ local var_lua = \($var as lua expr)
+ lua:add(var_lua)
+ if \$var.type == 'Var' then
+ lua:add_free_vars({var_lua:text()})
+ end
+ lua:add(' = ', \($value as lua expr))
+ end
+ return lua
+ ")
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ [$foozle, $y] = ["outer", "outer"]
+ externally (set global x local y) means:
+ external $foozle = "inner"
+ $y = "inner"
+ set global x local y
+ unless (($foozle == "inner") and ($y == "outer")): fail "external failed."
+(external $var = $value) compiles to:
+ $lua = ((SyntaxTree {.type = "Action", .source = $var.source, .1 = $var, .2 = "=", .3 = $value}) as lua)
+ $lua, remove free vars
+ return $lua
+test:
+ [$foozle, $y] = ["outer", "outer"]
+ externally (set global x local y) means:
+ with external [$foozle]:
+ $foozle = "inner"
+ $y = "inner"
+ set global x local y
+ unless (($foozle == "inner") and ($y == "outer")):
+ fail "'with external' failed."
+
+(with external $externs $body) compiles to:
+ $body_lua = ($body as lua)
+ lua> ("
+ \$body_lua:remove_free_vars(table.map(\$externs, function(v) return \(nomsu environment):compile(v):text() end))
+ ")
+ return $body_lua
+
+test:
+ [$x, $y] = [1, 2]
+ with [$z, $x = 999]:
+ assume $z == (nil)
+ $z = 999
+ unless ($z == 999):
+ fail "'with' failed."
+
+ unless ($x == 999):
+ fail "'with' assignment failed."
+
+ unless ($x == 1):
+ fail "'with' scoping failed"
+
+ unless ($z == (nil)):
+ fail "'with' scoping failed"
+
+(with $assignments $body) compiles to:
+ lua> ("
+ local \$defs = LuaCode()
+ for i, \$item in ipairs(\$assignments) do
+ if i > 1 then \$defs:add("\\n") end
+ local item_lua = \($item as lua)
+ if \$item.type == 'Action' and \$item.stub == '1 =' then
+ item_lua:remove_free_vars(item_lua.free_vars)
+ end
+ \$defs:add("local ", item_lua, ";")
+ end
+ ")
+
+ return
+ Lua ("
+ do
+ \$defs
+ \($body as lua)
+ end -- 'with' block
+ ")
+
+# Math Operators
+test:
+ unless ((5 wrapped around 2) == 1):
+ fail "mod not working"
+
+[$x wrapped around $y, $x mod $y] all compile to
+ "((\($x as lua expr)) % (\($y as lua expr)))"
+
+# 3-part chained comparisons
+# (uses a lambda to avoid re-evaluating middle value, while still being an expression)
+test:
+ $calls = 0
+ (one) means:
+ external $calls = ($calls + 1)
+ return 1
+
+ unless (0 <= (one) <= 2):
+ fail "Three-way chained comparison failed."
+
+ unless ($calls == 1):
+ fail "Three-way comparison evaluated middle value multiple times"
+($x < $y < $z) parses as ((($a $b $c) -> (($a < $b) and ($b < $c))) $x $y $z)
+($x <= $y < $z) parses as ((($a $b $c) -> (($a <= $b) and ($b < $c))) $x $y $z)
+($x < $y <= $z) parses as ((($a $b $c) -> (($a < $b) and ($b <= $c))) $x $y $z)
+($x <= $y <= $z) parses as ((($a $b $c) -> (($a <= $b) and ($b <= $c))) $x $y $z)
+($x > $y > $z) parses as ((($a $b $c) -> (($a > $b) and ($b > $c))) $x $y $z)
+($x >= $y > $z) parses as ((($a $b $c) -> (($a >= $b) and ($b > $c))) $x $y $z)
+($x > $y >= $z) parses as ((($a $b $c) -> (($a > $b) and ($b >= $c))) $x $y $z)
+($x >= $y >= $z) parses as ((($a $b $c) -> (($a >= $b) and ($b >= $c))) $x $y $z)
+
+# TODO: optimize for common case where x,y,z are all either variables or number literals
+# Boolean Operators
+test:
+ (barfer) means (fail "short circuiting failed")
+ assume (((no) and (barfer)) == (no))
+ assume ((no) or (yes))
+ assume ((yes) or (barfer))
+($x and $y) compiles to "(\($x as lua expr) and \($y as lua expr))"
+($x or $y) compiles to "(\($x as lua expr) or \($y as lua expr))"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# Bitwise Operators
+# TODO: implement OR, XOR, AND for multiple operands?
+test:
+ assume ((~ (~ 5)) == 5)
+ assume ((1 | 4) == 5)
+ assume ((1 ~ 3) == 2)
+ assume ((1 & 3) == 1)
+ assume ((1 << 2) == 4)
+ assume ((4 >> 2) == 1)
+
+# Lua 5.3 introduced bit operators like | and &. Use them when possible, otherwise
+ fall back to bit.bor(), bit.band(), etc.
+lua> "if \((is jit) or ((Lua version) == "Lua 5.2")) then"
+[NOT $, ~ $] all compile to "bit.bnot(\($ as lua expr))"
+[$x OR $y, $x | $y] all compile to
+ "bit.bor(\($x as lua expr), \($y as lua expr))"
+
+[$x XOR $y, $x ~ $y] all compile to
+ "bit.bxor(\($x as lua expr), \($y as lua expr))"
+
+[$x AND $y, $x & $y] all compile to
+ "bit.band(\($x as lua expr), \($y as lua expr))"
+
+[$x LSHIFT $shift, $x << $shift] all compile to
+ "bit.lshift(\($x as lua expr), \($shift as lua expr))"
+
+[$x RSHIFT $shift, $x >> $shift] all compile to
+ "bit.rshift(\($x as lua expr), \($shift as lua expr))"
+
+lua> "else"
+[NOT $, ~ $] all compile to "~(\($ as lua expr))"
+[$x OR $y, $x | $y] all compile to "(\($x as lua expr) | \($y as lua expr))"
+[$x XOR $y, $x ~ $y] all compile to "(\($x as lua expr) ~ \($y as lua expr))"
+[$x AND $y, $x & $y] all compile to "(\($x as lua expr) & \($y as lua expr))"
+[$x LSHIFT $shift, $x << $shift] all compile to
+ "(\($x as lua expr) << \($shift as lua expr))"
+
+[$x RSHIFT $shift, $x >> $shift] all compile to
+ "(\($x as lua expr) >> \($shift as lua expr))"
+
+lua> "end"
+
+# Unary operators
+test:
+ assume ((- 5) == -5)
+ assume ((not (yes)) == (no))
+(- $) compiles to "(- \($ as lua expr))"
+(not $) compiles to "(not \($ as lua expr))"
+test:
+ assume ((size of [1, 2, 3]) == 3)
+ assume ((#[1, 2, 3]) == 3)
+[#$list, size of $list] all compile to "(#\($list as lua expr))"
+($list is empty) compiles to "(#\($list as lua expr) == 0)"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# Update operators
+test:
+ $x = 1
+ $x += 1
+ unless ($x == 2):
+ fail "+= failed"
+ $x *= 2
+ unless ($x == 4):
+ fail "*= failed"
+ wrap $x around 3
+ unless ($x == 1):
+ fail "wrap around failed"
+($var += $) parses as ($var = (($var or 0) + $))
+($var -= $) parses as ($var = (($var or 0) - $))
+($var *= $) parses as ($var = (($var or 1) * $))
+($var /= $) parses as ($var = ($var / $))
+($var ^= $) parses as ($var = ($var ^ $))
+($var and= $) parses as ($var = ($var and $))
+($var or= $) parses as ($var = ($var or $))
+(wrap $var around $) parses as ($var = ($var wrapped around $))
diff --git a/lib/core/text.nom b/lib/core/text.nom
new file mode 100644
index 0000000..1351af6
--- /dev/null
+++ b/lib/core/text.nom
@@ -0,0 +1,78 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file contains some definitions of text escape sequences, including ANSI console
+ color codes.
+
+use "core/metaprogramming"
+use "core/operators"
+use "core/control_flow"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test:
+ assume "\[1, 2, 3]" == "[1, 2, 3]"
+ assume "foo = \(1 + 2)!" == "foo = 3!"
+ assume ("
+ one
+ two
+ ") == ("
+ one
+ two
+ ")
+ assume "nogap" == "nogap"
+ assume (["x", "y"], joined with ",") == "x,y"
+ assume (["x", "y"], joined) == "xy"
+ assume ("BAR", byte 2) == 65
+ assume ("BAR", bytes 1 to 2) == [66, 65]
+ assume ("asdf", capitalized) == "Asdf"
+ assume ("asdf", uppercase) == "ASDF"
+ assume ("asdf", with "s" -> "X") == "aXdf"
+ assume
+ ("
+ one
+ two
+
+ "), lines
+ ..== ["one", "two", ""]
+
+ ($spec とは $body) parses as ($spec means $body)
+
+test:
+ $こんにちは = "こんにちは"
+ ($ と言う) とは "\($)世界"
+ assume ($こんにちは と言う) == "こんにちは世界"
+
+($expr for $match in $text matching $patt) compiles to:
+ define mangler
+ return
+ Lua ("
+ (function()
+ local \(mangle "comprehension") = List{}
+ for \($match as lua expr) in (\($text as lua expr)):gmatch(\($patt as lua expr)) do
+ \(mangle "comprehension")[#\(mangle "comprehension")+1] = \($expr as lua)
+ end
+ return \(mangle "comprehension")
+ end)()
+ ")
+
+test:
+ assume "\n" == (newline)
+
+test:
+ assume (0xDEADBEEF as hex) == "0xDEADBEEF"
+
+externally ($num as hex) means:
+ if ($num < 0):
+ return ("-0x%X", formatted with (- $num))
+ ..else:
+ return ("0x%X", formatted with $num)
+
+# Text literals
+$escapes = {
+ .nl = "\n", .newline = "\n", .tab = "\t", .bell = "\a", .cr = "\r", ."carriage return" = "\r"
+ .backspace = "\b", ."form feed" = "\f", .formfeed = "\f", ."vertical tab" = "\v"
+}
+
+for $name = $str in $escapes:
+ with [$lua = (Lua (quote $str))]:
+ $(COMPILE RULES).$name = (-> $lua)
diff --git a/lib/file_hash.nom b/lib/file_hash/init.nom
index 70446ca..7b428cd 100644
--- a/lib/file_hash.nom
+++ b/lib/file_hash/init.nom
@@ -2,8 +2,8 @@
#
This file defines some actions for hashing files and looking up files by hash.
-use "lib/os"
-use "lib/base64"
+use "filesystem"
+use "base64"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/lib/os.nom b/lib/filesystem/init.nom
index 87b3426..a228bdc 100644
--- a/lib/os.nom
+++ b/lib/filesystem/init.nom
@@ -1,6 +1,6 @@
#!/usr/bin/env nomsu -V6.14
#
- This file defines some actions that interact with the operating system and filesystem.
+ This file defines some actions that interact with the filesystem.
externally (files for $path) means:
$files = (=lua "Files.list(\$path)")
@@ -8,19 +8,6 @@ externally (files for $path) means:
$files = (List $files)
return $files
-externally (=sh $cmd) means:
- lua> ("
- local result = io.popen(\$cmd)
- local contents = result:read("*a")
- result:close()
- return contents
- ")
-
-external $(sh> $) = $os.execute
-
-test:
- read file "lib/os.nom"
-
external $(read file $filename) = $Files.read
externally [
write to file $filename $text, to file $filename write $text
diff --git a/lib/progressbar/init.nom b/lib/progressbar/init.nom
new file mode 100644
index 0000000..a7ff5aa
--- /dev/null
+++ b/lib/progressbar/init.nom
@@ -0,0 +1,16 @@
+# A progress bar
+
+externally ($x / $w progress bar) means:
+ $x = ($x clamped between 0 and $w)
+ $bits = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]
+ $middle =
+ "" if ($x == $w) else
+ $bits.(1 + (floor ((#$bits) * ($x mod 1))))
+
+ return ("
+ \027[32;40m\($bits, last, rep (floor $x))\$middle\
+ ..\(" ", rep ($w - ((floor $x) + 1)))\027[0m
+ ")
+
+externally ($w wide $ progress bar) means
+ ($ * $w) / $w progress bar
diff --git a/lib/shell/init.nom b/lib/shell/init.nom
new file mode 100644
index 0000000..6a8970e
--- /dev/null
+++ b/lib/shell/init.nom
@@ -0,0 +1,12 @@
+#
+ This file defines some actions for running shell commands.
+
+externally (=sh $cmd) means:
+ lua> ("
+ local result = io.popen(\$cmd)
+ local contents = result:read("*a")
+ result:close()
+ return contents
+ ")
+
+external $(sh> $) = $os.execute
diff --git a/lib/things.nom b/lib/things.nom
index 84682b3..95f175d 100644
--- a/lib/things.nom
+++ b/lib/things.nom
@@ -36,7 +36,7 @@ test:
assume (($d, bark) == "Bark!")
a (Corgi) is a thing:
- $it [set up, gets pissed off] like a (Dog)
+ $it can [set up, gets pissed off] like a (Dog)
($it, as text) means "Dogloaf \{: for $k = $v in $it: add $k = $v}"
($its, sploot) means "sploooot"
[($its, bark), ($its, woof)] all mean:
diff --git a/lib/tools/find.nom b/lib/tools/find.nom
new file mode 100755
index 0000000..aa4bc7b
--- /dev/null
+++ b/lib/tools/find.nom
@@ -0,0 +1,95 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This is a tool to find syntax trees matching a pattern. "*" is a wildcard
+ that will match any subtree, and "**" is a wildcard that will match any
+ 0 or more subtrees. "**" is greedy, so extra arguments after it will
+ not match.
+
+ nomsu -t find [flags] "* squared" file1 file2...
+
+ Flags:
+ -l List only the names of the files with matches
+ --wildcard=<wildcard> Specify a custom wildcard (in case you need to
+ match an action with a "*" in the name)
+
+ Output:
+ <filename>:<line number>:
+ <matching lines>
+
+use "filesystem"
+use "consolecolor"
+use "commandline"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+command line program with $args:
+ $wildcard = ($args.wildcard or "%*")
+ $pattern = $args.extras.1
+ if (any of [not $pattern, $pattern == "*", $pattern == "**"]):
+ usage ("
+ nomsu -t find [-l] [--wildcard=<wildcard>] <pattern>, where <pattern> is valid Nomsu code
+ ")
+ $pattern = ($pattern, with "\$wildcard\$wildcard" -> "$multi_wildcard")
+ $pattern = ($pattern, with $wildcard -> "$wildcard")
+ $pattern_tree = ($pattern parsed)
+ ($tree matches $patt) means:
+ when:
+ (not ($tree is syntax tree)): return (no)
+ (($patt.type == "Var") and ($patt.1 == "wildcard")): return (yes)
+ ($tree.type != $patt.type): return (no)
+ ($tree.type == "Action"):
+ if (($tree, get stub) != ($patt, get stub)): return (no)
+
+ for $ in 1 to (#$patt):
+ if ($patt.$ is syntax tree):
+ if ($patt.$ == \$multi_wildcard): return (yes)
+ unless ($tree.$ matches $patt.$): return (no)
+ ..else:
+ unless ($tree.$ == $patt.$): return (no)
+
+ if ((#$tree) != (#$patt)): return (no)
+ return (yes)
+ $filenames = ($args.extras, from 2 to -1)
+ if ((#$filenames) == 0):
+ say ("
+ Warning: searching stdin (ctrl-d to abort). To avoid this message, use nomsu -t find -
+ ")
+ $filenames = ["stdin"]
+
+ for $filename in $filenames:
+ $file = (read file $filename)
+ unless $file:
+ fail "File does not exist: \$filename"
+ $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file)
+ try:
+ $tree = ($code parsed)
+ ..if it fails $msg:
+ say
+ red ("
+ \$filename failed to parse:
+ \$msg
+ ")
+ $tree = (nil)
+
+ unless $tree:
+ do next $filename
+
+ $results = []
+ for $t in recursive $tree:
+ if ($t matches $pattern_tree):
+ $line_num = ($file, line number at $t.source.start)
+ $results, add {
+ .line = $line_num, .text = "\(blue "\$filename:\$line_num:")\n\(source lines of $t)"
+ }
+
+ for $sub in $t:
+ if ($sub is syntax tree):
+ recurse $t on $sub
+
+ if $args.l:
+ if ((#$results) > 0):
+ say $filename
+ ..else:
+ sort $results by $ -> $.line
+ for $ in $results:
+ say $.text
diff --git a/lib/tools/format.nom b/lib/tools/format.nom
new file mode 100755
index 0000000..e60d12e
--- /dev/null
+++ b/lib/tools/format.nom
@@ -0,0 +1,45 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ Auto-format Nomsu code. Usage:
+ nomsu -t format [-i] file1 file2...
+
+ If the "-i" flag is used, the file will be edited in-place.
+ If the "-q" flag is used and an error occurs, the original file will be printed.
+ If no files are passed in, this will read from stdin.
+
+use "filesystem"
+use "commandline"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+command line program with $args:
+ $filenames = $args.extras
+ if ((#$filenames) == 0):
+ say ("
+ Warning: reading from stdin (ctrl-d to abort). To avoid this message, use nomsu -t format -
+ ")
+ $filenames = ["stdin"]
+
+ for $filename in $filenames:
+ $file = (read file $filename)
+ unless $file:
+ fail "File does not exist: \$filename"
+ $leading_indent = ($file, matching "\n*([ ]*)")
+ $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file)
+ try:
+ $tree = ($code parsed)
+ ..if it fails $msg:
+ if $args.q:
+ $formatted = $file
+ ..else:
+ say $msg
+
+ if ($tree and (not $formatted)):
+ $formatted =
+ "\$leading_indent\($tree as nomsu, text, with "\n" -> "\n\$leading_indent")"
+
+ if $formatted:
+ if $args.i:
+ write $formatted to file $filename
+ ..else:
+ say $formatted inline
diff --git a/lib/tools/parse.nom b/lib/tools/parse.nom
new file mode 100755
index 0000000..63cc247
--- /dev/null
+++ b/lib/tools/parse.nom
@@ -0,0 +1,47 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ Tool to print out a parse tree of files in an easy-to-read format. Usage:
+ nomsu tools/parse.nom file1 file2 directory1 ...
+
+use "filesystem"
+use "commandline"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+externally (print tree $t at indent $indent) means:
+ if $t.type is:
+ "Action":
+ say "\($indent)Action (\($t.stub)):"
+ for $arg in $t:
+ if ($arg is syntax tree):
+ print tree $arg at indent "\$indent "
+
+ "MethodCall":
+ say "\($indent)MethodCall on:"
+ print tree $t.1 at indent "\$indent "
+ print tree $t.2 at indent "\$indent "
+
+ "Number":
+ say "\$indent\($t.1)"
+
+ "Var":
+ say "\($indent)$\($t.1)"
+
+ else:
+ say "\$indent\($t.type):"
+ for $arg in $t:
+ when:
+ ($arg is syntax tree):
+ print tree $arg at indent "\$indent "
+
+ else:
+ say "\$indent \(quote $arg)"
+
+command line program with $args:
+ for $filename in $args.extras:
+ $file = (read file $filename)
+ unless $file:
+ fail "File does not exist: \$filename"
+ $nomsu = (NomsuCode from (Source $filename 1 (size of $file)) $file)
+ $tree = ($nomsu parsed)
+ print tree $tree at indent ""
diff --git a/lib/tools/repl.nom b/lib/tools/repl.nom
new file mode 100755
index 0000000..2c0d3df
--- /dev/null
+++ b/lib/tools/repl.nom
@@ -0,0 +1,86 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This file defines a Read-Evaluate-Print-Loop (REPL) for Nomsu
+
+use "consolecolor"
+use "filesystem"
+use "commandline"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+externally (help) means:
+ say ("
+ This is the Nomsu v\(Nomsu version) interactive console.
+ You can type in Nomsu code here and hit 'enter' twice to run it.
+ To exit, type 'exit' or 'quit' and hit enter twice.
+ ")
+
+command line program with $args:
+ say ("
+
+ \(bright)\(underscore)Welcome to the Nomsu v\(Nomsu version) interactive console!\
+ ..\(reset color)
+ press 'enter' twice to run a command
+
+ ")
+
+ repeat:
+ say (bright (yellow ">> ")) inline
+ $buff = []
+ repeat:
+ say (bright) inline
+ $line = ($io.read "*L")
+ say (reset color) inline
+ if (($line == "\n") or (not $line)):
+ if ((size of $buff) > 0):
+ # clear the line
+ say "\027[1A\027[2K" inline
+ go to (run buffer)
+ $buff, add ($line, with "\t" -> " ")
+ say (dim (yellow ".. ")) inline
+
+ --- (run buffer) ---
+
+ if ((size of $buff) == 0): stop
+ $buff = ($buff, joined)
+ spoof file $buff
+ try:
+ $tree = ($buff parsed)
+ ..if it fails with $err:
+ say $err
+ do next
+
+ unless $tree:
+ do next
+
+ for $chunk in $tree:
+ try:
+ $lua = ($chunk as lua)
+ ..if it fails with $err: say $err
+
+ unless $lua:
+ do next
+
+ # TODO: this is a bit hacky, it just defaults variables to global
+ so that stuff mostly works across multiple lines. It would be
+ nicer if local variables actually worked.
+ $lua, remove free vars
+ try:
+ $ret = (run $lua)
+ ..if it fails with $err: say $err
+ ..if it succeeds:
+ if (type of $ret) is:
+ "nil":
+ do nothing
+
+ "boolean":
+ say "= \("yes" if $ret else "no")"
+
+ "table":
+ if $ret.as_nomsu:
+ say "= \($ret, as nomsu)"
+ ..else:
+ say "= \$ret"
+
+ else:
+ say "= \$ret"
diff --git a/lib/tools/replace.nom b/lib/tools/replace.nom
new file mode 100755
index 0000000..fdb8e38
--- /dev/null
+++ b/lib/tools/replace.nom
@@ -0,0 +1,147 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ This is a tool to replace syntax trees with something new.
+
+ Usage:
+ nomsu -t replace [-i] [-f] [-q] [--literal="$v1 $v2..."] <pattern> <replacement> file1 file2...
+
+ Example:
+ nomsu -t replace "($1 and $2) and $3" "all of [$1, $2, $3]" my_file.nom
+
+ If the "-i" flag is used, the file(s) will be edited in-place.
+ When editing in-place, if the "-f" flag is not used, each change will be
+ run past the user first.
+ If the "-q" flag is used and a file fails to parse, the original file
+ contents will be output.
+ If no files are passed in, this will read from stdin.
+
+use "filesystem"
+use "consolecolor"
+use "commandline"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+command line program with $args:
+ if ((#$args.extras) < 2):
+ fail ("
+ Usage: nomsu -t replace [--literal="$v1 $v2..."] <pattern> <replacement> file1 file2...
+ ")
+ $pattern = $args.extras.1
+ $replacement = $args.extras.2
+ $pattern_tree = ($pattern parsed)
+ $replacement_tree = ($replacement parsed)
+ $literal_vars = {}
+ if $args.literal:
+ for $var in ($args.literal, all matches of "$([^ ]*)"):
+ $literal_vars.$var = (yes)
+
+ if (($pattern_tree.type == "Var") and (not $literal_vars.($pattern_tree.1))):
+ fail "Pattern matches every part of the file."
+
+ $pattern_vars = {
+ : for $ in recursive $pattern_tree:
+ if (($.type == "Var") and (not $literal_vars.($.1))): add $.1
+ for $child in $:
+ if ($child is a "Syntax Tree"):
+ recurse $ on $child
+ }
+
+ # TODO: support wildcards and unpacking
+ e.g. nomsu -t replace "test(: $test; *$more_tests)" "*$more_tests; *$test"
+ ($tree matches $patt with $substitution_values) means:
+ # TODO: optimize
+ $substitution_values = {: for $k = $v in $substitution_values: add $k = $v}
+ when:
+ (not ($tree is syntax tree)): return (no)
+ (($patt.type == "Var") and $pattern_vars.($patt.1)):
+ if $substitution_values.($patt.1):
+ if ($tree == $substitution_values.($patt.1)):
+ return $substitution_values
+ ..else:
+ return (nil)
+ ..else:
+ $substitution_values.($patt.1) = $tree
+ return $substitution_values
+ ($tree.type != $patt.type): return (nil)
+ ($tree.type == "Action"):
+ if (($tree, get stub) != ($patt, get stub)): return (nil)
+
+ for $ in 1 to (#$patt):
+ if ($patt.$ is syntax tree):
+ $new_values = ($tree.$ matches $patt.$ with $substitution_values)
+ unless $new_values:
+ return (nil)
+
+ for $k = $v in $new_values:
+ $substitution_values.$k = $v
+ ..else:
+ unless ($tree.$ == $patt.$): return (nil)
+
+ if ((#$tree) != (#$patt)): return (nil)
+ return $substitution_values
+ $filenames = ($args.extras, from 3 to -1)
+ if ((#$filenames) == 0):
+ say ("
+ Warning: searching stdin (ctrl-d to abort). To avoid this message, use nomsu -t find -
+ ")
+ $filenames = ["stdin"]
+
+ for $filename in $filenames:
+ $file = (read file $filename)
+ unless $file:
+ fail "File does not exist: \$filename"
+ $code = (NomsuCode from ($Source $filename 1 (size of $file)) $file)
+ try:
+ $tree = ($code parsed)
+ ..if it fails $msg:
+ if $args.q:
+ unless $args.i: say $code
+ ..else:
+ say $msg
+
+ unless $tree:
+ do next $filename
+
+ $replaced = {}
+ $matched = {}
+ $user_answers = {}
+ ($tree with replacements) means
+ $tree, map
+ for $t:
+ $values = ($t matches $pattern_tree with {})
+ if $values:
+ $matched.$t = (yes)
+ for $k = $v in $values:
+ $values.$k = ($v with replacements)
+ $ret = ($replacement_tree with vars $values)
+ if ($args.i and (not $args.f)):
+ if ($user_answers.$t == (nil)):
+ if ((#$user_answers) > 0): say ""
+ $user_answers.$t = "n"
+ say "\(bright)Should this:"
+ say ("
+ \(bright)\(yellow)\("\(($t with replacements) as nomsu)", with "\n" -> "\n ")\
+ ..\(reset color)
+ ")
+ say "\(bright)..be replaced with:"
+
+ say ("
+ \(bright)\(blue)\("\($ret as nomsu)", with "\n" -> "\n ")\(reset color)
+ ")
+
+ $user_answers.$t = (ask "\(bright)..? [Y/n]\(reset color) ")
+
+ if ($user_answers.$t == "n"): return (nil)
+ $replaced.$t = (yes)
+ return $ret
+ $tree2 = ($tree with replacements)
+ if $args.i:
+ if ((#$user_answers) > 0): say ""
+ say ("
+ \(#$replaced)/\(#$matched) replacement\("" if ((#$replaced) == 1) else "s") in \$filename
+ ")
+
+ if ((#$replaced) > 0):
+ write "\($tree2 as nomsu)" to file $filename
+ ..else:
+ say ($tree2 as nomsu)
diff --git a/lib/tools/test.nom b/lib/tools/test.nom
new file mode 100755
index 0000000..4663bd4
--- /dev/null
+++ b/lib/tools/test.nom
@@ -0,0 +1,65 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ Tool to run all tests in a file (i.e. the code block inside a call to 'test $'). Usage:
+ nomsu tools/test.nom file1 file2 directory1 ...
+
+use "filesystem"
+use "consolecolor"
+use "commandline"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+command line program with $args:
+ for $filename in $args.extras at $i:
+ $file = (read file $filename)
+ unless $file:
+ fail "Couldn't find \$filename"
+
+ $(test environment) = (new environment)
+ $(test environment), export $filename
+ $version =
+ $file, matching ("
+ #![^
+ ]* nomsu %-V[ ]*([^
+ ]*)
+ ")
+ $file_tests = []
+ for $src = $test in $(test environment).TESTS:
+ if $version:
+ $test = ("
+ #!/usr/bin/env nomsu -V\$version
+ \$test
+ ")
+ $file_tests, add {.test = $test, .source = $src}
+
+ unless ($file_tests is empty):
+ sort $file_tests by $ -> $.source
+ say "[ .. ] \$filename" inline
+ $io.flush()
+
+ if $args.v: say ""
+
+ $failures = []
+ for $ in $file_tests:
+ if $args.v:
+ say " \(yellow ($.test, with "\n" -> "\n "))"
+ try:
+ $(test environment), run $.test
+ ..if it fails with $msg:
+ $src = ($Source, from string $.source)
+ $l1 = ($file, line number at $src.start)
+ $l2 = ($file, line number at $src.stop)
+ $failures, add "\(yellow "\($src.filename):\($l1)-\$l2:")\n\(bright (red ($msg, indented)))"
+
+ if ($failures is empty):
+ if $args.v:
+ say (green "PASS")
+ ..else:
+ say "\r[\(green "PASS")"
+ ..else:
+ if $args.v:
+ say (red (bright "FAIL"))
+ ..else:
+ say "\r[\(red (bright "FAIL"))"
+ say "\($failures, joined with "\n", indented)"
+
diff --git a/lib/tools/upgrade.nom b/lib/tools/upgrade.nom
new file mode 100755
index 0000000..1ef91b9
--- /dev/null
+++ b/lib/tools/upgrade.nom
@@ -0,0 +1,43 @@
+#!/usr/bin/env nomsu -V6.14
+#
+ Tool to automatically update code from old versions of Nomsu. Usage:
+ nomsu tools/upgrade.nom [-i] file1 file2 directory1 ...
+ If "-i" is the first argument, upgrades will be performed in-place. Otherwise, the
+ upgraded code will be printed.
+
+use "compatibility"
+use "filesystem"
+use "consolecolor"
+use "commandline"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+command line program with $args:
+ $inplace = ($args.i or $args.inplace)
+ $start_version = $args."upgrade-from"
+ $version = ($args."upgrade-to" or (Nomsu version))
+ $test = ($args.t or $args.test)
+ for $filename in $args.extras:
+ $file = (read file $filename)
+ unless $file:
+ fail "File does not exist: \$filename"
+ $leading_indent = ($file, matching "\n*([ ]*)")
+ $code = (NomsuCode from (Source $filename 1 (size of $file)) $file)
+ $tree = ($code parsed $start_version)
+ $uptree =
+ $tree upgraded from ($start_version or ($tree.version or (Nomsu version))) to
+ $version
+ $text = "\$leading_indent\($uptree as nomsu, text, with "\n" -> "\n\$leading_indent")"
+ when:
+ $inplace:
+ say "Upgraded \$filename"
+ write $text to file $filename
+
+ $test:
+ if ($uptree == $tree):
+ say (dim "\$filename will not be changed")
+ ..else:
+ say (bright "\$filename will be changed")
+
+ else:
+ say $text inline