diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2017-03-07 00:36:55 -0800 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2017-03-07 00:36:58 -0800 |
commit | 848ebc1e6335cd1a34e07662242a367fefbc5229 (patch) | |
tree | cd8bd1c747c1bc30857c2fca329013c05b933491 /099hardware_checks.cc | |
parent | 8ccf992d317a867f5e477b2dee5db90b2c5ded3a (diff) | |
download | mu-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.cc | 63 |
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) |