1 //: Structured programming
  2 //:
  3 //: Our jump recipes are quite inconvenient to use, so Mu provides a
  4 //: lightweight tool called 'transform_braces' to work in a slightly more
  5 //: convenient format with nested braces:
  6 //:
  7 //:   {
  8 //:     some instructions
  9 //:     {
 10 //:       more instructions
 11 //:     }
 12 //:   }
 13 //:
 14 //: Braces are just labels, they require no special parsing. The pseudo
 15 //: instructions 'loop' and 'break' jump to just after the enclosing '{' and
 16 //: '}' respectively.
 17 //:
 18 //: Conditional and unconditional 'loop' and 'break' should give us 80% of the
 19 //: benefits of the control-flow primitives we're used to in other languages,
 20 //: like 'if', 'while', 'for', etc.
 21 
 22 :(scenarios transform)
 23 :(scenario brace_conversion)
 24 def main [
 25   {
 26   ¦ break
 27   ¦ 1:num <- copy 0
 28   }
 29 ]
 30 +transform: --- transform braces for recipe main
 31 +transform: jump 1:offset
 32 +transform: copy ...
 33 
 34 :(before "End Instruction Modifying Transforms")
 35 Transform.push_back(transform_braces);  // idempotent
 36 
 37 :(code)
 38 void transform_braces(const recipe_ordinal r) {
 39   const int OPEN = 0, CLOSE = 1;
 40   // use signed integer for step index because we'll be doing arithmetic on it
 41   list<pair<int/*OPEN/CLOSE*/, /*step*/int> > braces;
 42   trace(9991, "transform") << "--- transform braces for recipe " << get(Recipe, r).name << end();
 43   for (int index = 0;  index < SIZE(get(Recipe, r).steps);  ++index) {
 44   ¦ const instruction& inst = get(Recipe, r).steps.at(index);
 45   ¦ if (inst.label == "{") {
 46   ¦ ¦ trace(9993, "transform") << maybe(get(Recipe, r).name) << "push (open, " << index << ")" << end();
 47   ¦ ¦ braces.push_back(pair<int,int>(OPEN, index));
 48   ¦ }
 49   ¦ if (inst.label == "}") {
 50   ¦ ¦ trace(9993, "transform") << "push (close, " << index << ")" << end();
 51   ¦ ¦ braces.push_back(pair<int,int>(CLOSE, index));
 52   ¦ }
 53   }
 54   stack</*step*/int> open_braces;
 55   for (int index = 0;  index < SIZE(get(Recipe, r).steps);  ++index) {
 56   ¦ instruction& inst = get(Recipe, r).steps.at(index);
 57   ¦ if (inst.label == "{") {
 58   ¦ ¦ open_braces.push(index);
 59   ¦ ¦ continue;
 60   ¦ }
 61   ¦ if (inst.label == "}") {
 62   ¦ ¦ if (open_braces.empty()) {
 63   ¦ ¦ ¦ raise << "missing '{' in '" << get(Recipe, r).name << "'\n" << end();
 64   ¦ ¦ ¦ return;
 65   ¦ ¦ }
 66   ¦ ¦ open_braces.pop();
 67   ¦ ¦ continue;
 68   ¦ }
 69   ¦ if (inst.is_label) continue;
 70   ¦ if (inst.name != "loop"
 71   ¦ ¦ ¦ ¦&& inst.name != "loop-if"
 72   ¦ ¦ ¦ ¦&& inst.name != "loop-unless"
 73   ¦ ¦ ¦ ¦&& inst.name != "break"
 74   ¦ ¦ ¦ ¦&& inst.name != "break-if"
 75   ¦ ¦ ¦ ¦&& inst.name != "break-unless") {
 76   ¦ ¦ trace(9992, "transform") << inst.name << " ..." << end();
 77   ¦ ¦ continue;
 78   ¦ }
 79   ¦ // check for errors
 80   ¦ if (inst.name.find("-if") != string::npos || inst.name.find("-unless") != string::npos) {
 81   ¦ ¦ if (inst.ingredients.empty()) {
 82   ¦ ¦ ¦ raise << "'" << inst.name << "' expects 1 or 2 ingredients, but got none\n" << end();
 83   ¦ ¦ ¦ continue;
 84   ¦ ¦ }
 85   ¦ }
 86   ¦ // update instruction operation
 87   ¦ string old_name = inst.name;  // save a copy
 88   ¦ if (inst.name.find("-if") != string::npos) {
 89   ¦ ¦ inst.name = "jump-if";
 90   ¦ ¦ inst.operation = JUMP_IF;
 91   ¦ }
 92   ¦ else if (inst.name.find("-unless") != string::npos) {
 93   ¦ ¦ inst.name = "jump-unless";
 94   ¦ ¦ inst.operation = JUMP_UNLESS;
 95   ¦ }
 96   ¦ else {
 97   ¦ ¦ inst.name = "jump";
 98   ¦ ¦ inst.operation = JUMP;
 99   ¦ }
100   ¦ // check for explicitly provided targets
101   ¦ if (inst.name.find("-if") != string::npos || inst.name.find("-unless") != string::npos) {
102   ¦ ¦ // conditional branches check arg 1
103   ¦ ¦ if (SIZE(inst.ingredients) > 1 && is_literal(inst.ingredients.at(1))) {
104   ¦ ¦ ¦ trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(1).name << ":offset" << end();
105   ¦ ¦ ¦ continue;
106   ¦ ¦ }
107   ¦ }
108   ¦ else {
109   ¦ ¦ // unconditional branches check arg 0
110   ¦ ¦ if (!inst.ingredients.empty() && is_literal(inst.ingredients.at(0))) {
111   ¦ ¦ ¦ trace(9992, "transform") << "jump " << inst.ingredients.at(0).name << ":offset" << end();
112   ¦ ¦ ¦ continue;
113   ¦ ¦ }
114   ¦ }
115   ¦ // if implicit, compute target
116   ¦ reagent target;
117   ¦ target.type = new type_tree("offset");
118   ¦ target.set_value(0);
119   ¦ if (open_braces.empty())
120   ¦ ¦ raise << "'" << old_name << "' needs a '{' before\n" << end();
121   ¦ else if (old_name.find("loop") != string::npos)
122   ¦ ¦ target.set_value(open_braces.top()-index);
123   ¦ else  // break instruction
124   ¦ ¦ target.set_value(matching_brace(open_braces.top(), braces, r) - index - 1);
125   ¦ inst.ingredients.push_back(target);
126   ¦ // log computed target
127   ¦ if (inst.name == "jump")
128   ¦ ¦ trace(9992, "transform") << "jump " << no_scientific(target.value) << ":offset" << end();
129   ¦ else
130   ¦ ¦ trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(0).name << ", " << no_scientific(target.value) << ":offset" << end();
131   }
132 }
133 
134 // returns a signed integer not just so that we can return -1 but also to
135 // enable future signed arithmetic
136 int matching_brace(int index, const list<pair<int, int> >& braces, recipe_ordinal r) {
137   int stacksize = 0;
138   for (list<pair<int, int> >::const_iterator p = braces.begin();  p != braces.end();  ++p) {
139   ¦ if (p->second < index) continue;
140   ¦ stacksize += (p->first ? 1 : -1);
141   ¦ if (stacksize == 0) return p->second;
142   }
143   raise << maybe(get(Recipe, r).name) << "unbalanced '{'\n" << end();
144   return SIZE(get(Recipe, r).steps);  // exit current routine
145 }
146 
147 :(scenario loop)
148 def main [
149   1:num <- copy 0
150   2:num <- copy 0
151   {
152   ¦ 3:num <- copy 0
153   ¦ loop
154   }
155 ]
156 +transform: --- transform braces for recipe main
157 +transform: copy ...
158 +transform: copy ...
159 +transform: copy ...
160 +transform: jump -2:offset
161 
162 :(scenario break_empty_block)
163 def main [
164   1:num <- copy 0
165   {
166   ¦ break
167   }
168 ]
169 +transform: --- transform braces for recipe main
170 +transform: copy ...
171 +transform: jump 0:offset
172 
173 :(scenario break_cascading)
174 def main [
175   1:num <- copy 0
176   {
177   ¦ break
178   }
179   {
180   ¦ break
181   }
182 ]
183 +transform: --- transform braces for recipe main
184 +transform: copy ...
185 +transform: jump 0:offset
186 +transform: jump 0:offset
187 
188 :(scenario break_cascading_2)
189 def main [
190   1:num <- copy 0
191   2:num <- copy 0
192   {
193   ¦ break
194   ¦ 3:num <- copy 0
195   }
196   {
197   ¦ break
198   }
199 ]
200 +transform: --- transform braces for recipe main
201 +transform: copy ...
202 +transform: copy ...
203 +transform: jump 1:offset
204 +transform: copy ...
205 +transform: jump 0:offset
206 
207 :(scenario break_if)
208 def main [
209   1:num <- copy 0
210   2:num <- copy 0
211   {
212   ¦ break-if 2:num
213   ¦ 3:num <- copy 0
214   }
215   {
216   ¦ break
217   }
218 ]
219 +transform: --- transform braces for recipe main
220 +transform: copy ...
221 +transform: copy ...
222 +transform: jump-if 2, 1:offset
223 +transform: copy ...
224 +transform: jump 0:offset
225 
226 :(scenario break_nested)
227 def main [
228   1:num <- copy 0
229   {
230   ¦ 2:num <- copy 0
231   ¦ break
232   ¦ {
233   ¦ ¦ 3:num <- copy 0
234   ¦ }
235   ¦ 4:num <- copy 0
236   }
237 ]
238 +transform: jump 4:offset
239 
240 :(scenario break_nested_degenerate)
241 def main [
242   1:num <- copy 0
243   {
244   ¦ 2:num <- copy 0
245   ¦ break
246   ¦ {
247   ¦ }
248   ¦ 4:num <- copy 0
249   }
250 ]
251 +transform: jump 3:offset
252 
253 :(scenario break_nested_degenerate_2)
254 def main [
255   1:num <- copy 0
256   {
257   ¦ 2:num <- copy 0
258   ¦ break
259   ¦ {
260   ¦ }
261   }
262 ]
263 +transform: jump 2:offset
264 
265 :(scenario break_label)
266 % Hide_errors = true;
267 def main [
268   1:num <- copy 0
269   {
270   ¦ break +foo:offset
271   }
272 ]
273 +transform: jump +foo:offset
274 
275 :(scenario break_unless)
276 def main [
277   1:num <- copy 0
278   2:num <- copy 0
279   {
280   ¦ break-unless 2:num
281   ¦ 3:num <- copy 0
282   }
283 ]
284 +transform: --- transform braces for recipe main
285 +transform: copy ...
286 +transform: copy ...
287 +transform: jump-unless 2, 1:offset
288 +transform: copy ...
289 
290 :(scenario loop_unless)
291 def main [
292   1:num <- copy 0
293   2:num <- copy 0
294   {
295   ¦ loop-unless 2:num
296   ¦ 3:num <- copy 0
297   }
298 ]
299 +transform: --- transform braces for recipe main
300 +transform: copy ...
301 +transform: copy ...
302 +transform: jump-unless 2, -1:offset
303 +transform: copy ...
304 
305 :(scenario loop_nested)
306 def main [
307   1:num <- copy 0
308   {
309   ¦ 2:num <- copy 0
310   ¦ {
311   ¦ ¦ 3:num <- copy 0
312   ¦ }
313   ¦ loop-if 4:bool
314   ¦ 5:num <- copy 0
315   }
316 ]
317 +transform: --- transform braces for recipe main
318 +transform: jump-if 4, -5:offset
319 
320 :(scenario loop_label)
321 def main [
322   1:num <- copy 0
323   +foo
324   2:num <- copy 0
325 ]
326 +transform: --- transform braces for recipe main
327 +transform: copy ...
328 +transform: copy ...
329 
330 //: test how things actually run
331 :(scenarios run)
332 :(scenario brace_conversion_and_run)
333 def test-factorial [
334   1:num <- copy 5
335   2:num <- copy 1
336   {
337   ¦ 3:bool <- equal 1:num, 1
338   ¦ break-if 3:bool
339 #    $print 1:num
340   ¦ 2:num <- multiply 2:num, 1:num
341   ¦ 1:num <- subtract 1:num, 1
342   ¦ loop
343   }
344   4:num <- copy 2:num  # trigger a read
345 ]
346 +mem: location 2 is 120
347 
348 :(scenario break_outside_braces_fails)
349 % Hide_errors = true;
350 def main [
351   break
352 ]
353 +error: 'break' needs a '{' before
354 
355 :(scenario break_conditional_without_ingredient_fails)
356 % Hide_errors = true;
357 def main [
358   {
359   ¦ break-if
360   }
361 ]
362 +error: 'break-if' expects 1 or 2 ingredients, but got none
363 
364 //: Using break we can now implement conditional returns.
365 
366 :(scenario return_if)
367 def main [
368   1:num <- test1
369 ]
370 def test1 [
371   return-if 0, 34
372   return 35
373 ]
374 +mem: storing 35 in location 1
375 
376 :(scenario return_if_2)
377 def main [
378   1:num <- test1
379 ]
380 def test1 [
381   return-if 1, 34
382   return 35
383 ]
384 +mem: storing 34 in location 1
385 
386 :(before "End Rewrite Instruction(curr, recipe result)")
387 // rewrite `return-if a, b, c, ...` to
388 //   ```
389 //   {
390 //     break-unless a
391 //     return b, c, ...
392 //   }
393 //   ```
394 if (curr.name == "return-if" || curr.name == "reply-if") {
395   if (curr.products.empty()) {
396   ¦ emit_return_block(result, "break-unless", curr);
397   ¦ curr.clear();
398   }
399   else {
400   ¦ raise << "'" << curr.name << "' never yields any products\n" << end();
401   }
402 }
403 // rewrite `return-unless a, b, c, ...` to
404 //   ```
405 //   {
406 //     break-if a
407 //     return b, c, ...
408 //   }
409 //   ```
410 if (curr.name == "return-unless" || curr.name == "reply-unless") {
411   if (curr.products.empty()) {
412   ¦ emit_return_block(result, "break-if", curr);
413   ¦ curr.clear();
414   }
415   else {
416   ¦ raise << "'" << curr.name << "' never yields any products\n" << end();
417   }
418 }
419 
420 :(code)
421 void emit_return_block(recipe& out, const string& break_command, const instruction& inst) {
422   const vector<reagent>& ingredients = inst.ingredients;
423   reagent/*copy*/ condition = ingredients.at(0);
424   vector<reagent> return_ingredients;
425   copy(++ingredients.begin(), ingredients.end(), inserter(return_ingredients, return_ingredients.end()));
426 
427   // {
428   instruction open_label;  open_label.is_label=true;  open_label.label = "{";
429   out.steps.push_back(open_label);
430 
431   // <break command> <condition>
432   instruction break_inst;
433   break_inst.operation = get(Recipe_ordinal, break_command);
434   break_inst.name = break_command;
435   break_inst.ingredients.push_back(condition);
436   out.steps.push_back(break_inst);
437 
438   // return <return ingredients>
439   instruction return_inst;
440   return_inst.operation = get(Recipe_ordinal, "return");
441   return_inst.name = "return";
442   return_inst.ingredients.swap(return_ingredients);
443   return_inst.original_string = inst.original_string;
444   out.steps.push_back(return_inst);
445 
446   // }
447   instruction close_label;  close_label.is_label=true;  close_label.label = "}";
448   out.steps.push_back(close_label);
449 }
450 
451 //: Make sure these pseudo recipes get consistent numbers in all tests, even
452 //: though they aren't implemented. Allows greater flexibility in ordering
453 //: transforms.
454 
455 :(before "End Primitive Recipe Declarations")
456 BREAK,
457 BREAK_IF,
458 BREAK_UNLESS,
459 LOOP,
460 LOOP_IF,
461 LOOP_UNLESS,
462 :(before "End Primitive Recipe Numbers")
463 put(Recipe_ordinal, "break", BREAK);
464 put(Recipe_ordinal, "break-if", BREAK_IF);
465 put(Recipe_ordinal, "break-unless", BREAK_UNLESS);
466 put(Recipe_ordinal, "loop", LOOP);
467 put(Recipe_ordinal, "loop-if", LOOP_IF);
468 put(Recipe_ordinal, "loop-unless", LOOP_UNLESS);
469 :(before "End Primitive Recipe Checks")
470 case BREAK: break;
471 case BREAK_IF: break;
472 case BREAK_UNLESS: break;
473 case LOOP: break;
474 case LOOP_IF: break;
475 case LOOP_UNLESS: break;