code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(486 lines)
1 #!/usr/bin/env nomsu -V7.0.0
2 ###
3 This file contains compile-time actions that define basic control flow structures
4 like "if" statements and loops.
6 use "core/metaprogramming"
7 use "core/operators"
9 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 ### No-Op
12 test:
13 do nothing
14 (do nothing) compiles to ""
16 ### Conditionals
17 test:
18 if (no):
19 fail "conditional fail"
21 (if $condition $if_body) compiles to ("
22 if \($condition as lua expr) then
23 \($if_body as lua)
24 end
25 ")
27 test:
28 unless (yes):
29 fail "conditional fail"
31 (unless $condition $unless_body) parses as (if (not $condition) $unless_body)
33 if $condition $if_body else $else_body, unless $condition $else_body else $if_body
34 ] all compile to ("
35 if \($condition as lua expr) then
36 \($if_body as lua)
37 else
38 \($else_body as lua)
39 end
40 ")
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)
59 $when_true_expr if $condition else $when_false_expr
60 $when_true_expr if $condition otherwise $when_false_expr
61 $when_false_expr unless $condition else $when_true_expr
62 $when_false_expr unless $condition then $when_true_expr
63 ] all compile to:
64 ### If $when_true_expr is guaranteed to be truthy, we can use Lua's idiomatic
65 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 lua
73 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) then
78 return \($when_true_expr as lua expr)
79 else
80 return \($when_false_expr as lua expr)
81 end
82 end)())
83 ")
85 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
87 ### GOTOs
88 test:
89 $i = 0
90 --- $loop ---
91 $i += 1
92 unless ($i == 10):
93 go to $loop
94 assume ($i == 10)
95 --- (Loop) ---
96 $i -= 1
97 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") else
104 $label as lua identifier
105 )::
108 (go to $label) compiles to ("
109 goto label_\(
110 ($label.stub, as lua id) if ($label.type == "Action") else
111 $label as lua identifier
115 ### Basic loop control
116 (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 loops
132 test:
133 $x = 0
134 repeat while ($x < 10): $x += 1
135 assume ($x == 10)
136 repeat while ($x < 20): stop
137 assume ($x == 10)
138 repeat while ($x < 20):
139 $x += 1
140 if (yes):
141 do next
142 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) do
149 \($body as lua)
152 if ($body, contains \(do next)):
153 $lua, add "\n ::continue::"
155 $lua, add "\nend --while-loop"
156 return $lua
158 (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 loop
169 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) do
186 if $key:
187 $loop, add ("
189 local \($key as lua identifier) = \($value as lua identifier) - _start + 1;
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 do
203 if $key:
204 $loop, add ("
206 local \($key as lua identifier) = (\($value as lua identifier) - _start)/_step + 1
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)) do
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 block
225 $lua =
226 Lua ("
227 do -- for-loop
228 \$loop
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 vars
250 [($value as lua identifier, text), $key and ($key as lua identifier, text)]
251 return $lua
253 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 $k
260 if ($v == 20):
261 do next $v
263 $result, add "\$k = \$v"
264 assume (($result sorted) == ["c = 30", "d = 40", "e = 50"])
266 ### Numeric range for loops
267 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 -2
276 do next $inner
277 $nums, add $inner
278 if ($inner == 5):
279 stop $outer
281 assume ($nums == [1, -2, 3, -2, 3, 4, 3, 4, 5])
283 ### repeat $n times is a shorthand:
284 test:
285 $x = 0
286 repeat 5 times:
287 $x += 1
288 assume $x == 5
289 (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 nothing
298 (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'?
318 for $line in $body:
319 unless
320 (($line.type == "Action") and (#$line >= 2)) and
321 $line.(#$line) is "Block" syntax tree
322 ..:
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.
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.
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.
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.
360 $code, add "\nend --when"
361 return $code
363 test:
364 if 5 is:
365 1 2 3:
366 fail "bad switch statement"
368 4 5:
369 do nothing
371 5 6:
372 fail "bad switch statement"
374 else:
375 fail "bad switch statement"
377 ### Switch statement
378 [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 mangler
383 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'?
389 for $line in $body:
390 unless
391 (($line.type == "Action") and (#$line >= 2)) and
392 $line.(#$line) is "Block" syntax tree
393 ..:
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.
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.
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.
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.
430 $code, add "\nend --when"
431 return Lua ("
432 do --if $ is...
433 local \(mangle "branch value") = \($branch_value as lua expr)
434 \$code
435 end -- if $ is...
438 ### Do/finally
439 (do $action) compiles to ("
441 \($action as lua)
442 end -- do
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 $2
457 ..else:
458 $flat, add $
460 assume (sorted $flat) == [1, 2, 3, 4, 5, 6]
462 ### Recurion control flow
463 (recurse $v on $x) compiles to
464 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 ("
470 local _stack_\($var as lua expr) = a_List{\($structure as lua expr)}
471 while #_stack_\($var as lua expr) > 0 do
472 \($var as lua expr) = table.remove(_stack_\($var as lua expr), 1)
473 \($body as lua)
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