aboutsummaryrefslogtreecommitdiff
path: root/core/control_flow.nom
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2018-02-02 15:48:28 -0800
committerBruce Hill <bitbucket@bruce-hill.com>2018-02-02 15:49:42 -0800
commit505fec2a61d2571317cc4bbc36ec0f4822a63f9d (patch)
treec2b37e9db8e2f958fbca0caa0a9c4924912a37a9 /core/control_flow.nom
parent513c721198b2256235a95c98d161ab1bb51e6671 (diff)
Restructured the nomsu files to group all the essentials into core/ and
all the optionals into lib/. lib/core.nom and tests/all.nom are no longer needed now.
Diffstat (limited to 'core/control_flow.nom')
-rw-r--r--core/control_flow.nom467
1 files changed, 467 insertions, 0 deletions
diff --git a/core/control_flow.nom b/core/control_flow.nom
new file mode 100644
index 0000000..315c4ef
--- /dev/null
+++ b/core/control_flow.nom
@@ -0,0 +1,467 @@
+#..
+ This file contains compile-time actions that define basic control flow structures
+ like "if" statements and loops.
+
+use "core/metaprogramming.nom"
+use "core/text.nom"
+use "core/operators.nom"
+
+# No-Op
+immediately:
+ compile [do nothing] to {statements:""}
+
+# Conditionals
+immediately:
+ compile [if %condition %if_body] to:
+ %if_body <- (%if_body as lua)
+ return {..}
+ locals: %if_body's "locals"
+ statements:".."
+ if \(%condition as lua expr) then
+ \((%if_body's "statements") or "\(%if_body's "expr");")
+ end
+ parse [unless %condition %unless_body] as: if (not %condition) %unless_body
+
+ compile [if %condition %if_body else %else_body, unless %condition %else_body else %if_body] to:
+ %if_body <- (%if_body as lua)
+ %else_body <- (%else_body as lua)
+ lua> ".."
+ local \%locals = {unpack(\%if_body.locals or {})};
+ for i,loc in ipairs(\%else_body.locals or {}) do table.insert(\%locals, loc); end
+ utils.deduplicate(\%locals);
+ return {..}
+ locals:%locals
+ statements:".."
+ if \(%condition as lua expr) then
+ \((%if_body's "statements") or "\(%if_body's "expr");")
+ else
+ \((%else_body's "statements") or "\(%else_body's "expr");")
+ 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"
+immediately
+ compile [..]
+ %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
+ ..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: (%when_true_expr's "type") in {Text:yes, List:yes, Dict:yes, Number:yes}
+ return {..}
+ expr:"(\(%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 {..}
+ expr: ".."
+ (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
+immediately:
+ compile [=== %label ===, --- %label ---, *** %label ***] to {..}
+ statements:"::label_\(%label as lua identifier)::;"
+ compile [go to %label] to {..}
+ statements:"goto label_\(%label as lua identifier);"
+
+# Basic loop control
+immediately:
+ compile [do next] to {statements:"continue;"}
+ compile [stop] to {statements:"break;"}
+
+# Helper function
+immediately:
+ compile [if %tree has subtree %subtree where %condition %body] to:
+ %body_lua <- (%body as lua)
+ %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
+ return {..}
+ locals: %body_lua's "locals"
+ statements:".."
+ for \(%subtree as lua expr) in coroutine.wrap(function() nomsu:walk_tree(\(%tree as lua expr)) end) do
+ if type(\(%subtree as lua expr)) == 'table' and \(%subtree as lua expr).type then
+ if \(%condition as lua expr) then
+ \%body_statements
+ break;
+ end
+ end
+ end
+
+# While loops
+immediately:
+ compile [do next repeat] to {statements:"goto continue_repeat;"}
+ compile [stop repeating] to {statements:"goto stop_repeat;"}
+ compile [repeat while %condition %body] to
+ %body_lua <- (%body as lua)
+ %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat")
+ ..: %body_statments +<- "\n::continue_repeat::;"
+ %code <- ".."
+ while \(%condition as lua expr) do
+ \%body_statements
+ end --while-loop
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop repeating")
+ ..:
+ %code <- ".."
+ do -- scope of "stop repeating" label
+ \%code
+ ::stop_repeat::;
+ end -- end of "stop repeating" label scope
+ return {statements:%code, locals:%body_lua's "locals"}
+ parse [repeat %body] as: repeat while (yes) %body
+ parse [repeat until %condition %body] as: repeat while (not %condition) %body
+
+ compile [..]
+ repeat %n times %body
+ ..to:
+ %body_lua <- (%body as lua)
+ %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
+ if %body has subtree % where
+ ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat")
+ ..: %body_statements +<- "\n::continue_repeat::;"
+ %code <- ".."
+ for i=1,\(%n as lua expr) do
+ \%body_statements
+ end --numeric for-loop
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop repeating")
+ ..:
+ %code <- ".."
+ do -- scope of "stop repeating" label
+ \%code
+ ::stop_repeat::;
+ end -- end of "stop repeating" label scope
+ return {statements:%code, locals:%body_lua's "locals"}
+
+# For loop control flow:
+immediately:
+ compile [stop %var] to {..}
+ statements:"goto stop_\(%var as lua identifier);"
+ compile [do next %var] to {..}
+ statements:"goto continue_\(%var as lua identifier);"
+
+# Numeric range for loops
+immediately:
+ compile [..]
+ for %var from %start to %stop by %step %body
+ for %var from %start to %stop via %step %body
+ ..to:
+ %body_lua <- (%body as lua)
+ %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and
+ ((%'s "stub") is "do next %") and
+ ((3rd in (%'s "value"))'s "value") is (%var's "value")
+ ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;"
+
+ # This uses Lua's approach of only allowing loop-scoped variables in a loop
+ assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)"
+ %code <- ".."
+ for \(%var as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(%step as lua expr) do
+ \%body_statements
+ end --numeric for-loop
+
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and:
+ ((%'s "stub") is "stop %") and:
+ ((2nd in (%'s "value"))'s "value") is (%var's "value")
+ ..:
+ %code <- ".."
+ do -- scope for stopping for-loop
+ \%code
+ ::stop_\(%var as lua identifier)::;
+ end -- end of scope for stopping for-loop
+
+ return {statements:%code, locals:%body_lua's "locals"}
+
+immediately:
+ parse [for %var from %start to %stop %body] as: for %var from %start to %stop via 1 %body
+ parse [..]
+ for all %start to %stop by %step %body
+ for all %start to %stop via %step %body
+ ..as: for % from %start to %stop via %step %body
+ parse [for all %start to %stop %body] as: for all %start to %stop via 1 %body
+
+# For-each loop (lua's "ipairs()")
+immediately:
+ compile [for %var in %iterable %body] to:
+ %body_lua <- (%body as lua)
+ %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and
+ ((%'s "stub") is "do next %") and
+ ((3rd in (%'s "value"))'s "value") is (%var's "value")
+ ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;"
+ # This uses Lua's approach of only allowing loop-scoped variables in a loop
+ assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)"
+ %code <- ".."
+ for i,\(%var as lua expr) in ipairs(\(%iterable as lua expr)) do
+ \%body_statements
+ end --foreach-loop
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and
+ ((%'s "stub") is "stop %") and
+ ((2nd in (%'s "value"))'s "value") is (%var's "value")
+ ..:
+ %code <- ".."
+ do -- scope for stopping for-loop
+ \%code
+ ::stop_\(%var as lua identifier)::;
+ end -- end of scope for stopping for-loop
+ return {statements:%code, locals:%body_lua's "locals"}
+
+ parse [for all %iterable %body] as: for % in %iterable %body
+
+# Dict iteration (lua's "pairs()")
+immediately:
+ compile [for %key = %value in %iterable %body] to:
+ %body_lua <- (%body as lua)
+ %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and
+ ((%'s "stub") is "do next %") and
+ ((3rd in (%'s "value"))'s "value") is (%key's "value")
+ ..: %body_statements +<- "\n::continue_\(%key as lua identifier)::;"
+
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and
+ ((%'s "stub") is "do next %") and
+ ((3rd in (%'s "value"))'s "value") is (%value's "value")
+ ..: %body_statements +<- "\n::continue_\(%value as lua identifier)::;"
+
+ # This uses Lua's approach of only allowing loop-scoped variables in a loop
+ assume ((%key's "type") is "Var") or barf "Loop expected variable, not: \(%key's source code)"
+ assume ((%value's "type") is "Var") or barf "Loop expected variable, not: \(%value's source code)"
+ %code <- ".."
+ for \(%key as lua expr),\(%value as lua expr) in pairs(\(%iterable as lua expr)) do
+ \%body_statements
+ end --foreach-loop
+
+ %stop_labels <- ""
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and
+ ((%'s "stub") is "stop %") and
+ ((2nd in (%'s "value"))'s "value") is (%key's "value")
+ ..: %stop_labels +<- "\n::stop_\(%key as lua identifier)::;"
+
+ if %body has subtree % where:
+ ((%'s "type") = "FunctionCall") and
+ ((%'s "stub") is "stop %") and
+ ((2nd in (%'s "value"))'s "value") is (%value's "value")
+ ..: %stop_labels +<- "\n::stop_\(%value as lua identifier)::;"
+
+ if: %stop_labels is not ""
+ %code <- ".."
+ do -- scope for stopping for % = % loop
+ \%code\%stop_labels
+ end
+ return {statements:%code, locals:%body_lua's "locals"}
+
+# Switch statement/multi-branch if
+immediately:
+ compile [when %body] to:
+ %code <- ""
+ %fallthroughs <- []
+ %locals <- []
+ %is_first <- (yes)
+ %seen_else <- (no)
+ for %func_call in (%body's "value"):
+ assume ((%func_call's "type") is "FunctionCall") or barf ".."
+ Invalid format for 'when' statement. Only '*' blocks are allowed.
+ with [..]
+ %tokens <- (%func_call's "value")
+ %star <- (1st in %tokens)
+ %condition <- (2nd in %tokens)
+ %action <- (3rd in %tokens)
+ ..:
+ assume (=lua "\%star and \%star.type == 'Word' and \%star.value == '*'") or barf ".."
+ Invalid format for 'when' statement. Lines must begin with '*'
+ assume %condition or barf ".."
+ Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else"
+ if: %action is (nil)
+ lua> "table.insert(\%fallthroughs, \(%condition as lua expr));"
+ do next %func_call
+ %action <- (%action as lua)
+ %action_statements <- ((%action's "statements") or "\(%action's "expr");")
+ for %local in ((%action's "locals") or []):
+ lua> "table.insert(\%locals, \%local);"
+
+ if: =lua "\%condition.type == 'Word' and \%condition.value == 'else'"
+ assume (not %is_first) or barf "'else' clause cannot be first in 'when % = ?' block"
+ %code +<- ".."
+
+ else
+ \%action_statements
+ %seen_else <- (yes)
+ ..else:
+ assume (not %seen_else) or barf "'else' clause needs to be last in 'when' block"
+ lua> "table.insert(\%fallthroughs, \(%condition as lua expr));"
+ %condition_code <- (%fallthroughs joined with " or ")
+ %code +<- ".."
+
+ \("if" if %is_first else "elseif") \%condition_code then
+ \%action_statements
+
+ %fallthroughs <- []
+ %is_first <- (no)
+
+ assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block"
+ if: %code is not ""
+ %code +<- "\nend"
+ lua> "utils.deduplicate(\%locals);"
+ return {statements:%code, locals:%locals}
+
+# Switch statement
+immediately:
+ compile [when %branch_value = ? %body, when %branch_value is ? %body] to:
+ %code <- ""
+ %fallthroughs <- []
+ %locals <- []
+ %is_first <- (yes)
+ %seen_else <- (no)
+ for %func_call in (%body's "value"):
+ assume ((%func_call's "type") is "FunctionCall") or barf ".."
+ Invalid format for 'when' statement. Only '*' blocks are allowed.
+ %tokens <- (%func_call's "value")
+ with [%star<-(1st in %tokens), %condition<-(2nd in %tokens), %action<-(3rd in %tokens)]:
+ assume (=lua "\%star and \%star.type == 'Word' and \%star.value == '*'") or barf ".."
+ Invalid format for 'when' statement. Lines must begin with '*'
+ assume %condition or barf ".."
+ Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else"
+ if: %action is (nil)
+ lua> "table.insert(\%fallthroughs, \(%condition as lua expr))"
+ do next %func_call
+
+ %action <- (%action as lua)
+ %action_statements <- ((%action's "statements") or "\(%action's "expr");")
+ for %local in ((%action's "locals") or []):
+ lua> "table.insert(\%locals, \%local);"
+
+ if: =lua "\%condition.type == 'Word' and \%condition.value == 'else'"
+ assume (not %is_first) or barf "'else' clause cannot be first in 'when % = ?' block"
+ %code +<- ".."
+
+ else
+ \%action_statements
+ end
+ %seen_else <- (yes)
+ ..else:
+ assume (not %seen_else) or barf "'else' clause needs to be last in 'when % = ?' block"
+ lua> "table.insert(\%fallthroughs, \(%condition as lua expr));"
+ for %i = % in %fallthroughs
+ if: ((%'s "type") is "Text") or ((%'s "type") is "Number")
+ (%i'th in %fallthroughs) <- "branch_value == \%"
+ ..else
+ (%i'th in %fallthroughs) <- "utils.equivalent(branch_value, \%)"
+ %clause <- (%fallthroughs joined with " or ")
+ %code +<- ".."
+
+ \("if" if %is_first else "elseif") \%clause then
+ \%action_statements
+
+ %fallthroughs <- []
+ %is_first <- (no)
+
+ assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block"
+ assume (%code is not "") or barf "No body for 'when % = ?' block!"
+ unless %seen_else
+ %code +<- "\nend"
+ %code <- ".."
+ do --when % = ?
+ local branch_value = \(%branch_value as lua expr);\
+ ..\%code
+ end --when % = ?
+ lua> "utils.deduplicate(\%locals);"
+ return {statements:%code, locals:%locals}
+
+# Try/except
+immediately:
+ compile [..]
+ try %action and if it succeeds %success or if it barfs %fallback
+ try %action and if it barfs %fallback or if it succeeds %success
+ ..to:
+ %locals <- []
+ %action_lua <- (%action as lua)
+ %success_lua <- (%success as lua)
+ %fallback_lua <- (%fallback as lua)
+ %fallback <- (%fallback as lua)
+ for %block in [%action_lua, %success_lua, %fallback_lua]:
+ for %local in ((%block's "locals") or []):
+ lua> "table.insert(\%locals, \%local);"
+ lua> "utils.deduplicate(\%locals);"
+ return {..}
+ locals: %locals
+ statements: ".."
+ do
+ local fell_through = false;
+ local ok, ret = pcall(function()
+ \((%action_lua's "statements") or "\(%action_lua's "expr");")
+ fell_through = true;
+ end);
+ if ok then
+ \((%success_lua's "statements") or "\(%success_lua's "expr");")
+ end
+ if not ok then
+ \((%fallback_lua's "statements") or "\(%fallback_lua's "expr");")
+ elseif not fell_through then
+ return ret;
+ end
+ end
+ parse [try %action] as:
+ try %action and if it succeeds: do nothing
+ ..or if it barfs: do nothing
+ parse [try %action and if it barfs %fallback] as:
+ try %action and if it succeeds: do nothing
+ ..or if it barfs %fallback
+ parse [try %action and if it succeeds %success] as:
+ try %action and if it succeeds %success or if it barfs: do nothing
+
+# Do/finally:
+immediately:
+ compile [do %action] to:
+ %action <- (%action as lua)
+ return {..}
+ locals: %action's "locals"
+ statements: ".."
+ do
+ \((%action's "statements") or "\(%action's "expr");")
+ end
+
+ compile [do %action then always %final_action] to:
+ %action <- (%action as lua)
+ %final_action <- (%final_action as lua)
+ %locals <- []
+ for %sub_locals in [%action's "locals", %final_action's "locals"]:
+ for %local in %sub_locals:
+ lua> "table.insert(\%locals, \%local);"
+ lua> "utils.deduplicate(\%locals);"
+ return {..}
+ locals: %locals
+ statements: ".."
+ do
+ local fell_through = false;
+ local ok, ret1 = pcall(function()
+ \((%action's "statements") or "\(%action's "expr");")
+ fell_through = true;
+ end);
+ local ok2, ret2 = pcall(function()
+ \((%final_action's "statements") or "\(%final_action's "expr");")
+ end);
+ if not ok then error(ret1); end
+ if not ok2 then error(ret2); end
+ if not fell_through then
+ return ret1;
+ end
+ end
+