2607 - resolve some edge cases in static dispatch
This commit is contained in:
parent
afb467ea02
commit
691b529e53
|
@ -82,7 +82,7 @@ recipe main [
|
|||
$error: 0
|
||||
|
||||
:(code)
|
||||
// types_match with some special-cases
|
||||
// types_match with some leniency
|
||||
bool types_coercible(const reagent& lhs, const reagent& rhs) {
|
||||
if (types_match(lhs, rhs)) return true;
|
||||
if (is_mu_address(rhs) && is_mu_number(lhs)) return true;
|
||||
|
@ -99,34 +99,41 @@ bool types_match(const reagent& lhs, const reagent& rhs) {
|
|||
// End Matching Types For Literal(lhs)
|
||||
// allow writing 0 to any address
|
||||
if (is_mu_address(lhs)) return rhs.name == "0";
|
||||
if (!lhs.type) return false;
|
||||
if (lhs.type->value == get(Type_ordinal, "boolean"))
|
||||
return boolean_matches_literal(lhs, rhs);
|
||||
return size_of(lhs) == 1; // literals are always scalars
|
||||
}
|
||||
return types_strictly_match(lhs, rhs);
|
||||
}
|
||||
|
||||
bool boolean_matches_literal(const reagent& lhs, const reagent& rhs) {
|
||||
if (!is_literal(rhs)) return false;
|
||||
if (!lhs.type) return false;
|
||||
if (lhs.type->value != get(Type_ordinal, "boolean")) return false;
|
||||
return rhs.name == "0" || rhs.name == "1";
|
||||
}
|
||||
|
||||
// copy arguments because later layers will want to make changes to them
|
||||
// without perturbing the caller
|
||||
bool types_strictly_match(reagent lhs, reagent rhs) {
|
||||
if (is_literal(rhs) && lhs.type->value == get(Type_ordinal, "number")) return true;
|
||||
// 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_unsafe(rhs)) return true;
|
||||
// '_' never raises type error
|
||||
if (is_dummy(lhs)) return true;
|
||||
if (!lhs.type) return !rhs.type;
|
||||
return types_match(lhs.type, rhs.type);
|
||||
return types_strictly_match(lhs.type, rhs.type);
|
||||
}
|
||||
|
||||
// two types match if the second begins like the first
|
||||
// (trees perform the same check recursively on each subtree)
|
||||
bool types_match(type_tree* lhs, type_tree* rhs) {
|
||||
bool types_strictly_match(type_tree* lhs, type_tree* rhs) {
|
||||
if (!lhs) return true;
|
||||
if (!rhs || rhs->value == 0) {
|
||||
if (lhs->value == get(Type_ordinal, "array")) return false;
|
||||
if (lhs->value == get(Type_ordinal, "address")) return false;
|
||||
return size_of(rhs) == size_of(lhs);
|
||||
}
|
||||
if (!rhs) return lhs->value == 0;
|
||||
if (lhs->value != rhs->value) return false;
|
||||
return types_match(lhs->left, rhs->left) && types_match(lhs->right, rhs->right);
|
||||
return types_strictly_match(lhs->left, rhs->left) && types_strictly_match(lhs->right, rhs->right);
|
||||
}
|
||||
|
||||
bool is_raw(const reagent& r) {
|
||||
|
|
|
@ -62,6 +62,7 @@ case NEXT_INGREDIENT: {
|
|||
else if (!types_match(product,
|
||||
current_call().ingredients.at(current_call().next_ingredient_to_process))) {
|
||||
raise_error << maybe(current_recipe_name()) << "wrong type for ingredient " << product.original_string << '\n' << end();
|
||||
// End next-ingredient Type Mismatch Error
|
||||
}
|
||||
products.push_back(
|
||||
current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
|
||||
|
|
|
@ -57,7 +57,7 @@ void check_type(map<string, type_tree*>& type, map<string, string_tree*>& type_n
|
|||
if (!contains_key(type_name, x.name)) {
|
||||
type_name[x.name] = x.properties.at(0).second;
|
||||
}
|
||||
if (!types_match(type[x.name], x.type))
|
||||
if (!types_strictly_match(type[x.name], x.type))
|
||||
raise_error << maybe(get(Recipe, r).name) << x.name << " used with multiple types\n" << end();
|
||||
}
|
||||
|
||||
|
|
|
@ -140,10 +140,13 @@ void resolve_ambiguous_calls(recipe_ordinal r) {
|
|||
void replace_best_variant(instruction& inst, const recipe& caller_recipe) {
|
||||
trace(9992, "transform") << "instruction " << inst.name << end();
|
||||
vector<recipe_ordinal>& variants = get(Recipe_variants, inst.name);
|
||||
//? trace(9992, "transform") << "checking base: " << get(Recipe_ordinal, inst.name) << end();
|
||||
long long int best_score = variant_score(inst, get(Recipe_ordinal, inst.name));
|
||||
trace(9992, "transform") << "score for base: " << best_score << end();
|
||||
for (long long int i = 0; i < SIZE(variants); ++i) {
|
||||
//? trace(9992, "transform") << "checking variant " << i << ": " << variants.at(i) << end();
|
||||
long long int current_score = variant_score(inst, variants.at(i));
|
||||
trace(9992, "transform") << "checking variant " << i << ": " << current_score << end();
|
||||
trace(9992, "transform") << "score for variant " << i << ": " << current_score << end();
|
||||
if (current_score > best_score) {
|
||||
inst.name = get(Recipe, variants.at(i)).name;
|
||||
best_score = current_score;
|
||||
|
@ -153,7 +156,7 @@ void replace_best_variant(instruction& inst, const recipe& caller_recipe) {
|
|||
}
|
||||
|
||||
long long int variant_score(const instruction& inst, recipe_ordinal variant) {
|
||||
long long int result = 100;
|
||||
long long int result = 1000;
|
||||
if (variant == -1) return -1; // ghost from a previous test
|
||||
//? cerr << "variant score: " << inst.to_string() << '\n';
|
||||
if (!contains_key(Recipe, variant)) {
|
||||
|
@ -177,11 +180,16 @@ long long int variant_score(const instruction& inst, recipe_ordinal variant) {
|
|||
trace(9993, "transform") << "strict match: ingredient " << i << end();
|
||||
//? cerr << "strict match: ingredient " << i << '\n';
|
||||
}
|
||||
else if (boolean_matches_literal(header_ingredients.at(i), inst.ingredients.at(i))) {
|
||||
// slight penalty for coercing literal to boolean (prefer direct conversion to number if possible)
|
||||
trace(9993, "transform") << "boolean matches literal: ingredient " << i << end();
|
||||
result--;
|
||||
}
|
||||
else {
|
||||
// slight penalty for modifying type
|
||||
// slightly larger penalty for modifying type in other ways
|
||||
trace(9993, "transform") << "non-strict match: ingredient " << i << end();
|
||||
//? cerr << "non-strict match: ingredient " << i << '\n';
|
||||
result--;
|
||||
result-=10;
|
||||
}
|
||||
}
|
||||
//? cerr << "=== done checking ingredients\n";
|
||||
|
@ -201,11 +209,16 @@ long long int variant_score(const instruction& inst, recipe_ordinal variant) {
|
|||
trace(9993, "transform") << "strict match: product " << i << end();
|
||||
//? cerr << "strict match: product " << i << '\n';
|
||||
}
|
||||
else if (boolean_matches_literal(header_products.at(i), inst.products.at(i))) {
|
||||
// slight penalty for coercing literal to boolean (prefer direct conversion to number if possible)
|
||||
trace(9993, "transform") << "boolean matches literal: product " << i << end();
|
||||
result--;
|
||||
}
|
||||
else {
|
||||
// slight penalty for modifying type
|
||||
// slightly larger penalty for modifying type in other ways
|
||||
trace(9993, "transform") << "non-strict match: product " << i << end();
|
||||
//? cerr << "non-strict match: product " << i << '\n';
|
||||
result--;
|
||||
result-=10;
|
||||
}
|
||||
}
|
||||
// the greater the number of unused ingredients/products, the lower the score
|
||||
|
@ -279,3 +292,54 @@ recipe foo x:number -> y:number [
|
|||
]
|
||||
+error: foo: wrong type for ingredient x:number
|
||||
-mem: storing 34 in location 1
|
||||
|
||||
:(scenario static_dispatch_dispatches_literal_to_boolean_before_character)
|
||||
recipe main [
|
||||
1:number/raw <- foo 0 # valid literal for boolean
|
||||
]
|
||||
recipe foo x:character -> y:number [
|
||||
local-scope
|
||||
load-ingredients
|
||||
reply 34
|
||||
]
|
||||
recipe foo x:boolean -> y:number [
|
||||
local-scope
|
||||
load-ingredients
|
||||
reply 35
|
||||
]
|
||||
# boolean variant is preferred
|
||||
+mem: storing 35 in location 1
|
||||
|
||||
:(scenario static_dispatch_dispatches_literal_to_character_when_out_of_boolean_range)
|
||||
recipe main [
|
||||
1:number/raw <- foo 97 # not a valid literal for boolean
|
||||
]
|
||||
recipe foo x:character -> y:number [
|
||||
local-scope
|
||||
load-ingredients
|
||||
reply 34
|
||||
]
|
||||
recipe foo x:boolean -> y:number [
|
||||
local-scope
|
||||
load-ingredients
|
||||
reply 35
|
||||
]
|
||||
# character variant is preferred
|
||||
+mem: storing 34 in location 1
|
||||
|
||||
:(scenario static_dispatch_dispatches_literal_to_number_if_at_all_possible)
|
||||
recipe main [
|
||||
1:number/raw <- foo 97
|
||||
]
|
||||
recipe foo x:character -> y:number [
|
||||
local-scope
|
||||
load-ingredients
|
||||
reply 34
|
||||
]
|
||||
recipe foo x:number -> y:number [
|
||||
local-scope
|
||||
load-ingredients
|
||||
reply 35
|
||||
]
|
||||
# number variant is preferred
|
||||
+mem: storing 35 in location 1
|
||||
|
|
13
999spaces.cc
13
999spaces.cc
|
@ -69,3 +69,16 @@ assert(Max_callstack_depth == 9989);
|
|||
//: 56 check reply instructions against header
|
||||
//: end checks
|
||||
//: end transforms
|
||||
|
||||
//:: Summary of type-checking in different phases
|
||||
//: when dispatching instructions we accept first recipe that:
|
||||
//: strictly matches all types
|
||||
//: maps literal 0 or literal 1 to boolean for some ingredients
|
||||
//: performs some other acceptable type conversion
|
||||
//: literal 0 -> address
|
||||
//: literal -> character
|
||||
//: when checking instructions we ensure that types match, and that literals map to some scalar
|
||||
//: (address can only map to literal 0)
|
||||
//: (boolean can only map to literal 0 or literal 1)
|
||||
//: (but conditionals can take any scalar)
|
||||
//: at runtime we perform no checks
|
||||
|
|
Loading…
Reference in New Issue
Block a user