From db1f56c8449d2ea3d158753fe37bac5a750a2566 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sun, 29 Nov 2015 14:18:52 -0800 Subject: 2611 --- html/000organization.cc.html | 1 + html/001help.cc.html | 10 + html/003trace.cc.html | 6 +- html/010vm.cc.html | 45 +- html/011load.cc.html | 26 +- html/012transform.cc.html | 9 + html/013update_operation.cc.html | 2 +- html/014literal_string.cc.html | 22 +- html/020run.cc.html | 12 +- html/021check_instruction.cc.html | 95 +- html/029tools.cc.html | 5 +- html/030container.cc.html | 52 +- html/031address.cc.html | 10 +- html/032array.cc.html | 40 +- html/035call_ingredient.cc.html | 15 +- html/036call_reply.cc.html | 3 +- html/038scheduler.cc.html | 5 +- html/040brace.cc.html | 2 +- html/041jump_target.cc.html | 2 +- html/042name.cc.html | 35 +- html/043new.cc.html | 10 - html/044space.cc.html | 16 +- html/045space_surround.cc.html | 4 +- html/047global.cc.html | 4 +- html/048check_type_by_name.cc.html | 12 +- html/050scenario.cc.html | 28 +- html/051scenario_test.mu.html | 4 +- html/052tangle.cc.html | 2 +- html/053rewrite_stash.cc.html | 85 ++ html/054dilated_reagent.cc.html | 26 +- html/055parse_tree.cc.html | 4 +- html/056recipe_header.cc.html | 18 +- html/057static_dispatch.cc.html | 241 ++++- html/058shape_shifting_container.cc.html | 11 +- html/059shape_shifting_recipe.cc.html | 227 ++++- html/070string.mu.html | 1356 ------------------------- html/070text.mu.html | 1352 ++++++++++++++++++++++++ html/071channel.mu.html | 58 +- html/073list.mu.html | 87 ++ html/076stream.mu.html | 30 +- html/081print.mu.html | 301 +++--- html/082scenario_screen.cc.html | 17 +- html/083scenario_screen_test.mu.html | 4 +- html/084console.mu.html | 49 +- html/090trace_browser.cc.html | 30 + html/091run_interactive.cc.html | 36 +- html/092persist.cc.html | 2 +- html/998check_type_pointers.cc.html | 75 +- html/999spaces.cc.html | 51 + html/channel.mu.html | 8 +- html/chessboard.mu.html | 120 +-- html/edit/001-editor.mu.html | 14 +- html/edit/002-typing.mu.html | 6 +- html/edit/003-shortcuts.mu.html | 8 +- html/edit/004-programming-environment.mu.html | 162 +-- html/edit/005-sandbox.mu.html | 34 +- html/edit/008-sandbox-test.mu.html | 8 +- html/edit/009-sandbox-trace.mu.html | 6 +- html/edit/010-warnings.mu.html | 91 +- html/edit/011-editor-undo.mu.html | 4 - html/screen.mu.html | 4 +- index.html | 5 +- 62 files changed, 2784 insertions(+), 2223 deletions(-) create mode 100644 html/053rewrite_stash.cc.html delete mode 100644 html/070string.mu.html create mode 100644 html/070text.mu.html diff --git a/html/000organization.cc.html b/html/000organization.cc.html index bbc09e8d..a3822fb5 100644 --- a/html/000organization.cc.html +++ b/html/000organization.cc.html @@ -136,6 +136,7 @@ int main(int argc, // End One-time Setup + // Commandline Parsing // End Commandline Parsing return 0; // End Main diff --git a/html/001help.cc.html b/html/001help.cc.html index a6a34cdd..eaa49b8b 100644 --- a/html/001help.cc.html +++ b/html/001help.cc.html @@ -49,6 +49,9 @@ if (argc <= 1 || << "You can test directories just like files.\n" << "To pass ingredients to a mu program, provide them after '--':\n" << " mu file_or_dir1 file_or_dir2 ... -- ingredient1 ingredient2 ...\n" + << "\n" + << "To browse a trace generated by a previous run:\n" + << " mu browse-trace file\n" ; return 0; } @@ -146,6 +149,13 @@ template<typename T> typename T::mapped_type& get_or_insert} //: The contract: any container that relies on get_or_insert should never call //: contains_key. +//: +//: 7. istreams are a royal pain in the arse. You have to be careful about +//: what subclass you try to putback into. You have to watch out for the pesky +//: failbit and badbit. Just avoid eof() and use this helper instead. +bool has_data(istream& in) { + return in && !in.eof(); +} :(before "End Includes") #include<assert.h> diff --git a/html/003trace.cc.html b/html/003trace.cc.html index 022ad5f1..a1c0be03 100644 --- a/html/003trace.cc.html +++ b/html/003trace.cc.html @@ -192,10 +192,8 @@ trace_stream* Trace_stream = NULL// Top-level helper. IMPORTANT: can't nest. #define trace(...) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__) -// Errors and warnings should go straight to cerr by default since calls to trace() have -// some unfriendly constraints (they delay printing, they can't nest) -#define raise ((!Trace_stream || !Hide_warnings) ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream(Warning_depth, "warn")) -#define raise_error ((!Trace_stream || !Hide_errors) ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error")) +#define raise (!Trace_stream ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream(Warning_depth, "warn")) +#define raise_error (!Trace_stream ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error")) :(before "End Types") struct end {}; diff --git a/html/010vm.cc.html b/html/010vm.cc.html index f476aa61..976edf51 100644 --- a/html/010vm.cc.html +++ b/html/010vm.cc.html @@ -94,6 +94,7 @@ struct reagent { reagent(string s); reagent(); ~reagent(); + void clear(); reagent(const reagent& old); reagent& operator=(const reagent& old); void set_value(double v) { value = v; initialized = true; } @@ -270,7 +271,7 @@ reagent::reagent(string s istringstream in(s); in >> std::noskipws; // properties - while (!in.eof()) { + while (has_data(in)) { istringstream row(slurp_until(in, '/')); row >> std::noskipws; string key = slurp_until(row, ':'); @@ -295,7 +296,7 @@ reagent::reagent(string s string_tree* parse_property_list(istream& in) { skip_whitespace(in); - if (in.eof()) return NULL; + if (!has_data(in)) return NULL; string_tree* result = new string_tree(slurp_until(in, ':')); result->right = parse_property_list(in); return result; @@ -353,9 +354,18 @@ reagent& reagent::operator=(const reagent& } reagent::~reagent() { - for (long long int i = 0; i < SIZE(properties); ++i) - if (properties.at(i).second) delete properties.at(i).second; + clear(); +} + +void reagent::clear() { + for (long long int i = 0; i < SIZE(properties); ++i) { + if (properties.at(i).second) { + delete properties.at(i).second; + properties.at(i).second = NULL; + } + } delete type; + type = NULL; } type_tree::~type_tree() { delete left; @@ -510,31 +520,6 @@ string_tree* property(const reagent& rreturn NULL; } -bool deeply_equal(const string_tree* a, const string_tree* b) { - if (!a) return !b; - if (!b) return !a; - return a->value == b->value - && deeply_equal(a->left, b->left) - && deeply_equal(a->right, b->right); -} - -:(before "End Globals") -set<string> Literal_type_names; -:(before "End One-time Setup") -Literal_type_names.insert("literal"); -Literal_type_names.insert("number"); -Literal_type_names.insert("character"); -:(code) -bool deeply_equal_types(const string_tree* a, const string_tree* b) { - if (!a) return !b; - if (!b) return !a; - if (Literal_type_names.find(a->value) != Literal_type_names.end()) - return Literal_type_names.find(b->value) != Literal_type_names.end(); - return a->value == b->value - && deeply_equal_types(a->left, b->left) - && deeply_equal_types(a->right, b->right); -} - void dump_memory() { for (map<long long int, double>::iterator p = Memory.begin(); p != Memory.end(); ++p) { cout << p->first << ": " << no_scientific(p->second) << '\n'; @@ -573,7 +558,7 @@ void dump(const string_tree* x} void skip_whitespace(istream& in) { - while (!in.eof() && isspace(in.peek()) && in.peek() != '\n') { + while (in && isspace(in.peek()) && in.peek() != '\n') { in.get(); } } diff --git a/html/011load.cc.html b/html/011load.cc.html index 3e74bc02..7296d1bb 100644 --- a/html/011load.cc.html +++ b/html/011load.cc.html @@ -53,9 +53,9 @@ vector<recipe_ordinal> load(string form(istream& in) { in >> std::noskipws; vector<recipe_ordinal> result; - while (!in.eof()) { + while (has_data(in)) { skip_whitespace_and_comments(in); - if (in.eof()) break; + if (!has_data(in)) break; string command = next_word(in); // Command Handlers if (command == "recipe") { @@ -116,25 +116,25 @@ void slurp_body(istream& in(istream& in, instruction* curr) { curr->clear(); - if (in.eof()) { + if (!has_data(in)) { raise_error << "0: unbalanced '[' for recipe\n" << end(); return false; } skip_whitespace(in); - if (in.eof()) { + if (!has_data(in)) { raise_error << "1: unbalanced '[' for recipe\n" << end(); return false; } skip_whitespace_and_comments(in); - if (in.eof()) { + if (!has_data(in)) { raise_error << "2: unbalanced '[' for recipe\n" << end(); return false; } vector<string> words; - while (in.peek() != '\n' && !in.eof()) { + while (has_data(in) && in.peek() != '\n') { skip_whitespace(in); - if (in.eof()) { + if (!has_data(in)) { raise_error << "3: unbalanced '[' for recipe\n" << end(); return false; } @@ -151,7 +151,7 @@ bool next_instruction(istream& in->is_label = true; curr->label = words.at(0); trace(9993, "parse") << "label: " << curr->label << end(); - if (in.eof()) { + if (!has_data(in)) { raise_error << "7: unbalanced '[' for recipe\n" << end(); return false; } @@ -185,7 +185,7 @@ bool next_instruction(istream& in(vector<reagent>::iterator p = curr->products.begin(); p != curr->products.end(); ++p) { trace(9993, "parse") << " product: " << p->to_string() << end(); } - if (in.eof()) { + if (!has_data(in)) { raise_error << "9: unbalanced '[' for recipe\n" << end(); return false; } @@ -210,7 +210,7 @@ string Ignore("," :(code) void slurp_word(istream& in, ostream& out) { char c; - if (!in.eof() && Terminators.find(in.peek()) != string::npos) { + if (has_data(in) && Terminators.find(in.peek()) != string::npos) { in >> c; out << c; return; @@ -232,7 +232,7 @@ void skip_ignored_characters(istream& in(istream& in) { while (true) { - if (in.eof()) break; + if (!has_data(in)) break; if (isspace(in.peek())) in.get(); else if (in.peek() == ',') in.get(); else if (in.peek() == '#') skip_comment(in); @@ -241,9 +241,9 @@ void skip_whitespace_and_comments(istream& in } void skip_comment(istream& in) { - if (!in.eof() && in.peek() == '#') { + if (has_data(in) && in.peek() == '#') { in.get(); - while (!in.eof() && in.peek() != '\n') in.get(); + while (has_data(in) && in.peek() != '\n') in.get(); } } diff --git a/html/012transform.cc.html b/html/012transform.cc.html index 6770e137..7ddd15af 100644 --- a/html/012transform.cc.html +++ b/html/012transform.cc.html @@ -57,8 +57,16 @@ vector<transform_fn> Transform; :(after "int main") // Begin Transforms + // Begin Instruction Inserting/Deleting Transforms + // End Instruction Inserting/Deleting Transforms + + // Begin Instruction Modifying Transforms + // End Instruction Modifying Transforms // End Transforms + // Begin Checks + // End Checks + :(code) void transform_all() { trace(9990, "transform") << "=== transform_all()" << end(); @@ -73,6 +81,7 @@ void transform_all() {.transformed_until = t; } } +//? cerr << "wrapping up transform\n"; parse_int_reagents(); // do this after all other transforms have run // End Transform All } diff --git a/html/013update_operation.cc.html b/html/013update_operation.cc.html index c2377b97..7a6c5f78 100644 --- a/html/013update_operation.cc.html +++ b/html/013update_operation.cc.html @@ -33,7 +33,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } //: Once all code is loaded, save operation ids of instructions and check that //: nothing's undefined. -:(before "End Transforms") +:(before "End Instruction Modifying Transforms") Transform.push_back(update_instruction_operations); // idempotent :(code) diff --git a/html/014literal_string.cc.html b/html/014literal_string.cc.html index b137323a..024a0efc 100644 --- a/html/014literal_string.cc.html +++ b/html/014literal_string.cc.html @@ -55,17 +55,17 @@ recipe main [ put(Type_ordinal, "literal-string", 0); :(before "End next_word Special-cases") - if (in.peek() == '[') { - string result = slurp_quoted(in); - skip_whitespace(in); - skip_comment(in); - return result; - } +if (in.peek() == '[') { + string result = slurp_quoted(in); + skip_whitespace(in); + skip_comment(in); + return result; +} :(code) string slurp_quoted(istream& in) { ostringstream out; - assert(!in.eof()); assert(in.peek() == '['); out << static_cast<char>(in.get()); // slurp the '[' + assert(has_data(in)); assert(in.peek() == '['); out << static_cast<char>(in.get()); // slurp the '[' if (is_code_string(in, out)) slurp_quoted_comment_aware(in, out); else @@ -76,7 +76,7 @@ string slurp_quoted(istream& in// A string is a code string if it contains a newline before any non-whitespace // todo: support comments before the newline. But that gets messy. bool is_code_string(istream& in, ostream& out) { - while (!in.eof()) { + while (has_data(in)) { char c = in.get(); if (!isspace(c)) { in.putback(c); @@ -94,7 +94,7 @@ bool is_code_string(istream& in// strings. void slurp_quoted_comment_oblivious(istream& in, ostream& out) { int brace_depth = 1; - while (!in.eof()) { + while (has_data(in)) { char c = in.get(); if (c == '\\') { out << static_cast<char>(in.get()); @@ -105,7 +105,7 @@ void slurp_quoted_comment_oblivious(istream& if (c == ']') --brace_depth; if (brace_depth == 0) break; } - if (in.eof() && brace_depth > 0) { + if (!has_data(in) && brace_depth > 0) { raise_error << "unbalanced '['\n" << end(); out.clear(); } @@ -121,7 +121,7 @@ void slurp_quoted_comment_aware(istream& in} if (c == '#') { out << c; - while (!in.eof() && in.peek() != '\n') out << static_cast<char>(in.get()); + while (has_data(in) && in.peek() != '\n') out << static_cast<char>(in.get()); continue; } if (c == '[') { diff --git a/html/020run.cc.html b/html/020run.cc.html index 38ffeb73..e5ee3061 100644 --- a/html/020run.cc.html +++ b/html/020run.cc.html @@ -104,9 +104,7 @@ void run_current_routine() raise_error << "something wrote to location 0; this should never happen\n" << end(); put(Memory, 0, 0); } - // Read all ingredients from memory. - // Each ingredient loads a vector of values rather than a single value; mu - // permits operating on reagents spanning multiple locations. + // read all ingredients from memory, each potentially spanning multiple locations vector<vector<double> > ingredients; if (should_copy_ingredients()) { for (long long int i = 0; i < SIZE(current_instruction().ingredients); ++i) { @@ -115,7 +113,7 @@ void run_current_routine() //? Locations_read_by_instruction[current_instruction().name] += SIZE(ingredients.back()); } } - // Instructions below will write to 'products'. + // instructions below will write to 'products' vector<vector<double> > products; switch (current_instruction().operation) { // Primitive Recipe Implementations @@ -191,14 +189,14 @@ if (argc > 1} transform_all(); //? dump_recipe("handle-keyboard-event"), exit(0); - if (Run_tests) Recipe.erase(get(Recipe_ordinal, string("main"))); + if (Run_tests) Recipe.erase(get(Recipe_ordinal, "main")); // End Loading .mu Files } //: Step 3: if we aren't running tests, locate a recipe called 'main' and //: start running it. :(before "End Main") -if (!Run_tests && contains_key(Recipe_ordinal, string("main")) && contains_key(Recipe, get(Recipe_ordinal, string("main")))) { +if (!Run_tests && contains_key(Recipe_ordinal, "main") && contains_key(Recipe, get(Recipe_ordinal, "main"))) { setup(); //? Trace_file = "interactive"; //? START_TRACING_UNTIL_END_OF_SCOPE; @@ -209,7 +207,7 @@ if (!Run_tests && contains_key:(code) void run_main(int argc, char* argv[]) { - recipe_ordinal r = get(Recipe_ordinal, string("main")); + recipe_ordinal r = get(Recipe_ordinal, "main"); if (r) run(r); } diff --git a/html/021check_instruction.cc.html b/html/021check_instruction.cc.html index e203a386..585c2c3a 100644 --- a/html/021check_instruction.cc.html +++ b/html/021check_instruction.cc.html @@ -42,7 +42,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } //: sophisticated layer system I'd introduce the simpler version first and //: transform it in a separate layer or set of layers. -:(after "Transform.push_back(update_instruction_operations)") +:(before "End Checks") Transform.push_back(check_instruction); // idempotent :(code) @@ -61,7 +61,7 @@ void check_instruction(const recipe_ordinal rbreak; } for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!types_match(inst.products.at(i), inst.ingredients.at(i))) { + if (!types_coercible(inst.products.at(i), inst.ingredients.at(i))) { 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(); goto finish_checking_instruction; } @@ -106,57 +106,78 @@ recipe main [ ] +error: main: can't copy 34 to 1:address:number; types don't match +:(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 + :(code) -bool types_match(reagent lhs, reagent rhs) { +// 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; + // End types_coercible Special-cases + return false; +} + +bool types_match(const reagent& lhs, const reagent& rhs) { + // 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; + if (is_literal(rhs)) { + if (is_mu_array(lhs)) return false; + // 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; - // to sidestep type-checking, use /raw in the source. - // this is unsafe, and will be highlighted in red inside vim. just for some tests. - if (is_raw(rhs)) return true; - if (is_literal(rhs)) return valid_type_for_literal(lhs, rhs) && size_of(rhs) == size_of(lhs); if (!lhs.type) return !rhs.type; - return types_match(lhs.type, rhs.type); -} - -bool valid_type_for_literal(const reagent& lhs, const reagent& literal_rhs) { - if (is_mu_array(lhs)) return false; - // End valid_type_for_literal Special-cases - // allow writing 0 to any address - if (is_mu_address(lhs)) return literal_rhs.name == "0"; - return true; + 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 (lhs->value == get(Type_ordinal, "character") && rhs->value == get(Type_ordinal, "number")) return true; - if (lhs->value == get(Type_ordinal, "number") && rhs->value == get(Type_ordinal, "character")) return true; + 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); -} - -// hacky version that allows 0 addresses -bool types_match(const reagent lhs, const type_tree* rhs, const vector<double>& data) { - if (is_dummy(lhs)) return true; - if (rhs->value == 0) { - if (lhs.type->value == get(Type_ordinal, "array")) return false; - if (lhs.type->value == get(Type_ordinal, "address")) return scalar(data) && data.at(0) == 0; - return size_of(rhs) == size_of(lhs); - } - if (lhs.type->value != rhs->value) return false; - return types_match(lhs.type->left, rhs->left) && types_match(lhs.type->right, rhs->right); + return types_strictly_match(lhs->left, rhs->left) && types_strictly_match(lhs->right, rhs->right); } bool is_raw(const reagent& r) { return has_property(r, "raw"); } +bool is_unsafe(const reagent& r) { + return has_property(r, "unsafe"); +} + bool is_mu_array(reagent r) { if (!r.type) return false; if (is_literal(r)) return false; diff --git a/html/029tools.cc.html b/html/029tools.cc.html index 4f5242fe..4b78fe12 100644 --- a/html/029tools.cc.html +++ b/html/029tools.cc.html @@ -72,7 +72,7 @@ case TRACE: { break; } -//: a smarter but more limited version of 'trace' +//: simpler limited version of 'trace' :(before "End Primitive Recipe Declarations") STASH, @@ -117,9 +117,8 @@ string print_mu(const reagent& rreturn r.name+' '; // End print Special-cases(reagent r, data) ostringstream out; - for (long long i = 0; i < SIZE(data); ++i) { + for (long long i = 0; i < SIZE(data); ++i) out << no_scientific(data.at(i)) << ' '; - } return out.str(); } diff --git a/html/030container.cc.html b/html/030container.cc.html index 403c6a60..8ef0dda6 100644 --- a/html/030container.cc.html +++ b/html/030container.cc.html @@ -16,7 +16,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } .traceContains { color: #008000; } .SalientComment { color: #00ffff; } .cSpecial { color: #008000; } -.traceAbsent { color: #c00000; } .Comment { color: #9090ff; } .Delimiter { color: #a04060; } .Special { color: #ff6060; } @@ -191,11 +190,12 @@ case GET: { raise_error << maybe(get(Recipe, r).name) << "invalid offset " << offset_value << " for " << get(Type, base_type).name << '\n' << end(); break; } + if (inst.products.empty()) break; reagent product = inst.products.at(0); // Update GET product in Check const reagent element = element_type(base, offset_value); - if (!types_match(product, element)) { - raise_error << maybe(get(Recipe, r).name) << "'get' " << offset.original_string << " (" << offset_value << ") on " << get(Type, base_type).name << " can't be saved in " << product.original_string << "; type should be " << debug_string(element.type) << " but is " << debug_string(product.type) << '\n' << end(); + if (!types_coercible(product, element)) { + raise_error << maybe(get(Recipe, r).name) << "'get " << base.original_string << ", " << offset.original_string << "' should write to " << debug_string(element.type) << " but " << product.name << " has type " << debug_string(product.type) << '\n' << end(); break; } break; @@ -275,7 +275,17 @@ recipe main [ 14:number <- copy 36 15:address:number <- get 12:point-number/raw, 1:offset ] -+error: main: 'get' 1:offset (1) on point-number can't be saved in 15:address:number; type should be number but is <address : <number : <>>> ++error: main: 'get 12:point-number/raw, 1:offset' should write to number but 15 has type <address : <number : <>>> + +//: we might want to call 'get' without saving the results, say in a sandbox + +:(scenario get_without_product) +recipe main [ + 12:number <- copy 34 + 13:number <- copy 35 + get 12:point/raw, 1:offset # unsafe +] +# just don't die //:: To write to elements of containers, you need their address. @@ -326,8 +336,8 @@ case GET_ADDRESS: { reagent element = element_type(base, offset_value); // ..except for an address at the start element.type = new type_tree(get(Type_ordinal, "address"), element.type); - if (!types_match(product, element)) { - raise_error << maybe(get(Recipe, r).name) << "'get-address' " << offset.original_string << " (" << offset_value << ") on " << get(Type, base_type).name << " can't be saved in " << product.original_string << "; type should be " << debug_string(element.type) << '\n' << end(); + if (!types_coercible(product, element)) { + raise_error << maybe(get(Recipe, r).name) << "'get-address " << base.original_string << ", " << offset.original_string << "' should write to " << debug_string(element.type) << " but " << product.name << " has type " << debug_string(product.type) << '\n' << end(); break; } break; @@ -377,13 +387,16 @@ recipe main [ :(scenario get_address_product_type_mismatch) % Hide_errors = true; +container boolbool [ + x:boolean + y:boolean +] recipe main [ - 12:number <- copy 34 - 13:number <- copy 35 - 14:number <- copy 36 - 15:number <- get-address 12:point-number/raw, 1:offset + 12:boolean <- copy 1 + 13:boolean <- copy 0 + 15:boolean <- get-address 12:boolbool, 1:offset ] -+error: main: 'get-address' 1:offset (1) on point-number can't be saved in 15:number; type should be <address : <number : <>>> ++error: main: 'get-address 12:boolbool, 1:offset' should write to <address : <boolean : <>>> but 15 has type boolean //:: Allow containers to be defined in mu code. @@ -439,7 +452,7 @@ void insert_container(const string& command.push_back(get(Type_ordinal, name)); info.name = name; info.kind = kind; - while (!in.eof()) { + while (has_data(in)) { skip_whitespace_and_comments(in); string element = next_word(in); if (element == "]") break; @@ -448,7 +461,7 @@ void insert_container(const string& command.element_names.push_back(slurp_until(inner, ':')); trace(9993, "parse") << " element name: " << info.element_names.back() << end(); type_tree* new_type = NULL; - for (type_tree** curr_type = &new_type; !inner.eof(); curr_type = &(*curr_type)->right) { + for (type_tree** curr_type = &new_type; has_data(inner); curr_type = &(*curr_type)->right) { string type_name = slurp_until(inner, ':'); // End insert_container Special Uses(type_name) if (!contains_key(Type_ordinal, type_name) @@ -535,22 +548,23 @@ recipe main [ # integer is not a type 1:integer <- copy 0 ] -+error: main: unknown type in '1:integer <- copy 0' ++error: main: unknown type integer in '1:integer <- copy 0' :(scenario run_allows_type_definition_after_use) % Hide_errors = true; recipe main [ - 1:bar <- copy 0/raw + 1:bar <- copy 0/unsafe ] container bar [ x:number ] --error: unknown type: bar $error: 0 -:(after "Begin Transforms") +:(after "Begin Instruction Modifying Transforms") +// Begin Type Modifying Transforms Transform.push_back(check_or_set_invalid_types); // idempotent +// End Type Modifying Transforms :(code) void check_or_set_invalid_types(const recipe_ordinal r) { @@ -578,8 +592,10 @@ void check_or_set_invalid_types(type_tree* type(!contains_key(Type, type->value)) { if (type_name && contains_key(Type_ordinal, type_name->value)) type->value = get(Type_ordinal, type_name->value); + else if (type_name) + raise_error << block << "unknown type " << type_name->value << " in " << name << '\n' << end(); else - raise_error << block << "unknown type in " << name << '\n' << end(); + raise_error << block << "missing type in " << name << '\n' << end(); } check_or_set_invalid_types(type->left, type_name ? type_name->left : NULL, block, name); check_or_set_invalid_types(type->right, type_name ? type_name->right : NULL, block, name); diff --git a/html/031address.cc.html b/html/031address.cc.html index f2b0e28f..5a58fc2f 100644 --- a/html/031address.cc.html +++ b/html/031address.cc.html @@ -38,7 +38,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } :(scenario copy_indirect) recipe main [ - 1:address:number <- copy 2/raw + 1:address:number <- copy 2/unsafe 2:number <- copy 34 # This loads location 1 as an address and looks up *that* location. 3:number <- copy 1:address:number/lookup @@ -52,7 +52,7 @@ canonize(x); //: 'lookup' property :(scenario store_indirect) recipe main [ - 1:address:number <- copy 2/raw + 1:address:number <- copy 2/unsafe 1:address:number/lookup <- copy 34 ] +mem: storing 34 in location 2 @@ -96,7 +96,7 @@ void lookup_memory(reagent& x(x); } -:(after "bool types_match(reagent lhs, reagent rhs)") +:(after "bool types_strictly_match(reagent lhs, reagent rhs)") if (!canonize_type(lhs)) return false; if (!canonize_type(rhs)) return false; @@ -161,7 +161,7 @@ recipe main [ 1:number <- copy 2 2:number <- copy 34 3:number <- copy 35 - 4:address:number <- copy 5/raw + 4:address:number <- copy 5/unsafe *4:address:number <- get 1:address:point/lookup, 0:offset ] +mem: storing 34 in location 5 @@ -203,7 +203,7 @@ canonize(base); :(scenario lookup_abbreviation) recipe main [ - 1:address:number <- copy 2/raw + 1:address:number <- copy 2/unsafe 2:number <- copy 34 3:number <- copy *1:address:number ] diff --git a/html/032array.cc.html b/html/032array.cc.html index 8c0ce105..d2689076 100644 --- a/html/032array.cc.html +++ b/html/032array.cc.html @@ -119,7 +119,7 @@ recipe main [ 2:number <- copy 14 3:number <- copy 15 4:number <- copy 16 - 5:address:array:number <- copy 1/raw + 5:address:array:number <- copy 1/unsafe 6:array:number <- copy *5:address:array:number ] +mem: storing 3 in location 6 @@ -194,7 +194,7 @@ case INDEX: { canonize_type(product); reagent element; element.type = new type_tree(*array_element(base.type)); - if (!types_match(product, element)) { + if (!types_coercible(product, element)) { raise_error << maybe(get(Recipe, r).name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << debug_string(element.type) << '\n' << end(); break; } @@ -238,7 +238,7 @@ recipe main [ 2:number <- copy 14 3:number <- copy 15 4:number <- copy 16 - 5:address:array:number <- copy 1/raw + 5:address:array:number <- copy 1/unsafe 6:number <- index *5:address:array:number, 1 ] +mem: storing 15 in location 6 @@ -268,7 +268,7 @@ recipe main [ 5:number <- copy 14 6:number <- copy 15 7:number <- copy 16 - 8:address:array:point <- copy 1/raw + 8:address:array:point <- copy 1/unsafe index *8:address:array:point, -1 ] +error: main: invalid index -1 @@ -283,11 +283,23 @@ recipe main [ 5:number <- copy 14 6:number <- copy 15 7:number <- copy 16 - 8:address:array:point <- copy 1/raw + 8:address:array:point <- copy 1/unsafe 9:number <- index *8:address:array:point, 0 ] +error: main: 'index' on *8:address:array:point can't be saved in 9:number; type should be point +//: we might want to call 'index' without saving the results, say in a sandbox + +:(scenario index_without_product) +recipe main [ + 1:array:number:3 <- create-array + 2:number <- copy 14 + 3:number <- copy 15 + 4:number <- copy 16 + index 1:array:number:3, 0 +] +# just don't die + //:: To write to elements of containers, you need their address. :(scenario index_address) @@ -322,7 +334,7 @@ case INDEX_ADDRESS: { reagent element; element.type = new type_tree(*array_element(base.type)); element.type = new type_tree(get(Type_ordinal, "address"), element.type); - if (!types_match(product, element)) { + if (!types_coercible(product, element)) { raise_error << maybe(get(Recipe, r).name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << debug_string(element.type) << '\n' << end(); break; } @@ -376,7 +388,7 @@ recipe main [ 5:number <- copy 14 6:number <- copy 15 7:number <- copy 16 - 8:address:array:point <- copy 1/raw + 8:address:array:point <- copy 1/unsafe index-address *8:address:array:point, -1 ] +error: main: invalid index -1 @@ -391,7 +403,7 @@ recipe main [ 5:number <- copy 14 6:number <- copy 15 7:number <- copy 16 - 8:address:array:point <- copy 1/raw + 8:address:array:point <- copy 1/unsafe 9:address:number <- index-address *8:address:array:point, 0 ] +error: main: 'index' on *8:address:array:point can't be saved in 9:address:number; type should be <address : <point : <>>> @@ -445,6 +457,18 @@ case LENGTH: { recipe_ordinal r = current_instruction().operation; if (r == CREATE_ARRAY || r == INDEX || r == INDEX_ADDRESS || r == LENGTH) return false; + +//: a particularly common array type is the string, or address:array:character +:(code) +bool is_mu_string(const reagent& x) { + return x.type + && x.type->value == get(Type_ordinal, "address") + && x.type->right + && x.type->right->value == get(Type_ordinal, "array") + && x.type->right->right + && x.type->right->right->value == get(Type_ordinal, "character") + && x.type->right->right->right == NULL; +} diff --git a/html/035call_ingredient.cc.html b/html/035call_ingredient.cc.html index ff46da6f..34c4c270 100644 --- a/html/035call_ingredient.cc.html +++ b/html/035call_ingredient.cc.html @@ -55,7 +55,7 @@ recipe f [ :(before "End call Fields") vector<vector<double> > ingredient_atoms; -vector<type_tree*> ingredient_types; +vector<reagent> ingredients; long long int next_ingredient_to_process; :(before "End call Constructor") next_ingredient_to_process = 0; @@ -65,12 +65,7 @@ for (long long int i = 0().ingredient_atoms.push_back(ingredients.at(i)); reagent ingredient = call_instruction.ingredients.at(i); canonize_type(ingredient); - current_call().ingredient_types.push_back(ingredient.type); - ingredient.type = NULL; // release long-lived pointer -} -:(before "End call Destructor") -for (long long int i = 0; i < SIZE(ingredient_types); ++i) { - delete ingredient_types.at(i); + current_call().ingredients.push_back(ingredient); } :(before "End Primitive Recipe Declarations") @@ -97,10 +92,10 @@ case NEXT_INGREDIENT: { if (!is_mu_string(product)) raise_error << "main: wrong type for ingredient " << product.original_string << '\n' << end(); } - else if (!types_match(product, - current_call().ingredient_types.at(current_call().next_ingredient_to_process), - current_call().ingredient_atoms.at(current_call().next_ingredient_to_process))) { + else if (!types_coercible(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)); diff --git a/html/036call_reply.cc.html b/html/036call_reply.cc.html index 45622e07..1784c8f9 100644 --- a/html/036call_reply.cc.html +++ b/html/036call_reply.cc.html @@ -73,13 +73,14 @@ case REPLY: { break; } for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) { - if (!types_match(caller_instruction.products.at(i), reply_inst.ingredients.at(i))) { + if (!types_coercible(caller_instruction.products.at(i), reply_inst.ingredients.at(i))) { raise_error << maybe(callee) << "reply ingredient " << reply_inst.ingredients.at(i).original_string << " can't be saved in " << caller_instruction.products.at(i).original_string << '\n' << end(); reagent lhs = reply_inst.ingredients.at(i); canonize_type(lhs); reagent rhs = caller_instruction.products.at(i); canonize_type(rhs); raise_error << debug_string(lhs.type) << " vs " << debug_string(rhs.type) << '\n' << end(); + // End reply Type Mismatch Error goto finish_reply; } } diff --git a/html/038scheduler.cc.html b/html/038scheduler.cc.html index 16b3efbb..56396b68 100644 --- a/html/038scheduler.cc.html +++ b/html/038scheduler.cc.html @@ -146,7 +146,7 @@ Current_routine = NULL;//: special case for the very first routine :(replace{} "void run_main(int argc, char* argv[])") void run_main(int argc, char* argv[]) { - recipe_ordinal r = get(Recipe_ordinal, string("main")); + recipe_ordinal r = get(Recipe_ordinal, "main"); if (r) { routine* main_routine = new routine(r); // Update main_routine @@ -200,8 +200,7 @@ case START_RUNNING: { new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i)); reagent ingredient = current_instruction().ingredients.at(i); canonize_type(ingredient); - new_routine->calls.front().ingredient_types.push_back(ingredient.type); - ingredient.type = NULL; // release long-lived pointer + new_routine->calls.front().ingredients.push_back(ingredient); } Routines.push_back(new_routine); products.resize(1); diff --git a/html/040brace.cc.html b/html/040brace.cc.html index 6f994e22..a65f6e0f 100644 --- a/html/040brace.cc.html +++ b/html/040brace.cc.html @@ -65,7 +65,7 @@ recipe main [ +transform: jump 1:offset +transform: copy ... -:(after "Begin Transforms") +:(before "End Instruction Modifying Transforms") Transform.push_back(transform_braces); // idempotent :(code) diff --git a/html/041jump_target.cc.html b/html/041jump_target.cc.html index e85829db..341cf3c2 100644 --- a/html/041jump_target.cc.html +++ b/html/041jump_target.cc.html @@ -52,7 +52,7 @@ recipe main [ :(before "End Mu Types Initialization") put(Type_ordinal, "label", 0); -:(before "Transform.push_back(transform_braces)") +:(before "End Instruction Modifying Transforms") Transform.push_back(transform_labels); // idempotent :(code) diff --git a/html/042name.cc.html b/html/042name.cc.html index da006079..aa0a1fe9 100644 --- a/html/042name.cc.html +++ b/html/042name.cc.html @@ -54,7 +54,7 @@ recipe main [ +error: main: use before set: y # todo: detect conditional defines -:(after "Transform.push_back(check_or_set_invalid_types") // there'll be other transforms relating to types; they all need to happen first +:(before "End Instruction Modifying Transforms") Transform.push_back(transform_names); // idempotent :(before "End Globals") @@ -66,31 +66,32 @@ for (long long int i = 0:(code) void transform_names(const recipe_ordinal r) { - trace(9991, "transform") << "--- transform names for recipe " << get(Recipe, r).name << end(); -//? cerr << "--- transform names for recipe " << get(Recipe, r).name << '\n'; + recipe& caller = get(Recipe, r); + trace(9991, "transform") << "--- transform names for recipe " << caller.name << end(); +//? cerr << "--- transform names for recipe " << caller.name << '\n'; bool names_used = false; bool numeric_locations_used = false; map<string, long long int>& names = Name[r]; // store the indices 'used' so far in the map long long int& curr_idx = names[""]; ++curr_idx; // avoid using index 0, benign skip in some other cases - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - instruction& inst = get(Recipe, r).steps.at(i); + for (long long int i = 0; i < SIZE(caller.steps); ++i) { + instruction& inst = caller.steps.at(i); // End transform_names(inst) Special-cases // map names to addresses for (long long int in = 0; in < SIZE(inst.ingredients); ++in) { if (is_numeric_location(inst.ingredients.at(in))) numeric_locations_used = true; if (is_named_location(inst.ingredients.at(in))) names_used = true; - if (disqualified(inst.ingredients.at(in), inst, get(Recipe, r).name)) continue; + if (disqualified(inst.ingredients.at(in), inst, caller.name)) continue; if (!already_transformed(inst.ingredients.at(in), names)) { - raise_error << maybe(get(Recipe, r).name) << "use before set: " << inst.ingredients.at(in).name << '\n' << end(); + raise_error << maybe(caller.name) << "use before set: " << inst.ingredients.at(in).name << '\n' << end(); } inst.ingredients.at(in).set_value(lookup_name(inst.ingredients.at(in), r)); } for (long long int out = 0; out < SIZE(inst.products); ++out) { if (is_numeric_location(inst.products.at(out))) numeric_locations_used = true; if (is_named_location(inst.products.at(out))) names_used = true; - if (disqualified(inst.products.at(out), inst, get(Recipe, r).name)) continue; + if (disqualified(inst.products.at(out), inst, caller.name)) continue; if (names.find(inst.products.at(out).name) == names.end()) { trace(9993, "name") << "assign " << inst.products.at(out).name << " " << curr_idx << end(); names[inst.products.at(out).name] = curr_idx; @@ -100,7 +101,7 @@ void transform_names(const recipe_ordinal r} } if (names_used && numeric_locations_used) - raise_error << maybe(get(Recipe, r).name) << "mixing variable names and numeric addresses\n" << end(); + raise_error << maybe(caller.name) << "mixing variable names and numeric addresses\n" << end(); } bool disqualified(/*mutable*/ reagent& x, const instruction& inst, const string& recipe_name) { @@ -240,8 +241,10 @@ if (inst.name == < if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) { // since first non-address in base type must be a container, we don't have to canonize type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type, get(Recipe, r).name); - inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); - trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end(); + if (contains_key(Type, base_type)) { // otherwise we'll raise an error elsewhere + inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); + trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end(); + } } } @@ -249,8 +252,8 @@ if (inst.name == < :(scenarios transform) :(scenario transform_names_handles_containers) recipe main [ - a:point <- copy 0/raw - b:number <- copy 0/raw + a:point <- copy 0/unsafe + b:number <- copy 0/unsafe ] +name: assign a 1 +name: assign b 3 @@ -279,8 +282,10 @@ if (inst.name == < if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) { // since first non-address in base type must be an exclusive container, we don't have to canonize type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type, get(Recipe, r).name); - inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); - trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end(); + if (contains_key(Type, base_type)) { // otherwise we'll raise an error elsewhere + inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); + trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end(); + } } } diff --git a/html/043new.cc.html b/html/043new.cc.html index 35398d9f..81a1c1c7 100644 --- a/html/043new.cc.html +++ b/html/043new.cc.html @@ -459,16 +459,6 @@ long long int unicode_length(const string& s< return result; } -bool is_mu_string(const reagent& x) { - return x.type - && x.type->value == get(Type_ordinal, "address") - && x.type->right - && x.type->right->value == get(Type_ordinal, "array") - && x.type->right->right - && x.type->right->right->value == get(Type_ordinal, "character") - && x.type->right->right->right == NULL; -} - string read_mu_string(long long int address) { long long int size = get_or_insert(Memory, address); if (size == 0) return ""; diff --git a/html/044space.cc.html b/html/044space.cc.html index 0bd414cb..10e456cb 100644 --- a/html/044space.cc.html +++ b/html/044space.cc.html @@ -42,7 +42,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } # then location 0 is really location 11, location 1 is really location 12, and so on. recipe main [ 10:number <- copy 5 # pretend array; in practice we'll use new - default-space:address:array:location <- copy 10/raw + default-space:address:array:location <- copy 10/unsafe 1:number <- copy 23 ] +mem: storing 23 in location 12 @@ -54,8 +54,8 @@ recipe main [ # pretend array 1000:number <- copy 5 # actual start of this recipe - default-space:address:array:location <- copy 1000/raw - 1:address:number <- copy 3/raw + default-space:address:array:location <- copy 1000/unsafe + 1:address:number <- copy 3/unsafe 8:number/raw <- copy *1:address:number ] +mem: storing 34 in location 8 @@ -105,8 +105,8 @@ recipe main [ # pretend array 1000:number <- copy 5 # actual start of this recipe - default-space:address:array:location <- copy 1000/raw - 1:address:point <- copy 12/raw + default-space:address:array:location <- copy 1000/unsafe + 1:address:point <- copy 12/unsafe 9:number/raw <- get *1:address:point, 1:offset ] +mem: storing 35 in location 9 @@ -125,8 +125,8 @@ recipe main [ # pretend array 1000:number <- copy 5 # actual start of this recipe - default-space:address:array:location <- copy 1000/raw - 1:address:array:number <- copy 12/raw + default-space:address:array:location <- copy 1000/unsafe + 1:address:array:number <- copy 12/unsafe 9:number/raw <- index *1:address:array:number, 1 ] +mem: storing 35 in location 9 @@ -259,7 +259,7 @@ long long int address(long long int offset:(scenario get_default_space) recipe main [ - default-space:address:array:location <- copy 10/raw + default-space:address:array:location <- copy 10/unsafe 1:address:array:location/raw <- copy default-space:address:array:location ] +mem: storing 10 in location 1 diff --git a/html/045space_surround.cc.html b/html/045space_surround.cc.html index da74d215..29d58818 100644 --- a/html/045space_surround.cc.html +++ b/html/045space_surround.cc.html @@ -42,8 +42,8 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } recipe main [ 10:number <- copy 5 # pretend array 20:number <- copy 5 # pretend array - default-space:address:array:location <- copy 10/raw - 0:address:array:location/names:dummy <- copy 20/raw # later layers will explain the /names: property + default-space:address:array:location <- copy 10/unsafe + 0:address:array:location/names:dummy <- copy 20/unsafe # later layers will explain the /names: property 1:number <- copy 32 1:number/space:1 <- copy 33 ] diff --git a/html/047global.cc.html b/html/047global.cc.html index da8d790f..b2017a82 100644 --- a/html/047global.cc.html +++ b/html/047global.cc.html @@ -42,8 +42,8 @@ recipe main [ 10:number <- copy 5 20:number <- copy 5 # actual start of this recipe - global-space:address:array:location <- copy 20/raw - default-space:address:array:location <- copy 10/raw + global-space:address:array:location <- copy 20/unsafe + default-space:address:array:location <- copy 10/unsafe 1:number <- copy 23 1:number/space:global <- copy 24 ] diff --git a/html/048check_type_by_name.cc.html b/html/048check_type_by_name.cc.html index d091ba31..13703a0e 100644 --- a/html/048check_type_by_name.cc.html +++ b/html/048check_type_by_name.cc.html @@ -21,7 +21,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } .Delimiter { color: #a04060; } .Special { color: #ff6060; } .CommentedCode { color: #6c6c6c; } -.Todo { color: #000000; background-color: #ffff00; padding-bottom: 1px; } --> @@ -49,11 +48,11 @@ recipe main [ ] +error: main: x used with multiple types -:(before "Transform.push_back(check_or_set_invalid_types)") -Transform.push_back(check_types_by_name); // idempotent +:(after "Begin Instruction Modifying Transforms") +Transform.push_back(check_or_set_types_by_name); // idempotent :(code) -void check_types_by_name(const recipe_ordinal r) { +void check_or_set_types_by_name(const recipe_ordinal r) { trace(9991, "transform") << "--- deduce types for recipe " << get(Recipe, r).name << end(); //? cerr << "--- deduce types for recipe " << get(Recipe, r).name << '\n'; map<string, type_tree*> type; @@ -82,7 +81,6 @@ void deduce_missing_type(map<string(map<string, type_tree*>& type, map<string, string_tree*>& type_name, const reagent& x, const recipe_ordinal r) { if (is_literal(x)) return; - if (is_raw(x)) return; // TODO: delete this // if you use raw locations you're probably doing something unsafe if (is_integer(x.name)) return; if (!x.type) return; // will throw a more precise error elsewhere @@ -93,7 +91,7 @@ void check_type(map<string(!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(); } @@ -130,7 +128,7 @@ recipe main [ y:address:charcter <- new character:type *y <- copy 67 ] -+error: main: unknown type in 'y:address:charcter <- new character:type' ++error: main: unknown type charcter in 'y:address:charcter <- new character:type' diff --git a/html/050scenario.cc.html b/html/050scenario.cc.html index c586b575..523ea59f 100644 --- a/html/050scenario.cc.html +++ b/html/050scenario.cc.html @@ -233,9 +233,13 @@ case RUN: { case RUN: { ostringstream tmp; tmp << "recipe run" << Next_recipe_ordinal << " [ " << current_instruction().ingredients.at(0).name << " ]"; +//? cerr << "before load\n"; vector<recipe_ordinal> tmp_recipe = load(tmp.str()); +//? cerr << "before bind\n"; bind_special_scenario_names(tmp_recipe.at(0)); +//? cerr << "before transform\n"; transform_all(); +//? cerr << "end\n"; if (Trace_stream) { ++Trace_stream->callstack_depth; trace(9998, "trace") << "run: incrementing callstack depth to " << Trace_stream->callstack_depth << end(); @@ -306,7 +310,7 @@ void check_memory(const string& s; while (true) { skip_whitespace_and_comments(in); - if (in.eof()) break; + if (!has_data(in)) break; string lhs = next_word(in); if (!is_integer(lhs)) { check_type(lhs, in); @@ -341,7 +345,10 @@ void check_memory(const string& s(const string& lhs, istream& in) { reagent x(lhs); - if (x.properties.at(0).second->value == "string") { + const string_tree* type_name = x.properties.at(0).second; + if (type_name->value == "array" + && type_name->right && type_name->right->value == "character" + && !type_name->right->right) { x.set_value(to_integer(x.name)); skip_whitespace_and_comments(in); string _assign = next_word(in); @@ -362,7 +369,7 @@ void check_string(long long int address(9999, "run") << "checking string length at " << address << end(); if (get_or_insert(Memory, address) != SIZE(literal)) { if (Current_scenario && !Scenario_testing_scenario) - raise_error << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end(); + raise_error << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << " (" << read_mu_string(address) << ")\n" << end(); else raise_error << "expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end(); if (!Scenario_testing_scenario) { @@ -377,7 +384,7 @@ void check_string(long long int address(get_or_insert(Memory, address+i) != literal.at(i)) { if (Current_scenario && !Scenario_testing_scenario) { // genuine test in a mu file - raise_error << "\nF - " << Current_scenario->name << ": expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << '\n' << end(); + raise_error << "\nF - " << Current_scenario->name << ": expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << " ('" << static_cast<char>(get_or_insert(Memory, address+i)) << "')\n" << end(); } else { // just testing scenario support @@ -412,7 +419,7 @@ recipe main [ 3:number <- copy 98 # 'b' 4:number <- copy 99 # 'c' memory-should-contain [ - 1:string <- [ab] + 1:array:character <- [ab] ] ] +error: expected location 1 to contain length 2 of string [ab] but saw 3 @@ -424,7 +431,7 @@ recipe main [ 3:number <- copy 98 # 'b' 4:number <- copy 99 # 'c' memory-should-contain [ - 1:string <- [abc] + 1:array:character <- [abc] ] ] +run: checking string length at 1 @@ -467,25 +474,22 @@ case TRACE_SHOULD_CONTAIN: { :(code) // simplified version of check_trace_contents() that emits errors rather // than just printing to stderr -bool check_trace(const string& expected) { +void check_trace(const string& expected) { Trace_stream->newline(); vector<trace_line> expected_lines = parse_trace(expected); - if (expected_lines.empty()) return true; + if (expected_lines.empty()) return; long long int curr_expected_line = 0; for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { if (expected_lines.at(curr_expected_line).label != p->label) continue; if (expected_lines.at(curr_expected_line).contents != trim(p->contents)) continue; // match ++curr_expected_line; - if (curr_expected_line == SIZE(expected_lines)) { - return true; - } + if (curr_expected_line == SIZE(expected_lines)) return; } raise_error << "missing [" << expected_lines.at(curr_expected_line).contents << "] " << "in trace with label " << expected_lines.at(curr_expected_line).label << '\n' << end(); Passed = false; - return false; } vector<trace_line> parse_trace(const string& expected) { diff --git a/html/051scenario_test.mu.html b/html/051scenario_test.mu.html index d38539dd..074da198 100644 --- a/html/051scenario_test.mu.html +++ b/html/051scenario_test.mu.html @@ -60,7 +60,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -scenario check_string_in_memory [ +scenario check_text_in_memory [ run [ 1:number <- copy 3 2:character <- copy 97 # 'a' @@ -68,7 +68,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } 4:character <- copy 99 # 'c' ] memory-should-contain [ - 1:string <- [abc] + 1:array:character <- [abc] ] ] diff --git a/html/052tangle.cc.html b/html/052tangle.cc.html index 4fc37210..131de37a 100644 --- a/html/052tangle.cc.html +++ b/html/052tangle.cc.html @@ -88,7 +88,7 @@ else if (command == " //: after all recipes are loaded, insert fragments at appropriate labels. -:(after "Begin Transforms") +:(after "Begin Instruction Inserting/Deleting Transforms") Transform.push_back(insert_fragments); // NOT idempotent //: We might need to perform multiple passes, in case inserted fragments diff --git a/html/053rewrite_stash.cc.html b/html/053rewrite_stash.cc.html new file mode 100644 index 00000000..ba867faf --- /dev/null +++ b/html/053rewrite_stash.cc.html @@ -0,0 +1,85 @@ + + + + +Mu - 053rewrite_stash.cc + + + + + + + + + + +
+//: when encountering other types, try to convert them to strings using
+//: 'to-text'
+
+:(before "End Instruction Inserting/Deleting Transforms")
+Transform.push_back(rewrite_stashes_to_text);
+
+:(code)
+void rewrite_stashes_to_text(recipe_ordinal r) {
+  recipe& caller = get(Recipe, r);
+  if (contains_named_locations(caller))
+    rewrite_stashes_to_text_named(caller);
+  // in recipes without named locations, 'stash' is still not configurable
+}
+
+bool contains_named_locations(const recipe& caller) {
+  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
+    const instruction& inst = caller.steps.at(i);
+    for (long long int in = 0; in < SIZE(inst.ingredients); ++in)
+      if (is_named_location(inst.ingredients.at(in)))
+        return true;
+    for (long long int out = 0; out < SIZE(inst.products); ++out)
+      if (is_named_location(inst.products.at(out)))
+        return true;
+  }
+  return false;
+}
+
+void rewrite_stashes_to_text_named(recipe& caller) {
+  static long long int stash_instruction_idx = 0;
+  vector<instruction> new_instructions;
+  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
+    instruction& inst = caller.steps.at(i);
+    if (inst.name == "stash") {
+      for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
+        if (is_literal(inst.ingredients.at(j))) continue;
+        if (is_mu_string(inst.ingredients.at(j))) continue;
+        instruction def;
+        def.name = "to-text-line";
+        def.ingredients.push_back(inst.ingredients.at(j));
+        ostringstream ingredient_name;
+        ingredient_name << "stash_" << stash_instruction_idx << '_' << j << ":address:array:character";
+        def.products.push_back(reagent(ingredient_name.str()));
+        new_instructions.push_back(def);
+        inst.ingredients.at(j).clear();  // reclaim old memory
+        inst.ingredients.at(j) = reagent(ingredient_name.str());
+      }
+    }
+    new_instructions.push_back(inst);
+  }
+  new_instructions.swap(caller.steps);
+}
+
+ + + diff --git a/html/054dilated_reagent.cc.html b/html/054dilated_reagent.cc.html index 4f107a15..02f714e4 100644 --- a/html/054dilated_reagent.cc.html +++ b/html/054dilated_reagent.cc.html @@ -15,11 +15,13 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } * { font-size: 1.05em; } .traceContains { color: #008000; } .cSpecial { color: #008000; } +.PreProc { color: #c000c0; } .Comment { color: #9090ff; } .Delimiter { color: #a04060; } .Special { color: #ff6060; } .Identifier { color: #804000; } .Constant { color: #00a0a0; } +.Error { color: #ffffff; background-color: #ff6060; padding-bottom: 1px; } --> @@ -42,26 +44,32 @@ recipe main [ ] +parse: product: {"1": "number", "foo": "bar"} +:(scenario load_trailing_space_after_curly_bracket) +recipe main [ + # line below has a space at the end + { +] +# successfully parsed + //: First augment next_word to group balanced brackets together. :(before "End next_word Special-cases") - if (in.peek() == '(') - return slurp_balanced_bracket(in); - // treat curlies mostly like parens, but don't mess up labels - if (start_of_dilated_reagent(in)) - return slurp_balanced_bracket(in); +if (in.peek() == '(') + return slurp_balanced_bracket(in); +// treat curlies mostly like parens, but don't mess up labels +if (start_of_dilated_reagent(in)) + return slurp_balanced_bracket(in); :(code) // A curly is considered a label if it's the last thing on a line. Dilated // reagents should remain all on one line. -// -// Side-effect: This might delete some whitespace after an initial '{'. bool start_of_dilated_reagent(istream& in) { if (in.peek() != '{') return false; + long long int pos = in.tellg(); in.get(); // slurp '{' skip_whitespace(in); char next = in.peek(); - in.putback('{'); + in.seekg(pos); return next != '\n'; } @@ -107,7 +115,7 @@ if (s.at(s); in >> std::noskipws; in.get(); // skip '{' - while (!in.eof()) { + while (has_data(in)) { string key = slurp_key(in); if (key.empty()) continue; if (key == "}") continue; diff --git a/html/055parse_tree.cc.html b/html/055parse_tree.cc.html index 7d9e158f..14142686 100644 --- a/html/055parse_tree.cc.html +++ b/html/055parse_tree.cc.html @@ -61,7 +61,7 @@ string_tree* parse_string_tree(const string& string_tree* parse_string_tree(istream& in) { skip_whitespace(in); - if (in.eof()) return NULL; + if (!has_data(in)) return NULL; if (in.peek() == ')') { in.get(); return NULL; @@ -74,7 +74,7 @@ string_tree* parse_string_tree(istream& inNULL
; string_tree** curr = &result; while (in.peek() != ')') { - assert(!in.eof()); + assert(has_data(in)); *curr = new string_tree(""); skip_whitespace(in); skip_ignored_characters(in); diff --git a/html/056recipe_header.cc.html b/html/056recipe_header.cc.html index 5000a703..a1c6dd82 100644 --- a/html/056recipe_header.cc.html +++ b/html/056recipe_header.cc.html @@ -164,12 +164,12 @@ if (curr.name == < recipe add2 x:number, y:number -> z:number [ local-scope load-ingredients - z:address:number <- copy 0/raw + z:address:number <- copy 0/unsafe reply z ] +error: add2: replied with the wrong type at 'reply z' -:(after "Transform.push_back(check_types_by_name)") +:(before "End Checks") Transform.push_back(check_reply_instructions_against_header); // idempotent :(code) @@ -181,9 +181,7 @@ void check_reply_instructions_against_header(cons for (long long int i = 0; i < SIZE(caller_recipe.steps); ++i) { const instruction& inst = caller_recipe.steps.at(i); if (inst.name != "reply") continue; - if (SIZE(caller_recipe.products) != SIZE(inst.ingredients)) - raise_error << maybe(caller_recipe.name) << "tried to reply the wrong number of products in '" << inst.to_string() << "'\n" << end(); - for (long long int i = 0; i < SIZE(caller_recipe.products); ++i) { + for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { if (!types_match(caller_recipe.products.at(i), inst.ingredients.at(i))) raise_error << maybe(caller_recipe.name) << "replied with the wrong type at '" << inst.to_string() << "'\n" << end(); } @@ -202,11 +200,11 @@ recipe add2 x:number, x:number :(before "End recipe Fields") map<string, int> ingredient_index; -:(after "Transform.push_back(insert_fragments)") -Transform.push_back(check_and_update_header_reagents); // idempotent +:(after "Begin Instruction Modifying Transforms") +Transform.push_back(check_header_ingredients); // idempotent :(code) -void check_and_update_header_reagents(const recipe_ordinal r) { +void check_header_ingredients(const recipe_ordinal r) { recipe& caller_recipe = get(Recipe, r); if (caller_recipe.products.empty()) return; trace(9991, "transform") << "--- checking reply instructions against header for " << caller_recipe.name << end(); @@ -232,7 +230,7 @@ recipe add2 x:number, y:number +mem: storing 8 in location 1 -:(before "Transform.push_back(check_reply_instructions_against_header)") +:(after "Begin Type Modifying Transforms") Transform.push_back(deduce_types_from_header); // idempotent :(code) @@ -299,7 +297,7 @@ recipe add2 x:number, y:number +mem: storing 8 in location 1 -:(after "Transform.push_back(check_and_update_header_reagents)") +:(after "Transform.push_back(check_header_ingredients)") Transform.push_back(fill_in_reply_ingredients); // idempotent :(code) diff --git a/html/057static_dispatch.cc.html b/html/057static_dispatch.cc.html index 243b9c28..1c7b28b7 100644 --- a/html/057static_dispatch.cc.html +++ b/html/057static_dispatch.cc.html @@ -14,11 +14,13 @@ pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background- body { font-family: monospace; color: #eeeeee; background-color: #080808; } * { font-size: 1.05em; } .traceContains { color: #008000; } -.Identifier { color: #804000; } +.traceAbsent { color: #c00000; } +.cSpecial { color: #008000; } +.CommentedCode { color: #6c6c6c; } .Comment { color: #9090ff; } .Delimiter { color: #a04060; } .Special { color: #ff6060; } -.CommentedCode { color: #6c6c6c; } +.Identifier { color: #804000; } .Constant { color: #00a0a0; } --> @@ -65,12 +67,11 @@ for (map<string,:(before "End Load Recipe Header(result)") if (contains_key(Recipe_ordinal, result.name)) { const recipe_ordinal r = get(Recipe_ordinal, result.name); -//? if (variant_already_exists(result)) cerr << "AAAAAAAAAAAAAAAAAA variant already exists " << result.name << '\n'; if ((!contains_key(Recipe, r) || get(Recipe, r).has_header) && !variant_already_exists(result)) { string new_name = next_unused_recipe_name(result.name); put(Recipe_ordinal, new_name, Next_recipe_ordinal++); - get(Recipe_variants, result.name).push_back(get(Recipe_ordinal, new_name)); + get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, new_name)); result.name = new_name; } } @@ -82,7 +83,7 @@ else { :(code) bool variant_already_exists(const recipe& rr) { - const vector<recipe_ordinal>& variants = get(Recipe_variants, rr.name); + const vector<recipe_ordinal>& variants = get_or_insert(Recipe_variants, rr.name); for (long long int i = 0; i < SIZE(variants); ++i) { if (contains_key(Recipe, variants.at(i)) && all_reagents_match(rr, get(Recipe, variants.at(i)))) { @@ -110,11 +111,24 @@ bool all_reagents_match(const recipe& r1return true; } -bool exact_match(type_tree* a, type_tree* b) { - if (a == b) return true; +:(before "End Globals") +set<string> Literal_type_names; +:(before "End One-time Setup") +Literal_type_names.insert("number"); +Literal_type_names.insert("character"); +:(code) +bool deeply_equal_types(const string_tree* a, const string_tree* b) { + if (!a) return !b; + if (!b) return !a; + if (a->value == "literal" && b->value == "literal") + return true; + if (a->value == "literal") + return Literal_type_names.find(b->value) != Literal_type_names.end(); + if (b->value == "literal") + return Literal_type_names.find(a->value) != Literal_type_names.end(); return a->value == b->value - && exact_match(a->left, b->left) - && exact_match(a->right, b->right); + && deeply_equal_types(a->left, b->left) + && deeply_equal_types(a->right, b->right); } string next_unused_recipe_name(const string& recipe_name) { @@ -141,32 +155,33 @@ recipe test a:number, b:number +mem: storing 2 in location 7 -//: after insert_fragments (tangle) and before computing operation ids //: 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(deduce_types_from_header)") +:(after "End Type Modifying Transforms") Transform.push_back(resolve_ambiguous_calls); // idempotent :(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(); +//? cerr << "--- resolve ambiguous calls for recipe " << caller_recipe.name << '\n'; for (long long int index = 0; index < SIZE(caller_recipe.steps); ++index) { instruction& inst = caller_recipe.steps.at(index); if (inst.is_label) continue; - if (!contains_key(Recipe_variants, inst.name)) continue; - assert(!get(Recipe_variants, inst.name).empty()); + if (get_or_insert(Recipe_variants, inst.name).empty()) continue; replace_best_variant(inst, caller_recipe); } -//? if (caller_recipe.name == "main") cerr << "=============== " << debug_string(caller_recipe) << '\n'; } 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; @@ -176,32 +191,74 @@ void replace_best_variant(instruction& inst} long long int variant_score(const instruction& inst, recipe_ordinal variant) { + 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)) { + assert(variant < MAX_PRIMITIVE_RECIPES); + return -1; + } const vector<reagent>& header_ingredients = get(Recipe, variant).ingredients; if (SIZE(inst.ingredients) < SIZE(header_ingredients)) { trace(9993, "transform") << "too few ingredients" << end(); +//? cerr << "too few ingredients\n"; return -1; } +//? cerr << "=== checking ingredients\n"; for (long long int i = 0; i < SIZE(header_ingredients); ++i) { if (!types_match(header_ingredients.at(i), inst.ingredients.at(i))) { trace(9993, "transform") << "mismatch: ingredient " << i << end(); +//? cerr << "mismatch: ingredient " << i << '\n'; return -1; } + if (types_strictly_match(header_ingredients.at(i), inst.ingredients.at(i))) { + 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 { + // 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-=10; + } } +//? cerr << "=== done checking ingredients\n"; if (SIZE(inst.products) > SIZE(get(Recipe, variant).products)) { trace(9993, "transform") << "too few products" << end(); +//? cerr << "too few products\n"; return -1; } const vector<reagent>& header_products = get(Recipe, variant).products; for (long long int i = 0; i < SIZE(inst.products); ++i) { if (!types_match(header_products.at(i), inst.products.at(i))) { trace(9993, "transform") << "mismatch: product " << i << end(); +//? cerr << "mismatch: product " << i << '\n'; return -1; } + if (types_strictly_match(header_products.at(i), inst.products.at(i))) { + 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 { + // 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-=10; + } } - // the greater the number of unused ingredients, the lower the score - return 100 - (SIZE(get(Recipe, variant).products)-SIZE(inst.products)) - - (SIZE(inst.ingredients)-SIZE(get(Recipe, variant).ingredients)); // ok to go negative + // the greater the number of unused ingredients/products, the lower the score + return result - (SIZE(get(Recipe, variant).products)-SIZE(inst.products)) + - (SIZE(inst.ingredients)-SIZE(get(Recipe, variant).ingredients)); // ok to go negative } :(scenario static_dispatch_disabled_on_headerless_definition) @@ -223,6 +280,154 @@ recipe test a:number -> z:number [ z <- copy 1 ] +warn: redefining recipe test + +:(scenario static_dispatch_on_primitive_names) +recipe main [ + 1:number <- copy 34 + 2:number <- copy 34 + 3:boolean <- equal 1:number, 2:number + 4:boolean <- copy 0/false + 5:boolean <- copy 0/false + 6:boolean <- equal 4:boolean, 5:boolean +] + +# temporarily hardcode number equality to always fail +recipe equal x:number, y:number -> z:boolean [ + local-scope + load-ingredients + z <- copy 0/false +] +# comparing numbers used overload ++mem: storing 0 in location 3 +# comparing booleans continues to use primitive ++mem: storing 1 in location 6 + +:(scenario static_dispatch_prefers_literals_to_be_numbers_rather_than_addresses) +recipe main [ + 1:number <- foo 0 +] +recipe foo x:address:number -> y:number [ + reply 34 +] +recipe foo x:number -> y:number [ + reply 35 +] ++mem: storing 35 in location 1 + +:(scenario static_dispatch_on_non_literal_character_ignores_variant_with_numbers) +% Hide_errors = true; +recipe main [ + local-scope + x:character <- copy 10/newline + 1:number/raw <- foo x +] +recipe foo x:number -> y:number [ + load-ingredients + reply 34 +] ++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 + +//: after we make all attempts to dispatch, any unhandled cases will end up at +//: some wrong variant and trigger an error while trying to load-ingredients + +:(scenario static_dispatch_shows_clear_error_on_missing_variant) +% Hide_errors = true; +recipe main [ + 1:number <- foo 34 +] +recipe foo x:boolean -> y:number [ + local-scope + load-ingredients + reply 35 +] ++error: foo: wrong type for ingredient x:boolean ++error: (we're inside recipe foo x:boolean -> y:number) ++error: (we're trying to call '1:number <- foo 34' inside recipe main) + +:(before "End next-ingredient Type Mismatch Error") +raise_error << " (we're inside " << header_label(current_call().running_recipe) << ")\n" << end(); +raise_error << " (we're trying to call '" << to_instruction(*++Current_routine->calls.begin()).to_string() << "' inside " << header_label((++Current_routine->calls.begin())->running_recipe) << ")\n" << end(); + +:(scenario static_dispatch_shows_clear_error_on_missing_variant_2) +% Hide_errors = true; +recipe main [ + 1:boolean <- foo 34 +] +recipe foo x:number -> y:number [ + local-scope + load-ingredients + reply x +] ++error: foo: reply ingredient x can't be saved in 1:boolean ++error: (we just returned from recipe foo x:number -> y:number) + +:(before "End reply Type Mismatch Error") +raise_error << " (we just returned from " << header_label(caller_instruction.operation) << ")\n" << end(); + +:(code) +string header_label(recipe_ordinal r) { + const recipe& caller = get(Recipe, r); + ostringstream out; + out << "recipe " << caller.name; + for (long long int i = 0; i < SIZE(caller.ingredients); ++i) + out << ' ' << caller.ingredients.at(i).original_string; + if (!caller.products.empty()) out << " ->"; + for (long long int i = 0; i < SIZE(caller.products); ++i) + out << ' ' << caller.products.at(i).original_string; + return out.str(); +} diff --git a/html/058shape_shifting_container.cc.html b/html/058shape_shifting_container.cc.html index 0402d54b..6229c527 100644 --- a/html/058shape_shifting_container.cc.html +++ b/html/058shape_shifting_container.cc.html @@ -21,7 +21,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } .Delimiter { color: #a04060; } .SalientComment { color: #00ffff; } .Identifier { color: #804000; } -.CommentedCode { color: #6c6c6c; } --> @@ -84,7 +83,7 @@ void read_type_ingredients(string& name(Type_ordinal, name, Next_type_ordinal++); type_info& info = get_or_insert(Type, get(Type_ordinal, name)); long long int next_type_ordinal = START_TYPE_INGREDIENTS; - while (!in.eof()) { + while (has_data(in)) { string curr = slurp_until(in, ':'); if (info.type_ingredient_names.find(curr) != info.type_ingredient_names.end()) { raise_error << "can't repeat type ingredient names in a single container definition\n" << end(); @@ -130,13 +129,7 @@ long long int size_of_type_ingredient(const type_ } assert(curr); assert(!curr->left); // unimplemented - if (!contains_key(Type, curr->value)) { - // temporarily while we're still ironing out kinks; eventually replace with a raise_error -//? DUMP(""); - cerr << "missing type " << debug_string(curr) << '\n'; - exit(0); - } - assert(contains_key(Type, curr->value)); + if (!contains_key(Type, curr->value)) return 0; trace(9999, "type") << "type deduced to be " << get(Type, curr->value).name << "$" << end(); type_tree tmp(curr->value); if (curr->right) diff --git a/html/059shape_shifting_recipe.cc.html b/html/059shape_shifting_recipe.cc.html index 13c436c4..ce450999 100644 --- a/html/059shape_shifting_recipe.cc.html +++ b/html/059shape_shifting_recipe.cc.html @@ -72,34 +72,58 @@ if (Current_routine->< //: Make sure we don't match up literals with type ingredients without //: specialization. -:(before "End valid_type_for_literal Special-cases") +:(before "End Matching Types For Literal(lhs)") if (contains_type_ingredient_name(lhs)) return false; //: We'll be creating recipes without loading them from anywhere by -//: *specializing* existing recipes, so make sure we don't clear any of those -//: when we start running tests. +//: *specializing* existing recipes. +//: +//: Keep track of these new recipes in a separate variable in addition to +//: recently_added_recipes, so that edit/ can clear them before reloading to +//: regenerate errors. +:(before "End Globals") +vector<recipe_ordinal> recently_added_shape_shifting_recipes; +:(before "End Setup") +//? cerr << "setup: clearing recently-added shape-shifting recipes\n"; +recently_added_shape_shifting_recipes.clear(); + +//: make sure we don't clear any of these recipes when we start running tests :(before "End Loading .mu Files") recently_added_recipes.clear(); recently_added_types.clear(); +//? cerr << "clearing recently-added shape-shifting recipes\n"; +recently_added_shape_shifting_recipes.clear(); + +//: save original name of specialized recipes +:(before "End recipe Fields") +string original_name; +//: original name is only set during load +:(before "End recipe Refinements") +result.original_name = result.name; :(before "End Instruction Dispatch(inst, best_score)") if (best_score == -1) { -//? if (inst.name == "push-duplex") Trace_stream = new trace_stream; trace(9992, "transform") << "no variant found; searching for variant with suitable type ingredients" << end(); +//? cerr << "no variant found for " << inst.name << "; searching for variant with suitable type ingredients" << '\n'; recipe_ordinal exemplar = pick_matching_shape_shifting_variant(variants, inst, best_score); if (exemplar) { trace(9992, "transform") << "found variant to specialize: " << exemplar << ' ' << get(Recipe, exemplar).name << end(); - variants.push_back(new_variant(exemplar, inst, caller_recipe)); +//? cerr << "found variant to specialize: " << exemplar << ' ' << get(Recipe, exemplar).name << '\n'; + recipe_ordinal new_recipe_ordinal = new_variant(exemplar, inst, caller_recipe); + variants.push_back(new_recipe_ordinal); + // perform all transforms on the new specialization + const string& new_name = get(Recipe, variants.back()).name; + trace(9992, "transform") << "transforming new specialization: " << new_name << end(); +//? cerr << "transforming new specialization: " << new_name << '\n'; + for (long long int t = 0; t < SIZE(Transform); ++t) { + (*Transform.at(t))(new_recipe_ordinal); + } + get(Recipe, new_recipe_ordinal).transformed_until = SIZE(Transform)-1; //? cerr << "-- replacing " << inst.name << " with " << get(Recipe, variants.back()).name << '\n' << debug_string(get(Recipe, variants.back())); inst.name = get(Recipe, variants.back()).name; trace(9992, "transform") << "new specialization: " << inst.name << end(); +//? cerr << "new specialization: " << inst.name << '\n'; } -//? if (inst.name == "push-duplex") { -//? cerr << "======== {\n"; -//? cerr << inst.to_string() << '\n'; -//? DUMP(""); -//? cerr << "======== }\n"; -//? } } :(code) @@ -126,64 +150,96 @@ long long int shape_shifting_variant_score(const //? cerr << "======== " << inst.to_string() << '\n'; if (!any_type_ingredient_in_header(variant)) { trace(9993, "transform") << "no type ingredients" << end(); +//? cerr << "no type ingredients\n"; return -1; } const vector<reagent>& header_ingredients = get(Recipe, variant).ingredients; if (SIZE(inst.ingredients) < SIZE(header_ingredients)) { trace(9993, "transform") << "too few ingredients" << end(); +//? cerr << "too few ingredients\n"; return -1; } for (long long int i = 0; i < SIZE(header_ingredients); ++i) { if (!deeply_equal_concrete_types(header_ingredients.at(i), inst.ingredients.at(i))) { trace(9993, "transform") << "mismatch: ingredient " << i << end(); +//? cerr << "mismatch: ingredient " << i << ": " << debug_string(header_ingredients.at(i)) << " vs " << debug_string(inst.ingredients.at(i)) << '\n'; return -1; } } if (SIZE(inst.products) > SIZE(get(Recipe, variant).products)) { trace(9993, "transform") << "too few products" << end(); +//? cerr << "too few products\n"; return -1; } const vector<reagent>& header_products = get(Recipe, variant).products; for (long long int i = 0; i < SIZE(inst.products); ++i) { if (!deeply_equal_concrete_types(header_products.at(i), inst.products.at(i))) { trace(9993, "transform") << "mismatch: product " << i << end(); +//? cerr << "mismatch: product " << i << '\n'; return -1; } } // the greater the number of unused ingredients, the lower the score return 100 - (SIZE(get(Recipe, variant).products)-SIZE(inst.products)) - - (SIZE(inst.ingredients)-SIZE(get(Recipe, variant).ingredients)); // ok to go negative + - (SIZE(inst.ingredients)-SIZE(get(Recipe, variant).ingredients)) // ok to go negative + + number_of_concrete_types(variant); } bool any_type_ingredient_in_header(recipe_ordinal variant) { - for (long long int i = 0; i < SIZE(get(Recipe, variant).ingredients); ++i) { - if (contains_type_ingredient_name(get(Recipe, variant).ingredients.at(i))) + const recipe& caller = get(Recipe, variant); + for (long long int i = 0; i < SIZE(caller.ingredients); ++i) { + if (contains_type_ingredient_name(caller.ingredients.at(i))) return true; } - for (long long int i = 0; i < SIZE(get(Recipe, variant).products); ++i) { - if (contains_type_ingredient_name(get(Recipe, variant).products.at(i))) + for (long long int i = 0; i < SIZE(caller.products); ++i) { + if (contains_type_ingredient_name(caller.products.at(i))) return true; } return false; } bool deeply_equal_concrete_types(reagent lhs, reagent rhs) { -//? cerr << debug_string(lhs) << " vs " << debug_string(rhs) << '\n'; -//? bool result = deeply_equal_concrete_types(lhs.properties.at(0).second, rhs.properties.at(0).second, rhs); -//? cerr << " => " << result << '\n'; -//? return result; -//? cerr << "== " << debug_string(lhs) << " vs " << debug_string(rhs) << '\n'; canonize_type(lhs); canonize_type(rhs); return deeply_equal_concrete_types(lhs.properties.at(0).second, rhs.properties.at(0).second, rhs); } +long long int number_of_concrete_types(recipe_ordinal r) { + const recipe& caller = get(Recipe, r); + long long int result = 0; + for (long long int i = 0; i < SIZE(caller.ingredients); ++i) + result += number_of_concrete_types(caller.ingredients.at(i)); + for (long long int i = 0; i < SIZE(caller.products); ++i) + result += number_of_concrete_types(caller.products.at(i)); + return result; +} + +long long int number_of_concrete_types(const reagent& r) { + return number_of_concrete_types(r.properties.at(0).second); +} + +long long int number_of_concrete_types(const string_tree* type) { + if (!type) return 0; + long long int result = 0; + if (!type->value.empty() && !is_type_ingredient_name(type->value)) + result++; + result += number_of_concrete_types(type->left); + result += number_of_concrete_types(type->right); + return result; +} + bool deeply_equal_concrete_types(const string_tree* lhs, const string_tree* rhs, const reagent& rhs_reagent) { if (!lhs) return !rhs; if (!rhs) return !lhs; if (is_type_ingredient_name(lhs->value)) return true; // type ingredient matches anything - if (Literal_type_names.find(lhs->value) != Literal_type_names.end()) - return Literal_type_names.find(rhs->value) != Literal_type_names.end(); + if (lhs->value == "literal" && rhs->value == "literal") + return true; + if (lhs->value == "literal" + && Literal_type_names.find(rhs->value) != Literal_type_names.end()) + return true; + if (rhs->value == "literal" + && Literal_type_names.find(lhs->value) != Literal_type_names.end()) + return true; if (rhs->value == "literal" && lhs->value == "address") return rhs_reagent.name == "0"; //? cerr << lhs->value << " vs " << rhs->value << '\n'; @@ -215,6 +271,7 @@ recipe_ordinal new_variant(recipe_ordinal exempla assert(contains_key(Recipe, exemplar)); assert(!contains_key(Recipe, new_recipe_ordinal)); recently_added_recipes.push_back(new_recipe_ordinal); + recently_added_shape_shifting_recipes.push_back(new_recipe_ordinal); put(Recipe, new_recipe_ordinal, get(Recipe, exemplar)); recipe& new_recipe = get(Recipe, new_recipe_ordinal); new_recipe.name = new_name; @@ -232,11 +289,6 @@ recipe_ordinal new_variant(recipe_ordinal exempla if (error) return exemplar; } ensure_all_concrete_types(new_recipe, get(Recipe, exemplar)); - // finally, perform all transforms on the new specialization - for (long long int t = 0; t < SIZE(Transform); ++t) { - (*Transform.at(t))(new_recipe_ordinal); - } - new_recipe.transformed_until = SIZE(Transform)-1; return new_recipe_ordinal; } @@ -244,20 +296,20 @@ void compute_type_names(recipe& variant(9993, "transform") << "compute type names: " << variant.name << end(); map<string, string_tree*> type_names; for (long long int i = 0; i < SIZE(variant.ingredients); ++i) - save_or_deduce_type_name(variant.ingredients.at(i), type_names); + save_or_deduce_type_name(variant.ingredients.at(i), type_names, variant); for (long long int i = 0; i < SIZE(variant.products); ++i) - save_or_deduce_type_name(variant.products.at(i), type_names); + save_or_deduce_type_name(variant.products.at(i), type_names, variant); for (long long int i = 0; i < SIZE(variant.steps); ++i) { instruction& inst = variant.steps.at(i); trace(9993, "transform") << " instruction: " << inst.to_string() << end(); for (long long int in = 0; in < SIZE(inst.ingredients); ++in) - save_or_deduce_type_name(inst.ingredients.at(in), type_names); + save_or_deduce_type_name(inst.ingredients.at(in), type_names, variant); for (long long int out = 0; out < SIZE(inst.products); ++out) - save_or_deduce_type_name(inst.products.at(out), type_names); + save_or_deduce_type_name(inst.products.at(out), type_names, variant); } } -void save_or_deduce_type_name(reagent& x, map<string, string_tree*>& type_name) { +void save_or_deduce_type_name(reagent& x, map<string, string_tree*>& type_name, const recipe& variant) { trace(9994, "transform") << " checking " << x.to_string() << ": " << debug_string(x.properties.at(0).second) << end(); if (!x.properties.at(0).second && contains_key(type_name, x.name)) { x.properties.at(0).second = new string_tree(*get(type_name, x.name)); @@ -265,7 +317,7 @@ void save_or_deduce_type_name(reagent& xreturn; } if (!x.properties.at(0).second) { - raise << "unknown type for " << x.original_string << '\n' << end(); + raise_error << maybe(variant.original_name) << "unknown type for " << x.original_string << " (check the name for typos)\n" << end(); return; } if (contains_key(type_name, x.name)) return; @@ -306,6 +358,7 @@ void accumulate_type_ingredients(const reagent&am void accumulate_type_ingredients(const string_tree* exemplar_type, const string_tree* refinement_type, map<string, const string_tree*>& mappings, const recipe& exemplar, const reagent& exemplar_reagent, const instruction& call_instruction, const recipe& caller_recipe, bool* error) { if (!exemplar_type) return; if (!refinement_type) { + // todo: make this smarter; only warn if exemplar_type contains some *new* type ingredient raise_error << maybe(exemplar.name) << "missing type ingredient in " << exemplar_reagent.original_string << '\n' << end(); return; } @@ -317,10 +370,7 @@ void accumulate_type_ingredients(const string_tre } if (!contains_key(mappings, exemplar_type->value)) { trace(9993, "transform") << "adding mapping from " << exemplar_type->value << " to " << debug_string(refinement_type) << end(); - if (refinement_type->value == "literal") - put(mappings, exemplar_type->value, new string_tree("number")); - else - put(mappings, exemplar_type->value, new string_tree(*refinement_type)); + put(mappings, exemplar_type->value, new string_tree(*refinement_type)); } else { if (!deeply_equal_types(get(mappings, exemplar_type->value), refinement_type)) { @@ -329,6 +379,11 @@ void accumulate_type_ingredients(const string_tre *error = true; return; } +//? cerr << exemplar_type->value << ": " << debug_string(get(mappings, exemplar_type->value)) << " <= " << debug_string(refinement_type) << '\n'; + if (get(mappings, exemplar_type->value)->value == "literal") { + delete get(mappings, exemplar_type->value); + put(mappings, exemplar_type->value, new string_tree(*refinement_type)); + } } } else { @@ -342,20 +397,20 @@ void replace_type_ingredients(recipe& new_rec if (mappings.empty()) return; trace(9993, "transform") << "replacing in recipe header ingredients" << end(); for (long long int i = 0; i < SIZE(new_recipe.ingredients); ++i) - replace_type_ingredients(new_recipe.ingredients.at(i), mappings); + replace_type_ingredients(new_recipe.ingredients.at(i), mappings, new_recipe); trace(9993, "transform") << "replacing in recipe header products" << end(); for (long long int i = 0; i < SIZE(new_recipe.products); ++i) - replace_type_ingredients(new_recipe.products.at(i), mappings); + replace_type_ingredients(new_recipe.products.at(i), mappings, new_recipe); // update its body for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) { instruction& inst = new_recipe.steps.at(i); trace(9993, "transform") << "replacing in instruction '" << inst.to_string() << "'" << end(); for (long long int j = 0; j < SIZE(inst.ingredients); ++j) - replace_type_ingredients(inst.ingredients.at(j), mappings); + replace_type_ingredients(inst.ingredients.at(j), mappings, new_recipe); for (long long int j = 0; j < SIZE(inst.products); ++j) - replace_type_ingredients(inst.products.at(j), mappings); + replace_type_ingredients(inst.products.at(j), mappings, new_recipe); // special-case for new: replace type ingredient in first ingredient *value* - if (inst.name == "new" && inst.ingredients.at(0).name.at(0) != '[') { + if (inst.name == "new" && inst.ingredients.at(0).properties.at(0).second->value != "literal-string") { string_tree* type_name = parse_string_tree(inst.ingredients.at(0).name); replace_type_ingredients(type_name, mappings); inst.ingredients.at(0).name = type_name->to_string(); @@ -364,10 +419,13 @@ void replace_type_ingredients(recipe& new_rec } } -void replace_type_ingredients(reagent& x, const map<string, const string_tree*>& mappings) { +void replace_type_ingredients(reagent& x, const map<string, const string_tree*>& mappings, const recipe& caller) { trace(9993, "transform") << "replacing in ingredient " << x.original_string << end(); // replace properties - assert(x.properties.at(0).second); + if (!x.properties.at(0).second) { + raise_error << "specializing " << caller.original_name << ": missing type for " << x.original_string << '\n' << end(); + return; + } replace_type_ingredients(x.properties.at(0).second, mappings); // refresh types from properties delete x.type; @@ -381,7 +439,10 @@ void replace_type_ingredients(string_tree* type(is_type_ingredient_name(type->value) && contains_key(mappings, type->value)) { const string_tree* replacement = get(mappings, type->value); trace(9993, "transform") << type->value << " => " << debug_string(replacement) << end(); - type->value = replacement->value; + if (replacement->value == "literal") + type->value = "number"; + else + type->value = replacement->value; if (replacement->left) type->left = new string_tree(*replacement->left); if (replacement->right) type->right = new string_tree(*replacement->right); } @@ -684,6 +745,80 @@ container d2:_elem [ ] +mem: storing 34 in location 1 +mem: storing 35 in location 2 + +:(scenario missing_type_in_shape_shifting_recipe) +% Hide_errors = true; +recipe main [ + a:d1:number <- merge 3 + foo a +] +recipe foo a:d1:_elem -> b:number [ + local-scope + load-ingredients + copy e # no such variable + reply 34 +] +container d1:_elem [ + x:_elem +] ++error: foo: unknown type for e (check the name for typos) ++error: specializing foo: missing type for e +# and it doesn't crash + +:(scenario missing_type_in_shape_shifting_recipe_2) +% Hide_errors = true; +recipe main [ + a:d1:number <- merge 3 + foo a +] +recipe foo a:d1:_elem -> b:number [ + local-scope + load-ingredients + get e, x:offset # unknown variable in a 'get', which does some extra checking + reply 34 +] +container d1:_elem [ + x:_elem +] ++error: foo: unknown type for e (check the name for typos) ++error: specializing foo: missing type for e +# and it doesn't crash + +:(scenarios transform) +:(scenario specialize_recursive_shape_shifting_recipe) +recipe main [ + 1:number <- copy 34 + 2:number <- foo 1:number +] +recipe foo x:_elem -> y:number [ + local-scope + load-ingredients + { + break + y:number <- foo x + } + reply y +] ++transform: new specialization: foo_2 +# transform terminates + +:(scenarios run) +:(scenario specialize_most_similar_variant) +recipe main [ + 1:address:number <- new number:type + 2:number <- foo 1:address:number +] +recipe foo x:_elem -> y:number [ + local-scope + load-ingredients + reply 34 +] +recipe foo x:address:_elem -> y:number [ + local-scope + load-ingredients + reply 35 +] ++mem: storing 35 in location 2 diff --git a/html/070string.mu.html b/html/070string.mu.html deleted file mode 100644 index 08edff08..00000000 --- a/html/070string.mu.html +++ /dev/null @@ -1,1356 +0,0 @@ - - - - -Mu - 070string.mu - - - - - - - - - - -
-# Some useful helpers for dealing with strings.
-
-recipe string-equal [
-  local-scope
-  a:address:array:character <- next-ingredient
-  a-len:number <- length *a
-  b:address:array:character <- next-ingredient
-  b-len:number <- length *b
-  # compare lengths
-  {
-    trace 99, [string-equal], [comparing lengths]
-    length-equal?:boolean <- equal a-len, b-len
-    break-if length-equal?
-    reply 0
-  }
-  # compare each corresponding character
-  trace 99, [string-equal], [comparing characters]
-  i:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal i, a-len
-    break-if done?
-    a2:character <- index *a, i
-    b2:character <- index *b, i
-    {
-      chars-match?:boolean <- equal a2, b2
-      break-if chars-match?
-      reply 0
-    }
-    i <- add i, 1
-    loop
-  }
-  reply 1
-]
-
-scenario string-equal-reflexive [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    3:boolean/raw <- string-equal x, x
-  ]
-  memory-should-contain [
-    3 <- 1  # x == x for all x
-  ]
-]
-
-scenario string-equal-identical [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    y:address:array:character <- new [abc]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 1  # abc == abc
-  ]
-]
-
-scenario string-equal-distinct-lengths [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    y:address:array:character <- new [abcd]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 0  # abc != abcd
-  ]
-  trace-should-contain [
-    string-equal: comparing lengths
-  ]
-  trace-should-not-contain [
-    string-equal: comparing characters
-  ]
-]
-
-scenario string-equal-with-empty [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new []
-    y:address:array:character <- new [abcd]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 0  # "" != abcd
-  ]
-]
-
-scenario string-equal-common-lengths-but-distinct [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    y:address:array:character <- new [abd]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 0  # abc != abd
-  ]
-]
-
-# A new type to help incrementally construct strings.
-container buffer [
-  length:number
-  data:address:array:character
-]
-
-recipe new-buffer [
-  local-scope
-  result:address:buffer <- new buffer:type
-  len:address:number <- get-address *result, length:offset
-  *len:address:number <- copy 0
-  s:address:address:array:character <- get-address *result, data:offset
-  capacity:number, found?:boolean <- next-ingredient
-  assert found?, [new-buffer must get a capacity argument]
-  *s <- new character:type, capacity
-  reply result
-]
-
-recipe grow-buffer [
-  local-scope
-  in:address:buffer <- next-ingredient
-  # double buffer size
-  x:address:address:array:character <- get-address *in, data:offset
-  oldlen:number <- length **x
-  newlen:number <- multiply oldlen, 2
-  olddata:address:array:character <- copy *x
-  *x <- new character:type, newlen
-  # copy old contents
-  i:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal i, oldlen
-    break-if done?
-    src:character <- index *olddata, i
-    dest:address:character <- index-address **x, i
-    *dest <- copy src
-    i <- add i, 1
-    loop
-  }
-  reply in
-]
-
-recipe buffer-full? [
-  local-scope
-  in:address:buffer <- next-ingredient
-  len:number <- get *in, length:offset
-  s:address:array:character <- get *in, data:offset
-  capacity:number <- length *s
-  result:boolean <- greater-or-equal len, capacity
-  reply result
-]
-
-# in <- buffer-append in:address:buffer, c:character
-recipe buffer-append [
-  local-scope
-  in:address:buffer <- next-ingredient
-  c:character <- next-ingredient
-  len:address:number <- get-address *in, length:offset
-  {
-    # backspace? just drop last character if it exists and return
-    backspace?:boolean <- equal c, 8/backspace
-    break-unless backspace?
-    empty?:boolean <- lesser-or-equal *len, 0
-    reply-if empty?, in/same-as-ingredient:0
-    *len <- subtract *len, 1
-    reply in/same-as-ingredient:0
-  }
-  {
-    # grow buffer if necessary
-    full?:boolean <- buffer-full? in
-    break-unless full?
-    in <- grow-buffer in
-  }
-  s:address:array:character <- get *in, data:offset
-  dest:address:character <- index-address *s, *len
-  *dest <- copy c
-  *len <- add *len, 1
-  reply in/same-as-ingredient:0
-]
-
-scenario buffer-append-works [
-  run [
-    local-scope
-    x:address:buffer <- new-buffer 3
-    s1:address:array:character <- get *x:address:buffer, data:offset
-    x:address:buffer <- buffer-append x:address:buffer, 97  # 'a'
-    x:address:buffer <- buffer-append x:address:buffer, 98  # 'b'
-    x:address:buffer <- buffer-append x:address:buffer, 99  # 'c'
-    s2:address:array:character <- get *x:address:buffer, data:offset
-    1:boolean/raw <- equal s1:address:array:character, s2:address:array:character
-    2:array:character/raw <- copy *s2:address:array:character
-    +buffer-filled
-    x:address:buffer <- buffer-append x:address:buffer, 100  # 'd'
-    s3:address:array:character <- get *x:address:buffer, data:offset
-    10:boolean/raw <- equal s1:address:array:character, s3:address:array:character
-    11:number/raw <- get *x:address:buffer, length:offset
-    12:array:character/raw <- copy *s3:address:array:character
-  ]
-  memory-should-contain [
-    # before +buffer-filled
-    1 <- 1   # no change in data pointer
-    2 <- 3   # size of data
-    3 <- 97  # data
-    4 <- 98
-    5 <- 99
-    # in the end
-    10 <- 0   # data pointer has grown
-    11 <- 4   # final length
-    12 <- 6   # but data's capacity has doubled
-    13 <- 97  # data
-    14 <- 98
-    15 <- 99
-    16 <- 100
-    17 <- 0
-    18 <- 0
-  ]
-]
-
-scenario buffer-append-handles-backspace [
-  run [
-    local-scope
-    x:address:buffer <- new-buffer 3
-    x <- buffer-append x, 97  # 'a'
-    x <- buffer-append x, 98  # 'b'
-    x <- buffer-append x, 8/backspace
-    s:address:array:character <- buffer-to-array x
-    1:array:character/raw <- copy *s
-  ]
-  memory-should-contain [
-    1 <- 1   # length
-    2 <- 97  # contents
-    3 <- 0
-  ]
-]
-
-# result:address:array:character <- integer-to-decimal-string n:number
-recipe integer-to-decimal-string [
-  local-scope
-  n:number <- next-ingredient
-  # is it zero?
-  {
-    break-if n
-    result:address:array:character <- new [0]
-    reply result
-  }
-  # save sign
-  negate-result:boolean <- copy 0
-  {
-    negative?:boolean <- lesser-than n, 0
-    break-unless negative?
-    negate-result <- copy 1
-    n <- multiply n, -1
-  }
-  # add digits from right to left into intermediate buffer
-  tmp:address:buffer <- new-buffer 30
-  digit-base:number <- copy 48  # '0'
-  {
-    done?:boolean <- equal n, 0
-    break-if done?
-    n, digit:number <- divide-with-remainder n, 10
-    c:character <- add digit-base, digit
-    tmp:address:buffer <- buffer-append tmp, c
-    loop
-  }
-  # add sign
-  {
-    break-unless negate-result:boolean
-    tmp <- buffer-append tmp, 45  # '-'
-  }
-  # reverse buffer into string result
-  len:number <- get *tmp, length:offset
-  buf:address:array:character <- get *tmp, data:offset
-  result:address:array:character <- new character:type, len
-  i:number <- subtract len, 1  # source index, decreasing
-  j:number <- copy 0  # destination index, increasing
-  {
-    # while i >= 0
-    done?:boolean <- lesser-than i, 0
-    break-if done?
-    # result[j] = tmp[i]
-    src:character <- index *buf, i
-    dest:address:character <- index-address *result, j
-    *dest <- copy src
-    i <- subtract i, 1
-    j <- add j, 1
-    loop
-  }
-  reply result
-]
-
-recipe buffer-to-array [
-  local-scope
-  in:address:buffer <- next-ingredient
-  {
-    # propagate null buffer
-    break-if in
-    reply 0
-  }
-  len:number <- get *in, length:offset
-  s:address:array:character <- get *in, data:offset
-  # we can't just return s because it is usually the wrong length
-  result:address:array:character <- new character:type, len
-  i:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal i, len
-    break-if done?
-    src:character <- index *s, i
-    dest:address:character <- index-address *result, i
-    *dest <- copy src
-    i <- add i, 1
-    loop
-  }
-  reply result
-]
-
-scenario integer-to-decimal-digit-zero [
-  run [
-    1:address:array:character/raw <- integer-to-decimal-string 0
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [0]
-  ]
-]
-
-scenario integer-to-decimal-digit-positive [
-  run [
-    1:address:array:character/raw <- integer-to-decimal-string 234
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [234]
-  ]
-]
-
-scenario integer-to-decimal-digit-negative [
-  run [
-    1:address:array:character/raw <- integer-to-decimal-string -1
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2 <- 2
-    3 <- 45  # '-'
-    4 <- 49  # '1'
-  ]
-]
-
-# result:address:array:character <- string-append a:address:array:character, b:address:array:character
-recipe string-append [
-  local-scope
-  # result = new character[a.length + b.length]
-  a:address:array:character <- next-ingredient
-  a-len:number <- length *a
-  b:address:array:character <- next-ingredient
-  b-len:number <- length *b
-  result-len:number <- add a-len, b-len
-  result:address:array:character <- new character:type, result-len
-  # copy a into result
-  result-idx:number <- copy 0
-  i:number <- copy 0
-  {
-    # while i < a.length
-    a-done?:boolean <- greater-or-equal i, a-len
-    break-if a-done?
-    # result[result-idx] = a[i]
-    out:address:character <- index-address *result, result-idx
-    in:character <- index *a, i
-    *out <- copy in
-    i <- add i, 1
-    result-idx <- add result-idx, 1
-    loop
-  }
-  # copy b into result
-  i <- copy 0
-  {
-    # while i < b.length
-    b-done?:boolean <- greater-or-equal i, b-len
-    break-if b-done?
-    # result[result-idx] = a[i]
-    out:address:character <- index-address *result, result-idx
-    in:character <- index *b, i
-    *out <- copy in
-    i <- add i, 1
-    result-idx <- add result-idx, 1
-    loop
-  }
-  reply result
-]
-
-scenario string-append-1 [
-  run [
-    1:address:array:character/raw <- new [hello,]
-    2:address:array:character/raw <- new [ world!]
-    3:address:array:character/raw <- string-append 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [hello, world!]
-  ]
-]
-
-scenario replace-character-in-string [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 98/b, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [azc]
-  ]
-]
-
-recipe string-replace [
-  local-scope
-  s:address:array:character <- next-ingredient
-  oldc:character <- next-ingredient
-  newc:character <- next-ingredient
-  from:number, _ <- next-ingredient  # default to 0
-  len:number <- length *s
-  i:number <- find-next s, oldc, from
-  done?:boolean <- greater-or-equal i, len
-  reply-if done?, s/same-as-ingredient:0
-  dest:address:character <- index-address *s, i
-  *dest <- copy newc
-  i <- add i, 1
-  s <- string-replace s, oldc, newc, i
-  reply s/same-as-ingredient:0
-]
-
-scenario replace-character-at-start [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 97/a, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [zbc]
-  ]
-]
-
-scenario replace-character-at-end [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 99/c, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [abz]
-  ]
-]
-
-scenario replace-character-missing [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 100/d, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [abc]
-  ]
-]
-
-scenario replace-all-characters [
-  run [
-    1:address:array:character/raw <- new [banana]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 97/a, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [bznznz]
-  ]
-]
-
-# replace underscores in first with remaining args
-# result:address:array:character <- interpolate template:address:array:character, ...
-recipe interpolate [
-  local-scope
-  template:address:array:character <- next-ingredient
-  # compute result-len, space to allocate for result
-  tem-len:number <- length *template
-  result-len:number <- copy tem-len
-  {
-    # while arg received
-    a:address:array:character, arg-received?:boolean <- next-ingredient
-    break-unless arg-received?
-    # result-len = result-len + arg.length - 1 (for the 'underscore' being replaced)
-    a-len:number <- length *a
-    result-len <- add result-len, a-len
-    result-len <- subtract result-len, 1
-    loop
-  }
-  rewind-ingredients
-  _ <- next-ingredient  # skip template
-  result:address:array:character <- new character:type, result-len
-  # repeatedly copy sections of template and 'holes' into result
-  result-idx:number <- copy 0
-  i:number <- copy 0
-  {
-    # while arg received
-    a:address:array:character, arg-received?:boolean <- next-ingredient
-    break-unless arg-received?
-    # copy template into result until '_'
-    {
-      # while i < template.length
-      tem-done?:boolean <- greater-or-equal i, tem-len
-      break-if tem-done?, +done:label
-      # while template[i] != '_'
-      in:character <- index *template, i
-      underscore?:boolean <- equal in, 95/_
-      break-if underscore?
-      # result[result-idx] = template[i]
-      out:address:character <- index-address *result, result-idx
-      *out <- copy in
-      i <- add i, 1
-      result-idx <- add result-idx, 1
-      loop
-    }
-    # copy 'a' into result
-    j:number <- copy 0
-    {
-      # while j < a.length
-      arg-done?:boolean <- greater-or-equal j, a-len
-      break-if arg-done?
-      # result[result-idx] = a[j]
-      in:character <- index *a, j
-      out:address:character <- index-address *result, result-idx
-      *out <- copy in
-      j <- add j, 1
-      result-idx <- add result-idx, 1
-      loop
-    }
-    # skip '_' in template
-    i <- add i, 1
-    loop  # interpolate next arg
-  }
-  +done
-  # done with holes; copy rest of template directly into result
-  {
-    # while i < template.length
-    tem-done?:boolean <- greater-or-equal i, tem-len
-    break-if tem-done?
-    # result[result-idx] = template[i]
-    in:character <- index *template, i
-    out:address:character <- index-address *result, result-idx:number
-    *out <- copy in
-    i <- add i, 1
-    result-idx <- add result-idx, 1
-    loop
-  }
-  reply result
-]
-
-scenario interpolate-works [
-  run [
-    1:address:array:character/raw <- new [abc _]
-    2:address:array:character/raw <- new [def]
-    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [abc def]
-  ]
-]
-
-scenario interpolate-at-start [
-  run [
-    1:address:array:character/raw <- new [_, hello!]
-    2:address:array:character/raw <- new [abc]
-    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [abc, hello!]
-    16 <- 0  # out of bounds
-  ]
-]
-
-scenario interpolate-at-end [
-  run [
-    1:address:array:character/raw <- new [hello, _]
-    2:address:array:character/raw <- new [abc]
-    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [hello, abc]
-  ]
-]
-
-# result:boolean <- space? c:character
-recipe space? [
-  local-scope
-  c:character <- next-ingredient
-  # most common case first
-  result:boolean <- equal c, 32/space
-  reply-if result, result
-  result <- equal c, 10/newline
-  reply-if result, result
-  result <- equal c, 9/tab
-  reply-if result, result
-  result <- equal c, 13/carriage-return
-  reply-if result, result
-  # remaining uncommon cases in sorted order
-  # http://unicode.org code-points in unicode-set Z and Pattern_White_Space
-  result <- equal c, 11/ctrl-k
-  reply-if result, result
-  result <- equal c, 12/ctrl-l
-  reply-if result, result
-  result <- equal c, 133/ctrl-0085
-  reply-if result, result
-  result <- equal c, 160/no-break-space
-  reply-if result, result
-  result <- equal c, 5760/ogham-space-mark
-  reply-if result, result
-  result <- equal c, 8192/en-quad
-  reply-if result, result
-  result <- equal c, 8193/em-quad
-  reply-if result, result
-  result <- equal c, 8194/en-space
-  reply-if result, result
-  result <- equal c, 8195/em-space
-  reply-if result, result
-  result <- equal c, 8196/three-per-em-space
-  reply-if result, result
-  result <- equal c, 8197/four-per-em-space
-  reply-if result, result
-  result <- equal c, 8198/six-per-em-space
-  reply-if result, result
-  result <- equal c, 8199/figure-space
-  reply-if result, result
-  result <- equal c, 8200/punctuation-space
-  reply-if result, result
-  result <- equal c, 8201/thin-space
-  reply-if result, result
-  result <- equal c, 8202/hair-space
-  reply-if result, result
-  result <- equal c, 8206/left-to-right
-  reply-if result, result
-  result <- equal c, 8207/right-to-left
-  reply-if result, result
-  result <- equal c, 8232/line-separator
-  reply-if result, result
-  result <- equal c, 8233/paragraph-separator
-  reply-if result, result
-  result <- equal c, 8239/narrow-no-break-space
-  reply-if result, result
-  result <- equal c, 8287/medium-mathematical-space
-  reply-if result, result
-  result <- equal c, 12288/ideographic-space
-  reply result
-]
-
-# result:address:array:character <- trim s:address:array:character
-recipe trim [
-  local-scope
-  s:address:array:character <- next-ingredient
-  len:number <- length *s
-  # left trim: compute start
-  start:number <- copy 0
-  {
-    {
-      at-end?:boolean <- greater-or-equal start, len
-      break-unless at-end?
-      result:address:array:character <- new character:type, 0
-      reply result
-    }
-    curr:character <- index *s, start
-    whitespace?:boolean <- space? curr
-    break-unless whitespace?
-    start <- add start, 1
-    loop
-  }
-  # right trim: compute end
-  end:number <- subtract len, 1
-  {
-    not-at-start?:boolean <- greater-than end, start
-    assert not-at-start?, [end ran up against start]
-    curr:character <- index *s, end
-    whitespace?:boolean <- space? curr
-    break-unless whitespace?
-    end <- subtract end, 1
-    loop
-  }
-  # result = new character[end+1 - start]
-  new-len:number <- subtract end, start, -1
-  result:address:array:character <- new character:type, new-len
-  # copy the untrimmed parts between start and end
-  i:number <- copy start
-  j:number <- copy 0
-  {
-    # while i <= end
-    done?:boolean <- greater-than i, end
-    break-if done?
-    # result[j] = s[i]
-    src:character <- index *s, i
-    dest:address:character <- index-address *result, j
-    *dest <- copy src
-    i <- add i, 1
-    j <- add j, 1
-    loop
-  }
-  reply result
-]
-
-scenario trim-unmodified [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-left [
-  run [
-    1:address:array:character <- new [  abc]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-right [
-  run [
-    1:address:array:character <- new [abc  ]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-left-right [
-  run [
-    1:address:array:character <- new [  abc   ]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-newline-tab [
-  run [
-    1:address:array:character <- new [  abc
-]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-# next-index:number <- find-next text:address:array:character, pattern:character, idx:number
-recipe find-next [
-  local-scope
-  text:address:array:character <- next-ingredient
-  pattern:character <- next-ingredient
-  idx:number <- next-ingredient
-  len:number <- length *text
-  {
-    eof?:boolean <- greater-or-equal idx, len
-    break-if eof?
-    curr:character <- index *text, idx
-    found?:boolean <- equal curr, pattern
-    break-if found?
-    idx <- add idx, 1
-    loop
-  }
-  reply idx
-]
-
-scenario string-find-next [
-  run [
-    1:address:array:character <- new [a/b]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 1
-  ]
-]
-
-scenario string-find-next-empty [
-  run [
-    1:address:array:character <- new []
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 0
-  ]
-]
-
-scenario string-find-next-initial [
-  run [
-    1:address:array:character <- new [/abc]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 0  # prefix match
-  ]
-]
-
-scenario string-find-next-final [
-  run [
-    1:address:array:character <- new [abc/]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 3  # suffix match
-  ]
-]
-
-scenario string-find-next-missing [
-  run [
-    1:address:array:character <- new [abc]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 3  # no match
-  ]
-]
-
-scenario string-find-next-invalid-index [
-  run [
-    1:address:array:character <- new [abc]
-    2:number <- find-next 1:address:array:character, 47/slash, 4/start-index
-  ]
-  memory-should-contain [
-    2 <- 4  # no change
-  ]
-]
-
-scenario string-find-next-first [
-  run [
-    1:address:array:character <- new [ab/c/]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 2  # first '/' of multiple
-  ]
-]
-
-scenario string-find-next-second [
-  run [
-    1:address:array:character <- new [ab/c/]
-    2:number <- find-next 1:address:array:character, 47/slash, 3/start-index
-  ]
-  memory-should-contain [
-    2 <- 4  # second '/' of multiple
-  ]
-]
-
-# next-index:number <- find-substring text:address:array:character, pattern:address:array:character, idx:number
-# like find-next, but searches for multiple characters
-# fairly dumb algorithm
-recipe find-substring [
-  local-scope
-  text:address:array:character <- next-ingredient
-  pattern:address:array:character <- next-ingredient
-  idx:number <- next-ingredient
-  first:character <- index *pattern, 0
-  # repeatedly check for match at current idx
-  len:number <- length *text
-  {
-    # does some unnecessary work checking for substrings even when there isn't enough of text left
-    done?:boolean <- greater-or-equal idx, len
-    break-if done?
-    found?:boolean <- match-at text, pattern, idx
-    break-if found?
-    idx <- add idx, 1
-    # optimization: skip past indices that definitely won't match
-    idx <- find-next text, first, idx
-    loop
-  }
-  reply idx
-]
-
-scenario find-substring-1 [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bc]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1
-  ]
-]
-
-scenario find-substring-2 [
-  run [
-    1:address:array:character <- new [abcd]
-    2:address:array:character <- new [bc]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 1
-  ]
-  memory-should-contain [
-    3 <- 1
-  ]
-]
-
-scenario find-substring-no-match [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bd]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 3  # not found
-  ]
-]
-
-scenario find-substring-suffix-match [
-  run [
-    1:address:array:character <- new [abcd]
-    2:address:array:character <- new [cd]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 2
-  ]
-]
-
-scenario find-substring-suffix-match-2 [
-  run [
-    1:address:array:character <- new [abcd]
-    2:address:array:character <- new [cde]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 4  # not found
-  ]
-]
-
-# result:boolean <- match-at text:address:array:character, pattern:address:array:character, idx:number
-# checks if substring matches at index 'idx'
-recipe match-at [
-  local-scope
-  text:address:array:character <- next-ingredient
-  pattern:address:array:character <- next-ingredient
-  idx:number <- next-ingredient
-  pattern-len:number <- length *pattern
-  # check that there's space left for the pattern
-  {
-    x:number <- length *text
-    x <- subtract x, pattern-len
-    enough-room?:boolean <- lesser-or-equal idx, x
-    break-if enough-room?
-    reply 0/not-found
-  }
-  # check each character of pattern
-  pattern-idx:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal pattern-idx, pattern-len
-    break-if done?
-    c:character <- index *text, idx
-    exp:character <- index *pattern, pattern-idx
-    {
-      match?:boolean <- equal c, exp
-      break-if match?
-      reply 0/not-found
-    }
-    idx <- add idx, 1
-    pattern-idx <- add pattern-idx, 1
-    loop
-  }
-  reply 1/found
-]
-
-scenario match-at-checks-substring-at-index [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [ab]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # match found
-  ]
-]
-
-scenario match-at-reflexive [
-  run [
-    1:address:array:character <- new [abc]
-    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # match found
-  ]
-]
-
-scenario match-at-outside-bounds [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [a]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
-  ]
-  memory-should-contain [
-    3 <- 0  # never matches
-  ]
-]
-
-scenario match-at-empty-pattern [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new []
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # always matches empty pattern given a valid index
-  ]
-]
-
-scenario match-at-empty-pattern-outside-bound [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new []
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
-  ]
-  memory-should-contain [
-    3 <- 0  # no match
-  ]
-]
-
-scenario match-at-empty-text [
-  run [
-    1:address:array:character <- new []
-    2:address:array:character <- new [abc]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 0  # no match
-  ]
-]
-
-scenario match-at-empty-against-empty [
-  run [
-    1:address:array:character <- new []
-    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # matches because pattern is also empty
-  ]
-]
-
-scenario match-at-inside-bounds [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bc]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 1
-  ]
-  memory-should-contain [
-    3 <- 1  # matches inner substring
-  ]
-]
-
-scenario match-at-inside-bounds-2 [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bc]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 0  # no match
-  ]
-]
-
-# result:address:array:address:array:character <- split s:address:array:character, delim:character
-recipe split [
-  local-scope
-  s:address:array:character <- next-ingredient
-  delim:character <- next-ingredient
-  # empty string? return empty array
-  len:number <- length *s
-  {
-    empty?:boolean <- equal len, 0
-    break-unless empty?
-    result:address:array:address:array:character <- new location:type, 0
-    reply result
-  }
-  # count #pieces we need room for
-  count:number <- copy 1  # n delimiters = n+1 pieces
-  idx:number <- copy 0
-  {
-    idx <- find-next s, delim, idx
-    done?:boolean <- greater-or-equal idx, len
-    break-if done?
-    idx <- add idx, 1
-    count <- add count, 1
-    loop
-  }
-  # allocate space
-  result:address:array:address:array:character <- new location:type, count
-  # repeatedly copy slices start..end until delimiter into result[curr-result]
-  curr-result:number <- copy 0
-  start:number <- copy 0
-  {
-    # while next delim exists
-    done?:boolean <- greater-or-equal start, len
-    break-if done?
-    end:number <- find-next s, delim, start
-    # copy start..end into result[curr-result]
-    dest:address:address:array:character <- index-address *result, curr-result
-    *dest <- string-copy s, start, end
-    # slide over to next slice
-    start <- add end, 1
-    curr-result <- add curr-result, 1
-    loop
-  }
-  reply result
-]
-
-scenario string-split-1 [
-  run [
-    1:address:array:character <- new [a/b]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    5:address:array:character <- index *2:address:array:address:array:character, 1
-    10:array:character <- copy *4:address:array:character
-    20:array:character <- copy *5:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 2  # length of result
-    10:string <- [a]
-    20:string <- [b]
-  ]
-]
-
-scenario string-split-2 [
-  run [
-    1:address:array:character <- new [a/b/c]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    5:address:array:character <- index *2:address:array:address:array:character, 1
-    6:address:array:character <- index *2:address:array:address:array:character, 2
-    10:array:character <- copy *4:address:array:character
-    20:array:character <- copy *5:address:array:character
-    30:array:character <- copy *6:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 3  # length of result
-    10:string <- [a]
-    20:string <- [b]
-    30:string <- [c]
-  ]
-]
-
-scenario string-split-missing [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    10:array:character <- copy *4:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 1  # length of result
-    10:string <- [abc]
-  ]
-]
-
-scenario string-split-empty [
-  run [
-    1:address:array:character <- new []
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 0  # empty result
-  ]
-]
-
-scenario string-split-empty-piece [
-  run [
-    1:address:array:character <- new [a/b//c]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    5:address:array:character <- index *2:address:array:address:array:character, 1
-    6:address:array:character <- index *2:address:array:address:array:character, 2
-    7:address:array:character <- index *2:address:array:address:array:character, 3
-    10:array:character <- copy *4:address:array:character
-    20:array:character <- copy *5:address:array:character
-    30:array:character <- copy *6:address:array:character
-    40:array:character <- copy *7:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 4  # length of result
-    10:string <- [a]
-    20:string <- [b]
-    30:string <- []
-    40:string <- [c]
-  ]
-]
-
-# x:address:array:character, y:address:array:character <- split-first text:address:array:character, delim:character
-recipe split-first [
-  local-scope
-  text:address:array:character <- next-ingredient
-  delim:character <- next-ingredient
-  # empty string? return empty strings
-  len:number <- length *text
-  {
-    empty?:boolean <- equal len, 0
-    break-unless empty?
-    x:address:array:character <- new []
-    y:address:array:character <- new []
-    reply x, y
-  }
-  idx:number <- find-next text, delim, 0
-  x:address:array:character <- string-copy text, 0, idx
-  idx <- add idx, 1
-  y:address:array:character <- string-copy text, idx, len
-  reply x, y
-]
-
-scenario string-split-first [
-  run [
-    1:address:array:character <- new [a/b]
-    2:address:array:character, 3:address:array:character <- split-first 1:address:array:character, 47/slash
-    10:array:character <- copy *2:address:array:character
-    20:array:character <- copy *3:address:array:character
-  ]
-  memory-should-contain [
-    10:string <- [a]
-    20:string <- [b]
-  ]
-]
-
-# result:address:array:character <- string-copy buf:address:array:character, start:number, end:number
-# todo: make this generic
-recipe string-copy [
-  local-scope
-  buf:address:array:character <- next-ingredient
-  start:number <- next-ingredient
-  end:number <- next-ingredient
-  # if end is out of bounds, trim it
-  len:number <- length *buf
-  end:number <- min len, end
-  # allocate space for result
-  len <- subtract end, start
-  result:address:array:character <- new character:type, len
-  # copy start..end into result[curr-result]
-  src-idx:number <- copy start
-  dest-idx:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal src-idx, end
-    break-if done?
-    src:character <- index *buf, src-idx
-    dest:address:character <- index-address *result, dest-idx
-    *dest <- copy src
-    src-idx <- add src-idx, 1
-    dest-idx <- add dest-idx, 1
-    loop
-  }
-  reply result
-]
-
-scenario string-copy-copies-substring [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- string-copy 1:address:array:character, 1, 3
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [bc]
-  ]
-]
-
-scenario string-copy-out-of-bounds [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- string-copy 1:address:array:character, 2, 4
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [c]
-  ]
-]
-
-scenario string-copy-out-of-bounds-2 [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- string-copy 1:address:array:character, 3, 3
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- []
-  ]
-]
-
-recipe min [
-  local-scope
-  x:number <- next-ingredient
-  y:number <- next-ingredient
-  {
-    return-x?:boolean <- lesser-than x, y
-    break-if return-x?
-    reply y
-  }
-  reply x
-]
-
-recipe max [
-  local-scope
-  x:number <- next-ingredient
-  y:number <- next-ingredient
-  {
-    return-x?:boolean <- greater-than x, y
-    break-if return-x?
-    reply y
-  }
-  reply x
-]
-
- - - diff --git a/html/070text.mu.html b/html/070text.mu.html new file mode 100644 index 00000000..38851b6c --- /dev/null +++ b/html/070text.mu.html @@ -0,0 +1,1352 @@ + + + + +Mu - 070text.mu + + + + + + + + + + +
+# Some useful helpers for dealing with text (arrays of characters)
+
+# to-text-line gets called implicitly in various places
+# define it to be identical to 'to-text' by default
+recipe to-text-line x:_elem -> y:address:array:character [
+  local-scope
+  load-ingredients
+  y <- to-text x
+]
+
+# to-text on text is just the identity function
+recipe to-text x:address:array:character -> y:address:array:character [
+  local-scope
+  load-ingredients
+#?   $print [to-text text], 10/newline
+  reply x
+]
+
+recipe equal a:address:array:character, b:address:array:character -> result:boolean [
+  local-scope
+  load-ingredients
+  a-len:number <- length *a
+  b-len:number <- length *b
+  # compare lengths
+  {
+    trace 99, [text-equal], [comparing lengths]
+    length-equal?:boolean <- equal a-len, b-len
+    break-if length-equal?
+    reply 0
+  }
+  # compare each corresponding character
+  trace 99, [text-equal], [comparing characters]
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, a-len
+    break-if done?
+    a2:character <- index *a, i
+    b2:character <- index *b, i
+    {
+      chars-match?:boolean <- equal a2, b2
+      break-if chars-match?
+      reply 0
+    }
+    i <- add i, 1
+    loop
+  }
+  reply 1
+]
+
+scenario text-equal-reflexive [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    3:boolean/raw <- equal x, x
+  ]
+  memory-should-contain [
+    3 <- 1  # x == x for all x
+  ]
+]
+
+scenario text-equal-identical [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    y:address:array:character <- new [abc]
+    3:boolean/raw <- equal x, y
+  ]
+  memory-should-contain [
+    3 <- 1  # abc == abc
+  ]
+]
+
+scenario text-equal-distinct-lengths [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    y:address:array:character <- new [abcd]
+    3:boolean/raw <- equal x, y
+  ]
+  memory-should-contain [
+    3 <- 0  # abc != abcd
+  ]
+  trace-should-contain [
+    text-equal: comparing lengths
+  ]
+  trace-should-not-contain [
+    text-equal: comparing characters
+  ]
+]
+
+scenario text-equal-with-empty [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new []
+    y:address:array:character <- new [abcd]
+    3:boolean/raw <- equal x, y
+  ]
+  memory-should-contain [
+    3 <- 0  # "" != abcd
+  ]
+]
+
+scenario text-equal-common-lengths-but-distinct [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    y:address:array:character <- new [abd]
+    3:boolean/raw <- equal x, y
+  ]
+  memory-should-contain [
+    3 <- 0  # abc != abd
+  ]
+]
+
+# A new type to help incrementally construct texts.
+container buffer [
+  length:number
+  data:address:array:character
+]
+
+recipe new-buffer capacity:number -> result:address:buffer [
+  local-scope
+  load-ingredients
+  result <- new buffer:type
+  len:address:number <- get-address *result, length:offset
+  *len:address:number <- copy 0
+  s:address:address:array:character <- get-address *result, data:offset
+  *s <- new character:type, capacity
+  reply result
+]
+
+recipe grow-buffer in:address:buffer -> in:address:buffer [
+  local-scope
+  load-ingredients
+  # double buffer size
+  x:address:address:array:character <- get-address *in, data:offset
+  oldlen:number <- length **x
+  newlen:number <- multiply oldlen, 2
+  olddata:address:array:character <- copy *x
+  *x <- new character:type, newlen
+  # copy old contents
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, oldlen
+    break-if done?
+    src:character <- index *olddata, i
+    dest:address:character <- index-address **x, i
+    *dest <- copy src
+    i <- add i, 1
+    loop
+  }
+]
+
+recipe buffer-full? in:address:buffer -> result:boolean [
+  local-scope
+  load-ingredients
+  len:number <- get *in, length:offset
+  s:address:array:character <- get *in, data:offset
+  capacity:number <- length *s
+  result <- greater-or-equal len, capacity
+]
+
+# most broadly applicable definition of append to a buffer: just call to-text
+recipe append buf:address:buffer, x:_elem -> buf:address:buffer [
+  local-scope
+#?   $print [append _elem to buffer], 10/newline
+  load-ingredients
+  text:address:array:character <- to-text x
+  len:number <- length *text
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, len
+    break-if done?
+    c:character <- index *text, i
+    buf <- append buf, c
+    i <- add i, 1
+    loop
+  }
+]
+
+recipe append in:address:buffer, c:character -> in:address:buffer [
+  local-scope
+#?   $print [append character to buffer], 10/newline
+  load-ingredients
+  len:address:number <- get-address *in, length:offset
+  {
+    # backspace? just drop last character if it exists and return
+    backspace?:boolean <- equal c, 8/backspace
+    break-unless backspace?
+    empty?:boolean <- lesser-or-equal *len, 0
+    reply-if empty?
+    *len <- subtract *len, 1
+    reply
+  }
+  {
+    # grow buffer if necessary
+    full?:boolean <- buffer-full? in
+    break-unless full?
+    in <- grow-buffer in
+  }
+  s:address:array:character <- get *in, data:offset
+  dest:address:character <- index-address *s, *len
+  *dest <- copy c
+  *len <- add *len, 1
+]
+
+scenario buffer-append-works [
+  run [
+    local-scope
+    x:address:buffer <- new-buffer 3
+    s1:address:array:character <- get *x:address:buffer, data:offset
+    x:address:buffer <- append x:address:buffer, 97  # 'a'
+    x:address:buffer <- append x:address:buffer, 98  # 'b'
+    x:address:buffer <- append x:address:buffer, 99  # 'c'
+    s2:address:array:character <- get *x:address:buffer, data:offset
+    1:boolean/raw <- equal s1:address:array:character, s2:address:array:character
+    2:array:character/raw <- copy *s2:address:array:character
+    +buffer-filled
+    x:address:buffer <- append x:address:buffer, 100  # 'd'
+    s3:address:array:character <- get *x:address:buffer, data:offset
+    10:boolean/raw <- equal s1:address:array:character, s3:address:array:character
+    11:number/raw <- get *x:address:buffer, length:offset
+    12:array:character/raw <- copy *s3:address:array:character
+  ]
+  memory-should-contain [
+    # before +buffer-filled
+    1 <- 1   # no change in data pointer
+    2 <- 3   # size of data
+    3 <- 97  # data
+    4 <- 98
+    5 <- 99
+    # in the end
+    10 <- 0   # data pointer has grown
+    11 <- 4   # final length
+    12 <- 6   # but data's capacity has doubled
+    13 <- 97  # data
+    14 <- 98
+    15 <- 99
+    16 <- 100
+    17 <- 0
+    18 <- 0
+  ]
+]
+
+scenario buffer-append-handles-backspace [
+  run [
+    local-scope
+    x:address:buffer <- new-buffer 3
+    x <- append x, 97  # 'a'
+    x <- append x, 98  # 'b'
+    x <- append x, 8/backspace
+    s:address:array:character <- buffer-to-array x
+    1:array:character/raw <- copy *s
+  ]
+  memory-should-contain [
+    1 <- 1   # length
+    2 <- 97  # contents
+    3 <- 0
+  ]
+]
+
+recipe to-text n:number -> result:address:array:character [
+  local-scope
+  load-ingredients
+  # is n zero?
+  {
+    break-if n
+    result <- new [0]
+    reply
+  }
+  # save sign
+  negate-result:boolean <- copy 0
+  {
+    negative?:boolean <- lesser-than n, 0
+    break-unless negative?
+    negate-result <- copy 1
+    n <- multiply n, -1
+  }
+  # add digits from right to left into intermediate buffer
+  tmp:address:buffer <- new-buffer 30
+  digit-base:number <- copy 48  # '0'
+  {
+    done?:boolean <- equal n, 0
+    break-if done?
+    n, digit:number <- divide-with-remainder n, 10
+    c:character <- add digit-base, digit
+    tmp:address:buffer <- append tmp, c
+    loop
+  }
+  # add sign
+  {
+    break-unless negate-result:boolean
+    tmp <- append tmp, 45  # '-'
+  }
+  # reverse buffer into text result
+  len:number <- get *tmp, length:offset
+  buf:address:array:character <- get *tmp, data:offset
+  result <- new character:type, len
+  i:number <- subtract len, 1  # source index, decreasing
+  j:number <- copy 0  # destination index, increasing
+  {
+    # while i >= 0
+    done?:boolean <- lesser-than i, 0
+    break-if done?
+    # result[j] = tmp[i]
+    src:character <- index *buf, i
+    dest:address:character <- index-address *result, j
+    *dest <- copy src
+    i <- subtract i, 1
+    j <- add j, 1
+    loop
+  }
+]
+
+recipe buffer-to-array in:address:buffer -> result:address:array:character [
+  local-scope
+  load-ingredients
+  {
+    # propagate null buffer
+    break-if in
+    reply 0
+  }
+  len:number <- get *in, length:offset
+  s:address:array:character <- get *in, data:offset
+  # we can't just return s because it is usually the wrong length
+  result <- new character:type, len
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, len
+    break-if done?
+    src:character <- index *s, i
+    dest:address:character <- index-address *result, i
+    *dest <- copy src
+    i <- add i, 1
+    loop
+  }
+]
+
+scenario integer-to-decimal-digit-zero [
+  run [
+    1:address:array:character/raw <- to-text 0
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:array:character <- [0]
+  ]
+]
+
+scenario integer-to-decimal-digit-positive [
+  run [
+    1:address:array:character/raw <- to-text 234
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:array:character <- [234]
+  ]
+]
+
+scenario integer-to-decimal-digit-negative [
+  run [
+    1:address:array:character/raw <- to-text -1
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2 <- 2
+    3 <- 45  # '-'
+    4 <- 49  # '1'
+  ]
+]
+
+recipe append a:address:array:character, b:address:array:character -> result:address:array:character [
+  local-scope
+#?   $print [append text to text], 10/newline
+  load-ingredients
+  # result = new character[a.length + b.length]
+  a-len:number <- length *a
+  b-len:number <- length *b
+  result-len:number <- add a-len, b-len
+  result <- new character:type, result-len
+  # copy a into result
+  result-idx:number <- copy 0
+  i:number <- copy 0
+  {
+    # while i < a.length
+    a-done?:boolean <- greater-or-equal i, a-len
+    break-if a-done?
+    # result[result-idx] = a[i]
+    out:address:character <- index-address *result, result-idx
+    in:character <- index *a, i
+    *out <- copy in
+    i <- add i, 1
+    result-idx <- add result-idx, 1
+    loop
+  }
+  # copy b into result
+  i <- copy 0
+  {
+    # while i < b.length
+    b-done?:boolean <- greater-or-equal i, b-len
+    break-if b-done?
+    # result[result-idx] = a[i]
+    out:address:character <- index-address *result, result-idx
+    in:character <- index *b, i
+    *out <- copy in
+    i <- add i, 1
+    result-idx <- add result-idx, 1
+    loop
+  }
+]
+
+scenario text-append-1 [
+  run [
+    1:address:array:character/raw <- new [hello,]
+    2:address:array:character/raw <- new [ world!]
+    3:address:array:character/raw <- append 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:array:character <- [hello, world!]
+  ]
+]
+
+scenario replace-character-in-text [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- replace 1:address:array:character/raw, 98/b, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:array:character <- [azc]
+  ]
+]
+
+recipe replace s:address:array:character, oldc:character, newc:character -> s:address:array:character [
+  local-scope
+  load-ingredients
+  from:number, _ <- next-ingredient  # default to 0
+  len:number <- length *s
+  i:number <- find-next s, oldc, from
+  done?:boolean <- greater-or-equal i, len
+  reply-if done?, s/same-as-ingredient:0
+  dest:address:character <- index-address *s, i
+  *dest <- copy newc
+  i <- add i, 1
+  s <- replace s, oldc, newc, i
+]
+
+scenario replace-character-at-start [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- replace 1:address:array:character/raw, 97/a, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:array:character <- [zbc]
+  ]
+]
+
+scenario replace-character-at-end [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- replace 1:address:array:character/raw, 99/c, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:array:character <- [abz]
+  ]
+]
+
+scenario replace-character-missing [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- replace 1:address:array:character/raw, 100/d, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:array:character <- [abc]
+  ]
+]
+
+scenario replace-all-characters [
+  run [
+    1:address:array:character/raw <- new [banana]
+    1:address:array:character/raw <- replace 1:address:array:character/raw, 97/a, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:array:character <- [bznznz]
+  ]
+]
+
+# replace underscores in first with remaining args
+recipe interpolate template:address:array:character -> result:address:array:character [
+  local-scope
+  load-ingredients  # consume just the template
+  # compute result-len, space to allocate for result
+  tem-len:number <- length *template
+  result-len:number <- copy tem-len
+  {
+    # while ingredients remain
+    a:address:array:character, arg-received?:boolean <- next-ingredient
+    break-unless arg-received?
+    # result-len = result-len + arg.length - 1 (for the 'underscore' being replaced)
+    a-len:number <- length *a
+    result-len <- add result-len, a-len
+    result-len <- subtract result-len, 1
+    loop
+  }
+  rewind-ingredients
+  _ <- next-ingredient  # skip template
+  result:address:array:character <- new character:type, result-len
+  # repeatedly copy sections of template and 'holes' into result
+  result-idx:number <- copy 0
+  i:number <- copy 0
+  {
+    # while arg received
+    a:address:array:character, arg-received?:boolean <- next-ingredient
+    break-unless arg-received?
+    # copy template into result until '_'
+    {
+      # while i < template.length
+      tem-done?:boolean <- greater-or-equal i, tem-len
+      break-if tem-done?, +done:label
+      # while template[i] != '_'
+      in:character <- index *template, i
+      underscore?:boolean <- equal in, 95/_
+      break-if underscore?
+      # result[result-idx] = template[i]
+      out:address:character <- index-address *result, result-idx
+      *out <- copy in
+      i <- add i, 1
+      result-idx <- add result-idx, 1
+      loop
+    }
+    # copy 'a' into result
+    j:number <- copy 0
+    {
+      # while j < a.length
+      arg-done?:boolean <- greater-or-equal j, a-len
+      break-if arg-done?
+      # result[result-idx] = a[j]
+      in:character <- index *a, j
+      out:address:character <- index-address *result, result-idx
+      *out <- copy in
+      j <- add j, 1
+      result-idx <- add result-idx, 1
+      loop
+    }
+    # skip '_' in template
+    i <- add i, 1
+    loop  # interpolate next arg
+  }
+  +done
+  # done with holes; copy rest of template directly into result
+  {
+    # while i < template.length
+    tem-done?:boolean <- greater-or-equal i, tem-len
+    break-if tem-done?
+    # result[result-idx] = template[i]
+    in:character <- index *template, i
+    out:address:character <- index-address *result, result-idx:number
+    *out <- copy in
+    i <- add i, 1
+    result-idx <- add result-idx, 1
+    loop
+  }
+]
+
+scenario interpolate-works [
+  run [
+    1:address:array:character/raw <- new [abc _]
+    2:address:array:character/raw <- new [def]
+    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:array:character <- [abc def]
+  ]
+]
+
+scenario interpolate-at-start [
+  run [
+    1:address:array:character/raw <- new [_, hello!]
+    2:address:array:character/raw <- new [abc]
+    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:array:character <- [abc, hello!]
+    16 <- 0  # out of bounds
+  ]
+]
+
+scenario interpolate-at-end [
+  run [
+    1:address:array:character/raw <- new [hello, _]
+    2:address:array:character/raw <- new [abc]
+    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:array:character <- [hello, abc]
+  ]
+]
+
+# result:boolean <- space? c:character
+recipe space? c:character -> result:boolean [
+  local-scope
+  load-ingredients
+  # most common case first
+  result <- equal c, 32/space
+  reply-if result
+  result <- equal c, 10/newline
+  reply-if result
+  result <- equal c, 9/tab
+  reply-if result
+  result <- equal c, 13/carriage-return
+  reply-if result
+  # remaining uncommon cases in sorted order
+  # http://unicode.org code-points in unicode-set Z and Pattern_White_Space
+  result <- equal c, 11/ctrl-k
+  reply-if result
+  result <- equal c, 12/ctrl-l
+  reply-if result
+  result <- equal c, 133/ctrl-0085
+  reply-if result
+  result <- equal c, 160/no-break-space
+  reply-if result
+  result <- equal c, 5760/ogham-space-mark
+  reply-if result
+  result <- equal c, 8192/en-quad
+  reply-if result
+  result <- equal c, 8193/em-quad
+  reply-if result
+  result <- equal c, 8194/en-space
+  reply-if result
+  result <- equal c, 8195/em-space
+  reply-if result
+  result <- equal c, 8196/three-per-em-space
+  reply-if result
+  result <- equal c, 8197/four-per-em-space
+  reply-if result
+  result <- equal c, 8198/six-per-em-space
+  reply-if result
+  result <- equal c, 8199/figure-space
+  reply-if result
+  result <- equal c, 8200/punctuation-space
+  reply-if result
+  result <- equal c, 8201/thin-space
+  reply-if result
+  result <- equal c, 8202/hair-space
+  reply-if result
+  result <- equal c, 8206/left-to-right
+  reply-if result
+  result <- equal c, 8207/right-to-left
+  reply-if result
+  result <- equal c, 8232/line-separator
+  reply-if result
+  result <- equal c, 8233/paragraph-separator
+  reply-if result
+  result <- equal c, 8239/narrow-no-break-space
+  reply-if result
+  result <- equal c, 8287/medium-mathematical-space
+  reply-if result
+  result <- equal c, 12288/ideographic-space
+]
+
+recipe trim s:address:array:character -> result:address:array:character [
+  local-scope
+  load-ingredients
+  len:number <- length *s
+  # left trim: compute start
+  start:number <- copy 0
+  {
+    {
+      at-end?:boolean <- greater-or-equal start, len
+      break-unless at-end?
+      result <- new character:type, 0
+      reply
+    }
+    curr:character <- index *s, start
+    whitespace?:boolean <- space? curr
+    break-unless whitespace?
+    start <- add start, 1
+    loop
+  }
+  # right trim: compute end
+  end:number <- subtract len, 1
+  {
+    not-at-start?:boolean <- greater-than end, start
+    assert not-at-start?, [end ran up against start]
+    curr:character <- index *s, end
+    whitespace?:boolean <- space? curr
+    break-unless whitespace?
+    end <- subtract end, 1
+    loop
+  }
+  # result = new character[end+1 - start]
+  new-len:number <- subtract end, start, -1
+  result:address:array:character <- new character:type, new-len
+  # copy the untrimmed parts between start and end
+  i:number <- copy start
+  j:number <- copy 0
+  {
+    # while i <= end
+    done?:boolean <- greater-than i, end
+    break-if done?
+    # result[j] = s[i]
+    src:character <- index *s, i
+    dest:address:character <- index-address *result, j
+    *dest <- copy src
+    i <- add i, 1
+    j <- add j, 1
+    loop
+  }
+]
+
+scenario trim-unmodified [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- [abc]
+  ]
+]
+
+scenario trim-left [
+  run [
+    1:address:array:character <- new [  abc]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- [abc]
+  ]
+]
+
+scenario trim-right [
+  run [
+    1:address:array:character <- new [abc  ]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- [abc]
+  ]
+]
+
+scenario trim-left-right [
+  run [
+    1:address:array:character <- new [  abc   ]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- [abc]
+  ]
+]
+
+scenario trim-newline-tab [
+  run [
+    1:address:array:character <- new [  abc
+]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- [abc]
+  ]
+]
+
+recipe find-next text:address:array:character, pattern:character, idx:number -> next-index:number [
+  local-scope
+  text:address:array:character <- next-ingredient
+  pattern:character <- next-ingredient
+  idx:number <- next-ingredient
+  len:number <- length *text
+  {
+    eof?:boolean <- greater-or-equal idx, len
+    break-if eof?
+    curr:character <- index *text, idx
+    found?:boolean <- equal curr, pattern
+    break-if found?
+    idx <- add idx, 1
+    loop
+  }
+  reply idx
+]
+
+scenario text-find-next [
+  run [
+    1:address:array:character <- new [a/b]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 1
+  ]
+]
+
+scenario text-find-next-empty [
+  run [
+    1:address:array:character <- new []
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 0
+  ]
+]
+
+scenario text-find-next-initial [
+  run [
+    1:address:array:character <- new [/abc]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 0  # prefix match
+  ]
+]
+
+scenario text-find-next-final [
+  run [
+    1:address:array:character <- new [abc/]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 3  # suffix match
+  ]
+]
+
+scenario text-find-next-missing [
+  run [
+    1:address:array:character <- new [abc]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 3  # no match
+  ]
+]
+
+scenario text-find-next-invalid-index [
+  run [
+    1:address:array:character <- new [abc]
+    2:number <- find-next 1:address:array:character, 47/slash, 4/start-index
+  ]
+  memory-should-contain [
+    2 <- 4  # no change
+  ]
+]
+
+scenario text-find-next-first [
+  run [
+    1:address:array:character <- new [ab/c/]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 2  # first '/' of multiple
+  ]
+]
+
+scenario text-find-next-second [
+  run [
+    1:address:array:character <- new [ab/c/]
+    2:number <- find-next 1:address:array:character, 47/slash, 3/start-index
+  ]
+  memory-should-contain [
+    2 <- 4  # second '/' of multiple
+  ]
+]
+
+# search for a pattern of multiple characters
+# fairly dumb algorithm
+recipe find-next text:address:array:character, pattern:address:array:character, idx:number -> next-index:number [
+  local-scope
+  load-ingredients
+  first:character <- index *pattern, 0
+  # repeatedly check for match at current idx
+  len:number <- length *text
+  {
+    # does some unnecessary work checking even when there isn't enough of text left
+    done?:boolean <- greater-or-equal idx, len
+    break-if done?
+    found?:boolean <- match-at text, pattern, idx
+    break-if found?
+    idx <- add idx, 1
+    # optimization: skip past indices that definitely won't match
+    idx <- find-next text, first, idx
+    loop
+  }
+  reply idx
+]
+
+scenario find-next-text-1 [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bc]
+    3:number <- find-next 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1
+  ]
+]
+
+scenario find-next-text-2 [
+  run [
+    1:address:array:character <- new [abcd]
+    2:address:array:character <- new [bc]
+    3:number <- find-next 1:address:array:character, 2:address:array:character, 1
+  ]
+  memory-should-contain [
+    3 <- 1
+  ]
+]
+
+scenario find-next-no-match [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bd]
+    3:number <- find-next 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 3  # not found
+  ]
+]
+
+scenario find-next-suffix-match [
+  run [
+    1:address:array:character <- new [abcd]
+    2:address:array:character <- new [cd]
+    3:number <- find-next 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 2
+  ]
+]
+
+scenario find-next-suffix-match-2 [
+  run [
+    1:address:array:character <- new [abcd]
+    2:address:array:character <- new [cde]
+    3:number <- find-next 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 4  # not found
+  ]
+]
+
+# checks if pattern matches at index 'idx'
+recipe match-at text:address:array:character, pattern:address:array:character, idx:number -> result:boolean [
+  local-scope
+  load-ingredients
+  pattern-len:number <- length *pattern
+  # check that there's space left for the pattern
+  {
+    x:number <- length *text
+    x <- subtract x, pattern-len
+    enough-room?:boolean <- lesser-or-equal idx, x
+    break-if enough-room?
+    reply 0/not-found
+  }
+  # check each character of pattern
+  pattern-idx:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal pattern-idx, pattern-len
+    break-if done?
+    c:character <- index *text, idx
+    exp:character <- index *pattern, pattern-idx
+    {
+      match?:boolean <- equal c, exp
+      break-if match?
+      reply 0/not-found
+    }
+    idx <- add idx, 1
+    pattern-idx <- add pattern-idx, 1
+    loop
+  }
+  reply 1/found
+]
+
+scenario match-at-checks-pattern-at-index [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [ab]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # match found
+  ]
+]
+
+scenario match-at-reflexive [
+  run [
+    1:address:array:character <- new [abc]
+    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # match found
+  ]
+]
+
+scenario match-at-outside-bounds [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [a]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
+  ]
+  memory-should-contain [
+    3 <- 0  # never matches
+  ]
+]
+
+scenario match-at-empty-pattern [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new []
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # always matches empty pattern given a valid index
+  ]
+]
+
+scenario match-at-empty-pattern-outside-bound [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new []
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
+  ]
+  memory-should-contain [
+    3 <- 0  # no match
+  ]
+]
+
+scenario match-at-empty-text [
+  run [
+    1:address:array:character <- new []
+    2:address:array:character <- new [abc]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 0  # no match
+  ]
+]
+
+scenario match-at-empty-against-empty [
+  run [
+    1:address:array:character <- new []
+    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # matches because pattern is also empty
+  ]
+]
+
+scenario match-at-inside-bounds [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bc]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 1
+  ]
+  memory-should-contain [
+    3 <- 1  # match
+  ]
+]
+
+scenario match-at-inside-bounds-2 [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bc]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 0  # no match
+  ]
+]
+
+recipe split s:address:array:character, delim:character -> result:address:array:address:array:character [
+  local-scope
+  load-ingredients
+  # empty text? return empty array
+  len:number <- length *s
+  {
+    empty?:boolean <- equal len, 0
+    break-unless empty?
+    result <- new location:type, 0
+    reply
+  }
+  # count #pieces we need room for
+  count:number <- copy 1  # n delimiters = n+1 pieces
+  idx:number <- copy 0
+  {
+    idx <- find-next s, delim, idx
+    done?:boolean <- greater-or-equal idx, len
+    break-if done?
+    idx <- add idx, 1
+    count <- add count, 1
+    loop
+  }
+  # allocate space
+  result <- new location:type, count
+  # repeatedly copy slices start..end until delimiter into result[curr-result]
+  curr-result:number <- copy 0
+  start:number <- copy 0
+  {
+    # while next delim exists
+    done?:boolean <- greater-or-equal start, len
+    break-if done?
+    end:number <- find-next s, delim, start
+    # copy start..end into result[curr-result]
+    dest:address:address:array:character <- index-address *result, curr-result
+    *dest <- copy s, start, end
+    # slide over to next slice
+    start <- add end, 1
+    curr-result <- add curr-result, 1
+    loop
+  }
+]
+
+scenario text-split-1 [
+  run [
+    1:address:array:character <- new [a/b]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    5:address:array:character <- index *2:address:array:address:array:character, 1
+    10:array:character <- copy *4:address:array:character
+    20:array:character <- copy *5:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 2  # length of result
+    10:array:character <- [a]
+    20:array:character <- [b]
+  ]
+]
+
+scenario text-split-2 [
+  run [
+    1:address:array:character <- new [a/b/c]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    5:address:array:character <- index *2:address:array:address:array:character, 1
+    6:address:array:character <- index *2:address:array:address:array:character, 2
+    10:array:character <- copy *4:address:array:character
+    20:array:character <- copy *5:address:array:character
+    30:array:character <- copy *6:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 3  # length of result
+    10:array:character <- [a]
+    20:array:character <- [b]
+    30:array:character <- [c]
+  ]
+]
+
+scenario text-split-missing [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    10:array:character <- copy *4:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 1  # length of result
+    10:array:character <- [abc]
+  ]
+]
+
+scenario text-split-empty [
+  run [
+    1:address:array:character <- new []
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 0  # empty result
+  ]
+]
+
+scenario text-split-empty-piece [
+  run [
+    1:address:array:character <- new [a/b//c]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    5:address:array:character <- index *2:address:array:address:array:character, 1
+    6:address:array:character <- index *2:address:array:address:array:character, 2
+    7:address:array:character <- index *2:address:array:address:array:character, 3
+    10:array:character <- copy *4:address:array:character
+    20:array:character <- copy *5:address:array:character
+    30:array:character <- copy *6:address:array:character
+    40:array:character <- copy *7:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 4  # length of result
+    10:array:character <- [a]
+    20:array:character <- [b]
+    30:array:character <- []
+    40:array:character <- [c]
+  ]
+]
+
+recipe split-first text:address:array:character, delim:character -> x:address:array:character, y:address:array:character [
+  local-scope
+  load-ingredients
+  # empty text? return empty texts
+  len:number <- length *text
+  {
+    empty?:boolean <- equal len, 0
+    break-unless empty?
+    x:address:array:character <- new []
+    y:address:array:character <- new []
+    reply
+  }
+  idx:number <- find-next text, delim, 0
+  x:address:array:character <- copy text, 0, idx
+  idx <- add idx, 1
+  y:address:array:character <- copy text, idx, len
+]
+
+scenario text-split-first [
+  run [
+    1:address:array:character <- new [a/b]
+    2:address:array:character, 3:address:array:character <- split-first 1:address:array:character, 47/slash
+    10:array:character <- copy *2:address:array:character
+    20:array:character <- copy *3:address:array:character
+  ]
+  memory-should-contain [
+    10:array:character <- [a]
+    20:array:character <- [b]
+  ]
+]
+
+recipe copy buf:address:array:character, start:number, end:number -> result:address:array:character [
+  local-scope
+  load-ingredients
+  # if end is out of bounds, trim it
+  len:number <- length *buf
+  end:number <- min len, end
+  # allocate space for result
+  len <- subtract end, start
+  result:address:array:character <- new character:type, len
+  # copy start..end into result[curr-result]
+  src-idx:number <- copy start
+  dest-idx:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal src-idx, end
+    break-if done?
+    src:character <- index *buf, src-idx
+    dest:address:character <- index-address *result, dest-idx
+    *dest <- copy src
+    src-idx <- add src-idx, 1
+    dest-idx <- add dest-idx, 1
+    loop
+  }
+]
+
+scenario text-copy-copies-partial-text [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- copy 1:address:array:character, 1, 3
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- [bc]
+  ]
+]
+
+scenario text-copy-out-of-bounds [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- copy 1:address:array:character, 2, 4
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- [c]
+  ]
+]
+
+scenario text-copy-out-of-bounds-2 [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- copy 1:address:array:character, 3, 3
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:array:character <- []
+  ]
+]
+
+recipe min x:number, y:number -> z:number [
+  local-scope
+  load-ingredients
+  {
+    return-x?:boolean <- lesser-than x, y
+    break-if return-x?
+    reply y
+  }
+  reply x
+]
+
+recipe max x:number, y:number -> z:number [
+  local-scope
+  load-ingredients
+  {
+    return-x?:boolean <- greater-than x, y
+    break-if return-x?
+    reply y
+  }
+  reply x
+]
+
+ + + diff --git a/html/071channel.mu.html b/html/071channel.mu.html index 1bddb78c..ec4fc68d 100644 --- a/html/071channel.mu.html +++ b/html/071channel.mu.html @@ -67,10 +67,10 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] # result:address:channel <- new-channel capacity:number -recipe new-channel [ +recipe new-channel capacity:number -> result:address:channel [ local-scope - # result = new channel - result:address:channel <- new channel:type + load-ingredients + result <- new channel:type # result.first-full = 0 full:address:number <- get-address *result, first-full:offset *full <- copy 0 @@ -78,18 +78,14 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } free:address:number <- get-address *result, first-free:offset *free <- copy 0 # result.data = new location[ingredient+1] - capacity:number <- next-ingredient capacity <- add capacity, 1 # unused slot for 'full?' below dest:address:address:array:character <- get-address *result, data:offset *dest <- new character:type, capacity - reply result ] -# chan <- write chan:address:channel, val:character -recipe write [ +recipe write chan:address:channel, val:character -> chan:address:channel [ local-scope - chan:address:channel <- next-ingredient - val:character <- next-ingredient + load-ingredients { # block if chan is full full:boolean <- channel-full? chan @@ -111,13 +107,11 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } break-unless at-end? *free <- copy 0 } - reply chan/same-as-ingredient:0 ] -# result:character, chan <- read chan:address:channel -recipe read [ +recipe read chan:address:channel -> result:character, chan:address:channel [ local-scope - chan:address:channel <- next-ingredient + load-ingredients { # block if chan is empty empty?:boolean <- channel-empty? chan @@ -128,7 +122,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } # read result full:address:number <- get-address *chan, first-full:offset circular-buffer:address:array:character <- get *chan, data:offset - result:character <- index *circular-buffer, *full + result <- index *circular-buffer, *full # mark its slot as empty *full <- add *full, 1 { @@ -138,18 +132,16 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } break-unless at-end? *full <- copy 0 } - reply result, chan/same-as-ingredient:0 ] -recipe clear-channel [ +recipe clear-channel chan:address:channel -> chan:address:channel [ local-scope - chan:address:channel <- next-ingredient + load-ingredients { empty?:boolean <- channel-empty? chan break-if empty? _, chan <- read chan } - reply chan/same-as-ingredient:0 ] scenario channel-initialization [ @@ -219,21 +211,20 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ## helpers # An empty channel has first-empty and first-full both at the same value. -recipe channel-empty? [ +recipe channel-empty? chan:address:channel -> result:boolean [ local-scope - chan:address:channel <- next-ingredient + load-ingredients # return chan.first-full == chan.first-free full:number <- get *chan, first-full:offset free:number <- get *chan, first-free:offset - result:boolean <- equal full, free - reply result + result <- equal full, free ] # A full channel has first-empty just before first-full, wasting one slot. # (Other alternatives: https://en.wikipedia.org/wiki/Circular_buffer#Full_.2F_Empty_Buffer_Distinction) -recipe channel-full? [ +recipe channel-full? chan:address:channel -> result:boolean [ local-scope - chan:address:channel <- next-ingredient + load-ingredients # tmp = chan.first-free + 1 tmp:number <- get *chan, first-free:offset tmp <- add tmp, 1 @@ -246,17 +237,15 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } } # return chan.first-full == tmp full:number <- get *chan, first-full:offset - result:boolean <- equal full, tmp - reply result + result <- equal full, tmp ] # result:number <- channel-capacity chan:address:channel -recipe channel-capacity [ +recipe channel-capacity chan:address:channel -> result:number [ local-scope - chan:address:channel <- next-ingredient + load-ingredients q:address:array:character <- get *chan, data:offset - result:number <- length *q - reply result + result <- length *q ] scenario channel-new-empty-not-full [ @@ -312,11 +301,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] # helper for channels of characters in particular -# out <- buffer-lines in:address:channel, out:address:channel -recipe buffer-lines [ +recipe buffer-lines in:address:channel, out:address:channel -> out:address:channel, in:address:channel [ local-scope - in:address:channel <- next-ingredient - out:address:channel <- next-ingredient + load-ingredients # repeat forever { line:address:buffer <- new-buffer, 30 @@ -340,7 +327,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } loop +next-character:label } # append anything else - line <- buffer-append line, c + line <- append line, c line-done?:boolean <- equal c, 10/newline break-if line-done? # stop buffering on eof (currently only generated by fake console) @@ -362,7 +349,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } } loop } - reply out/same-as-ingredient:1 ] scenario buffer-lines-blocks-until-newline [ diff --git a/html/073list.mu.html b/html/073list.mu.html index c274436b..a14399dd 100644 --- a/html/073list.mu.html +++ b/html/073list.mu.html @@ -20,6 +20,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } .Comment { color: #9090ff; } .Constant { color: #00a0a0; } .Special { color: #ff6060; } +.CommentedCode { color: #6c6c6c; } .Delimiter { color: #a04060; } --> @@ -85,6 +86,92 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } 4 <- 3 ] ] + +recipe to-text in:address:list:_elem -> result:address:array:character [ + local-scope +#? $print [to text: list], 10/newline + load-ingredients + buf:address:buffer <- new-buffer 80 + buf <- to-buffer in, buf + result <- buffer-to-array buf +] + +# variant of 'to-text' which stops printing after a few elements (and so is robust to cycles) +recipe to-text-line in:address:list:_elem -> result:address:array:character [ + local-scope +#? $print [to text line: list], 10/newline + load-ingredients + buf:address:buffer <- new-buffer 80 + buf <- to-buffer in, buf, 6 # max elements to display + result <- buffer-to-array buf +] + +recipe to-buffer in:address:list:_elem, buf:address:buffer -> buf:address:buffer [ + local-scope +#? $print [to buffer: list], 10/newline + load-ingredients + { + break-if in + $print [000], 10/newline + buf <- append buf, 48/0 + reply + } + # append in.value to buf + val:_elem <- get *in, value:offset + buf <- append buf, val + # now prepare next + next:address:list:_elem <- rest in + nextn:number <- copy next +#? buf <- append buf, nextn + reply-unless next + space:character <- copy 32/space + buf <- append buf, space:character + s:address:array:character <- new [-> ] + n:number <- length *s + buf <- append buf, s + # and recurse + remaining:number, optional-ingredient-found?:boolean <- next-ingredient + { + break-if optional-ingredient-found? + # unlimited recursion + buf <- to-buffer next, buf + reply + } + { + break-unless remaining + # limited recursion + remaining <- subtract remaining, 1 + buf <- to-buffer next, buf, remaining + reply + } + # past recursion depth; insert ellipses and stop + s:address:array:character <- new [...] + append buf, s +] + +scenario stash-on-list-converts-to-text [ + run [ + x:address:list:number <- push 4, 0 + x <- push 5, x + x <- push 6, x + stash [foo foo], x + ] + trace-should-contain [ + app: foo foo 6 -> 5 -> 4 + ] +] + +scenario stash-handles-list-with-cycle [ + run [ + x:address:list:number <- push 4, 0 + y:address:address:list:number <- get-address *x, next:offset + *y <- copy x + stash [foo foo], x + ] + trace-should-contain [ + app: foo foo 4 -> 4 -> 4 -> 4 -> 4 -> 4 -> 4 -> ... + ] +] diff --git a/html/076stream.mu.html b/html/076stream.mu.html index feaa978b..91861d7d 100644 --- a/html/076stream.mu.html +++ b/html/076stream.mu.html @@ -13,7 +13,6 @@ pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; } body { font-family: monospace; color: #eeeeee; background-color: #080808; } * { font-size: 1.05em; } -.muControl { color: #c0a020; } .muRecipe { color: #ff8700; } .muData { color: #ffff00; } .Comment { color: #9090ff; } @@ -30,49 +29,46 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
-# new type to help incrementally read strings
+# new type to help incrementally read texts (arrays of characters)
 container stream [
   index:number
   data:address:array:character
 ]
 
