#!/usr/bin/env nomsu -V2.5.5.4
#
    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"
use "core/errors.nom"

# No-Op
test: do nothing
compile [do nothing] to (Lua "")

# Conditionals
test:
    if (no):
        barf "conditional fail"
compile [if %condition %if_body] to (..)
    Lua ".."
        if \(%condition as lua expr) then
            \(%if_body as lua statements)
        end

test:
    unless (yes):
        barf "conditional fail"
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 (..)
    Lua ".."
        if \(%condition as lua expr) then
            \(%if_body as lua statements)
        else
            \(%else_body as lua statements)
        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)
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 {Text:yes, List:yes, Dict:yes, Number:yes}.(%when_true_expr.type):
        return (..)
            Lua value ".."
                (\(%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 value ".."
                ((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)
compile [=== %label ===, --- %label ---, *** %label ***] to (..)
    Lua "::label_\(%label as lua identifier)::"

compile [go to %label] to (Lua "goto label_\(%label as lua identifier)")

# Basic loop control
compile [do next] to (Lua "goto continue")
compile [stop] to (Lua "break")

# While loops
test:
    %x = 0
    repeat while (%x < 10): %x += 1
    assume (%x == 10)
    repeat while (%x < 20): stop
    repeat while (%x < 20): stop repeating
    assume (%x == 10)
    repeat while (%x < 20):
        %x += 1
        if (yes): do next
        barf "Failed to 'do next'"
    
    assume (%x == 20)
    repeat while (%x < 30):
        %x += 1
        if (yes): do next repeat
        barf "Failed to 'do next repeat'"
    
    assume (%x == 30)
compile [do next repeat] to (Lua "goto continue_repeat")
compile [stop repeating] to (Lua "goto stop_repeat")
compile [repeat while %condition %body] to:
    %lua = (..)
        Lua ".."
            while \(%condition as lua expr) do
                \(%body as lua statements)
    
    if (%body has subtree \(do next)):
        to %lua write "\n    ::continue::"
    if (%body has subtree \(do next repeat)):
        to %lua write "\n    ::continue_repeat::"
    to %lua write "\nend --while-loop"
    if (%body has subtree \(stop repeating)):
        %lua = (..)
            Lua ".."
                do -- scope of "stop repeating" label
                    \%lua
                    ::stop_repeat::
                end -- end of "stop repeating" label scope
    
    return %lua

parse [repeat %body] as (repeat while (yes) %body)
parse [repeat until %condition %body] as (repeat while (not %condition) %body)

test:
    %x = 0
    repeat 10 times: %x += 1
    assume (%x == 10)
compile [repeat %n times %body] to:
    %lua = (..)
        Lua ".."
            for i=1,\(%n as lua expr) do
                \(%body as lua statements)
    
    if (%body has subtree \(do next)):
        to %lua write "\n    ::continue::"
    if (%body has subtree \(do next repeat)):
        to %lua write "\n    ::continue_repeat::"
    to %lua write "\nend --numeric for-loop"
    if (%body has subtree \(stop repeating)):
        %lua = (..)
            Lua ".."
                do -- scope of "stop repeating" label
                    \%lua
                    ::stop_repeat::
                end -- end of "stop repeating" label scope
    
    return %lua

# For loop control flow
compile [stop %var] to (Lua "goto stop_\(%var as lua identifier)")
compile [do next %var] to (Lua "goto continue_\(%var as lua identifier)")
compile [===stop %var ===, ---stop %var ---, ***stop %var ***] to (..)
    Lua "::stop_\(%var as lua identifier)::"

compile [===next %var ===, ---next %var ---, ***next %var ***] to (..)
    Lua "::continue_\(%var as lua identifier)::"

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

test:
    %nums = []
    for %x in 1 to 5: add %x to %nums
    assume (%nums == [1, 2, 3, 4, 5])
    %nums = []
    for %x in 1 to 5 via 2: add %x to %nums
    assume (%nums == [1, 3, 5])
    %nums = []
    for %outer in 1 to 100:
        for %inner in %outer to (%outer + 2):
            if (%inner == 2):
                add -2 to %nums
                do next %inner
            
            add %inner to %nums
            if (%inner == 5): stop %outer
    
    assume (%nums == [1, -2, 3, -2, 3, 4, 3, 4, 5])

# Numeric range for loops
compile [..]
    for %var in %start to %stop by %step %body
    for %var in %start to %stop via %step %body
..to:
    # This uses Lua's approach of only allowing loop-scoped variables in a loop
    unless (%var.type is "Var"):
        compile error at %var.source "Loop expected variable, not: %s"
    %lua = (..)
        Lua ".."
            for \(%var as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(..)
                %step as lua expr
            .. do
                \(%body as lua statements)
    
    if (%body has subtree \(do next)):
        to %lua write "\n    ::continue::"
    if (%body has subtree (\(do next %v) with vars {v:%var})):
        to %lua write "\n    \(compile as (===next %var ===))"
    to %lua write "\nend --numeric for-loop"
    if (%body has subtree (\(stop %v) with vars {v:%var})):
        %lua = (..)
            Lua ".."
                do -- scope for stopping for-loop
                    \%lua
                    \(compile as (===stop %var ===))
                end -- end of scope for stopping for-loop
    
    return %lua

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

parse [for %var in %start to %stop %body] as (..)
    for %var in %start to %stop via 1 %body

test:
    %a = [10, 20, 30, 40, 50]
    %b = []
    for %x in %a: add %x to %b
    assume (%a == %b)
    %b = []
    for %x in %a:
        if (%x == 10): do next %x
        if (%x == 50): stop %x
        add %x to %b
    
    assume (%b == [20, 30, 40])

# For-each loop (lua's "ipairs()")
compile [for %var in %iterable %body] to:
    # This uses Lua's approach of only allowing loop-scoped variables in a loop
    unless (%var.type is "Var"):
        compile error at %var.source "Loop expected variable, not: %s"
    %lua = (..)
        Lua ".."
            for i,\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do
                \(%body as lua statements)
    
    if (%body has subtree \(do next)):
        to %lua write "\n    ::continue::"
    if (%body has subtree (\(do next %v) with vars {v:%var})):
        to %lua write (Lua "\n\(compile as (===next %var ===))")
    to %lua write "\nend --foreach-loop"
    if (%body has subtree (\(stop %v) with vars {v:%var})):
        %lua = (..)
            Lua ".."
                do -- scope for stopping for-loop
                    \%lua
                    \(compile as (===stop %var ===))
                end -- end of scope for stopping for-loop
    
    return %lua

test:
    %d = {a:10, b:20, c:30, d:40, e:50}
    %result = []
    for %k = %v in %d:
        if (%k == "a"): do next %k
        if (%v == 20): do next %v
        add "\%k = \%v" to %result
    
    assume ((%result sorted) == ["c = 30", "d = 40", "e = 50"])

# Dict iteration (lua's "pairs()")
compile [..]
    for %key = %value in %iterable %body, for %key %value in %iterable %body
..to:
    # This uses Lua's approach of only allowing loop-scoped variables in a loop
    unless (%key.type is "Var"):
        compile error at %key.source "Loop expected variable, not: %s"
    unless (%value.type is "Var"):
        compile error at %value.source "Loop expected variable, not: %s"
    %lua = (..)
        Lua ".."
            for \(%key as lua identifier),\(%value as lua identifier) in pairs(\(..)
                %iterable as lua expr
            ..) do
                \(%body as lua statements)
    
    if (%body has subtree \(do next)):
        to %lua write "\n    ::continue::"
    if (%body has subtree (\(do next %v) with vars {v:%key})):
        to %lua write (Lua "\n\(compile as (===next %key ===))")
    if (%body has subtree (\(do next %v) with vars {v:%value})):
        to %lua write (Lua "\n\(compile as (===next %value ===))")
    to %lua write "\nend --foreach-loop"
    %stop_labels = (Lua "")
    if (%body has subtree (\(stop %v) with vars {v:%key})):
        to %stop_labels write "\n\(compile as (===stop %key ===))"
    if (%body has subtree (\(stop %v) with vars {v:%value})):
        to %stop_labels write "\n\(compile as (===stop %value ===))"
    if ((length of "\%stop_labels") > 0):
        %lua = (..)
            Lua ".."
                do -- scope for stopping for % = % loop
                    \%lua\%stop_labels
                end
    
    return %lua

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

test:
    if:
        (1 == 2) (100 < 0):
            barf "bad conditional"
        (1 == 0) (1 == 1) %not_a_variable.x: do nothing
        (1 == 1):
            barf "bad conditional"
        (1 == 2):
            barf "bad conditional"
        else:
            barf "bad conditional"

# Multi-branch conditional (if..elseif..else)
compile [if %body, when %body] to:
    %code = (Lua "")
    %clause = "if"
    %else_allowed = (yes)
    unless (%body.type is "Block"):
        compile error at %body.source "'if' expected a Block, but got: %s"
    for %line in %body:
        unless (..)
            ((%line.type is "Action") and ((length of %line) >= 2)) and (..)
                %line.(length of %line) is "Block" syntax tree
        ..:
            compile error at %line.source ".."
                Invalid line for 'if', each line should contain conditional expressions followed by a block, or "else" followed by a block:
                %s
        
        %action = %line.(length of %line)
        if ((%line.1 is "else") and ((length of %line) == 2)):
            unless %else_allowed:
                compile error at %line.source "Can't have two 'else' blocks"
            unless ((length of "\%code") > 0):
                compile error at %line.source ".."
                    Can't have an 'else' block without a preceeding condition
            
            to %code write ".."
                
                else
                    \(%action as lua statements)
            
            %else_allowed = (no)
        ..else:
            to %code write "\%clause "
            for %i in 1 to ((length of %line) - 1):
                unless (%line.%i is syntax tree):
                    compile error at %line.source ".."
                        Invalid condition for 'if' statement:
                        %s
                
                if (%i > 1):
                    to %code write " or "
                to %code write (%line.%i as lua expr)
            
            to %code write ".."
                 then
                    \(%action as lua statements)
            
            %clause = "\nelseif"
    
    if ((length of "\%code") == 0):
        compile error at %body.source "'if' block has an empty body"
    to %code write "\nend --when"
    return %code

test:
    if 5 is:
        1 2 3:
            barf "bad switch statement"
        4 5: do nothing
        5 6:
            barf "bad switch statement"
        else:
            barf "bad switch statement"

# Switch statement
compile [if %branch_value is %body, when %branch_value is %body] to:
    %code = (Lua "")
    %clause = "if"
    %else_allowed = (yes)
    unless (%body.type is "Block"):
        compile error at %body.source "'if' expected a Block, but got: %s"
    for %line in %body:
        unless (..)
            ((%line.type is "Action") and ((length of %line) >= 2)) and (..)
                %line.(length of %line) is "Block" syntax tree
        ..:
            compile error at %line.source ".."
                Invalid line for 'if % is % %', each line should contain expressions followed by a block, or "else" followed by a block:
                %s
        
        %action = %line.(length of %line)
        if ((%line.1 is "else") and ((length of %line) == 2)):
            unless %else_allowed:
                compile error at %line.source "Can't have two 'else' blocks"
            unless ((length of "\%code") > 0):
                compile error at %line.source ".."
                    Can't have an 'else' block without a preceeding condition
            
            to %code write ".."
                
                else
                    \(%action as lua statements)
            
            %else_allowed = (no)
        ..else:
            to %code write "\%clause "
            for %i in 1 to ((length of %line) - 1):
                unless (%line.%i is syntax tree):
                    compile error at %line.source ".."
                        Invalid condition for 'if' statement:
                        %s
                
                if (%i > 1):
                    to %code write " or "
                to %code write "branch_value == \(%line.%i as lua expr)"
            
            to %code write ".."
                 then
                    \(%action as lua statements)
            
            %clause = "\nelseif"
    
    if ((length of "\%code") == 0):
        compile error at %body.source "'if % is % %' block has an empty body"
    to %code write "\nend --when"
    return (..)
        Lua ".."
            do --if % is
                local branch_value = \(%branch_value as lua expr)
                \%code
            end --if % is

# Do/finally
compile [do %action] to (..)
    Lua ".."
        do
            \(%action as lua statements)
        end --do

test:
    %d = {}
    try:
        do:
            %d.x = "bad"
            barf
        ..then always: %d.x = "good"
    ..and if it barfs: do nothing
    
    assume (%d.x == "good")
compile [do %action then always %final_action] to (..)
    Lua ".."
        do
            local fell_through = false
            local ok, ret = pcall(function()
                \(%action as lua statements)
                fell_through = true
            end)
            \(%final_action as lua statements)
            if not ok then error(ret, 0) end
            if not fell_through then return ret end
        end

test:
    assume ((result of (: return 99)) == 99)

# Inline thunk:
compile [result of %body] to (Lua value "\(compile as ([] -> %body))()")

test:
    %t = [1, [2, [[3], 4], 5, [[[6]]]]]
    %flat = []
    for % in recursive %t:
        if ((type of %) is "table"):
            for %2 in %: recurse % on %2
        ..else: add % to %flat
    
    assume ((sorted %flat) == [1, 2, 3, 4, 5, 6])

# Recurion control flow
compile [for %var in recursive %structure %body] to (..)
    with local compile actions:
        compile [recurse %v on %x] to (..)
            Lua "table.insert(stack\(%v as lua id), \(%x as lua expr))"
        %lua = (..)
            Lua ".."
                do
                    local stack\(%var as lua id) = list{\(%structure as lua expr)}
                    while #stack\(%var as lua id) > 0 do
                        \(%var as lua expr) = table.remove(stack\(%var as lua id), 1)
                        \(%body as lua statements)
        
        if (%body has subtree \(do next)):
            to %lua write "\n        ::continue::"
        if (%body has subtree (\(do next %v) with vars {v:%var})):
            to %lua write "\n        \(compile as (===next %var ===))"
        to %lua write "\n    end -- Recursive loop"
        if (%body has subtree (\(stop %v) with vars {v:%var})):
            to %lua write "\n        \(compile as (===stop %var ===))"
        to %lua write "\nend -- Recursive scope"
        return %lua