(486 lines)
1 #!/usr/bin/env nomsu -V7.0.02 ###3 This file contains compile-time actions that define basic control flow structures4 like "if" statements and loops.6 use "core/metaprogramming"7 use "core/operators"9 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~11 ### No-Op12 test:13 do nothing14 (do nothing) compiles to ""16 ### Conditionals17 test:18 if (no):19 fail "conditional fail"21 (if $condition $if_body) compiles to ("22 if \($condition as lua expr) then23 \($if_body as lua)24 end25 ")27 test:28 unless (yes):29 fail "conditional fail"31 (unless $condition $unless_body) parses as (if (not $condition) $unless_body)32 [33 if $condition $if_body else $else_body, unless $condition $else_body else $if_body34 ] all compile to ("35 if \($condition as lua expr) then36 \($if_body as lua)37 else38 \($else_body as lua)39 end40 ")42 (else $) compiles to:43 at (this tree) fail ("44 Compile error: This 'else' is not connected to any 'if' or 'unless' condition.45 Hint: You should probably have a ".." in front of the "else", to indicate that it's attached \46 ..to the previous condition.47 ")49 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~51 ### Conditional expression (ternary operator)52 ### Note: this uses a function instead of "(condition and if_expr or else_expr)"53 because that breaks if $if_expr is falsey, e.g. "x < 5 and false or 99"54 test:55 assume ((1 if (yes) else 2) == 1)56 assume ((1 if (no) else 2) == 2)58 [59 $when_true_expr if $condition else $when_false_expr60 $when_true_expr if $condition otherwise $when_false_expr61 $when_false_expr unless $condition else $when_true_expr62 $when_false_expr unless $condition then $when_true_expr63 ] all compile to:64 ### If $when_true_expr is guaranteed to be truthy, we can use Lua's idiomatic65 equivalent of a conditional expression: (cond and if_true or if_false)66 if {.Text, .List, .Dict, .Number}.($when_true_expr.type):67 return Lua ("68 (\($condition as lua expr) and \($when_true_expr as lua expr) or \69 ..\($when_false_expr as lua expr))70 ")71 ..else:72 ### Otherwise, need to do an anonymous inline function (yuck, too bad lua73 doesn't have a proper ternary operator!)74 To see why this is necessary consider: (random()<.5 and false or 99)75 return Lua ("76 ((function()77 if \($condition as lua expr) then78 return \($when_true_expr as lua expr)79 else80 return \($when_false_expr as lua expr)81 end82 end)())83 ")85 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~87 ### GOTOs88 test:89 $i = 090 --- $loop ---91 $i += 192 unless ($i == 10):93 go to $loop94 assume ($i == 10)95 --- (Loop) ---96 $i -= 197 unless ($i == 0):98 go to (Loop)99 assume ($i == 0)101 (--- $label ---) compiles to ("102 ::label_\(103 ($label.stub, as lua id) if ($label.type == "Action") else104 $label as lua identifier105 )::106 ")108 (go to $label) compiles to ("109 goto label_\(110 ($label.stub, as lua id) if ($label.type == "Action") else111 $label as lua identifier112 )113 ")115 ### Basic loop control116 (stop $var) compiles to:117 if $var:118 return Lua "goto stop_\($var as lua identifier)"119 ..else:120 return Lua "break"122 (do next $var) compiles to:123 if $var:124 return Lua "goto continue_\($var as lua identifier)"125 ..else:126 return Lua "goto continue"128 (---stop $var ---) compiles to "::stop_\($var as lua identifier)::"129 (---next $var ---) compiles to "::continue_\($var as lua identifier)::"131 ### While loops132 test:133 $x = 0134 repeat while ($x < 10): $x += 1135 assume ($x == 10)136 repeat while ($x < 20): stop137 assume ($x == 10)138 repeat while ($x < 20):139 $x += 1140 if (yes):141 do next142 fail "Failed to 'do next'"143 assume ($x == 20)145 (repeat while $condition $body) compiles to:146 $lua =147 Lua ("148 while \($condition as lua expr) do149 \($body as lua)150 ")152 if ($body, contains \(do next)):153 $lua, add "\n ::continue::"155 $lua, add "\nend --while-loop"156 return $lua158 (repeat $body) parses as (repeat while (yes) $body)159 (repeat until $condition $body) parses as (repeat while (not $condition) $body)161 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~163 ### For-each loop (lua's "ipairs()")164 (for $var in $iterable $body) compiles to:165 unless $var:166 at (this tree) fail "No var here"168 ### This uses Lua's approach of only allowing loop-scoped variables in a loop169 if (($var.type == "Action") and ($var.stub == "1 =")):170 [$key, $value] = [$var.1, $var.3]171 ..else:172 [$key, $value] = [nil, $var]174 unless $value:175 at (this tree) fail "No value here"177 ### Numeric loop:178 if (($iterable.type == "Action") and (($iterable, get stub) == "1 to")):179 [$start, $stop] = [$iterable.1, $iterable.3]180 $loop =181 Lua ("182 local _start = \($start as lua expr)183 for \($value as lua identifier)=_start,\($stop as lua expr) do184 ")186 if $key:187 $loop, add ("189 local \($key as lua identifier) = \($value as lua identifier) - _start + 1;190 ")192 go to (loop set)194 ### Numeric loop with step:195 if (($iterable.type == "Action") and (($iterable, get stub) == "1 to 2 by")):196 [$start, $stop, $step] = [$iterable.1, $iterable.3, $iterable.5]197 $loop =198 Lua ("199 local _start, _step = \($start as lua expr), \($step as lua expr);200 for \($value as lua identifier)=_start,\($stop as lua expr),_step do201 ")203 if $key:204 $loop, add ("206 local \($key as lua identifier) = (\($value as lua identifier) - _start)/_step + 1207 ")209 go to (loop set)211 ### for $ in (...):212 if $key:213 $loop =214 Lua ("215 for \($key as lua identifier),\($value as lua identifier) in pairs(\216 ..\($iterable as lua expr)) do217 ")218 ..else:219 $loop =220 Lua "for _i,\($value as lua identifier) in _ipairs(\($iterable as lua expr)) do"222 --- (loop set) ---224 ### TODO: don't always wrap in block225 $lua =226 Lua ("227 do -- for-loop228 \$loop229 \;230 ")231 $lua, add ($body as lua)232 if ($body, contains \(do next)):233 $lua, add "\n ::continue::"235 if ($key and ($body, contains \(do next \$key))):236 $lua, add "\n " (\(---next \$key ---) as lua)238 if ($body, contains \(do next \$value)):239 $lua, add "\n " (\(---next \$value ---) as lua)241 $lua, add "\n end"242 if ($key and ($body, contains \(stop \$key))):243 $lua, add "\n " (\(---stop \$key ---) as lua)245 if ($body, contains \(stop \$value)):246 $lua, add "\n " (\(---stop \$value ---) as lua)248 $lua, add "\nend -- for-loop"249 $lua, remove free vars250 [($value as lua identifier, text), $key and ($key as lua identifier, text)]251 return $lua253 test:254 $d = {.a = 10, .b = 20, .c = 30, .d = 40, .e = 50}255 $result = []256 for ($k = $v) in $d:257 if ($k == "a"):258 do next $k260 if ($v == 20):261 do next $v263 $result, add "\$k = \$v"264 assume (($result sorted) == ["c = 30", "d = 40", "e = 50"])266 ### Numeric range for loops267 test:268 assume ([: for $ in (1 to 5): add $] == [1, 2, 3, 4, 5])269 assume ([: for $ in (1 to 5 by 2): add $] == [1, 3, 5])270 assume ([: for $ in (5 to 1): add $] == [])271 $nums = []272 for $outer in (1 to 100):273 for $inner in ($outer to ($outer + 2)):274 if ($inner == 2):275 $nums, add -2276 do next $inner277 $nums, add $inner278 if ($inner == 5):279 stop $outer281 assume ($nums == [1, -2, 3, -2, 3, 4, 3, 4, 5])283 ### repeat $n times is a shorthand:284 test:285 $x = 0286 repeat 5 times:287 $x += 1288 assume $x == 5289 (repeat $n times $body) parses as (for (=lua "_i") in (1 to $n by 1) $body)291 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~293 test:294 when:295 (1 == 2) (100 < 0):296 fail "bad conditional"297 (1 == 0) (1 == 1) $not_a_variable.x: do nothing298 (1 == 1):299 fail "bad conditional"301 (1 == 2):302 fail "bad conditional"304 else:305 fail "bad conditional"307 ### Multi-branch conditional (if..elseif..else)308 (when $body) compiles to:309 $code = (Lua "")310 $clause = "if"311 $else_allowed = (yes)312 unless ($body.type == "Block"):313 at $body fail ("314 Compile error: 'if' expected a Block, but got a \($body.type).315 Hint: Perhaps you forgot to put a ':' after 'if'?316 ")318 for $line in $body:319 unless320 (($line.type == "Action") and (#$line >= 2)) and321 $line.(#$line) is "Block" syntax tree322 ..:323 at $line fail ("324 Compile error: Invalid line for the body of an 'if' block.325 Hint: Each line should contain one or more conditional expressions followed by a block, \326 ..or "else" followed by a block.327 ")328 $action = $line.(#$line)329 if (($line.1 == "else") and (#$line == 2)):330 unless $else_allowed:331 at $line fail ("332 Compile error: You can't have two 'else' blocks.333 Hint: Merge all of the 'else' blocks together.334 ")336 unless (#"\$code" > 0):337 at $line fail ("338 Compile error: You can't have an 'else' block without a preceding condition.339 Hint: If you want the code in this block to always execute, you don't need a conditional \340 ..block around it. Otherwise, make sure the 'else' block comes last.341 ")343 $code, add "\nelse\n " ($action as lua)344 $else_allowed = (no)345 ..else:346 $code, add $clause " "347 for $i in (1 to (#$line - 1)):348 if ($i > 1):349 $code, add " or "350 $code, add ($line.$i as lua expr)351 $code, add " then\n " ($action as lua)352 $clause = "\nelseif"354 if (#"\$code" == 0):355 at $body fail ("356 Compile error: 'if' block has an empty body.357 Hint: This means nothing would happen, so the 'if' block should be deleted.358 ")360 $code, add "\nend --when"361 return $code363 test:364 if 5 is:365 1 2 3:366 fail "bad switch statement"368 4 5:369 do nothing371 5 6:372 fail "bad switch statement"374 else:375 fail "bad switch statement"377 ### Switch statement378 [if $branch_value is $body, when $branch_value is $body] all compile to:379 $code = (Lua "")380 $clause = "if"381 $else_allowed = (yes)382 define mangler383 unless ($body.type == "Block"):384 at $body fail ("385 Compile error: 'if' expected a Block, but got a \($body.type).386 Hint: Perhaps you forgot to put a ':' after the 'is'?387 ")389 for $line in $body:390 unless391 (($line.type == "Action") and (#$line >= 2)) and392 $line.(#$line) is "Block" syntax tree393 ..:394 at $line fail ("395 Compile error: Invalid line for 'if' block.396 Hint: Each line should contain expressions followed by a block, or "else" followed by a block.397 ")398 $action = $line.(#$line)399 if (($line.1 == "else") and (#$line == 2)):400 unless $else_allowed:401 at $line fail ("402 Compile error: You can't have two 'else' blocks.403 Hint: Merge all of the 'else' blocks together.404 ")406 unless (#"\$code" > 0):407 at $line fail ("408 Compile error: You can't have an 'else' block without a preceding condition.409 Hint: If you want the code in this block to always execute, you don't need a conditional \410 ..block around it. Otherwise, make sure the 'else' block comes last.411 ")413 $code, add "\nelse\n " ($action as lua)414 $else_allowed = (no)415 ..else:416 $code, add $clause " "417 for $i in (1 to (#$line - 1)):418 if ($i > 1):419 $code, add " or "420 $code, add "\(mangle "branch value") == " ($line.$i as lua expr)421 $code, add " then\n " ($action as lua)422 $clause = "\nelseif"424 if (#"\$code" == 0):425 at $body fail ("426 Compile error: 'if' block has an empty body.427 Hint: This means nothing would happen, so the 'if' block should be deleted.428 ")430 $code, add "\nend --when"431 return Lua ("432 do --if $ is...433 local \(mangle "branch value") = \($branch_value as lua expr)434 \$code435 end -- if $ is...436 ")438 ### Do/finally439 (do $action) compiles to ("440 do441 \($action as lua)442 end -- do443 ")445 test:446 assume ((result of: return 99) == 99)448 ### Inline thunk:449 (result of $body) compiles to "\(\(->(\$body)) as lua)()"450 test:451 $t = [1, [2, [[3], 4], 5, [[[6]]]]]452 $flat = []453 for $ in recursive $t:454 if ((lua type of $) == "table"):455 for $2 in $:456 recurse $ on $2457 ..else:458 $flat, add $460 assume (sorted $flat) == [1, 2, 3, 4, 5, 6]462 ### Recurion control flow463 (recurse $v on $x) compiles to464 Lua "table.insert(_stack_\($v as lua expr), \($x as lua expr))"466 (for $var in recursive $structure $body) compiles to:467 $lua =468 Lua ("469 do470 local _stack_\($var as lua expr) = a_List{\($structure as lua expr)}471 while #_stack_\($var as lua expr) > 0 do472 \($var as lua expr) = table.remove(_stack_\($var as lua expr), 1)473 \($body as lua)474 ")476 if ($body, contains \(do next)):477 $lua, add "\n ::continue::"479 if ($body, contains \(do next \$var)):480 $lua, add "\n \(\(---next \$var ---) as lua)"482 $lua, add "\n end -- Recursive loop"483 if ($body, contains \(stop \$var)):484 $lua, add "\n \(\(---stop \$var ---) as lua)"485 $lua, add "\nend -- Recursive scope"486 return $lua