-recipe new-stream [
+recipe new-stream s:address:array:character -> result:address:stream [
   local-scope
-  result:address:stream <- new stream:type
+  load-ingredients
+  result <- new stream:type
   i:address:number <- get-address *result, index:offset
   *i <- copy 0
   d:address:address:array:character <- get-address *result, data:offset
-  *d <- next-ingredient
-  reply result
+  *d <- copy s
 ]
 
-recipe rewind-stream [
+recipe rewind-stream in:address:stream -> in:address:stream [
   local-scope
-  in:address:stream <- next-ingredient
+  load-ingredients
   x:address:number <- get-address *in, index:offset
   *x <- copy 0
-  reply in/same-as-arg:0
 ]
 
-recipe read-line [
+recipe read-line in:address:stream -> result:address:array:character, in:address:stream [
   local-scope
-  in:address:stream <- next-ingredient
+  load-ingredients
   idx:address:number <- get-address *in, index:offset
   s:address:array:character <- get *in, data:offset
   next-idx:number <- find-next s, 10/newline, *idx
-  result:address:array:character <- string-copy s, *idx, next-idx
+  result <- copy s, *idx, next-idx
   *idx <- add next-idx, 1  # skip newline
-  reply result
 ]
 
-recipe end-of-stream? [
+recipe end-of-stream? in:address:stream -> result:boolean [
   local-scope
-  in:address:stream <- next-ingredient
+  load-ingredients
   idx:number <- get *in, index:offset
   s:address:array:character <- get *in, data:offset
   len:number <- length *s
-  result:boolean <- greater-or-equal idx, len
-  reply result
+  result <- greater-or-equal idx, len
 ]
 
diff --git a/html/081print.mu.html b/html/081print.mu.html index 19efbb1c..7bfdc7dc 100644 --- a/html/081print.mu.html +++ b/html/081print.mu.html @@ -48,13 +48,14 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } color:number ] -recipe new-fake-screen [ +recipe new-fake-screen w:number, h:number -> result:address:screen [ local-scope - result:address:screen <- new screen:type + load-ingredients + result <- new screen:type width:address:number <- get-address *result, num-columns:offset - *width <- next-ingredient + *width <- copy w height:address:number <- get-address *result, num-rows:offset - *height <- next-ingredient + *height <- copy h row:address:number <- get-address *result, cursor-row:offset *row <- copy 0 column:address:number <- get-address *result, cursor-column:offset @@ -62,18 +63,17 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } bufsize:number <- multiply *width, *height buf:address:address:array:screen-cell <- get-address *result, data:offset *buf <- new screen-cell:type, bufsize - clear-screen result - reply result + result <- clear-screen result ] -recipe clear-screen [ +recipe clear-screen screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists { - break-unless sc + break-unless screen # clear fake screen - buf:address:array:screen-cell <- get *sc, data:offset + buf:address:array:screen-cell <- get *screen, data:offset max:number <- length *buf i:number <- copy 0 { @@ -88,32 +88,31 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } loop } # reset cursor - x:address:number <- get-address *sc, cursor-row:offset + x:address:number <- get-address *screen, cursor-row:offset *x <- copy 0 - x <- get-address *sc, cursor-column:offset + x <- get-address *screen, cursor-column:offset *x <- copy 0 - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen clear-display - reply sc/same-as-ingredient:0 ] -recipe sync-screen [ +recipe sync-screen screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient + screen:address:screen <- next-ingredient { - break-if sc + break-if screen sync-display } # do nothing for fake screens ] -recipe fake-screen-is-empty? [ +recipe fake-screen-is-empty? screen:address:screen -> result:boolean [ local-scope - sc:address:screen <- next-ingredient - reply-unless sc, 1/true - buf:address:array:screen-cell <- get *sc, data:offset + load-ingredients + reply-unless screen, 1/true + buf:address:array:screen-cell <- get *screen, data:offset i:number <- copy 0 len:number <- length *buf { @@ -129,10 +128,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } reply 1/true ] -recipe print-character [ +recipe print screen:address:screen, c:character -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient - c:character <- next-ingredient + load-ingredients color:number, color-found?:boolean <- next-ingredient { # default color to white @@ -149,20 +147,20 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } { # if x exists # (handle special cases exactly like in the real screen) - break-unless sc - width:number <- get *sc, num-columns:offset - height:number <- get *sc, num-rows:offset + break-unless screen + width:number <- get *screen, num-columns:offset + height:number <- get *screen, num-rows:offset # if cursor is out of bounds, silently exit - row:address:number <- get-address *sc, cursor-row:offset + row:address:number <- get-address *screen, cursor-row:offset legal?:boolean <- greater-or-equal *row, 0 - reply-unless legal?, sc + reply-unless legal? legal? <- lesser-than *row, height - reply-unless legal?, sc - column:address:number <- get-address *sc, cursor-column:offset + reply-unless legal? + column:address:number <- get-address *screen, cursor-column:offset legal? <- greater-or-equal *column, 0 - reply-unless legal?, sc + reply-unless legal? legal? <- lesser-than *column, width - reply-unless legal?, sc + reply-unless legal? # special-case: newline { newline?:boolean <- equal c, 10/newline @@ -176,12 +174,12 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } *column <- copy 0 *row <- add *row, 1 } - reply sc/same-as-ingredient:0 + reply } # save character in fake screen index:number <- multiply *row, width index <- add index, *column - buf:address:array:screen-cell <- get *sc, data:offset + buf:address:array:screen-cell <- get *screen, data:offset len:number <- length *buf # special-case: backspace { @@ -200,7 +198,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } cursor-color:address:number <- get-address *cursor, color:offset *cursor-color <- copy 7/white } - reply sc/same-as-ingredient:0 + reply } cursor:address:screen-cell <- index-address *buf, index cursor-contents:address:character <- get-address *cursor, contents:offset @@ -214,17 +212,16 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } break-if at-right? *column <- add *column, 1 } - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen print-character-to-display c, color, bg-color - reply sc/same-as-ingredient:0 ] scenario print-character-at-top-left [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height - 1:address:screen <- print-character 1:address:screen, 97 # 'a' + 1:address:screen <- print 1:address:screen, 97 # 'a' 2:address:array:screen-cell <- get *1:address:screen, data:offset 3:array:screen-cell <- copy *2:address:array:screen-cell ] @@ -236,10 +233,10 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -scenario print-character-color [ +scenario print-character-in-color [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height - 1:address:screen <- print-character 1:address:screen, 97/a, 1/red + 1:address:screen <- print 1:address:screen, 97/a, 1/red 2:address:array:screen-cell <- get *1:address:screen, data:offset 3:array:screen-cell <- copy *2:address:array:screen-cell ] @@ -254,8 +251,8 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } scenario print-backspace-character [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height - 1:address:screen <- print-character 1:address:screen, 97 # 'a' - 1:address:screen <- print-character 1:address:screen, 8 # backspace + 1:address:screen <- print 1:address:screen, 97 # 'a' + 1:address:screen <- print 1:address:screen, 8 # backspace 2:number <- get *1:address:screen, cursor-column:offset 3:address:array:screen-cell <- get *1:address:screen, data:offset 4:array:screen-cell <- copy *3:address:array:screen-cell @@ -272,9 +269,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } scenario print-extra-backspace-character [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height - 1:address:screen <- print-character 1:address:screen, 97 # 'a' - 1:address:screen <- print-character 1:address:screen, 8 # backspace - 1:address:screen <- print-character 1:address:screen, 8 # backspace + 1:address:screen <- print 1:address:screen, 97 # 'a' + 1:address:screen <- print 1:address:screen, 8 # backspace + 1:address:screen <- print 1:address:screen, 8 # backspace 2:number <- get *1:address:screen, cursor-column:offset 3:address:array:screen-cell <- get *1:address:screen, data:offset 4:array:screen-cell <- copy *3:address:array:screen-cell @@ -288,12 +285,12 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -scenario print-at-right-margin [ +scenario print-character-at-right-margin [ run [ 1:address:screen <- new-fake-screen 2/width, 2/height - 1:address:screen <- print-character 1:address:screen, 97 # 'a' - 1:address:screen <- print-character 1:address:screen, 98 # 'b' - 1:address:screen <- print-character 1:address:screen, 99 # 'c' + 1:address:screen <- print 1:address:screen, 97 # 'a' + 1:address:screen <- print 1:address:screen, 98 # 'b' + 1:address:screen <- print 1:address:screen, 99 # 'c' 2:number <- get *1:address:screen, cursor-column:offset 3:address:array:screen-cell <- get *1:address:screen, data:offset 4:array:screen-cell <- copy *3:address:array:screen-cell @@ -312,8 +309,8 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } scenario print-newline-character [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height - 1:address:screen <- print-character 1:address:screen, 97 # 'a' - 1:address:screen <- print-character 1:address:screen, 10/newline + 1:address:screen <- print 1:address:screen, 97 # 'a' + 1:address:screen <- print 1:address:screen, 10/newline 2:number <- get *1:address:screen, cursor-row:offset 3:number <- get *1:address:screen, cursor-column:offset 4:address:array:screen-cell <- get *1:address:screen, data:offset @@ -332,9 +329,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } scenario print-newline-at-bottom-line [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height - 1:address:screen <- print-character 1:address:screen, 10/newline - 1:address:screen <- print-character 1:address:screen, 10/newline - 1:address:screen <- print-character 1:address:screen, 10/newline + 1:address:screen <- print 1:address:screen, 10/newline + 1:address:screen <- print 1:address:screen, 10/newline + 1:address:screen <- print 1:address:screen, 10/newline 2:number <- get *1:address:screen, cursor-row:offset 3:number <- get *1:address:screen, cursor-column:offset ] @@ -344,15 +341,15 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -scenario print-at-bottom-right [ +scenario print-character-at-bottom-right [ run [ 1:address:screen <- new-fake-screen 2/width, 2/height - 1:address:screen <- print-character 1:address:screen, 10/newline - 1:address:screen <- print-character 1:address:screen, 97 # 'a' - 1:address:screen <- print-character 1:address:screen, 98 # 'b' - 1:address:screen <- print-character 1:address:screen, 99 # 'c' - 1:address:screen <- print-character 1:address:screen, 10/newline - 1:address:screen <- print-character 1:address:screen, 100 # 'd' + 1:address:screen <- print 1:address:screen, 10/newline + 1:address:screen <- print 1:address:screen, 97 # 'a' + 1:address:screen <- print 1:address:screen, 98 # 'b' + 1:address:screen <- print 1:address:screen, 99 # 'c' + 1:address:screen <- print 1:address:screen, 10/newline + 1:address:screen <- print 1:address:screen, 100 # 'd' 2:number <- get *1:address:screen, cursor-row:offset 3:number <- get *1:address:screen, cursor-column:offset 4:address:array:screen-cell <- get *1:address:screen, data:offset @@ -374,70 +371,65 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -recipe clear-line [ +recipe clear-line screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, clear line in fake screen { - break-unless sc - width:number <- get *sc, num-columns:offset - column:address:number <- get-address *sc, cursor-column:offset + break-unless screen + width:number <- get *screen, num-columns:offset + column:address:number <- get-address *screen, cursor-column:offset original-column:number <- copy *column # space over the entire line { right:number <- subtract width, 1 done?:boolean <- greater-or-equal *column, right break-if done? - print-character sc, [ ] # implicitly updates 'column' + print screen, [ ] # implicitly updates 'column' loop } # now back to where the cursor was *column <- copy original-column - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen clear-line-on-display - reply sc/same-as-ingredient:0 ] -recipe cursor-position [ +recipe cursor-position screen:address:screen -> row:number, column:number [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, lookup cursor in fake screen { - break-unless sc - row:number <- get *sc, cursor-row:offset - column:number <- get *sc, cursor-column:offset - reply row, column, sc/same-as-ingredient:0 + break-unless screen + row:number <- get *screen, cursor-row:offset + column:number <- get *screen, cursor-column:offset + reply } row, column <- cursor-position-on-display - reply row, column, sc/same-as-ingredient:0 ] -recipe move-cursor [ +recipe move-cursor screen:address:screen, new-row:number, new-column:number -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient - new-row:number <- next-ingredient - new-column:number <- next-ingredient + load-ingredients # if x exists, move cursor in fake screen { - break-unless sc - row:address:number <- get-address *sc, cursor-row:offset + break-unless screen + row:address:number <- get-address *screen, cursor-row:offset *row <- copy new-row - column:address:number <- get-address *sc, cursor-column:offset + column:address:number <- get-address *screen, cursor-column:offset *column <- copy new-column - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen move-cursor-on-display new-row, new-column - reply sc/same-as-ingredient:0 ] scenario clear-line-erases-printed-characters [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height # print a character - 1:address:screen <- print-character 1:address:screen, 97 # 'a' + 1:address:screen <- print 1:address:screen, 97 # 'a' # move cursor to start of line 1:address:screen <- move-cursor 1:address:screen, 0/row, 0/column # clear line @@ -463,193 +455,180 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -recipe cursor-down [ +recipe cursor-down screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, move cursor in fake screen { - break-unless sc + break-unless screen { # increment row unless it's already all the way down - height:number <- get *sc, num-rows:offset - row:address:number <- get-address *sc, cursor-row:offset + height:number <- get *screen, num-rows:offset + row:address:number <- get-address *screen, cursor-row:offset max:number <- subtract height, 1 at-bottom?:boolean <- greater-or-equal *row, max break-if at-bottom? *row <- add *row, 1 } - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen move-cursor-down-on-display - reply sc/same-as-ingredient:0 ] -recipe cursor-up [ +recipe cursor-up screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, move cursor in fake screen { - break-unless sc + break-unless screen { # decrement row unless it's already all the way up - row:address:number <- get-address *sc, cursor-row:offset + row:address:number <- get-address *screen, cursor-row:offset at-top?:boolean <- lesser-or-equal *row, 0 break-if at-top? *row <- subtract *row, 1 } - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen move-cursor-up-on-display - reply sc/same-as-ingredient:0 ] -recipe cursor-right [ +recipe cursor-right screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, move cursor in fake screen { - break-unless sc + break-unless screen { # increment column unless it's already all the way to the right - width:number <- get *sc, num-columns:offset - column:address:number <- get-address *sc, cursor-column:offset + width:number <- get *screen, num-columns:offset + column:address:number <- get-address *screen, cursor-column:offset max:number <- subtract width, 1 at-bottom?:boolean <- greater-or-equal *column, max break-if at-bottom? *column <- add *column, 1 } - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen move-cursor-right-on-display - reply sc/same-as-ingredient:0 ] -recipe cursor-left [ +recipe cursor-left screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, move cursor in fake screen { - break-unless sc + break-unless screen { # decrement column unless it's already all the way to the left - column:address:number <- get-address *sc, cursor-column:offset + column:address:number <- get-address *screen, cursor-column:offset at-top?:boolean <- lesser-or-equal *column, 0 break-if at-top? *column <- subtract *column, 1 } - reply sc/same-as-ingredient:0 + reply } # otherwise, real screen move-cursor-left-on-display - reply sc/same-as-ingredient:0 ] -recipe cursor-to-start-of-line [ +recipe cursor-to-start-of-line screen:address:screen -> screen:address:screen [ local-scope - sc:address:screen <- next-ingredient - row:number, _, sc <- cursor-position sc + load-ingredients + row:number <- cursor-position screen column:number <- copy 0 - sc <- move-cursor sc, row, column - reply sc/same-as-ingredient:0 + screen <- move-cursor screen, row, column ] -recipe cursor-to-next-line [ +recipe cursor-to-next-line screen:address:screen -> screen:address:screen [ local-scope - screen:address:screen <- next-ingredient + load-ingredients screen <- cursor-down screen screen <- cursor-to-start-of-line screen - reply screen/same-as-ingredient:0 ] -recipe screen-width [ +recipe screen-width screen:address:screen -> width:number [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, move cursor in fake screen { - break-unless sc - width:number <- get *sc, num-columns:offset - reply width + break-unless screen + width <- get *screen, num-columns:offset + reply } # otherwise, real screen - width:number <- display-width - reply width + width <- display-width ] -recipe screen-height [ +recipe screen-height screen:address:screen -> height:number [ local-scope - sc:address:screen <- next-ingredient + load-ingredients # if x exists, move cursor in fake screen { - break-unless sc - height:number <- get *sc, num-rows:offset - reply height + break-unless screen + height <- get *screen, num-rows:offset + reply } # otherwise, real screen - height:number <- display-height - reply height + height <- display-height ] -recipe hide-cursor [ +recipe hide-cursor screen:address:screen -> screen:address:screen [ local-scope - screen:address:screen <- next-ingredient + load-ingredients # if x exists (not real display), do nothing { break-unless screen - reply screen + reply } # otherwise, real screen hide-cursor-on-display - reply screen ] -recipe show-cursor [ +recipe show-cursor screen:address:screen -> screen:address:screen [ local-scope - screen:address:screen <- next-ingredient + load-ingredients # if x exists (not real display), do nothing { break-unless screen - reply screen + reply } # otherwise, real screen show-cursor-on-display - reply screen ] -recipe hide-screen [ +recipe hide-screen screen:address:screen -> screen:address:screen [ local-scope - screen:address:screen <- next-ingredient + load-ingredients # if x exists (not real display), do nothing # todo: help test this { break-unless screen - reply screen + reply } # otherwise, real screen hide-display - reply screen ] -recipe show-screen [ +recipe show-screen screen:address:screen -> screen:address:screen [ local-scope - screen:address:screen <- next-ingredient + load-ingredients # if x exists (not real display), do nothing # todo: help test this { break-unless screen - reply screen + reply } # otherwise, real screen show-display - reply screen ] -recipe print-string [ +recipe print screen:address:screen, s:address:array:character -> screen:address:screen [ local-scope - screen:address:screen <- next-ingredient - s:address:array:character <- next-ingredient + load-ingredients color:number, color-found?:boolean <- next-ingredient { # default color to white @@ -668,18 +647,17 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } done?:boolean <- greater-or-equal i, len break-if done? c:character <- index *s, i - print-character screen, c, color, bg-color + print screen, c, color, bg-color i <- add i, 1 loop } - reply screen/same-as-ingredient:0 ] -scenario print-string-stops-at-right-margin [ +scenario print-text-stops-at-right-margin [ run [ 1:address:screen <- new-fake-screen 3/width, 2/height 2:address:array:character <- new [abcd] - 1:address:screen <- print-string 1:address:screen, 2:address:array:character + 1:address:screen <- print 1:address:screen, 2:address:array:character 3:address:array:screen-cell <- get *1:address:screen, data:offset 4:array:screen-cell <- copy *3:address:array:screen-cell ] @@ -695,7 +673,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -recipe print-integer [ +recipe print-integer screen:address:screen, n:number -> screen:address:screen [ local-scope screen:address:screen <- next-ingredient n:number <- next-ingredient @@ -712,9 +690,8 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } bg-color <- copy 0/black } # todo: other bases besides decimal - s:address:array:character <- integer-to-decimal-string n - print-string screen, s, color, bg-color - reply screen/same-as-ingredient:0 + s:address:array:character <- to-text n + screen <- print screen, s, color, bg-color ] diff --git a/html/082scenario_screen.cc.html b/html/082scenario_screen.cc.html index 037fc9ff..bca387d9 100644 --- a/html/082scenario_screen.cc.html +++ b/html/082scenario_screen.cc.html @@ -21,6 +21,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } .Special { color: #ff6060; } .Identifier { color: #804000; } .Constant { color: #00a0a0; } +.CommentedCode { color: #6c6c6c; } --> @@ -43,7 +44,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } scenario screen-in-scenario [ assume-screen 5/width, 3/height run [ - screen:address:screen <- print-character screen:address:screen, 97 # 'a' + screen:address:screen <- print screen:address:screen, 97 # 'a' ] screen-should-contain [ # 01234 @@ -57,8 +58,8 @@ scenario screen-in-scenario [ scenario screen-in-scenario-unicode-color [ assume-screen 5/width, 3/height run [ - screen:address:screen <- print-character screen:address:screen, 955/greek-small-lambda, 1/red - screen:address:screen <- print-character screen:address:screen, 97/a + screen:address:screen <- print screen:address:screen, 955/greek-small-lambda, 1/red + screen:address:screen <- print screen:address:screen, 97/a ] screen-should-contain [ # 01234 @@ -73,8 +74,8 @@ scenario screen-in-scenario-unicode-color [ scenario screen-in-scenario-color [ assume-screen 5/width, 3/height run [ - screen:address:screen <- print-character screen:address:screen, 955/greek-small-lambda, 1/red - screen:address:screen <- print-character screen:address:screen, 97/a, 7/white + screen:address:screen <- print screen:address:screen, 955/greek-small-lambda, 1/red + screen:address:screen <- print screen:address:screen, 97/a, 7/white ] # screen-should-contain shows everything screen-should-contain [ @@ -106,7 +107,7 @@ scenario screen-in-scenario-color [ scenario screen-in-scenario-error [ assume-screen 5/width, 3/height run [ - screen:address:screen <- print-character screen:address:screen, 97 # 'a' + screen:address:screen <- print screen:address:screen, 97 # 'a' ] screen-should-contain [ # 01234 @@ -124,7 +125,7 @@ scenario screen-in-scenario-error [ scenario screen-in-scenario-color [ assume-screen 5/width, 3/height run [ - screen:address:screen <- print-character screen:address:screen, 97/a, 1/red + screen:address:screen <- print screen:address:screen, 97/a, 1/red ] screen-should-contain-in-color 2/green, [ # 01234 @@ -192,6 +193,8 @@ case SCREEN_SHOULD_CONTAIN: { } :(before "End Primitive Recipe Implementations") case SCREEN_SHOULD_CONTAIN: { +//? cerr << SIZE(get(Recipe_variants, "insert")) << '\n'; +//? cerr << debug_string(get(Recipe, get(Recipe_ordinal, "insert_4"))) << '\n'; if (!Passed) break; check_screen(current_instruction().ingredients.at(0).name, -1); break; diff --git a/html/083scenario_screen_test.mu.html b/html/083scenario_screen_test.mu.html index a93c7c20..2fdd57bc 100644 --- a/html/083scenario_screen_test.mu.html +++ b/html/083scenario_screen_test.mu.html @@ -33,7 +33,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } scenario print-character-at-top-left-2 [ assume-screen 3/width, 2/height run [ - screen:address:screen <- print-character screen:address:screen, 97/a + screen:address:screen <- print screen:address:screen, 97/a ] screen-should-contain [ .a . @@ -45,7 +45,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } assume-screen 5/width, 3/height run [ # print a character - screen:address:screen <- print-character screen:address:screen, 97/a + screen:address:screen <- print screen:address:screen, 97/a # move cursor to start of line screen:address:screen <- move-cursor screen:address:screen, 0/row, 0/column # clear line diff --git a/html/084console.mu.html b/html/084console.mu.html index c91544f9..b8619762 100644 --- a/html/084console.mu.html +++ b/html/084console.mu.html @@ -58,45 +58,45 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } data:address:array:event ] -recipe new-fake-console [ +recipe new-fake-console events:address:array:event -> result:address:console [ local-scope + load-ingredients result:address:console <- new console:type buf:address:address:array:event <- get-address *result, data:offset - *buf <- next-ingredient + *buf <- copy events idx:address:number <- get-address *result, index:offset *idx <- copy 0 - reply result ] -recipe read-event [ +recipe read-event console:address:console -> result:event, console:address:console, found?:boolean, quit?:boolean [ local-scope - x:address:console <- next-ingredient + load-ingredients { - break-unless x - idx:address:number <- get-address *x, index:offset - buf:address:array:event <- get *x, data:offset + break-unless console + idx:address:number <- get-address *console, index:offset + buf:address:array:event <- get *console, data:offset { max:number <- length *buf done?:boolean <- greater-or-equal *idx, max break-unless done? dummy:address:event <- new event:type - reply *dummy, x/same-as-ingredient:0, 1/found, 1/quit + reply *dummy, console/same-as-ingredient:0, 1/found, 1/quit } - result:event <- index *buf, *idx + result <- index *buf, *idx *idx <- add *idx, 1 - reply result, x/same-as-ingredient:0, 1/found, 0/quit + reply result, console/same-as-ingredient:0, 1/found, 0/quit } switch # real event source is infrequent; avoid polling it too much result:event, found?:boolean <- check-for-interaction - reply result, x/same-as-ingredient:0, found?, 0/quit + reply result, console/same-as-ingredient:0, found?, 0/quit ] # variant of read-event for just keyboard events. Discards everything that # isn't unicode, so no arrow keys, page-up/page-down, etc. But you still get # newlines, tabs, ctrl-d.. -recipe read-key [ +recipe read-key console:address:console -> result:character, console:address:console, found?:boolean, quit?:boolean [ local-scope - console:address:console <- next-ingredient + load-ingredients x:event, console, found?:boolean, quit?:boolean <- read-event console reply-if quit?, 0, console/same-as-ingredient:0, found?, quit? reply-unless found?, 0, console/same-as-ingredient:0, found?, quit? @@ -105,44 +105,39 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } reply *c, console/same-as-ingredient:0, 1/found, 0/quit ] -recipe send-keys-to-channel [ +recipe send-keys-to-channel console:address:console, chan:address:channel, screen:address:screen -> console:address:console, chan:address:channel, screen:address:screen [ local-scope - console:address:console <- next-ingredient - chan:address:channel <- next-ingredient - screen:address:screen <- next-ingredient + load-ingredients { c:character, console, found?:boolean, quit?:boolean <- read-key console loop-unless found? break-if quit? assert c, [invalid event, expected text] - screen <- print-character screen, c + screen <- print screen, c chan <- write chan, c loop } - reply console/same-as-ingredient:0, chan/same-as-ingredient:1, screen/same-as-ingredient:2 ] -recipe wait-for-event [ +recipe wait-for-event console:address:console -> console:address:console [ local-scope - console:address:console <- next-ingredient + load-ingredients { _, console, found?:boolean <- read-event console loop-unless found? } - reply console/same-as-ingredient:0 ] # use this helper to skip rendering if there's lots of other events queued up -recipe has-more-events? [ +recipe has-more-events? console:address:console -> result:boolean [ local-scope - console:address:console <- next-ingredient + load-ingredients { break-unless console # fake consoles should be plenty fast; never skip reply 0/false } - result:boolean <- interactions-left? - reply result + result <- interactions-left? ] diff --git a/html/090trace_browser.cc.html b/html/090trace_browser.cc.html index 62aa2fbe..3da39a9b 100644 --- a/html/090trace_browser.cc.html +++ b/html/090trace_browser.cc.html @@ -29,6 +29,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
+//: A debugging helper that lets you zoom in/out on a trace.
+
+//: browse the trace we just created
 :(before "End Primitive Recipe Declarations")
 _BROWSE_TRACE,
 :(before "End Primitive Recipe Numbers")
@@ -43,6 +46,14 @@ case _BROWSE_TRACE: {
   break;
 }
 
+//: browse a trace loaded from a file
+:(after "Commandline Parsing")
+if (argc == 3 && is_equal(argv[1], "browse-trace")) {
+  load_trace(argv[2]);
+  start_trace_browser();
+  return 0;
+}
+
 :(before "End Globals")
 set<long long int> Visible;
 long long int Top_of_screen = 0;
@@ -242,6 +253,25 @@ void render_line(int screen_row(col, screen_row, ' ', TB_WHITE, TB_BLACK);
   }
 }
+
+void load_trace(const char* filename) {
+  ifstream tin(filename);
+  if (!tin) {
+    cerr << "no such file: " << filename << '\n';
+    exit(1);
+  }
+  Trace_stream = new trace_stream;
+  while (has_data(tin)) {
+    int depth;
+    tin >> depth;
+    string label;
+    tin >> label;
+    if (*--label.end() == ':') label.erase(--label.end());
+    string line;
+    getline(tin, line);
+    Trace_stream->past_lines.push_back(trace_line(depth, label, line));
+  }
+}
 
diff --git a/html/091run_interactive.cc.html b/html/091run_interactive.cc.html index b96eeb50..94a1284a 100644 --- a/html/091run_interactive.cc.html +++ b/html/091run_interactive.cc.html @@ -15,6 +15,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } * { font-size: 1.05em; } .traceContains { color: #008000; } .cSpecial { color: #008000; } +.CommentedCode { color: #6c6c6c; } .Comment { color: #9090ff; } .Delimiter { color: #a04060; } .Special { color: #ff6060; } @@ -45,7 +46,7 @@ recipe main [ :(scenario run_interactive_empty) recipe main [ - 1:address:array:character <- copy 0/raw + 1:address:array:character <- copy 0/unsafe 2:address:array:character <- run-interactive 1:address:array:character ] # result is null @@ -140,6 +141,7 @@ bool run_interactive(long long int address} void run_code_begin() { +//? cerr << "loading new trace\n"; // stuff to undo later, in run_code_end() Hide_warnings = true; Hide_errors = true; @@ -152,6 +154,7 @@ void run_code_begin() {} void run_code_end() { +//? cerr << "back to old trace\n"; Hide_warnings = false; Hide_errors = false; Disable_redefine_warnings = false; @@ -160,6 +163,7 @@ void run_code_end() {NULL; Trace_file = Save_trace_file; Save_trace_file.clear(); + Recipe.erase(get(Recipe_ordinal, "interactive")); // keep past sandboxes from inserting errors } :(before "End Load Recipes") @@ -283,7 +287,7 @@ case _CLEANUP_RUN_INTERACTIVE: { break; } -:(scenario "run_interactive_returns_stringified_result") +:(scenario "run_interactive_converts_result_to_text") recipe main [ # try to interactively add 2 and 2 1:address:array:character <- new [add 2, 2] @@ -293,13 +297,13 @@ recipe main [ # first letter in the output should be '4' in unicode +mem: storing 52 in location 11 -:(scenario "run_interactive_returns_string") +:(scenario "run_interactive_returns_text") recipe main [ # try to interactively add 2 and 2 1:address:array:character <- new [ x:address:array:character <- new [a] y:address:array:character <- new [b] - z:address:array:character <- string-append x:address:array:character, y:address:array:character + z:address:array:character <- append x:address:array:character, y:address:array:character ] 2:address:array:character <- run-interactive 1:address:array:character 10:array:character <- copy 2:address:array:character/lookup @@ -450,25 +454,45 @@ case RELOAD: { } :(before "End Primitive Recipe Implementations") case RELOAD: { +//? cerr << "== reload\n"; // clear any containers in advance for (long long int i = 0; i < SIZE(recently_added_types); ++i) { Type_ordinal.erase(get(Type, recently_added_types.at(i)).name); Type.erase(recently_added_types.at(i)); } + for (map<string, vector<recipe_ordinal> >::iterator p = Recipe_variants.begin(); p != Recipe_variants.end(); ++p) { +//? cerr << p->first << ":\n"; + vector<recipe_ordinal>& variants = p->second; + for (long long int i = 0; i < SIZE(p->second); ++i) { + if (variants.at(i) == -1) continue; + if (find(recently_added_shape_shifting_recipes.begin(), recently_added_shape_shifting_recipes.end(), variants.at(i)) != recently_added_shape_shifting_recipes.end()) { +//? cerr << " " << variants.at(i) << ' ' << get(Recipe, variants.at(i)).name << '\n'; + variants.at(i) = -1; // ghost + } + } + } + for (long long int i = 0; i < SIZE(recently_added_shape_shifting_recipes); ++i) { +//? cerr << "erasing " << get(Recipe, recently_added_shape_shifting_recipes.at(i)).name << '\n'; + Recipe_ordinal.erase(get(Recipe, recently_added_shape_shifting_recipes.at(i)).name); + Recipe.erase(recently_added_shape_shifting_recipes.at(i)); + } + recently_added_shape_shifting_recipes.clear(); string code = read_mu_string(ingredients.at(0).at(0)); run_code_begin(); routine* save_current_routine = Current_routine; Current_routine = NULL; vector<recipe_ordinal> recipes_reloaded = load(code); - for (long long int i = 0; i < SIZE(recipes_reloaded); ++i) { + // clear a few things from previous runs + // ad hoc list; we've probably missed a few + for (long long int i = 0; i < SIZE(recipes_reloaded); ++i) Name.erase(recipes_reloaded.at(i)); - } transform_all(); Trace_stream->newline(); // flush trace Current_routine = save_current_routine; products.resize(1); products.at(0).push_back(trace_error_warning_contents()); run_code_end(); // wait until we're done with the trace contents +//? cerr << "reload done\n"; break; } diff --git a/html/092persist.cc.html b/html/092persist.cc.html index cce4913f..c2056b8c 100644 --- a/html/092persist.cc.html +++ b/html/092persist.cc.html @@ -88,7 +88,7 @@ string slurp(const string& filename(!fin) return result.str(); // don't bother checking errno const int N = 1024; char buf[N]; - while (!fin.eof()) { + while (has_data(fin)) { bzero(buf, N); fin.read(buf, N-1); // leave at least one null result << buf; diff --git a/html/998check_type_pointers.cc.html b/html/998check_type_pointers.cc.html index 6532702e..308113a8 100644 --- a/html/998check_type_pointers.cc.html +++ b/html/998check_type_pointers.cc.html @@ -13,10 +13,8 @@ pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; } body { font-family: monospace; color: #eeeeee; background-color: #080808; } * { font-size: 1.05em; } -.cSpecial { color: #008000; } -.Constant { color: #00a0a0; } -.Delimiter { color: #a04060; } -.Identifier { color: #804000; } +.Comment { color: #9090ff; } +.CommentedCode { color: #6c6c6c; } --> @@ -28,39 +26,42 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
-:(before "End Transform All")
-check_type_pointers();
-
-:(code)
-void check_type_pointers() {
-  for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
-    if (any_type_ingredient_in_header(p->first)) continue;
-    const recipe& r = p->second;
-    for (long long int i = 0; i < SIZE(r.steps); ++i) {
-      const instruction& inst = r.steps.at(i);
-      for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
-        if (!inst.ingredients.at(j).type) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type\n" << end();
-          return;
-        }
-        if (!inst.ingredients.at(j).properties.at(0).second) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type name\n" << end();
-          return;
-        }
-      }
-      for (long long int j = 0; j < SIZE(inst.products); ++j) {
-        if (!inst.products.at(j).type) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type\n" << end();
-          return;
-        }
-        if (!inst.products.at(j).properties.at(0).second) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type name\n" << end();
-          return;
-        }
-      }
-    }
-  }
-}
+//: enable this when tracking down null types
+//: (but it interferes with edit/; since recipes created in the environment
+//: can raise warnings here which will stop running the entire environment)
+//? :(before "End Transform All")
+//? check_type_pointers();
+//? 
+//? :(code)
+//? void check_type_pointers() {
+//?   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
+//?     if (any_type_ingredient_in_header(p->first)) continue;
+//?     const recipe& r = p->second;
+//?     for (long long int i = 0; i < SIZE(r.steps); ++i) {
+//?       const instruction& inst = r.steps.at(i);
+//?       for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
+//?         if (!inst.ingredients.at(j).type) {
+//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type\n" << end();
+//?           return;
+//?         }
+//?         if (!inst.ingredients.at(j).properties.at(0).second) {
+//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type name\n" << end();
+//?           return;
+//?         }
+//?       }
+//?       for (long long int j = 0; j < SIZE(inst.products); ++j) {
+//?         if (!inst.products.at(j).type) {
+//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type\n" << end();
+//?           return;
+//?         }
+//?         if (!inst.products.at(j).properties.at(0).second) {
+//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type name\n" << end();
+//?           return;
+//?         }
+//?       }
+//?     }
+//?   }
+//? }
 
