https://github.com/akkartik/mu/blob/master/040brace.cc
  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 bool OPEN = false, CLOSE = true;
 40   // use signed integer for step index because we'll be doing arithmetic on it
 41   list<pair<bool/*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<bool,int>(OPEN, index));
 48     }
 49     if (inst.label == "}") {
 50       trace(9993, "transform") << "push (close, " << index << ")" << end();
 51       braces.push_back(pair<bool,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 << maybe(get(Recipe, r).name) << "unbalanced '}'\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 << maybe(get(Recipe, r).name) << "'" << 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(new type_tree("offset"));
117     target.set_value(0);
118     if (open_braces.empty())
119       raise << maybe(get(Recipe, r).name) << "'" << old_name << "' needs a '{' before\n" << end();
120     else if (old_name.find("loop") != string::npos)
121       target.set_value(open_braces.top()-index);
122     else  // break instruction
123       target.set_value(matching_brace(open_braces.top(), braces, r) - index - 1);
124     inst.ingredients.push_back(target);
125     // log computed target
126     if (inst.name == "jump")
127       trace(9992, "transform") << "jump " << no_scientific(target.value) << ":offset" << end();
128     else
129       trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(0).name << ", " << no_scientific(target.value) << ":offset" << end();
130   }
131 }
132 
133 // returns a signed integer not just so that we can return -1 but also to
134 // enable future signed arithmetic
135 int matching_brace(int index, const list<pair<bool, int> >& braces, recipe_ordinal r) {
136   int stacksize = 0;
137   for (list<pair<bool, int> >::const_iterator p = braces.begin();  p != braces.end();  ++p) {
138     if (p->second < index) continue;
139     stacksize += (p->first ? 1 : -1);
140     if (stacksize == 0) return p->second;
141   }
142   raise << maybe(get(Recipe, r).name) << "unbalanced '{'\n" << end();
143   return SIZE(get(Recipe, r).steps);  // exit current routine
144 }
145 
146 :(scenario loop)
147 def main [
148   1:num <- copy 0
149   2:num <- copy 0
150   {
151     3:num <- copy 0
152     loop
153   }
154 ]
155 +transform: --- transform braces for recipe main
156 +transform: copy ...
157 +transform: copy ...
158 +transform: copy ...
159 +transform: jump -2:offset
160 
161 :(scenario break_empty_block)
162 def main [
163   1:num <- copy 0
164   {
165     break
166   }
167 ]
168 +transform: --- transform braces for recipe main
169 +transform: copy ...
170 +transform: jump 0:offset
171 
172 :(scenario break_cascading)
173 def main [
174   1:num <- copy 0
175   {
176     break
177   }
178   {
179     break
180   }
181 ]
182 +transform: --- transform braces for recipe main
183 +transform: copy ...
184 +transform: jump 0:offset
185 +transform: jump 0:offset
186 
187 :(scenario break_cascading_2)
188 def main [
189   1:num <- copy 0
190   2:num <- copy 0
191   {
192     break
193     3:num <- copy 0
194   }
195   {
196     break
197   }
198 ]
199 +transform: --- transform braces for recipe main
200 +transform: copy ...
201 +transform: copy ...
202 +transform: jump 1:offset
203 +transform: copy ...
204 +transform: jump 0:offset
205 
206 :(scenario break_if)
207 def main [
208   1:num <- copy 0
209   2:num <- copy 0
210   {
211     break-if 2:num
212     3:num <- copy 0
213   }
214   {
215     break
216   }
217 ]
218 +transform: --- transform braces for recipe main
219 +transform: copy ...
220 +transform: copy ...
221 +transform: jump-if 2, 1:offset
222 +transform: copy ...
223 +transform: jump 0:offset
224 
225 :(scenario break_nested)
226 def main [
227   1:num <- copy 0
228   {
229     2:num <- copy 0
230     break
231     {
232       3:num <- copy 0
233     }
234     4:num <- copy 0
235   }
236 ]
237 +transform: jump 4:offset
238 
239 :(scenario break_nested_degenerate)
240 def main [
241   1:num <- copy 0
242   {
243     2:num <- copy 0
244     break
245     {
246     }
247     4:num <- copy 0
248   }
249 ]
250 +transform: jump 3:offset
251 
252 :(scenario break_nested_degenerate_2)
253 def main [
254   1:num <- copy 0
255   {
256     2:num <- copy 0
257     break
258     {
259     }
260   }
261 ]
262 +transform: jump 2:offset
263 
264 :(scenario break_label)
265 % Hide_errors = true;
266 def main [
267   1:num <- copy 0
268   {
269     break +foo:offset
270   }
271 ]
272 +transform: jump +foo:offset
273 
274 :(scenario break_unless)
275 def main [
276   1:num <- copy 0
277   2:num <- copy 0
278   {
279     break-unless 2:num
280     3:num <- copy 0
281   }
282 ]
283 +transform: --- transform braces for recipe main
284 +transform: copy ...
285 +transform: copy ...
286 +transform: jump-unless 2, 1:offset
287 +transform: copy ...
288 
289 :(scenario loop_unless)
290 def main [
291   1:num <- copy 0
292   2:num <- copy 0
293   {
294     loop-unless 2:num
295     3:num <- copy 0
296   }
297 ]
298 +transform: --- transform braces for recipe main
299 +transform: copy ...
300 +transform: copy ...
301 +transform: jump-unless 2, -1:offset
302 +transform: copy ...
303 
304 :(scenario loop_nested)
305 def main [
306   1:num <- copy 0
307   {
308     2:num <- copy 0
309     {
310       3:num <- copy 0
311     }
312     loop-if 4:bool
313     5:num <- copy 0
314   }
315 ]
316 +transform: --- transform braces for recipe main
317 +transform: jump-if 4, -5:offset
318 
319 :(scenario loop_label)
320 def main [
321   1:num <- copy 0
322   +foo
323   2:num <- copy 0
324 ]
325 +transform: --- transform braces for recipe main
326 +transform: copy ...
327 +transform: copy ...
328 
329 //: test how things actually run
330 :(scenarios run)
331 :(scenario brace_conversion_and_run)
332 def test-factorial [
333   1:num <- copy 5
334   2:num <- copy 1
335   {
336     3:bool <- equal 1:num, 1
337     break-if 3:bool
338 #    $print 1:num
339     2:num <- multiply 2:num, 1:num
340     1:num <- subtract 1:num, 1
341     loop
342   }
343   4:num <- copy 2:num  # trigger a read
344 ]
345 +mem: location 2 is 120
346 
347 :(scenario break_outside_braces_fails)
348 % Hide_errors = true;
349 def main [
350   break
351 ]
352 +error: main: 'break' needs a '{' before
353 
354 :(scenario break_conditional_without_ingredient_fails)
355 % Hide_errors = true;
356 def main [
357   {
358     break-if
359   }
360 ]
361 +error: main: 'break-if' expects 1 or 2 ingredients, but got none
362 
363 //: Using break we can now implement conditional returns.
364 
365 :(scenario return_if)
366 def main [
367   1:num <- test1
368 ]
369 def test1 [
370   return-if 0, 34
371   return 35
372 ]
373 +mem: storing 35 in location 1
374 
375 :(scenario return_if_2)
376 def main [
377   1:num <- test1
378 ]
379 def test1 [
380   return-if 1, 34
381   return 35
382 ]
383 +mem: storing 34 in location 1
384 
385 :(before "End Rewrite Instruction(curr, recipe result)")
386 // rewrite 'return-if a, b, c, ...' to
387 //   ```
388 //   {
389 //     break-unless a
390 //     return b, c, ...
391 //   }
392 //   ```
393 if (curr.name == "return-if" || curr.name == "reply-if") {
394   if (curr.products.empty()) {
395     emit_return_block(result, "break-unless", curr);
396     curr.clear();
397   }
398   else {
399     raise << "'" << curr.name << "' never yields any products\n" << end();
400   }
401 }
402 // rewrite 'return-unless a, b, c, ...' to
403 //   ```
404 //   {
405 //     break-if a
406 //     return b, c, ...
407 //   }
408 //   ```
409 if (curr.name == "return-unless" || curr.name == "reply-unless") {
410   if (curr.products.empty()) {
411     emit_return_block(result, "break-if", curr);
412     curr.clear();
413   }
414   else {
415     raise << "'" << curr.name << "' never yields any products\n" << end();
416   }
417 }
418 
419 :(code)
420 void emit_return_block(recipe& out, const string& break_command, const instruction& inst) {
421   const vector<reagent>& ingredients = inst.ingredients;
422   reagent/*copy*/ condition = ingredients.at(0);
423   vector<reagent> return_ingredients;
424   copy(++ingredients.begin(), ingredients.end(), inserter(return_ingredients, return_ingredients.end()));
425 
426   // {
427   instruction open_label;  open_label.is_label=true;  open_label.label = "{";
428   out.steps.push_back(open_label);
429 
430   // <break command> <condition>
431   instruction break_inst;
432   break_inst.operation = get(Recipe_ordinal, break_command);
433   break_inst.name = break_command;
434   break_inst.ingredients.push_back(condition);
435   out.steps.push_back(break_inst);
436 
437   // return <return ingredients>
438   instruction return_inst;
439   return_inst.operation = get(Recipe_ordinal, "return");
440   return_inst.name = "return";
441   return_inst.ingredients.swap(return_ingredients);
442   return_inst.original_string = inst.original_string;
443   out.steps.push_back(return_inst);
444 
445   // }
446   instruction close_label;  close_label.is_label=true;  close_label.label = "}";
447   out.steps.push_back(close_label);
448 }
449 
450 //: Make sure these pseudo recipes get consistent numbers in all tests, even
451 //: though they aren't implemented. Allows greater flexibility in ordering
452 //: transforms.
453 
454 :(before "End Primitive Recipe Declarations")
455 BREAK,
456 BREAK_IF,
457 BREAK_UNLESS,
458 LOOP,
459 LOOP_IF,
460 LOOP_UNLESS,
461 :(before "End Primitive Recipe Numbers")
462 put(Recipe_ordinal, "break", BREAK);
463 put(Recipe_ordinal, "break-if", BREAK_IF);
464 put(Recipe_ordinal, "break-unless", BREAK_UNLESS);
465 put(Recipe_ordinal, "loop", LOOP);
466 put(Recipe_ordinal, "loop-if", LOOP_IF);
467 put(Recipe_ordinal, "loop-unless", LOOP_UNLESS);
468 :(before "End Primitive Recipe Checks")
469 case BREAK: break;
470 case BREAK_IF: break;
471 case BREAK_UNLESS: break;
472 case LOOP: break;
473 case LOOP_IF: break;
474 case LOOP_UNLESS: break;