2015-10-01 06:31:43 +00:00
|
|
|
//: Introduce a new transform to perform various checks in instructions before
|
|
|
|
//: we start running them. It'll be extensible, so that we can add checks for
|
|
|
|
//: new recipes as we extend 'run' to support them.
|
2015-10-01 23:25:25 +00:00
|
|
|
//:
|
|
|
|
//: Doing checking in a separate part complicates things, because the values
|
|
|
|
//: of variables in memory and the processor (current_recipe_name,
|
|
|
|
//: current_instruction) aren't available at checking time. If I had a more
|
|
|
|
//: sophisticated layer system I'd introduce the simpler version first and
|
|
|
|
//: transform it in a separate layer or set of layers.
|
2015-09-30 07:07:33 +00:00
|
|
|
|
2015-11-29 06:17:47 +00:00
|
|
|
:(before "End Checks")
|
2015-11-07 01:29:52 +00:00
|
|
|
Transform.push_back(check_instruction); // idempotent
|
2015-09-30 07:07:33 +00:00
|
|
|
|
|
|
|
:(code)
|
2015-09-30 09:01:59 +00:00
|
|
|
void check_instruction(const recipe_ordinal r) {
|
2015-11-06 19:06:58 +00:00
|
|
|
trace(9991, "transform") << "--- perform checks for recipe " << get(Recipe, r).name << end();
|
2015-11-07 01:03:02 +00:00
|
|
|
//? cerr << "--- perform checks for recipe " << get(Recipe, r).name << '\n';
|
2015-09-30 07:07:33 +00:00
|
|
|
map<string, vector<type_ordinal> > metadata;
|
2015-11-06 19:06:58 +00:00
|
|
|
for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
|
|
|
|
instruction& inst = get(Recipe, r).steps.at(i);
|
2015-10-01 23:33:34 +00:00
|
|
|
if (inst.is_label) continue;
|
2015-09-30 07:07:33 +00:00
|
|
|
switch (inst.operation) {
|
2015-09-30 09:01:59 +00:00
|
|
|
// Primitive Recipe Checks
|
2015-09-30 07:07:33 +00:00
|
|
|
case COPY: {
|
|
|
|
if (SIZE(inst.products) != SIZE(inst.ingredients)) {
|
2016-02-19 21:42:45 +00:00
|
|
|
raise_error << "ingredients and products should match in '" << to_string(inst) << "'\n" << end();
|
2015-09-30 07:07:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
|
2015-11-27 18:32:34 +00:00
|
|
|
if (!types_coercible(inst.products.at(i), inst.ingredients.at(i))) {
|
2015-11-06 19:06:58 +00:00
|
|
|
raise_error << maybe(get(Recipe, r).name) << "can't copy " << inst.ingredients.at(i).original_string << " to " << inst.products.at(i).original_string << "; types don't match\n" << end();
|
2015-09-30 07:07:33 +00:00
|
|
|
goto finish_checking_instruction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2015-09-30 09:01:59 +00:00
|
|
|
// End Primitive Recipe Checks
|
2015-09-30 07:07:33 +00:00
|
|
|
default: {
|
2015-09-30 09:01:59 +00:00
|
|
|
// Defined Recipe Checks
|
|
|
|
// End Defined Recipe Checks
|
2015-09-30 07:07:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
finish_checking_instruction:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
:(scenario copy_checks_reagent_count)
|
2015-10-07 05:15:45 +00:00
|
|
|
% Hide_errors = true;
|
2015-09-30 07:07:33 +00:00
|
|
|
recipe main [
|
|
|
|
1:number <- copy 34, 35
|
|
|
|
]
|
2015-10-07 05:15:45 +00:00
|
|
|
+error: ingredients and products should match in '1:number <- copy 34, 35'
|
2015-09-30 07:07:33 +00:00
|
|
|
|
|
|
|
:(scenario write_scalar_to_array_disallowed)
|
2015-10-07 05:15:45 +00:00
|
|
|
% Hide_errors = true;
|
2015-09-30 07:07:33 +00:00
|
|
|
recipe main [
|
|
|
|
1:array:number <- copy 34
|
|
|
|
]
|
2015-10-07 05:15:45 +00:00
|
|
|
+error: main: can't copy 34 to 1:array:number; types don't match
|
2015-09-30 07:07:33 +00:00
|
|
|
|
|
|
|
:(scenario write_scalar_to_array_disallowed_2)
|
2015-10-07 05:15:45 +00:00
|
|
|
% Hide_errors = true;
|
2015-09-30 07:07:33 +00:00
|
|
|
recipe main [
|
|
|
|
1:number, 2:array:number <- copy 34, 35
|
|
|
|
]
|
2015-10-07 05:15:45 +00:00
|
|
|
+error: main: can't copy 35 to 2:array:number; types don't match
|
2015-09-30 07:07:33 +00:00
|
|
|
|
|
|
|
:(scenario write_scalar_to_address_disallowed)
|
2015-10-07 05:15:45 +00:00
|
|
|
% Hide_errors = true;
|
2015-09-30 07:07:33 +00:00
|
|
|
recipe main [
|
|
|
|
1:address:number <- copy 34
|
|
|
|
]
|
2015-10-07 05:15:45 +00:00
|
|
|
+error: main: can't copy 34 to 1:address:number; types don't match
|
2015-09-30 07:07:33 +00:00
|
|
|
|
2015-11-27 18:32:34 +00:00
|
|
|
:(scenario write_address_to_number_allowed)
|
|
|
|
% Hide_errors = true;
|
|
|
|
recipe main [
|
|
|
|
1:address:number <- copy 12/unsafe
|
|
|
|
2:number <- copy 1:address:number
|
|
|
|
]
|
|
|
|
+mem: storing 12 in location 2
|
|
|
|
$error: 0
|
|
|
|
|
2016-01-31 06:17:36 +00:00
|
|
|
:(scenario write_boolean_to_number_allowed)
|
|
|
|
% Hide_errors = true;
|
|
|
|
recipe main [
|
|
|
|
1:boolean <- copy 1/true
|
|
|
|
2:number <- copy 1:boolean
|
|
|
|
]
|
|
|
|
+mem: storing 1 in location 2
|
|
|
|
$error: 0
|
|
|
|
|
2015-09-30 07:07:33 +00:00
|
|
|
:(code)
|
2015-11-29 19:43:25 +00:00
|
|
|
// types_match with some leniency
|
2016-01-31 06:17:36 +00:00
|
|
|
bool types_coercible(const reagent& to, const reagent& from) {
|
|
|
|
if (types_match(to, from)) return true;
|
|
|
|
if (is_mu_address(from) && is_mu_number(to)) return true;
|
|
|
|
if (is_mu_boolean(from) && is_mu_number(to)) return true;
|
2015-11-28 06:01:23 +00:00
|
|
|
// End types_coercible Special-cases
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-01-31 06:22:52 +00:00
|
|
|
bool types_match(const reagent& to, const reagent& from) {
|
2015-11-28 06:14:05 +00:00
|
|
|
// to sidestep type-checking, use /unsafe in the source.
|
|
|
|
// this will be highlighted in red inside vim. just for setting up some tests.
|
2016-01-31 06:22:52 +00:00
|
|
|
if (is_unsafe(from)) return true;
|
|
|
|
if (is_literal(from)) {
|
|
|
|
if (is_mu_array(to)) return false;
|
|
|
|
// End Matching Types For Literal(to)
|
2015-11-28 06:14:05 +00:00
|
|
|
// allow writing 0 to any address
|
2016-01-31 06:22:52 +00:00
|
|
|
if (is_mu_address(to)) return from.name == "0";
|
|
|
|
if (!to.type) return false;
|
|
|
|
if (to.type->value == get(Type_ordinal, "boolean"))
|
|
|
|
return boolean_matches_literal(to, from);
|
|
|
|
return size_of(to) == 1; // literals are always scalars
|
2015-11-28 06:01:23 +00:00
|
|
|
}
|
2016-01-31 06:22:52 +00:00
|
|
|
return types_strictly_match(to, from);
|
2015-11-28 02:10:15 +00:00
|
|
|
}
|
|
|
|
|
2016-02-12 00:13:10 +00:00
|
|
|
bool types_strictly_match_except_literal_against_boolean(const reagent& to, const reagent& from) {
|
|
|
|
// to sidestep type-checking, use /unsafe in the source.
|
|
|
|
// this will be highlighted in red inside vim. just for setting up some tests.
|
|
|
|
if (is_literal(from)
|
|
|
|
&& to.type && to.type->value == get(Type_ordinal, "boolean"))
|
|
|
|
return boolean_matches_literal(to, from);
|
|
|
|
return types_strictly_match(to, from);
|
|
|
|
}
|
|
|
|
|
2016-01-31 06:22:52 +00:00
|
|
|
bool boolean_matches_literal(const reagent& to, const reagent& from) {
|
|
|
|
if (!is_literal(from)) return false;
|
|
|
|
if (!to.type) return false;
|
|
|
|
if (to.type->value != get(Type_ordinal, "boolean")) return false;
|
|
|
|
return from.name == "0" || from.name == "1";
|
2015-11-29 19:43:25 +00:00
|
|
|
}
|
|
|
|
|
2015-11-16 01:33:52 +00:00
|
|
|
// copy arguments because later layers will want to make changes to them
|
|
|
|
// without perturbing the caller
|
2016-01-31 06:22:52 +00:00
|
|
|
bool types_strictly_match(reagent to, reagent from) {
|
|
|
|
if (is_literal(from) && to.type->value == get(Type_ordinal, "number")) return true;
|
2015-11-22 19:51:36 +00:00
|
|
|
// to sidestep type-checking, use /unsafe in the source.
|
|
|
|
// this will be highlighted in red inside vim. just for setting up some tests.
|
2016-01-31 06:22:52 +00:00
|
|
|
if (is_unsafe(from)) return true;
|
2015-11-28 06:14:05 +00:00
|
|
|
// '_' never raises type error
|
2016-01-31 06:22:52 +00:00
|
|
|
if (is_dummy(to)) return true;
|
|
|
|
if (!to.type) return !from.type;
|
|
|
|
return types_strictly_match(to.type, from.type);
|
2015-10-26 04:42:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// two types match if the second begins like the first
|
|
|
|
// (trees perform the same check recursively on each subtree)
|
2016-01-31 06:22:52 +00:00
|
|
|
bool types_strictly_match(type_tree* to, type_tree* from) {
|
|
|
|
if (!to) return true;
|
|
|
|
if (!from) return to->value == 0;
|
|
|
|
if (to->value != from->value) return false;
|
|
|
|
return types_strictly_match(to->left, from->left) && types_strictly_match(to->right, from->right);
|
2015-09-30 07:07:33 +00:00
|
|
|
}
|
|
|
|
|
2015-11-22 19:51:36 +00:00
|
|
|
bool is_unsafe(const reagent& r) {
|
|
|
|
return has_property(r, "unsafe");
|
|
|
|
}
|
|
|
|
|
2015-09-30 07:07:33 +00:00
|
|
|
bool is_mu_array(reagent r) {
|
2015-10-26 04:42:18 +00:00
|
|
|
if (!r.type) return false;
|
2015-09-30 07:07:33 +00:00
|
|
|
if (is_literal(r)) return false;
|
2015-11-06 19:06:58 +00:00
|
|
|
return r.type->value == get(Type_ordinal, "array");
|
2015-09-30 07:07:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool is_mu_address(reagent r) {
|
2015-10-26 04:42:18 +00:00
|
|
|
if (!r.type) return false;
|
2015-09-30 07:07:33 +00:00
|
|
|
if (is_literal(r)) return false;
|
2015-11-06 19:06:58 +00:00
|
|
|
return r.type->value == get(Type_ordinal, "address");
|
2015-09-30 07:07:33 +00:00
|
|
|
}
|
2015-09-30 08:40:15 +00:00
|
|
|
|
2016-01-31 06:17:36 +00:00
|
|
|
bool is_mu_boolean(reagent r) {
|
|
|
|
if (!r.type) return false;
|
|
|
|
if (is_literal(r)) return false;
|
|
|
|
return r.type->value == get(Type_ordinal, "boolean");
|
|
|
|
}
|
|
|
|
|
2015-09-30 08:40:15 +00:00
|
|
|
bool is_mu_number(reagent r) {
|
2015-10-26 04:42:18 +00:00
|
|
|
if (!r.type) return false;
|
2015-12-28 17:33:33 +00:00
|
|
|
if (is_literal(r)) {
|
|
|
|
if (!r.properties.at(0).second) return false;
|
2015-10-27 03:00:38 +00:00
|
|
|
return r.properties.at(0).second->value == "literal-number"
|
|
|
|
|| r.properties.at(0).second->value == "literal";
|
2015-12-28 17:33:33 +00:00
|
|
|
}
|
2015-11-06 19:06:58 +00:00
|
|
|
if (r.type->value == get(Type_ordinal, "character")) return true; // permit arithmetic on unicode code points
|
|
|
|
return r.type->value == get(Type_ordinal, "number");
|
2015-09-30 08:57:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool is_mu_scalar(reagent r) {
|
2015-10-26 04:42:18 +00:00
|
|
|
if (!r.type) return false;
|
2015-09-30 08:57:23 +00:00
|
|
|
if (is_literal(r))
|
2015-10-27 03:00:38 +00:00
|
|
|
return !r.properties.at(0).second || r.properties.at(0).second->value != "literal-string";
|
2015-09-30 08:57:23 +00:00
|
|
|
if (is_mu_array(r)) return false;
|
|
|
|
return size_of(r) == 1;
|
2015-09-30 08:40:15 +00:00
|
|
|
}
|