diff --git a/html/999spaces.cc.html b/html/999spaces.cc.html index dac58e9d..531efb53 100644 --- a/html/999spaces.cc.html +++ b/html/999spaces.cc.html @@ -61,6 +61,57 @@ assert(Next_recipe_ordinal == (Initial_callstack_depth == 101); assert(Max_callstack_depth == 9989); //: 9990-9999 - intra-instruction lines (mostly label mem) + +//:: Summary of transforms and their dependencies +//: begin transforms +//: begin instruction inserting transforms +//: 52 insert fragments +//: ↳ 52.2 check fragments +//: --- +//: 53 rewrite 'stash' instructions +//: end instruction inserting transforms +//: +//: begin instruction modifying transforms +//: 56.2 check header ingredients +//: ↳ 56.4 fill in reply ingredients +//: +//: begin type modifying transforms +//: 56.3 deduce types from header +//: 48 check or set types by name +//: --- +//: 30 check or set invalid containers +//: end type modifying transforms +//: ↳ 57 static dispatch +//: --- +//: 13 update instruction operation +//: 40 transform braces +//: 41 transform labels +//: +//: ↱ 46 collect surrounding spaces +//: 42 transform names +//: end instruction modifying transforms +//: +//: begin checks +//: --- +//: 21 check instruction +//: ↳ 43 transform 'new' to 'allocate' +//: +//: 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 diff --git a/html/channel.mu.html b/html/channel.mu.html index 12babbde..15a75d1f 100644 --- a/html/channel.mu.html +++ b/html/channel.mu.html @@ -32,10 +32,10 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 # example program: communicating between routines using channels
 
-recipe producer [
+recipe producer chan:address:channel -> chan:address:channel [
   # produce characters 1 to 5 on a channel
   local-scope
-  chan:address:channel <- next-ingredient
+  load-ingredients
   # n = 0
   n:character <- copy 0
   {
@@ -50,10 +50,10 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   }
 ]
 
-recipe consumer [
+recipe consumer chan:address:channel -> chan:address:channel [
   # consume and print integers from a channel
   local-scope
-  chan:address:channel <- next-ingredient
+  load-ingredients
   {
     # read an integer from the channel
     n:character, chan:address:channel <- read chan
diff --git a/html/chessboard.mu.html b/html/chessboard.mu.html
index 5e11ad01..338e92dc 100644
--- a/html/chessboard.mu.html
+++ b/html/chessboard.mu.html
@@ -15,14 +15,14 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 * { font-size: 1.05em; }
 .muControl { color: #c0a020; }
 .muRecipe { color: #ff8700; }
-.SalientComment { color: #00ffff; }
+.muScenario { color: #00af00; }
 .muData { color: #ffff00; }
+.Special { color: #ff6060; }
 .Comment { color: #9090ff; }
 .Constant { color: #00a0a0; }
-.Special { color: #ff6060; }
+.SalientComment { color: #00ffff; }
 .CommentedCode { color: #6c6c6c; }
 .Delimiter { color: #a04060; }
-.muScenario { color: #00af00; }
 -->
 
 
@@ -53,7 +53,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   #
   # Here the console and screen are both 0, which usually indicates real
   # hardware rather than a fake for testing as you'll see below.
-  0/screen, 0/console <- chessboard 0/screen, 0/console
+  chessboard 0/screen, 0/console
 
   close-console  # cleanup screen, keyboard and mouse
 ]
@@ -72,7 +72,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   run [
     screen:address:screen, console:address:console <- chessboard screen:address:screen, console:address:console
     # icon for the cursor
-    screen <- print-character screen, 9251/␣
+    screen <- print screen, 9251/␣
   ]
   screen-should-contain [
   #            1         2         3         4         5         6         7         8         9         10        11
@@ -102,10 +102,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 
 ## Here's how 'chessboard' is implemented.
 
-recipe chessboard [
+recipe chessboard screen:address:screen, console:address:console -> screen:address:screen, console:address:console [
   local-scope
-  screen:address:screen <- next-ingredient
-  console:address:console <- next-ingredient
+  load-ingredients
   board:address:array:address:array:character <- initial-position
   # hook up stdin
   stdin:address:channel <- new-channel 10/capacity
@@ -116,28 +115,28 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   {
     msg:address:array:character <- new [Stupid text-mode chessboard. White pieces in uppercase; black pieces in lowercase. No checking for legal moves.
 ]
-    print-string screen, msg
+    print screen, msg
     cursor-to-next-line screen
     print-board screen, board
     cursor-to-next-line screen
     msg <- new [Type in your move as <from square>-<to square>. For example: 'a2-a4'. Then press <enter>.
 ]
-    print-string screen, msg
+    print screen, msg
     cursor-to-next-line screen
     msg <- new [Hit 'q' to exit.
 ]
-    print-string screen, msg
+    print screen, msg
     {
       cursor-to-next-line screen
       msg <- new [move: ]
-      print-string screen, msg
+      screen <- print screen, msg
       m:address:move, quit:boolean, error:boolean <- read-move buffered-stdin, screen
       break-if quit, +quit:label
       buffered-stdin <- clear-channel buffered-stdin  # cleanup after error. todo: test this?
       loop-if error
     }
     board <- make-move board, m
-    clear-screen screen
+    screen <- clear-screen screen
     loop
   }
   +quit
@@ -145,15 +144,15 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 
 ## a board is an array of files, a file is an array of characters (squares)
 
-recipe new-board [
+recipe new-board initial-position:address:array:character -> board:address:array:address:array:character [
   local-scope
-  initial-position:address:array:character <- next-ingredient
+  load-ingredients
   # assert(length(initial-position) == 64)
   len:number <- length *initial-position
   correct-length?:boolean <- equal len, 64
   assert correct-length?, [chessboard had incorrect size]
   # board is an array of pointers to files; file is an array of characters
-  board:address:array:address:array:character <- new location:type, 8
+  board <- new location:type, 8
   col:number <- copy 0
   {
     done?:boolean <- equal col, 8
@@ -163,15 +162,13 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     col <- add col, 1
     loop
   }
-  reply board
 ]
 
-recipe new-file [
+recipe new-file position:address:array:character, index:number -> result:address:array:character [
   local-scope
-  position:address:array:character <- next-ingredient
-  index:number <- next-ingredient
+  load-ingredients
   index <- multiply index, 8
-  result:address:array:character <- new character:type, 8
+  result <- new character:type, 8
   row:number <- copy 0
   {
     done?:boolean <- equal row, 8
@@ -182,13 +179,11 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     index <- add index, 1
     loop
   }
-  reply result
 ]
 
-recipe print-board [
+recipe print-board screen:address:screen, board:address:array:address:array:character -> screen:address:screen [
   local-scope
-  screen:address:screen <- next-ingredient
-  board:address:array:address:array:character <- next-ingredient
+  load-ingredients
   row:number <- copy 7  # start printing from the top of the board
   # print each row
   {
@@ -198,7 +193,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     rank:number <- add row, 1
     print-integer screen, rank
     s:address:array:character <- new [ | ]
-    print-string screen, s
+    print screen, s
     # print each square in the row
     col:number <- copy 0
     {
@@ -206,8 +201,8 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       break-if done?:boolean
       f:address:array:character <- index *board, col
       c:character <- index *f, row
-      print-character screen, c
-      print-character screen, 32/space
+      print screen, c
+      print screen, 32/space
       col <- add col, 1
       loop
     }
@@ -217,15 +212,14 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   }
   # print file letters as legend
   s <- new [  +----------------]
-  print-string screen, s
+  print screen, s
   screen <- cursor-to-next-line screen
   s <- new [    a b c d e f g h]
-  screen <- print-string screen, s
+  screen <- print screen, s
   screen <- cursor-to-next-line screen
 ]
 
-# board:address:array:address:array:character <- initial-position
-recipe initial-position [
+recipe initial-position -> board:address:array:address:array:character [
   local-scope
   # layout in memory (in raster order):
   #   R P _ _ _ _ p r
@@ -245,8 +239,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 #?       66/B, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 98/b,
 #?       78/N, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 110/n,
 #?       82/R, 80/P, 32/blank, 32/blank, 32/blank, 32/blank, 112/p, 114/r
-  board:address:array:address:array:character <- new-board initial-position
-  reply board
+  board <- new-board initial-position
 ]
 
 scenario printing-the-board [
@@ -282,12 +275,10 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   to-rank:number
 ]
 
-# result:address:move, quit?:boolean, error?:boolean <- read-move stdin:address:channel, screen:address:screen
 # prints only error messages to screen
-recipe read-move [
+recipe read-move stdin:address:channel, screen:address:screen -> result:address:move, quit?:boolean, error?:boolean [
   local-scope
-  stdin:address:channel <- next-ingredient
-  screen:address:screen <- next-ingredient
+  load-ingredients
   from-file:number, quit?:boolean, error?:boolean <- read-file stdin, screen
   reply-if quit?, 0/dummy, quit?, error?
   reply-if error?, 0/dummy, quit?, error?
@@ -314,12 +305,10 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   reply result, quit?, error?
 ]
 
-# file:number, quit:boolean, error:boolean <- read-file stdin:address:channel, screen:address:screen
 # valid values for file: 0-7
-recipe read-file [
+recipe read-file stdin:address:channel, screen:address:screen -> file:number, quit:boolean, error:boolean [
   local-scope
-  stdin:address:channel <- next-ingredient
-  screen:address:screen <- next-ingredient
+  load-ingredients
   c:character, stdin <- read stdin
   {
     q-pressed?:boolean <- equal c, 81/Q
@@ -340,7 +329,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     newline?:boolean <- equal c, 10/newline
     break-unless newline?
     error-message:address:array:character <- new [that's not enough]
-    print-string screen, error-message
+    print screen, error-message
     reply 0/dummy, 0/quit, 1/error
   }
   file:number <- subtract c, 97/a
@@ -349,8 +338,8 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     above-min:boolean <- greater-or-equal file, 0
     break-if above-min
     error-message:address:array:character <- new [file too low: ]
-    print-string screen, error-message
-    print-character screen, c
+    print screen, error-message
+    print screen, c
     cursor-to-next-line screen
     reply 0/dummy, 0/quit, 1/error
   }
@@ -358,19 +347,17 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     below-max:boolean <- lesser-than file, 8
     break-if below-max
     error-message <- new [file too high: ]
-    print-string screen, error-message
-    print-character screen, c
+    print screen, error-message
+    print screen, c
     reply 0/dummy, 0/quit, 1/error
   }
   reply file, 0/quit, 0/error
 ]
 
-# rank:number <- read-rank stdin:address:channel, screen:address:screen
 # valid values: 0-7, -1 (quit), -2 (error)
-recipe read-rank [
+recipe read-rank stdin:address:channel, screen:address:screen -> rank:number, quit?:boolean, error?:boolean, stdin:address:channel, screen:address:screen [
   local-scope
-  stdin:address:channel <- next-ingredient
-  screen:address:screen <- next-ingredient
+  load-ingredients
   c:character, stdin <- read stdin
   {
     q-pressed?:boolean <- equal c, 8/Q
@@ -386,7 +373,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     newline?:boolean <- equal c, 10  # newline
     break-unless newline?
     error-message:address:array:character <- new [that's not enough]
-    print-string screen, error-message
+    print screen, error-message
     reply 0/dummy, 0/quit, 1/error
   }
   rank:number <- subtract c, 49/'1'
@@ -395,16 +382,16 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     above-min:boolean <- greater-or-equal rank, 0
     break-if above-min
     error-message <- new [rank too low: ]
-    print-string screen, error-message
-    print-character screen, c
+    print screen, error-message
+    print screen, c
     reply 0/dummy, 0/quit, 1/error
   }
   {
     below-max:boolean <- lesser-or-equal rank, 7
     break-if below-max
     error-message <- new [rank too high: ]
-    print-string screen, error-message
-    print-character screen, c
+    print screen, error-message
+    print screen, c
     reply 0/dummy, 0/quit, 1/error
   }
   reply rank, 0/quit, 0/error
@@ -412,20 +399,17 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 
 # read a character from the given channel and check that it's what we expect
 # return true on error
-recipe expect-from-channel [
+recipe expect-from-channel stdin:address:channel, expected:character, screen:address:screen -> result:boolean [
   local-scope
-  stdin:address:channel <- next-ingredient
-  expected:character <- next-ingredient
-  screen:address:screen <- next-ingredient
+  load-ingredients
   c:character, stdin <- read stdin
   {
     match?:boolean <- equal c, expected
     break-if match?
     s:address:array:character <- new [expected character not found]
-    print-string screen, s
+    print screen, s
   }
-  result:boolean <- not match?
-  reply result
+  result <- not match?
 ]
 
 scenario read-move-blocking [
@@ -592,21 +576,19 @@ F read-move-file: routine failed to pause after co
   ]
 ]
 
-recipe make-move [
+recipe make-move board:address:array:address:array:character, m:address:move -> board:address:array:address:array:character [
   local-scope
-  b:address:array:address:array:character <- next-ingredient
-  m:address:move <- next-ingredient
+  load-ingredients
   from-file:number <- get *m, from-file:offset
   from-rank:number <- get *m, from-rank:offset
   to-file:number <- get *m, to-file:offset
   to-rank:number <- get *m, to-rank:offset
-  f:address:array:character <- index *b, from-file
+  f:address:array:character <- index *board, from-file
   src:address:character/square <- index-address *f, from-rank
-  f <- index *b, to-file
+  f <- index *board, to-file
   dest:address:character/square <- index-address *f, to-rank
   *dest <- copy *src
   *src <- copy 32/space
-  reply b/same-as-ingredient:0
 ]
 
 scenario making-a-move [
diff --git a/html/edit/001-editor.mu.html b/html/edit/001-editor.mu.html
index cf10dc6c..59982193 100644
--- a/html/edit/001-editor.mu.html
+++ b/html/edit/001-editor.mu.html
@@ -35,7 +35,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 
 ## the basic editor data structure, and how it displays text to the screen
 
-# temporary main for this layer: just render the given string at the given
+# temporary main for this layer: just render the given text at the given
 # screen dimensions, then stop
 recipe! main text:address:array:character [
   local-scope
@@ -48,7 +48,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   close-console
 ]
 
-scenario editor-initially-prints-string-to-screen [
+scenario editor-initially-prints-text-to-screen [
   assume-screen 10/width, 5/height
   run [
     1:address:array:character <- new [abc]
@@ -82,7 +82,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 # creates a new editor widget and renders its initial appearance to screen
 #   top/left/right constrain the screen area available to the new editor
 #   right is exclusive
-recipe new-editor s:address:array:character, screen:address:screen, left:number, right:number -> result:address:editor-data [
+recipe new-editor s:address:array:character, screen:address:screen, left:number, right:number -> result:address:editor-data, screen:address:screen [
   local-scope
   load-ingredients
   # no clipping of bounds
@@ -228,14 +228,14 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       at-right?:boolean <- equal column, right
       break-unless at-right?
       # print wrap icon
-      print-character screen, 8617/loop-back-to-left, 245/grey
+      print screen, 8617/loop-back-to-left, 245/grey
       column <- copy left
       row <- add row, 1
       screen <- move-cursor screen, row, column
       # don't increment curr
       loop +next-character:label
     }
-    print-character screen, c, color
+    print screen, c, color
     curr <- next curr
     prev <- next prev
     column <- add column, 1
@@ -259,13 +259,13 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   reply row, column, screen/same-as-ingredient:0, editor/same-as-ingredient:1
 ]
 
-recipe clear-line-delimited screen:address:screen, column:number, right:number [
+recipe clear-line-delimited screen:address:screen, column:number, right:number -> screen:address:screen [
   local-scope
   load-ingredients
   {
     done?:boolean <- greater-than column, right
     break-if done?
-    print-character screen, 32/space
+    screen <- print screen, 32/space
     column <- add column, 1
     loop
   }
diff --git a/html/edit/002-typing.mu.html b/html/edit/002-typing.mu.html
index 538bbf5d..efed71b6 100644
--- a/html/edit/002-typing.mu.html
+++ b/html/edit/002-typing.mu.html
@@ -259,7 +259,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     overflow?:boolean <- and at-bottom?, at-right?
     break-if overflow?
     move-cursor screen, save-row, save-column
-    print-character screen, c
+    print screen, c
     go-render? <- copy 0/false
     reply
   }
@@ -281,7 +281,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       currc:character <- get *curr, value:offset
       at-newline?:boolean <- equal currc, 10/newline
       break-if at-newline?
-      print-character screen, currc
+      print screen, currc
       curr-column <- add curr-column, 1
       curr <- next curr
       loop
@@ -1070,7 +1070,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   {
     continue?:boolean <- lesser-or-equal x, right  # right is inclusive, to match editor-data semantics
     break-unless continue?
-    print-character screen, style, color, bg-color
+    print screen, style, color, bg-color
     x <- add x, 1
     loop
   }
diff --git a/html/edit/003-shortcuts.mu.html b/html/edit/003-shortcuts.mu.html
index aabe23e0..5518a680 100644
--- a/html/edit/003-shortcuts.mu.html
+++ b/html/edit/003-shortcuts.mu.html
@@ -152,13 +152,13 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     currc:character <- get *curr, value:offset
     at-newline?:boolean <- equal currc, 10/newline
     break-if at-newline?
-    screen <- print-character screen, currc
+    screen <- print screen, currc
     curr-column <- add curr-column, 1
     curr <- next curr
     loop
   }
   # we're guaranteed not to be at the right margin
-  screen <- print-character screen, 32/space
+  screen <- print screen, 32/space
   go-render? <- copy 0/false
 ]
 
@@ -392,13 +392,13 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     currc:character <- get *curr, value:offset
     at-newline?:boolean <- equal currc, 10/newline
     break-if at-newline?
-    screen <- print-character screen, currc
+    screen <- print screen, currc
     curr-column <- add curr-column, 1
     curr <- next curr
     loop
   }
   # we're guaranteed not to be at the right margin
-  screen <- print-character screen, 32/space
+  screen <- print screen, 32/space
   go-render? <- copy 0/false
 ]
 
diff --git a/html/edit/004-programming-environment.mu.html b/html/edit/004-programming-environment.mu.html
index 172b1dcc..a2001520 100644
--- a/html/edit/004-programming-environment.mu.html
+++ b/html/edit/004-programming-environment.mu.html
@@ -20,7 +20,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
 .Comment { color: #9090ff; }
 .Constant { color: #00a0a0; }
 .SalientComment { color: #00ffff; }
-.CommentedCode { color: #6c6c6c; }
 .Delimiter { color: #a04060; }
 .muScenario { color: #00af00; }
 -->
@@ -70,7 +69,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   assert button-on-screen?, [screen too narrow for menu]
   screen <- move-cursor screen, 0/row, button-start
   run-button:address:array:character <- new [ run (F4) ]
-  print-string screen, run-button, 255/white, 161/reddish
+  print screen, run-button, 255/white, 161/reddish
   # dotted line down the middle
   divider:number, _ <- divide-with-remainder width, 2
   draw-vertical screen, divider, 1/top, height, 9482/vertical-dotted
@@ -303,7 +302,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   ]
   # show the cursor at the right window
   run [
-    print-character screen:address:screen, 9251/␣/cursor
+    print screen:address:screen, 9251/␣/cursor
   ]
   screen-should-contain [
     .           run (F4)           .
@@ -343,7 +342,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   assume-console []
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
-    print-character screen:address:screen, 9251/␣/cursor
+    print screen:address:screen, 9251/␣/cursor
   ]
   # is cursor at the right place?
   screen-should-contain [
@@ -358,7 +357,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
-    print-character screen:address:screen, 9251/␣/cursor
+    print screen:address:screen, 9251/␣/cursor
   ]
   # cursor should still be right
   screen-should-contain [
@@ -392,7 +391,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
-    print-character screen:address:screen, 9251/␣/cursor
+    print screen:address:screen, 9251/␣/cursor
   ]
   # cursor moves to end of old line
   screen-should-contain [
@@ -417,7 +416,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   assert button-on-screen?, [screen too narrow for menu]
   screen <- move-cursor screen, 0/row, button-start
   run-button:address:array:character <- new [ run (F4) ]
-  print-string screen, run-button, 255/white, 161/reddish
+  print screen, run-button, 255/white, 161/reddish
   # dotted line down the middle
   trace 11, [app], [render divider]
   divider:number, _ <- divide-with-remainder width, 2
@@ -486,9 +485,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   screen <- move-cursor screen, cursor-row, cursor-column
 ]
 
-# print a string 's' to 'editor' in 'color' starting at 'row'
+# print a text 's' to 'editor' in 'color' starting at 'row'
 # clear rest of last line, move cursor to next line
-recipe render-string screen:address:screen, s:address:array:character, left:number, right:number, color:number, row:number -> row:number, screen:address:screen [
+recipe render screen:address:screen, s:address:array:character, left:number, right:number, color:number, row:number -> row:number, screen:address:screen [
   local-scope
   load-ingredients
   reply-unless s
@@ -509,7 +508,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       at-right?:boolean <- equal column, right
       break-unless at-right?
       # print wrap icon
-      print-character screen, 8617/loop-back-to-left, 245/grey
+      print screen, 8617/loop-back-to-left, 245/grey
       column <- copy left
       row <- add row, 1
       screen <- move-cursor screen, row, column
@@ -524,7 +523,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       {
         done?:boolean <- greater-than column, right
         break-if done?
-        print-character screen, 32/space
+        print screen, 32/space
         column <- add column, 1
         loop
       }
@@ -533,7 +532,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       screen <- move-cursor screen, row, column
       loop +next-character:label
     }
-    print-character screen, c, color
+    print screen, c, color
     column <- add column, 1
     loop
   }
@@ -546,8 +545,8 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   move-cursor screen, row, left
 ]
 
-# like 'render-string' but with colorization for comments like in the editor
-recipe render-code-string screen:address:screen, s:address:array:character, left:number, right:number, row:number -> row:number, screen:address:screen [
+# like 'render' for texts, but with colorization for comments like in the editor
+recipe render-code screen:address:screen, s:address:array:character, left:number, right:number, row:number -> row:number, screen:address:screen [
   local-scope
   load-ingredients
   reply-unless s
@@ -564,13 +563,13 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     done? <- greater-or-equal row, screen-height
     break-if done?
     c:character <- index *s, i
-    <character-c-received>  # only line different from render-string
+    <character-c-received>  # only line different from render
     {
       # at right? wrap.
       at-right?:boolean <- equal column, right
       break-unless at-right?
       # print wrap icon
-      print-character screen, 8617/loop-back-to-left, 245/grey
+      print screen, 8617/loop-back-to-left, 245/grey
       column <- copy left
       row <- add row, 1
       screen <- move-cursor screen, row, column
@@ -585,7 +584,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       {
         done?:boolean <- greater-than column, right
         break-if done?
-        print-character screen, 32/space
+        print screen, 32/space
         column <- add column, 1
         loop
       }
@@ -594,7 +593,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
       screen <- move-cursor screen, row, column
       loop +next-character:label
     }
-    print-character screen, c, color
+    print screen, c, color
     column <- add column, 1
     loop
   }
@@ -632,131 +631,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   }
 ]
 
-# ctrl-x - maximize/unmaximize the side with focus
-
-scenario maximize-side [
-  trace-until 100/app  # trace too long
-  assume-screen 30/width, 5/height
-  # initialize both halves of screen
-  1:address:array:character <- new [abc]
-  2:address:array:character <- new [def]
-  3:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character, 2:address:array:character
-  screen <- render-all screen, 3:address:programming-environment-data
-  screen-should-contain [
-    .           run (F4)           .
-    .abc            ┊def           .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.
-    .               ┊              .
-  ]
-  # hit ctrl-x
-  assume-console [
-    press ctrl-x
-  ]
-  run [
-    event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
-  ]
-  # only left side visible
-  screen-should-contain [
-    .           run (F4)           .
-    .abc                           .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈.
-    .                              .
-  ]
-  # hit any key to toggle back
-  assume-console [
-    press ctrl-x
-  ]
-  run [
-    event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
-  ]
-  screen-should-contain [
-    .           run (F4)           .
-    .abc            ┊def           .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.
-    .               ┊              .
-  ]
-]
-
-#? # ctrl-t - browse trace
-#? after <global-type> [
-#?   {
-#?     browse-trace?:boolean <- equal *c, 20/ctrl-t
-#?     break-unless browse-trace?
-#?     $browse-trace
-#?     screen <- render-all screen, env:address:programming-environment-data
-#?     loop +next-event:label
-#?   }
-#? ]
-
-container programming-environment-data [
-  maximized?:boolean
-]
-
-after <global-type> [
-  {
-    maximize?:boolean <- equal *c, 24/ctrl-x
-    break-unless maximize?
-    screen, console <- maximize screen, console, env:address:programming-environment-data
-    loop +next-event:label
-  }
-]
-
-recipe maximize screen:address:screen, console:address:console, env:address:programming-environment-data -> screen:address:screen, console:address:console [
-  local-scope
-  load-ingredients
-  hide-screen screen
-  # maximize one of the sides
-  maximized?:address:boolean <- get-address *env, maximized?:offset
-  *maximized? <- copy 1/true
-  #
-  sandbox-in-focus?:boolean <- get *env, sandbox-in-focus?:offset
-  {
-    break-if sandbox-in-focus?
-    editor:address:editor-data <- get *env, recipes:offset
-    right:address:number <- get-address *editor, right:offset
-    *right <- screen-width screen
-    *right <- subtract *right, 1
-    screen <- render-recipes screen, env
-  }
-  {
-    break-unless sandbox-in-focus?
-    editor:address:editor-data <- get *env, current-sandbox:offset
-    left:address:number <- get-address *editor, left:offset
-    *left <- copy 0
-    screen <- render-sandbox-side screen, env
-  }
-  show-screen screen
-]
-
-# when maximized, wait for any event and simply unmaximize
-after <handle-event> [
-  {
-    maximized?:address:boolean <- get-address *env, maximized?:offset
-    break-unless *maximized?
-    *maximized? <- copy 0/false
-    # undo maximize
-    {
-      break-if *sandbox-in-focus?
-      editor:address:editor-data <- get *env, recipes:offset
-      right:address:number <- get-address *editor, right:offset
-      *right <- screen-width screen
-      *right <- divide *right, 2
-      *right <- subtract *right, 1
-    }
-    {
-      break-unless *sandbox-in-focus?
-      editor:address:editor-data <- get *env, current-sandbox:offset
-      left:address:number <- get-address *editor, left:offset
-      *left <- screen-width screen
-      *left <- divide *left, 2
-      *left <- add *left, 1
-    }
-    render-all screen, env
-    show-screen screen
-    loop +next-event:label
-  }
-]
-
 ## helpers
 
 recipe draw-vertical screen:address:screen, col:number, y:number, bottom:number [
@@ -777,7 +651,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     continue?:boolean <- lesser-than y, bottom
     break-unless continue?
     screen <- move-cursor screen, y, col
-    print-character screen, style, color
+    print screen, style, color
     y <- add y, 1
     loop
   }
diff --git a/html/edit/005-sandbox.mu.html b/html/edit/005-sandbox.mu.html
index 85fdf7da..9c219526 100644
--- a/html/edit/005-sandbox.mu.html
+++ b/html/edit/005-sandbox.mu.html
@@ -229,7 +229,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   local-scope
   load-ingredients
   screen <- move-cursor screen, 0, 2
-  screen <- print-string screen, msg, color, 238/grey/background
+  screen <- print screen, msg, color, 238/grey/background
 ]
 
 recipe save-sandboxes env:address:programming-environment-data [
@@ -244,12 +244,12 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   {
     break-unless curr
     data:address:array:character <- get *curr, data:offset
-    filename:address:array:character <- integer-to-decimal-string idx
+    filename:address:array:character <- to-text idx
     save filename, data
     {
       expected-response:address:array:character <- get *curr, expected-response:offset
       break-unless expected-response
-      filename <- string-append filename, suffix
+      filename <- append filename, suffix
       save filename, expected-response
     }
     idx <- add idx, 1
@@ -285,7 +285,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   row <- add row, 1
   screen <- move-cursor screen, row, left
   clear-line-delimited screen, left, right
-  print-character screen, 120/x, 245/grey
+  print screen, 120/x, 245/grey
   # save menu row so we can detect clicks to it later
   starting-row:address:number <- get-address *sandbox, starting-row-on-screen:offset
   *starting-row <- copy row
@@ -293,7 +293,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   row <- add row, 1
   screen <- move-cursor screen, row, left
   sandbox-data:address:array:character <- get *sandbox, data:offset
-  row, screen <- render-code-string screen, sandbox-data, left, right, row
+  row, screen <- render-code screen, sandbox-data, left, right, row
   code-ending-row:address:number <- get-address *sandbox, code-ending-row-on-screen:offset
   *code-ending-row <- copy row
   # render sandbox warnings, screen or response, in that order
@@ -310,7 +310,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     break-unless empty-screen?
     *response-starting-row <- copy row
     <render-sandbox-response>
-    row, screen <- render-string screen, sandbox-response, left, right, 245/grey, row
+    row, screen <- render screen, sandbox-response, left, right, 245/grey, row
   }
   +render-sandbox-end
   at-bottom?:boolean <- greater-or-equal row, screen-height
@@ -331,7 +331,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   idx:number <- copy 0
   curr:address:address:sandbox-data <- get-address *env, sandbox:offset
   {
-    filename:address:array:character <- integer-to-decimal-string idx
+    filename:address:array:character <- to-text idx
     contents:address:array:character <- restore filename
     break-unless contents  # stop at first error; assuming file didn't exist
     # create new sandbox for file
@@ -340,7 +340,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     *data <- copy contents
     # restore expected output for sandbox if it exists
     {
-      filename <- string-append filename, suffix
+      filename <- append filename, suffix
       contents <- restore filename
       break-unless contents
       expected-response:address:address:array:character <- get-address **curr, expected-response:offset
@@ -361,7 +361,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   reply-unless sandbox-screen
   # print 'screen:'
   header:address:array:character <- new [screen:]
-  row <- render-string screen, header, left, right, 245/grey, row
+  row <- render screen, header, left, right, 245/grey, row
   screen <- move-cursor screen, row, left
   # start printing sandbox-screen
   column:number <- copy left
@@ -381,9 +381,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     column <- copy left
     screen <- move-cursor screen, row, column
     # initial leader for each row: two spaces and a '.'
-    print-character screen, 32/space, 245/grey
-    print-character screen, 32/space, 245/grey
-    print-character screen, 46/full-stop, 245/grey
+    print screen, 32/space, 245/grey
+    print screen, 32/space, 245/grey
+    print screen, 46/full-stop, 245/grey
     column <- add left, 3
     {
       # print row
@@ -398,19 +398,19 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
         break-unless white?
         color <- copy 245/grey
       }
-      print-character screen, c, color
+      print screen, c, color
       column <- add column, 1
       i <- add i, 1
       loop
     }
     # print final '.'
-    print-character screen, 46/full-stop, 245/grey
+    print screen, 46/full-stop, 245/grey
     column <- add column, 1
     {
       # clear rest of current line
       line-done?:boolean <- greater-than column, right
       break-if line-done?
-      print-character screen, 32/space
+      print screen, 32/space
       column <- add column, 1
       loop
     }
@@ -514,7 +514,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
   {
     break-unless curr
     c:character <- get *curr, value:offset
-    buffer-append buf, c
+    buf <- append buf, c
     curr <- next curr
     loop
   }
@@ -535,7 +535,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; }
     4:array:character <- copy *3:address:array:character
   ]
   memory-should-contain [
-    4:string <- [abdefc]
+    4:array:character <- [abdefc]
   ]
 ]
 
diff --git a/html/edit/008-sandbox-test.mu.html b/html/edit/008-sandbox-test.mu.html index 08b62ddc..bdf918e8 100644 --- a/html/edit/008-sandbox-test.mu.html +++ b/html/edit/008-sandbox-test.mu.html @@ -79,7 +79,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] # cursor should remain unmoved run [ - print-character screen:address:screen, 9251/␣/cursor + print screen:address:screen, 9251/␣/cursor ] screen-should-contain [ . run (F4) . @@ -191,14 +191,14 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } break-unless sandbox-response expected-response:address:array:character <- get *sandbox, expected-response:offset break-unless expected-response # fall-through to print in grey - response-is-expected?:boolean <- string-equal expected-response, sandbox-response + response-is-expected?:boolean <- equal expected-response, sandbox-response { break-if response-is-expected?:boolean - row, screen <- render-string screen, sandbox-response, left, right, 1/red, row + row, screen <- render screen, sandbox-response, left, right, 1/red, row } { break-unless response-is-expected?:boolean - row, screen <- render-string screen, sandbox-response, left, right, 2/green, row + row, screen <- render screen, sandbox-response, left, right, 2/green, row } jump +render-sandbox-end:label } diff --git a/html/edit/009-sandbox-trace.mu.html b/html/edit/009-sandbox-trace.mu.html index 071f62af..6d5603bd 100644 --- a/html/edit/009-sandbox-trace.mu.html +++ b/html/edit/009-sandbox-trace.mu.html @@ -65,7 +65,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] run [ event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data - print-character screen:address:screen, 9251/␣/cursor + print screen:address:screen, 9251/␣/cursor ] # trace now printed and cursor shouldn't have budged screen-should-contain [ @@ -94,7 +94,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] run [ event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data - print-character screen:address:screen, 9251/␣/cursor + print screen:address:screen, 9251/␣/cursor ] # trace hidden again screen-should-contain [ @@ -238,7 +238,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } break-unless display-trace? sandbox-trace:address:array:character <- get *sandbox, trace:offset break-unless sandbox-trace # nothing to print; move on - row, screen <- render-string, screen, sandbox-trace, left, right, 245/grey, row + row, screen <- render screen, sandbox-trace, left, right, 245/grey, row } <render-sandbox-trace-done> ] diff --git a/html/edit/010-warnings.mu.html b/html/edit/010-warnings.mu.html index 335bfb42..6d746bf5 100644 --- a/html/edit/010-warnings.mu.html +++ b/html/edit/010-warnings.mu.html @@ -20,6 +20,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } .Comment { color: #9090ff; } .Constant { color: #00a0a0; } .SalientComment { color: #00ffff; } +.CommentedCode { color: #6c6c6c; } .Delimiter { color: #a04060; } .muScenario { color: #00af00; } --> @@ -73,7 +74,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } { recipe-warnings:address:array:character <- get *env, recipe-warnings:offset break-unless recipe-warnings - row, screen <- render-string screen, recipe-warnings, left, right, 1/red, row + row, screen <- render screen, recipe-warnings, left, right, 1/red, row } ] @@ -89,6 +90,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } warnings:address:address:array:character <- get-address *sandbox, warnings:offset trace:address:address:array:character <- get-address *sandbox, trace:offset fake-screen:address:address:screen <- get-address *sandbox, screen:offset +#? $print [run-interactive], 10/newline *response, *warnings, *fake-screen, *trace, completed?:boolean <- run-interactive data { break-if *warnings @@ -96,6 +98,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } *warnings <- new [took too long! ] } +#? $print [done with run-interactive], 10/newline ] # make sure we render any trace @@ -104,7 +107,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } sandbox-warnings:address:array:character <- get *sandbox, warnings:offset break-unless sandbox-warnings *response-starting-row <- copy 0 # no response - row, screen <- render-string screen, sandbox-warnings, left, right, 1/red, row + row, screen <- render screen, sandbox-warnings, left, right, 1/red, row # don't try to print anything more for this sandbox jump +render-sandbox-end:label } @@ -150,6 +153,88 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] +scenario run-hides-warnings-from-past-sandboxes [ + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + 1:address:array:character <- new [] + 2:address:array:character <- new [get foo, x:offset] # invalid + 3:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character, 2:address:array:character + assume-console [ + press F4 # generate error + ] + run [ + event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data + ] + assume-console [ + left-click 3, 80 + press ctrl-k + type [add 2, 2] # valid code + press F4 # error should disappear + ] + run [ + event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data + ] + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. + . ┊ x. + . ┊add 2, 2 . + . ┊4 . + . ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. + . ┊ . + ] +] + +scenario run-updates-warnings-for-shape-shifting-recipes [ + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + # define a shape-shifting recipe with an error + 1:address:array:character <- new [recipe foo x:_elem -> z:_elem [ +local-scope +load-ingredients +z <- add x, [a] +]] + 2:address:array:character <- new [foo 2] + 3:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character, 2:address:array:character + assume-console [ + press F4 + ] + run [ + event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data + ] + screen-should-contain [ + . run (F4) . + .recipe foo x:_elem -> z:_elem [ ┊ . + .local-scope ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. + .load-ingredients ┊ x. + .z <- add x, [a] ┊foo 2 . + .] ┊foo_2: 'add' requires number ingredients, but go↩. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊t [a] . + . ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. + . ┊ . + ] + # now rerun everything + assume-console [ + press F4 + ] + run [ + event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data + ] + # error should remain unchanged + screen-should-contain [ + . run (F4) . + .recipe foo x:_elem -> z:_elem [ ┊ . + .local-scope ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. + .load-ingredients ┊ x. + .z <- add x, [a] ┊foo 2 . + .] ┊foo_2: 'add' requires number ingredients, but go↩. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊t [a] . + . ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. + . ┊ . + ] +] + scenario run-shows-missing-type-warnings [ trace-until 100/app # trace too long assume-screen 100/width, 15/height @@ -183,7 +268,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } recipe foo « x <- copy 0 ] - string-replace 1:address:array:character, 171/«, 91 # '[' + replace 1:address:array:character, 171/«, 91 # '[' 2:address:array:character <- new [foo] 3:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character, 2:address:array:character assume-console [ diff --git a/html/edit/011-editor-undo.mu.html b/html/edit/011-editor-undo.mu.html index 5a3f537e..801ca648 100644 --- a/html/edit/011-editor-undo.mu.html +++ b/html/edit/011-editor-undo.mu.html @@ -2105,10 +2105,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } . . ] ] - -# todo: -# operations for recipe side and each sandbox-data -# undo delete sandbox as a separate primitive on the status bar
diff --git a/html/screen.mu.html b/html/screen.mu.html index 6efea923..ea2b0232 100644 --- a/html/screen.mu.html +++ b/html/screen.mu.html @@ -35,12 +35,12 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } recipe main [ open-console - print-character 0/screen, 97/a, 2/red + print 0/screen, 97/a, 2/red 1:number/raw, 2:number/raw <- cursor-position 0/screen wait-for-event 0/console clear-screen 0/screen move-cursor 0/screen, 0/row, 4/column - print-character 0/screen, 98/b + print 0/screen, 98/b wait-for-event 0/console move-cursor 0/screen, 0/row, 0/column clear-line 0/screen diff --git a/index.html b/index.html index 24b8e0c0..e498b82f 100644 --- a/index.html +++ b/index.html @@ -166,7 +166,7 @@ various address spaces in the core, and the conventions that regulate their use in previous layers.

Part IV: beginnings of a standard library -

070string.mu: strings in Mu are +

070text.mu: strings in Mu are bounds-checked rather than null-terminated. They're also unicode-aware (code points only; no control characters, no combining characters, no normalization).
071channel.mu: channels are Mu's @@ -239,7 +239,8 @@ golden/expected. Any future changes to the output will then be flagged in red.
edit/009-sandbox-trace.mu: click on code in a sandbox to open up a drawer containing its trace. The trace can be added to using the stash -command. +command, which renders arbitrary data structures using to-text +with the appropriate recipe header.
edit/010-warnings.mu: support for rendering warnings on both the left and in each sandbox.
edit/011-editor-undo.mu: -- cgit 1.4.1-2-gfad0