about summary refs log tree commit diff stats
path: root/072recipe.cc
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-03-12 18:56:55 -0700
committerKartik Agaram <vc@akkartik.com>2019-03-12 19:14:12 -0700
commit4a943d4ed313eff001504c2b5c472266e86a38af (patch)
treea5757233a8c81b303a808f251180c7344071ed51 /072recipe.cc
parent43711b0e9f18e0225ce14687fb6ea0902aa6fc61 (diff)
downloadmu-4a943d4ed313eff001504c2b5c472266e86a38af.tar.gz
5001 - drop the :(scenario) DSL
I've been saying for a while[1][2][3] that adding extra abstractions makes
things harder for newcomers, and adding new notations doubly so. And then
I notice this DSL in my own backyard. Makes me feel like a hypocrite.

[1] https://news.ycombinator.com/item?id=13565743#13570092
[2] https://lobste.rs/s/to8wpr/configuration_files_are_canary_warning
[3] https://lobste.rs/s/mdmcdi/little_languages_by_jon_bentley_1986#c_3miuf2

The implementation of the DSL was also highly hacky:

a) It was happening in the tangle/ tool, but was utterly unrelated to tangling
layers.

b) There were several persnickety constraints on the different kinds of
lines and the specific order they were expected in. I kept finding bugs
where the translator would silently do the wrong thing. Or the error messages
sucked, and readers may be stuck looking at the generated code to figure
out what happened. Fixing error messages would require a lot more code,
which is one of my arguments against DSLs in the first place: they may
be easy to implement, but they're hard to design to go with the grain of
the underlying platform. They require lots of iteration. Is that effort
worth prioritizing in this project?

On the other hand, the DSL did make at least some readers' life easier,
the ones who weren't immediately put off by having to learn a strange syntax.
There were fewer quotes to parse, fewer backslash escapes.

Anyway, since there are also people who dislike having to put up with strange
syntaxes, we'll call that consideration a wash and tear this DSL out.

---

This commit was sheer drudgery. Hopefully it won't need to be redone with
a new DSL because I grow sick of backslashes.
Diffstat (limited to '072recipe.cc')
-rw-r--r--072recipe.cc623
1 files changed, 360 insertions, 263 deletions
diff --git a/072recipe.cc b/072recipe.cc
index 43b1d013..a3bb00bf 100644
--- a/072recipe.cc
+++ b/072recipe.cc
@@ -2,16 +2,21 @@
 //: also like to make the recipe a variable, pass recipes to "higher-order"
 //: recipes, return recipes from recipes and so on.
 
