aboutsummaryrefslogtreecommitdiff
path: root/lib/control_flow.nom
diff options
context:
space:
mode:
Diffstat (limited to 'lib/control_flow.nom')
-rw-r--r--lib/control_flow.nom467
1 files changed, 0 insertions, 467 deletions
diff --git a/lib/control_flow.nom b/lib/control_flow.nom
deleted file mode 100644
index 75cf6fd..0000000
--- a/lib/control_flow.nom
+++ /dev/null
@@ -1,467 +0,0 @@
-#..
- This file contains compile-time actions that define basic control flow structures
- like "if" statements and loops.
-
-use "lib/metaprogramming.nom"
-use "lib/text.nom"
-use "lib/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
-