//: Transform to maintain multiple variants of a recipe depending on the //: number and types of the ingredients and products. Allows us to use nice //: names like 'print' or 'length' in many mutually extensible ways. :(scenario static_dispatch) def main [ 7:number/raw <- test 3 ] def test a:number -> z:number [ z <- copy 1 ] def test a:number, b:number -> z:number [ z <- copy 2 ] +mem: storing 1 in location 7 //: When loading recipes, accumulate variants if headers don't collide, and //: flag an error if headers collide. :(before "End Globals") map > Recipe_variants; :(before "End One-time Setup") put(Recipe_variants, "main", vector()); // since we manually added main to Recipe_ordinal :(before "End Globals") map > Recipe_variants_snapshot; :(before "End save_snapshots") Recipe_variants_snapshot = Recipe_variants; :(before "End restore_snapshots") Recipe_variants = Recipe_variants_snapshot; :(before "End Load Recipe Header(result)") // there can only ever be one variant for main if (result.name != "main" && contains_key(Recipe_ordinal, result.name)) { const recipe_ordinal r = get(Recipe_ordinal, result.name); if (!contains_key(Recipe, r) || get(Recipe, r).has_header) { string new_name = matching_variant_name(result); if (new_name.empty()) { // variant doesn't already exist new_name = next_unused_recipe_name(result.name); put(Recipe_ordinal, new_name, Next_recipe_ordinal++); get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, new_name)); } trace(9999, "load") << "switching " << result.name << " to " << new_name << end(); result.name = new_name; } } else { // save first variant put(Recipe_ordinal, result.name, Next_recipe_ordinal++); get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, result.name)); } :(code) string matching_variant_name(const recipe& rr) { const vector& variants = get_or_insert(Recipe_variants, rr.name); for (int i = 0; i < SIZE(variants); ++i) { if (!contains_key(Recipe, variants.at(i))) continue; const recipe& candidate = get(Recipe, variants.at(i)); if (!all_reagents_match(rr, candidate)) continue; return candidate.name; } return ""; } bool all_reagents_match(const recipe& r1, const recipe& r2) { if (SIZE(r1.ingredients) != SIZE(r2.ingredients)) return false; if (SIZE(r1.products) != SIZE(r2.products)) return false; for (int i = 0; i < SIZE(r1.ingredients); ++i) { if (!deeply_equal_type_names(r1.ingredients.at(i), r2.ingredients.at(i))) { return false; } } for (int i = 0; i < SIZE(r1.products); ++i) { if (!deeply_equal_type_names(r1.products.at(i), r2.products.at(i))) { return false; } } return true; } :(before "End Globals") set Literal_type_names; :(before "End One-time Setup") Literal_type_names.insert("number"); Literal_type_names.insert("character"); :(code) bool deeply_equal_type_names(const reagent& a, const reagent& b) { return deeply_equal_type_names(a.type, b.type); } bool deeply_equal_type_names(const type_tree* a, const type_tree* b) { if (!a) return !b; if (!b) return !a; if (a->name == "literal" && b->name == "literal") return true; if (a->name == "literal") return Literal_type_names.find(b->name) != Literal_type_names.end(); if (b->name == "literal") return Literal_type_names.find(a->name) != Literal_type_names.end(); return a->name == b->name && deeply_equal_type_names(a->left, b->left) && deeply_equal_type_names(a->right, b->right); } string next_unused_recipe_name(const string& recipe_name) { for (int i = 2; ; ++i) { ostringstream out; out << recipe_name << '_' << i; if (!contains_key(Recipe_ordinal, out.str())) return out.str(); } } //: Once all the recipes are loaded, transform their bodies to replace each //: call with the most suitable variant. :(scenario static_dispatch_picks_most_similar_variant) def main [ 7:number/raw <- test 3, 4, 5 ] def test a:number -> z:number [ z <- copy 1 ] def test a:number, b:number -> z:number [ z <- copy 2 ] +mem: storing 2 in location 7 //: support recipe headers in a previous transform to fill in missing types :(before "End check_or_set_invalid_types") for (int i = 0; i < SIZE(caller.ingredients); ++i) check_or_set_invalid_types(caller.ingredients.at(i).type, maybe(caller.name), "recipe header ingredient"); for (int i = 0; i < SIZE(caller.products); ++i) check_or_set_invalid_types(caller.products.at(i).type, maybe(caller.name), "recipe header product"); //: after filling in all missing types (because we'll be introducing 'blank' types in this transform in a later layer, for shape-shifting recipes) :(after "Transform.push_back(transform_names)") Transform.push_back(resolve_ambiguous_calls); // idempotent //: In a later layer we'll introduce recursion in resolve_ambiguous_calls, by //: having it generate code for shape-shifting recipes and then transform such //: code. This data structure will help error messages be more useful. //: //: We're punning the 'call' data structure just because it has slots for //: calling recipe and calling instruction. :(before "End Globals") list resolve_stack; :(code) void resolve_ambiguous_calls(recipe_ordinal r) { recipe& caller_recipe = get(Recipe, r); trace(9991, "transform") << "--- resolve ambiguous calls for recipe " << caller_recipe.name << end(); for (int index = 0; index < SIZE(caller_recipe.steps); ++index) { instruction& inst = caller_recipe.steps.at(index); if (inst.is_label) continue; if (non_ghost_size(get_or_insert(Recipe_variants, inst.name)) == 0) continue; trace(9992, "transform") << "instruction "
# tests for the interpreter

proc loops(a: var int) =
  discard
  #var
  #  b: int
  #b = glob
  #while b != 0:
  #  b = b + 1
  #a = b

proc mymax(a, b: int): int =
  #loops(result)
  result = a
  if b > a: result = b

proc test(a, b: int) =
  var
    x, y: int
  x = 0
  y = 7
  if x == a + b * 3 - 7 or
      x == 8 or
      x == y and y > -56 and y < 699:
    y = 0
  elif y == 78 and x == 0:
    y = 1
  elif y == 0 and x == 0:
    y = 2
  else:
    y = 3

type
  TTokType = enum
    tkNil, tkType, tkConst, tkVar, tkSymbol, tkIf,
    tkWhile, tkFor, tkLoop, tkCase, tkLabel, tkGoto

proc testCase(t: TTokType): int =
  case t