//: A big convenience high-level languages provide is the ability to name memory //: locations. In mu, a transform called 'convert-names' provides this //: convenience. :(scenarios run) :(scenario "convert_names") recipe main [ x:integer <- copy 0:literal ] +name: assign x 1 +run: instruction main/0 +mem: storing 0 in location 1 :(scenario "convert_names_warns") hide warnings recipe main [ x:integer <- copy y:integer ] +warn: use before set: y in main :(after "int main") Transform.push_back(transform_names); :(before "End Globals") unordered_map > Name; :(before "End One-time Setup") Name.clear(); :(before "Clear state for recipes_added_by_test") for (size_t i = 0; i < recipes_added_by_test.size(); ++i) { Name.erase(recipes_added_by_test[i]); } :(code) void transform_names(const recipe_number r) { unordered_map& names = Name[r]; int curr_idx = 1; //? cout << "Recipe " << r << '\n'; //? 2 //? cout << Recipe[r].steps.size(); //? 1 for (size_t i = 0; i < Recipe[r].steps.size(); ++i) { //? cout << "instruction " << i << '\n'; //? 2 instruction& inst = Recipe[r].steps[i]; // Per-recipe Transforms Go Here // map names to addresses for (size_t in = 0; in < inst.ingredients.size(); ++in) { if (is_raw(inst.ingredients[in])) continue; //? cout << "ingredient " << inst.ingredients[in].name << '\n'; //? 1 if (inst.ingredients[in].name == "default-space") inst.ingredients[in].initialized = true; if (inst.ingredients[in].name != "default-space" && inst.ingredients[in].types[0] && inst.ingredients[in].name.find_first_not_of("0123456789-.") != string::npos) { if (names.find(inst.ingredients[in].name) == names.end()) { raise << "use before set: " << inst.ingredients[in].name << " in " << Recipe[r].name; } inst.ingredients[in].set_value(names[inst.ingredients[in].name]); } } for (size_t out = 0; out < inst.products.size(); ++out) { if (is_raw(inst.products[out])) continue; //? cout << "product " << out << '/' << inst.products.size() << " " << inst.products[out].name << '\n'; //? 4 //? cout << inst.products[out].types[0] << '\n'; //? 1 if (inst.products[out].name == "default-space") inst.products[out].initialized = true; if (inst.products[out].name != "_" && inst.products[out].name != "default-space" && inst.products[out].types[0] && inst.products[out].name.find_first_not_of("0123456789-.") != string::npos) { if (names.find(inst.products[out].name) == names.end()) { trace("name") << "assign " << inst.products[out].name << " " << curr_idx; names[inst.products[out].name] = curr_idx; curr_idx += size_of(inst.products[out]); } inst.products[out].set_value(names[inst.products[out].name]); } } } } type_number skip_addresses(const vector& types) { for (size_t i = 0; i < types.size(); ++i) { if (types[i] != Type_number["address"]) return types[i]; } raise << "expected a record" << '\n' << die(); return -1; } int find_element_name(const type_number t, const string& name) { const type_info& record = Type[t]; //? cout << "looking for field " << name << " in type " << record.name << " with " << record.element_names.size() << " fields\n"; //? 1 for (size_t i = 0; i < record.element_names.size(); ++i) { if (record.element_names[i] == name) return i; } raise << "unknown element " << name << " in record " << t << '\n' << die(); return -1; } bool is_raw(const reagent& r) { for (size_t i = /*skip value+type*/1; i < r.properties.size(); ++i) { if (r.properties[i].first == "raw") return true; } return false; } :(scenario "convert_names_passes_dummy") # _ is just a dummy result that never gets consumed recipe main [ _, x:integer <- copy 0:literal ] +name: assign x 1 -name: assign _ 1 //: one reserved word that we'll need later :(scenario "convert_names_passes_default_space") recipe main [ default-space:integer, x:integer <- copy 0:literal ] +name: assign x 1 -name: assign default-space 1 //: an escape hatch to suppress name conversion that we'll use later :(scenario "convert_names_passes_raw") recipe main [ x:integer/raw <- copy 0:literal ] -name: assign x 1 //: update our running example record for the next test :(before "End Mu Types Initialization") Type[point].element_names.push_back("x"); Type[point].element_names.push_back("y"); :(scenario "convert_names_transforms_record_elements") recipe main [ a:integer <- get 0:point, y:offset b:integer <- get 0:point, x:offset ] +name: field y of type point is at offset 1 +name: field x of type point is at offset 0 :(after "Per-recipe Transforms") // replace element names of records with offsets if (inst.operation == Recipe_number["get"] || inst.operation == Recipe_number["get-address"]) { // at least 2 args, and second arg is offset assert(inst.ingredients.size() >= 2); //? cout << inst.ingredients[1].to_string() << '\n'; //? 1 assert(isa_literal(inst.ingredients[1])); if (inst.ingredients[1].name.find_first_not_of("0123456789") == string::npos) continue; // since first non-address in base type must be a record, we don't have to canonize type_number record = skip_addresses(inst.ingredients[0].types); inst.ingredients[1].set_value(find_element_name(record, inst.ingredients[1].name)); trace("name") << "field " << inst.ingredients[1].name << " of type " << Type[record].name << " is at offset " << inst.ingredients[1].value; } //: this test is actually illegal so can't call run :(scenarios transform_test) :(scenario "convert_names_handles_records") recipe main [ a:point <- copy 0:literal b:integer <- copy 0:literal ] +name: assign a 1 +name: assign b 3