-:(scenario call_literal_recipe)
-def main [
-  1:num <- call f, 34
-]
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 34 in location 1
+void test_call_literal_recipe() {
+  run(
+      "def main [\n"
+      "  1:num <- call f, 34\n"
+      "]\n"
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
 
 :(before "End Mu Types Initialization")
 put(Type_ordinal, "recipe-literal", 0);
@@ -51,14 +56,19 @@ bool is_matching_non_recipe_literal(const reagent& x, const string& name) {
 
 //: It's confusing to use variable names that are also recipe names. Always
 //: assume variable types override recipe literals.
-:(scenario error_on_recipe_literal_used_as_a_variable)
-% Hide_errors = true;
-def main [
-  local-scope
-  a:bool <- equal break 0
-  break:bool <- copy 0
-]
-+error: main: missing type for 'break' in 'a:bool <- equal break, 0'
+void test_error_on_recipe_literal_used_as_a_variable() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  local-scope\n"
+      "  a:bool <- equal break 0\n"
+      "  break:bool <- copy 0\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: missing type for 'break' in 'a:bool <- equal break, 0'\n"
+  );
+}
 
 :(before "End Primitive Recipe Declarations")
 CALL,
@@ -99,103 +109,139 @@ case CALL: {
   break;
 }
 
-:(scenario call_variable)
-def main [
-  {1: (recipe number -> number)} <- copy f
-  2:num <- call {1: (recipe number -> number)}, 34
-]
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 34 in location 2
-
-:(scenario call_literal_recipe_repeatedly)
-def main [
-  1:num <- call f, 34
-  1:num <- call f, 35
-]
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 34 in location 1
-+mem: storing 35 in location 1
-
-:(scenario call_shape_shifting_recipe)
-def main [
-  1:num <- call f, 34
-]
-def f x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 34 in location 1
-
-:(scenario call_shape_shifting_recipe_inside_shape_shifting_recipe)
-def main [
-  1:num <- f 34
-]
-def f x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- call g x
-]
-def g x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 34 in location 1
-
-:(scenario call_shape_shifting_recipe_repeatedly_inside_shape_shifting_recipe)
-def main [
-  1:num <- f 34
-]
-def f x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- call g x
-  y <- call g x
-]
-def g x:_elem -> y:_elem [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 34 in location 1
+:(code)
+void test_call_variable() {
+  run(
+      "def main [\n"
+      "  {1: (recipe number -> number)} <- copy f\n"
+      "  2:num <- call {1: (recipe number -> number)}, 34\n"
+      "]\n"
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 2\n"
+  );
+}
+
+void test_call_literal_recipe_repeatedly() {
+  run(
+      "def main [\n"
+      "  1:num <- call f, 34\n"
+      "  1:num <- call f, 35\n"
+      "]\n"
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+      "mem: storing 35 in location 1\n"
+  );
+}
+
+void test_call_shape_shifting_recipe() {
+  run(
+      "def main [\n"
+      "  1:num <- call f, 34\n"
+      "]\n"
+      "def f x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
+
+void test_call_shape_shifting_recipe_inside_shape_shifting_recipe() {
+  run(
+      "def main [\n"
+      "  1:num <- f 34\n"
+      "]\n"
+      "def f x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- call g x\n"
+      "]\n"
+      "def g x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
+
+void test_call_shape_shifting_recipe_repeatedly_inside_shape_shifting_recipe() {
+  run(
+      "def main [\n"
+      "  1:num <- f 34\n"
+      "]\n"
+      "def f x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- call g x\n"
+      "  y <- call g x\n"
+      "]\n"
+      "def g x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
 
 //:: check types for 'call' instructions
 
-:(scenario call_check_literal_recipe)
-% Hide_errors = true;
-def main [
-  1:num <- call f, 34
-]
-def f x:point -> y:point [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+error: main: ingredient 0 has the wrong type at '1:num <- call f, 34'
-+error: main: product 0 has the wrong type at '1:num <- call f, 34'
-
-:(scenario call_check_variable_recipe)
-% Hide_errors = true;
-def main [
-  {1: (recipe point -> point)} <- copy f
-  2:num <- call {1: (recipe point -> point)}, 34
-]
-def f x:point -> y:point [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+error: main: ingredient 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'
-+error: main: product 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'
+void test_call_check_literal_recipe() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  1:num <- call f, 34\n"
+      "]\n"
+      "def f x:point -> y:point [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: ingredient 0 has the wrong type at '1:num <- call f, 34'\n"
+      "error: main: product 0 has the wrong type at '1:num <- call f, 34'\n"
+  );
+}
+
+void test_call_check_variable_recipe() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  {1: (recipe point -> point)} <- copy f\n"
+      "  2:num <- call {1: (recipe point -> point)}, 34\n"
+      "]\n"
+      "def f x:point -> y:point [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: ingredient 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'\n"
+      "error: main: product 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'\n"
+  );
+}
 
 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
 if (inst.name == "call" && !inst.ingredients.empty() && is_recipe_literal(inst.ingredients.at(0))) {
@@ -339,31 +385,41 @@ bool is_mu_recipe(const reagent& r) {
   return r.type->left->atom && r.type->left->name == "recipe";
 }
 
-:(scenario copy_typecheck_recipe_variable)
-% Hide_errors = true;
-def main [
-  3:num <- copy 34  # abc def
-  {1: (recipe number -> number)} <- copy f  # store literal in a matching variable
-  {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)}  # mismatch between recipe variables
-]
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+error: main: can't copy '{1: (recipe number -> number)}' to '{2: (recipe boolean -> boolean)}'; types don't match
-
-:(scenario copy_typecheck_recipe_variable_2)
-% Hide_errors = true;
-def main [
-  {1: (recipe number -> number)} <- copy f  # mismatch with a recipe literal
-]
-def f x:bool -> y:bool [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+error: main: can't copy 'f' to '{1: (recipe number -> number)}'; types don't match
+void test_copy_typecheck_recipe_variable() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  3:num <- copy 34\n"
+      "  {1: (recipe number -> number)} <- copy f\n"  // store literal in a matching variable
+      "  {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)}\n"  // mismatch between recipe variables
+      "]\n"
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: can't copy '{1: (recipe number -> number)}' to '{2: (recipe boolean -> boolean)}'; types don't match\n"
+  );
+}
+
+void test_copy_typecheck_recipe_variable_2() {
+  Hide_errors = true;
+  run(
+      "def main [\n"
+      "  {1: (recipe number -> number)} <- copy f\n"  // mismatch with a recipe literal
+      "]\n"
+      "def f x:bool -> y:bool [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: main: can't copy 'f' to '{1: (recipe number -> number)}'; types don't match\n"
+  );
+}
 
 :(before "End Matching Types For Literal(to)")
 if (is_mu_recipe(to)) {
@@ -384,33 +440,42 @@ if (is_mu_recipe(to)) {
   return true;
 }
 
-:(scenario call_variable_compound_ingredient)
-def main [
-  {1: (recipe (address number) -> number)} <- copy f
-  2:&:num <- copy null
-  3:num <- call {1: (recipe (address number) -> number)}, 2:&:num
-]
-def f x:&:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- deaddress x
-]
-$error: 0
+:(code)
+void test_call_variable_compound_ingredient() {
+  run(
+      "def main [\n"
+      "  {1: (recipe (address number) -> number)} <- copy f\n"
+      "  2:&:num <- copy null\n"
+      "  3:num <- call {1: (recipe (address number) -> number)}, 2:&:num\n"
+      "]\n"
+      "def f x:&:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- deaddress x\n"
+      "]\n"
+  );
+  CHECK_TRACE_COUNT("error", 0);
+}
 
 //: make sure we don't accidentally break on a recipe literal
-:(scenario jump_forbidden_on_recipe_literals)
-% Hide_errors = true;
-def foo [
-  local-scope
-]
-def main [
-  local-scope
-  {
-    break-if foo
-  }
-]
-# error should be as if foo is not a recipe
-+error: main: missing type for 'foo' in 'break-if foo'
+void test_jump_forbidden_on_recipe_literals() {
+  Hide_errors = true;
+  run(
+      "def foo [\n"
+      "  local-scope\n"
+      "]\n"
+      "def main [\n"
+      "  local-scope\n"
+      "  {\n"
+      "    break-if foo\n"
+      "  }\n"
+      "]\n"
+  );
+  // error should be as if foo is not a recipe
+  CHECK_TRACE_CONTENTS(
+      "error: main: missing type for 'foo' in 'break-if foo'\n"
+  );
+}
 
 :(before "End JUMP_IF Checks")
 check_for_recipe_literals(inst, get(Recipe, r));
@@ -427,14 +492,19 @@ void check_for_recipe_literals(const instruction& inst, const recipe& caller) {
   }
 }
 
-:(scenario load_ingredients_missing_error_3)
-% Hide_errors = true;
-def foo {f: (recipe num -> num)} [
-  local-scope
-  b:num <- call f, 1
-]
-+error: foo: missing type for 'f' in 'b:num <- call f, 1'
-+error:   did you forget 'load-ingredients'?
+void test_load_ingredients_missing_error_3() {
+  Hide_errors = true;
+  run(
+      "def foo {f: (recipe num -> num)} [\n"
+      "  local-scope\n"
+      "  b:num <- call f, 1\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "error: foo: missing type for 'f' in 'b:num <- call f, 1'\n"
+      "error:   did you forget 'load-ingredients'?\n"
+  );
+}
 
 :(before "End Mu Types Initialization")
 put(Type_abbreviations, "function", new_type_tree("recipe"));
@@ -442,38 +512,49 @@ put(Type_abbreviations, "fn", new_type_tree("recipe"));
 
 //: copying functions to variables
 
-:(scenario copy_recipe_to_variable)
-def main [
-  {1: (fn number -> number)} <- copy f
-  2:num <- call {1: (function number -> number)}, 34
-]
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-+mem: storing 34 in location 2
-
-:(scenario copy_overloaded_recipe_to_variable)
-def main [
-  local-scope
-  {x: (fn num -> num)} <- copy f
-  1:num/raw <- call x, 34
-]
-# variant f
-def f x:bool -> y:bool [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-# variant f_2
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-# x contains f_2
-+mem: storing 34 in location 1
+:(code)
+void test_copy_recipe_to_variable() {
+  run(
+      "def main [\n"
+      "  {1: (fn number -> number)} <- copy f\n"
+      "  2:num <- call {1: (function number -> number)}, 34\n"
+      "]\n"
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 2\n"
+  );
+}
+
+void test_copy_overloaded_recipe_to_variable() {
+  run(
+      "def main [\n"
+      "  local-scope\n"
+      "  {x: (fn num -> num)} <- copy f\n"
+      "  1:num/raw <- call x, 34\n"
+      "]\n"
+      // variant f
+      "def f x:bool -> y:bool [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+      // variant f_2
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  // x contains f_2
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
 
 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
 if (inst.name == "copy") {
@@ -508,46 +589,56 @@ void construct_fake_call(const reagent& recipe_var, instruction& out) {
     out.products.push_back(copy(stem->left));
 }
 
-:(scenario copy_shape_shifting_recipe_to_variable)
-def main [
-  local-scope
-  {x: (fn num -> num)} <- copy f
-  1:num/raw <- call x, 34
-]
-def f x:_elem -> y:_elem [
-  local-scope
-  load-inputs
-  y <- copy x
-]
-+mem: storing 34 in location 1
+void test_copy_shape_shifting_recipe_to_variable() {
+  run(
+      "def main [\n"
+      "  local-scope\n"
+      "  {x: (fn num -> num)} <- copy f\n"
+      "  1:num/raw <- call x, 34\n"
+      "]\n"
+      "def f x:_elem -> y:_elem [\n"
+      "  local-scope\n"
+      "  load-inputs\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
 
 //: passing function literals to (higher-order) functions
 
-:(scenario pass_overloaded_recipe_literal_to_ingredient)
-# like copy_overloaded_recipe_to_variable except we bind 'x' in the course of
-# a 'call' rather than 'copy'
-def main [
-  1:num <- g f
-]
-def g {x: (fn num -> num)} -> result:num [
-  local-scope
-  load-ingredients
-  result <- call x, 34
-]
-# variant f
-def f x:bool -> y:bool [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-# variant f_2
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-# x contains f_2
-+mem: storing 34 in location 1
+void test_pass_overloaded_recipe_literal_to_ingredient() {
+  run(
+      // like test_copy_overloaded_recipe_to_variable, except we bind 'x' in
+      // the course of a 'call' rather than 'copy'
+      "def main [\n"
+      "  1:num <- g f\n"
+      "]\n"
+      "def g {x: (fn num -> num)} -> result:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  result <- call x, 34\n"
+      "]\n"
+      // variant f
+      "def f x:bool -> y:bool [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+      // variant f_2
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  // x contains f_2
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
 
 :(after "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
 for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
@@ -570,30 +661,36 @@ for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
   }
 }
 
-:(scenario return_overloaded_recipe_literal_to_caller)
-def main [
-  local-scope
-  {x: (fn num -> num)} <- g
-  1:num/raw <- call x, 34
-]
-def g -> {x: (fn num -> num)} [
-  local-scope
-  return f
-]
-# variant f
-def f x:bool -> y:bool [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-# variant f_2
-def f x:num -> y:num [
-  local-scope
-  load-ingredients
-  y <- copy x
-]
-# x contains f_2
-+mem: storing 34 in location 1
+:(code)
+void test_return_overloaded_recipe_literal_to_caller() {
+  run(
+      "def main [\n"
+      "  local-scope\n"
+      "  {x: (fn num -> num)} <- g\n"
+      "  1:num/raw <- call x, 34\n"
+      "]\n"
+      "def g -> {x: (fn num -> num)} [\n"
+      "  local-scope\n"
+      "  return f\n"
+      "]\n"
+      // variant f
+      "def f x:bool -> y:bool [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+      // variant f_2
+      "def f x:num -> y:num [\n"
+      "  local-scope\n"
+      "  load-ingredients\n"
+      "  y <- copy x\n"
+      "]\n"
+  );
+  // x contains f_2
+  CHECK_TRACE_CONTENTS(
+      "mem: storing 34 in location 1\n"
+  );
+}
 
 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
 if (inst.name == "return" || inst.name == "reply") {