about summary refs log tree commit diff stats
path: root/099hardware_checks.cc
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2017-03-07 00:36:55 -0800
committerKartik K. Agaram <vc@akkartik.com>2017-03-07 00:36:58 -0800
commit848ebc1e6335cd1a34e07662242a367fefbc5229 (patch)
treecd8bd1c747c1bc30857c2fca329013c05b933491 /099hardware_checks.cc
parent8ccf992d317a867f5e477b2dee5db90b2c5ded3a (diff)
downloadmu-848ebc1e6335cd1a34e07662242a367fefbc5229.tar.gz
3760 - force functions to use dependency-injection
This is the kind of check I want Mu to be anal about, not whitespace or
unused variables.
Diffstat (limited to '099hardware_checks.cc')
-rw-r--r--099hardware_checks.cc63
1 files changed, 63 insertions, 0 deletions
diff --git a/099hardware_checks.cc b/099hardware_checks.cc
new file mode 100644
index 00000000..a3dd6de5
--- /dev/null
+++ b/099hardware_checks.cc
@@ -0,0 +1,63 @@
+//: Let's raise errors when students use real hardware in any functions
+//: besides 'main'. Part of the goal is to teach them testing hygiene and
+//: dependency injection.
+//:
+//: This is easy to sidestep, it's for feedback rather than safety.
+
+:(before "End Globals")
+vector<type_tree*> Real_hardware_types;
+:(before "Begin transform_all")
+setup_real_hardware_types();
+:(before "End transform_all")
+teardown_real_hardware_types();
+:(code)
+void setup_real_hardware_types() {
+  Real_hardware_types.push_back(parse_type("address:screen"));
+  Real_hardware_types.push_back(parse_type("address:console"));
+  Real_hardware_types.push_back(parse_type("address:resources"));
+}
+type_tree* parse_type(string s) {
+  reagent x("x:"+s);
+  type_tree* result = x.type;
+  x.type = NULL;  // don't deallocate on return
+  return result;
+}
+void teardown_real_hardware_types() {
+  for (int i = 0;  i < SIZE(Real_hardware_types);  ++i)
+    delete Real_hardware_types.at(i);
+  Real_hardware_types.clear();
+}
+
+:(before "End Checks")
+Transform.push_back(check_for_misuse_of_real_hardware);
+:(code)
+void check_for_misuse_of_real_hardware(const recipe_ordinal r) {
+  const recipe& caller = get(Recipe, r);
+  if (caller.name == "main") return;
+  if (starts_with(caller.name, "scenario_")) return;
+  trace(9991, "transform") << "--- check if recipe " << caller.name << " has any dependency-injection mistakes" << end();
+  for (int index = 0;  index < SIZE(caller.steps);  ++index) {
+    const instruction& inst = caller.steps.at(index);
+    if (inst.operation < MAX_PRIMITIVE_RECIPES) continue;
+    for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
+      const reagent& ing = inst.ingredients.at(i);
+      if (!is_literal(ing) || ing.name != "0") continue;
+      const recipe& callee = get(Recipe, inst.operation);
+      if (!callee.has_header) continue;
+      if (i >= SIZE(callee.ingredients)) continue;
+      const reagent& expected_ing = callee.ingredients.at(i);
+      for (int j = 0;  j < SIZE(Real_hardware_types);  ++j) {
+        if (*Real_hardware_types.at(j) == *expected_ing.type)
+          raise << maybe(caller.name) << "'" << inst.original_string << "': only 'main' can pass 0 into a " << to_string(expected_ing.type) << '\n' << end();
+      }
+    }
+  }
+}
+
+:(scenarios transform)
+:(scenario warn_on_using_real_screen_directly_in_non_main_recipe)
+% Hide_errors = true;
+def foo [
+  print 0, 34
+]
++error: foo: 'print 0, 34': only 'main' can pass 0 into a (address screen)