diff options
Diffstat (limited to 'mu.cc.modified')
-rw-r--r-- | mu.cc.modified | 12001 |
1 files changed, 0 insertions, 12001 deletions
diff --git a/mu.cc.modified b/mu.cc.modified deleted file mode 100644 index e595855c..00000000 --- a/mu.cc.modified +++ /dev/null @@ -1,12001 +0,0 @@ -// Includes -#include<stdlib.h> - -#define SIZE(X) (assert((X).size() < (1LL<<(sizeof(long long int)*8-2))), static_cast<long long int>((X).size())) -#include<assert.h> - -#include<iostream> -using std::istream; -using std::ostream; -using std::iostream; -using std::cin; -using std::cout; -using std::cerr; - -#include<cstring> -#include<string> -using std::string; - -#include<cstdlib> - -#define CHECK_TRACE_CONTENTS(...) check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) - -#include<vector> -using std::vector; -#include<list> -using std::list; -#include<map> -using std::map; -#include<set> -using std::set; -#include<algorithm> - -#include<iostream> -using std::istream; -using std::ostream; -using std::cin; -using std::cout; -using std::cerr; -#include<iomanip> - -#include<sstream> -using std::istringstream; -using std::ostringstream; - -#include<fstream> -using std::ifstream; -using std::ofstream; - -#include"termbox/termbox.h" - -#define unused __attribute__((unused)) - -#include<utility> -using std::pair; -#include<math.h> - -#include<dirent.h> -#include<sys/stat.h> - - -#include <stack> -using std::stack; - -using std::min; -using std::max; - -using std::abs; - -#include<math.h> - -// End Includes - -// Types -// Mu types encode how the numbers stored in different parts of memory are -// interpreted. A location tagged as a 'character' type will interpret the -// value 97 as the letter 'a', while a different location of type 'number' -// would not. -// -// Unlike most computers today, mu stores types in a single big table, shared -// by all the mu programs on the computer. This is useful in providing a -// seamless experience to help understand arbitrary mu programs. -typedef long long int type_ordinal; -typedef long long int recipe_ordinal; - -typedef void (*test_fn)(void); - -struct trace_line { - int depth; // optional field just to help browse traces later - string label; - string contents; - trace_line(string l, string c) :depth(0), label(l), contents(c) {} - trace_line(int d, string l, string c) :depth(d), label(l), contents(c) {} -}; - -struct end {}; -// Recipes are lists of instructions. To perform or 'run' a recipe, the -// computer runs its instructions. -// Each instruction is either of the form: -// product1, product2, product3, ... <- operation ingredient1, ingredient2, ingredient3, ... -// or just a single 'label' starting with a non-alphanumeric character -// +label -// Labels don't do anything, they're just waypoints. -// Ingredients and products are a single species -- a reagent. Reagents refer -// either to numbers or to locations in memory along with 'type' tags telling -// us how to interpret them. They also can contain arbitrary other lists of -// properties besides types, but we're getting ahead of ourselves. -struct property { - vector<string> values; -}; - -// Types can range from a simple type ordinal, to arbitrarily complex trees of -// type parameters, like (map (address array character) (list number)) -struct type_tree { - string name; - type_ordinal value; - type_tree* left; - type_tree* right; - ~type_tree(); - type_tree(const type_tree& old); - // simple: type ordinal - explicit type_tree(string name, type_ordinal v) :name(name), value(v), left(NULL), right(NULL) {} - // intermediate: list of type ordinals - type_tree(string name, type_ordinal v, type_tree* r) :name(name), value(v), left(NULL), right(r) {} - // advanced: tree containing type ordinals - type_tree(type_tree* l, type_tree* r) :value(0), left(l), right(r) {} -}; - -struct string_tree { - string value; - string_tree* left; - string_tree* right; - ~string_tree(); - string_tree(const string_tree& old); - // simple: flat string - explicit string_tree(string v) :value(v), left(NULL), right(NULL) {} - // intermediate: list of strings - string_tree(string v, string_tree* r) :value(v), left(NULL), right(r) {} - // advanced: tree containing strings - string_tree(string_tree* l, string_tree* r) :left(l), right(r) {} -}; - -struct reagent { - string original_string; - string name; - type_tree* type; - vector<pair<string, string_tree*> > properties; - double value; - bool initialized; - reagent(string s); - reagent() :type(NULL), value(0), initialized(false) {} - ~reagent(); - void clear(); - reagent(const reagent& old); - reagent& operator=(const reagent& old); - void set_value(double v) { value = v; initialized = true; } -}; - -struct instruction { - bool is_label; - string label; // only if is_label - string name; // only if !is_label - string old_name; // before our automatic rewrite rules - string original_string; - recipe_ordinal operation; // get(Recipe_ordinal, name) - vector<reagent> ingredients; // only if !is_label - vector<reagent> products; // only if !is_label - mutable bool tangle_done; - // End instruction Fields - instruction(); - void clear(); - bool is_empty(); -}; - -struct recipe { - string name; - vector<instruction> steps; - long long int transformed_until; - bool has_header; - vector<reagent> ingredients; - vector<reagent> products; - map<string, int> ingredient_index; - - string original_name; - // End recipe Fields - recipe(); -}; - -// You can construct arbitrary new types. New types are either 'containers' -// with multiple 'elements' of other types, or 'exclusive containers' containing -// one of multiple 'variants'. (These are similar to C structs and unions, -// respectively, though exclusive containers implicitly include a tag element -// recording which variant they should be interpreted as.) -// -// For example, storing bank balance and name for an account might require a -// container, but if bank accounts may be either for individuals or groups, -// with different properties for each, that may require an exclusive container -// whose variants are individual-account and joint-account containers. -enum kind_of_type { - PRIMITIVE, - CONTAINER, - EXCLUSIVE_CONTAINER -}; - -struct type_info { - string name; - kind_of_type kind; - long long int size; // only if type is not primitive; primitives and addresses have size 1 (except arrays are dynamic) - vector<reagent> elements; - map<string, type_ordinal> type_ingredient_names; - - - // End type_info Fields - type_info() :kind(PRIMITIVE), size(0) {} -}; - -enum primitive_recipes { - IDLE = 0, - COPY, - ADD, - SUBTRACT, - MULTIPLY, - DIVIDE, - DIVIDE_WITH_REMAINDER, - SHIFT_LEFT, - SHIFT_RIGHT, - AND_BITS, - OR_BITS, - XOR_BITS, - FLIP_BITS, - AND, - OR, - NOT, - JUMP, - JUMP_IF, - JUMP_UNLESS, - EQUAL, - GREATER_THAN, - LESSER_THAN, - GREATER_OR_EQUAL, - LESSER_OR_EQUAL, - TRACE, - STASH, - HIDE_ERRORS, - SHOW_ERRORS, - TRACE_UNTIL, - _DUMP_TRACE, - _CLEAR_TRACE, - _SAVE_TRACE, - ASSERT, - _PRINT, - _EXIT, - _SYSTEM, - _DUMP_MEMORY, - _LOG, - GET, - GET_ADDRESS, - MERGE, - _DUMP, - _FOO, - CREATE_ARRAY, - INDEX, - INDEX_ADDRESS, - LENGTH, - MAYBE_CONVERT, - NEXT_INGREDIENT, - REWIND_INGREDIENTS, - INGREDIENT, - REPLY, - NEW, - ALLOCATE, - ABANDON, - TO_LOCATION_ARRAY, - BREAK, - BREAK_IF, - BREAK_UNLESS, - LOOP, - LOOP_IF, - LOOP_UNLESS, - RUN, - MEMORY_SHOULD_CONTAIN, - TRACE_SHOULD_CONTAIN, - TRACE_SHOULD_NOT_CONTAIN, - CHECK_TRACE_COUNT_FOR_LABEL, - NEXT_INGREDIENT_WITHOUT_TYPECHECKING, - CALL, - START_RUNNING, - ROUTINE_STATE, - RESTART, - STOP, - _DUMP_ROUTINES, - LIMIT_TIME, - WAIT_FOR_LOCATION, - WAIT_FOR_ROUTINE, - SWITCH, - RANDOM, - MAKE_RANDOM_NONDETERMINISTIC, - ROUND, - HASH, - HASH_OLD, - OPEN_CONSOLE, - CLOSE_CONSOLE, - CLEAR_DISPLAY, - SYNC_DISPLAY, - CLEAR_LINE_ON_DISPLAY, - PRINT_CHARACTER_TO_DISPLAY, - CURSOR_POSITION_ON_DISPLAY, - MOVE_CURSOR_ON_DISPLAY, - MOVE_CURSOR_DOWN_ON_DISPLAY, - MOVE_CURSOR_UP_ON_DISPLAY, - MOVE_CURSOR_RIGHT_ON_DISPLAY, - MOVE_CURSOR_LEFT_ON_DISPLAY, - DISPLAY_WIDTH, - DISPLAY_HEIGHT, - HIDE_CURSOR_ON_DISPLAY, - SHOW_CURSOR_ON_DISPLAY, - HIDE_DISPLAY, - SHOW_DISPLAY, - WAIT_FOR_SOME_INTERACTION, - CHECK_FOR_INTERACTION, - INTERACTIONS_LEFT, - CLEAR_DISPLAY_FROM, - SCREEN_SHOULD_CONTAIN, - SCREEN_SHOULD_CONTAIN_IN_COLOR, - _DUMP_SCREEN, - ASSUME_CONSOLE, - REPLACE_IN_CONSOLE, - _BROWSE_TRACE, - RUN_INTERACTIVE, - _START_TRACKING_PRODUCTS, - _STOP_TRACKING_PRODUCTS, - _MOST_RECENT_PRODUCTS, - SAVE_ERRORS_WARNINGS, - SAVE_APP_TRACE, - _CLEANUP_RUN_INTERACTIVE, - RELOAD, - RESTORE, - SAVE, - // End Primitive Recipe Declarations - MAX_PRIMITIVE_RECIPES, -}; -struct no_scientific { - double x; - explicit no_scientific(double y) :x(y) {} -}; - -typedef void (*transform_fn)(recipe_ordinal); - -// Book-keeping while running a recipe. -// Everytime a recipe runs another, we interrupt it and start running the new -// recipe. When that finishes, we continue this one where we left off. -// This requires maintaining a 'stack' of interrupted recipes or 'calls'. -struct call { - recipe_ordinal running_recipe; - long long int running_step_index; - vector<vector<double> > ingredient_atoms; - vector<reagent> ingredients; - long long int next_ingredient_to_process; - long long int default_space; - // End call Fields - call(recipe_ordinal r) { - running_recipe = r; - running_step_index = 0; - next_ingredient_to_process = 0; - - default_space = 0; - - // End call Constructor - } - ~call() { - // End call Destructor - } -}; -typedef list<call> call_stack; - -enum routine_state { - RUNNING, - COMPLETED, - DISCONTINUED, - WAITING, - // End routine States -}; -struct routine { - call_stack calls; - long long int alloc, alloc_max; - long long int global_space; - enum routine_state state; - long long int id; - // todo: really should be routine_id, but that's less efficient. - long long int parent_index; // only < 0 if there's no parent_index - long long int limit; - // only if state == WAITING - long long int waiting_on_location; - int old_value_of_waiting_location; - // only if state == WAITING - long long int waiting_on_routine; - // End routine Fields - routine(recipe_ordinal r); - bool completed() const; - const vector<instruction>& steps() const; -}; - -struct merge_check_point { - reagent container; - long long int container_element_index; - merge_check_point(const reagent& c, long long int i) :container(c), container_element_index(i) {} -}; - -struct merge_check_state { - stack<merge_check_point> data; -}; - -struct scenario { - string name; - string to_run; -}; - -// scan an array of characters in a unicode-aware, bounds-checked manner -struct raw_string_stream { - long long int index; - const long long int max; - const char* buf; - - raw_string_stream(const string&); - uint32_t get(); // unicode codepoint - uint32_t peek(); // unicode codepoint - bool at_end() const; - void skip_whitespace_and_comments(); -}; - -// End Types - -// prototypes are auto-generated in the makefile; define your functions in any order -#include "function_list" // by convention, files ending with '_list' are auto-generated - -// from http://stackoverflow.com/questions/152643/idiomatic-c-for-reading-from-a-const-map -template<typename T> typename T::mapped_type& get(T& map, typename T::key_type const& key) { - typename T::iterator iter(map.find(key)); - assert(iter != map.end()); - return iter->second; -} -template<typename T> typename T::mapped_type const& get(const T& map, typename T::key_type const& key) { - typename T::const_iterator iter(map.find(key)); - assert(iter != map.end()); - return iter->second; -} -template<typename T> typename T::mapped_type const& put(T& map, typename T::key_type const& key, typename T::mapped_type const& value) { - map[key] = value; - return map[key]; -} -template<typename T> bool contains_key(T& map, typename T::key_type const& key) { - return map.find(key) != map.end(); -} -template<typename T> typename T::mapped_type& get_or_insert(T& map, typename T::key_type const& key) { - return map[key]; -} -bool has_data(istream& in) { - return in && !in.eof(); -} - -// Globals -const test_fn Tests[] = { -}; - -bool Run_tests = false; -bool Passed = true; // set this to false inside any test to indicate failure -long Num_failures = 0; - -#define CHECK(X) \ - if (!(X)) { \ - ++Num_failures; \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << '\n'; \ - Passed = false; \ - return; /* Currently we stop at the very first failure. */ \ - } - -#define CHECK_EQ(X, Y) \ - if ((X) != (Y)) { \ - ++Num_failures; \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << " == " << #Y << '\n'; \ - cerr << " got " << (X) << '\n'; /* BEWARE: multiple eval */ \ - Passed = false; \ - return; /* Currently we stop at the very first failure. */ \ - } - -const int Max_depth = 9999; -const int Error_depth = 0; // definitely always print the error that caused death -const int Warning_depth = 1; -const int App_depth = 2; // temporarily where all mu code will trace to -const int Initial_callstack_depth = 101; -const int Max_callstack_depth = 9989; - -map<recipe_ordinal, recipe> Recipe; -map<string, recipe_ordinal> Recipe_ordinal; -recipe_ordinal Next_recipe_ordinal = 1; - -// Locations refer to a common 'memory'. Each location can store a number. -map<long long int, double> Memory; -map<string, type_ordinal> Type_ordinal; -map<type_ordinal, type_info> Type; -type_ordinal Next_type_ordinal = 1; -const string Ignore(","); // commas are ignored in mu except within [] strings -// word boundaries -const string Terminators("(){}"); -bool Disable_redefine_warnings = false; -vector<recipe_ordinal> Recently_added_recipes; -long long int Reserved_for_tests = 1000; -vector<transform_fn> Transform; - -routine* Current_routine = NULL; -map<string, long long int> Instructions_running; -map<string, long long int> Locations_read; -map<string, long long int> Locations_read_by_instruction; - -ofstream LOG; -vector<type_ordinal> Recently_added_types; -long long int foo = -1; -long long int Memory_allocated_until = Reserved_for_tests; -long long int Initial_memory_per_routine = 100000; -map<long long int, long long int> Free_list; -map<recipe_ordinal, map<string, long long int> > Name; -bool Warn_on_missing_default_space = false; -map<recipe_ordinal, recipe_ordinal> Surrounding_space; - -vector<scenario> Scenarios; -set<string> Scenario_names; - - -long long int Num_core_mu_tests = 0; -const scenario* Current_scenario = NULL; -bool Scenario_testing_scenario = false; -map<string /*label*/, recipe> Before_fragments, After_fragments; -set<string /*label*/> Fragments_used; -bool Transform_check_insert_fragments_Ran = false; -map<string, vector<recipe_ordinal> > Recipe_variants; -set<string> Literal_type_names; -list<call> resolve_stack; - -// We'll use large type ordinals to mean "the following type of the variable". -const int START_TYPE_INGREDIENTS = 2000; -vector<recipe_ordinal> Recently_added_shape_shifting_recipes; -vector<routine*> Routines; -long long int Current_routine_index = 0; -long long int Scheduling_interval = 500; -long long int Next_routine_id = 1; -long long int Display_row = 0, Display_column = 0; -bool Autodisplay = true; - -// Scenarios may not define default-space, so they should fit within the -// initial area of memory reserved for tests. We'll put the predefined -// variables available to them at the end of that region. -const long long int Max_variables_in_scenarios = Reserved_for_tests-100; -long long int Next_predefined_global_for_scenarios = Max_variables_in_scenarios; -// Scenario Globals. -const long long int SCREEN = Next_predefined_global_for_scenarios++; -const long long int CONSOLE = Next_predefined_global_for_scenarios++; -// End Scenario Globals. -map<string, long long int> Key; -set<long long int> Visible; -long long int Top_of_screen = 0; -long long int Last_printed_row = 0; -map<int, long long int> Trace_index; // screen row -> trace index - -bool Track_most_recent_products = false; -string Most_recent_products; -// End Globals - -bool Hide_errors = false; -bool Hide_warnings = false; -struct trace_stream { - vector<trace_line> past_lines; - // accumulator for current line - ostringstream* curr_stream; - string curr_label; - int curr_depth; - int callstack_depth; - int collect_depth; - ofstream null_stream; // never opens a file, so writes silently fail - trace_stream() :curr_stream(NULL), curr_depth(Max_depth), callstack_depth(0), collect_depth(Max_depth) {} - ~trace_stream() { if (curr_stream) delete curr_stream; } - - ostream& stream(string label) { - return stream(Max_depth, label); - } - - ostream& stream(int depth, string label) { - if (depth > collect_depth) return null_stream; - curr_stream = new ostringstream; - curr_label = label; - curr_depth = depth; - return *curr_stream; - } - - // be sure to call this before messing with curr_stream or curr_label - void newline() { - if (!curr_stream) return; - string curr_contents = curr_stream->str(); - if (curr_contents.empty()) return; - past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents)); // preserve indent in contents - if (!Hide_errors && curr_label == "error") - cerr << curr_label << ": " << curr_contents << '\n'; - else if (!Hide_warnings && curr_label == "warn") - cerr << curr_label << ": " << curr_contents << '\n'; - delete curr_stream; - curr_stream = NULL; - curr_label.clear(); - curr_depth = Max_depth; - } - - // Useful for debugging. - string readable_contents(string label) { // missing label = everything - ostringstream output; - label = trim(label); - for (vector<trace_line>::iterator p = past_lines.begin(); p != past_lines.end(); ++p) - if (label.empty() || label == p->label) { - output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n'; - } - return output.str(); - } -}; - - - -trace_stream* Trace_stream = NULL; - -// Top-level helper. IMPORTANT: can't nest. -#define trace(...) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__) -#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")) - -ostream& operator<<(ostream& os, unused end) { - if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) { - Current_routine->state = COMPLETED; - } - - - if (Trace_stream) Trace_stream->newline(); - return os; -} - -#define CLEAR_TRACE delete Trace_stream, Trace_stream = new trace_stream; - -#define DUMP(label) if (Trace_stream) cerr << Trace_stream->readable_contents(label); - -// All scenarios save their traces in the repo, just like code. This gives -// future readers more meat when they try to make sense of a new project. -static string Trace_dir = ".traces/"; -string Trace_file; - -// Trace_stream is a resource, lease_tracer uses RAII to manage it. -struct lease_tracer { - lease_tracer() { Trace_stream = new trace_stream; } - ~lease_tracer() { - if (!Trace_stream) return; // in case tests close Trace_stream - if (!Trace_file.empty()) { - ofstream fout((Trace_dir+Trace_file).c_str()); - fout << Trace_stream->readable_contents(""); - fout.close(); - } - delete Trace_stream, Trace_stream = NULL, Trace_file = ""; - } -}; - -#define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer; -bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) { - if (!Trace_stream) return false; - vector<string> expected_lines = split(expected, ""); - long long int curr_expected_line = 0; - while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) - ++curr_expected_line; - if (curr_expected_line == SIZE(expected_lines)) return true; - string label, contents; - split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (label != p->label) - continue; - - if (contents != trim(p->contents)) - continue; - - ++curr_expected_line; - while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) - ++curr_expected_line; - if (curr_expected_line == SIZE(expected_lines)) return true; - split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); - } - - ++Num_failures; - cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n"; - DUMP(label); - Passed = false; - return false; -} - -void split_label_contents(const string& s, string* label, string* contents) { - static const string delim(": "); - size_t pos = s.find(delim); - if (pos == string::npos) { - *label = ""; - *contents = trim(s); - } - else { - *label = trim(s.substr(0, pos)); - *contents = trim(s.substr(pos+SIZE(delim))); - } -} - - - -int trace_count(string label) { - return trace_count(label, ""); -} - -int trace_count(string label, string line) { - long result = 0; - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (label == p->label) { - if (line == "" || trim(line) == trim(p->contents)) - ++result; - } - } - return result; -} - -#define CHECK_TRACE_CONTAINS_ERROR() CHECK(trace_count("error") > 0) -#define CHECK_TRACE_DOESNT_CONTAIN_ERROR() \ - if (trace_count("error") > 0) { \ - ++Num_failures; \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \ - DUMP("error"); \ - Passed = false; \ - return; \ - } - -#define CHECK_TRACE_COUNT(label, count) \ - if (trace_count(label) != (count)) { \ - ++Num_failures; \ - cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): trace_count of " << label << " should be " << count << '\n'; \ - cerr << " got " << trace_count(label) << '\n'; /* multiple eval */ \ - DUMP(label); \ - Passed = false; \ - return; /* Currently we stop at the very first failure. */ \ - } - -bool trace_doesnt_contain(string label, string line) { - return trace_count(label, line) == 0; -} - -bool trace_doesnt_contain(string expected) { - vector<string> tmp = split_first(expected, ": "); - return trace_doesnt_contain(tmp.at(0), tmp.at(1)); -} - -#define CHECK_TRACE_DOESNT_CONTAIN(...) CHECK(trace_doesnt_contain(__VA_ARGS__)) - - - -vector<string> split(string s, string delim) { - vector<string> result; - size_t begin=0, end=s.find(delim); - while (true) { - if (end == string::npos) { - result.push_back(string(s, begin, string::npos)); - break; - } - result.push_back(string(s, begin, end-begin)); - begin = end+SIZE(delim); - end = s.find(delim, begin); - } - return result; -} - -vector<string> split_first(string s, string delim) { - vector<string> result; - size_t end=s.find(delim); - result.push_back(string(s, 0, end)); - if (end != string::npos) - result.push_back(string(s, end+SIZE(delim), string::npos)); - return result; -} - -string trim(const string& s) { - string::const_iterator first = s.begin(); - while (first != s.end() && isspace(*first)) - ++first; - if (first == s.end()) return ""; - - string::const_iterator last = --s.end(); - while (last != s.begin() && isspace(*last)) - --last; - ++last; - return string(first, last); -} - -trace_stream* Save_trace_stream = NULL; -string Save_trace_file; -vector<recipe_ordinal> Save_recently_added_recipes; -vector<recipe_ordinal> Save_recently_added_shape_shifting_recipes; -// End Tracing // hack to ensure most code in this layer comes before anything else - -int main() { - // Begin Transforms - // Begin Instruction Inserting/Deleting Transforms - Transform.push_back(insert_fragments); // NOT idempotent - Transform.push_back(check_insert_fragments); // idempotent - Transform.push_back(rewrite_stashes_to_text); - // End Instruction Inserting/Deleting Transforms - // Begin Instruction Modifying Transforms - Transform.push_back(check_header_ingredients); // idempotent - Transform.push_back(fill_in_reply_ingredients); // idempotent - Transform.push_back(check_or_set_types_by_name); // idempotent - // Begin Type Modifying Transforms - Transform.push_back(deduce_types_from_header); // idempotent - Transform.push_back(check_or_set_invalid_types); // idempotent - // End Type Modifying Transforms - Transform.push_back(collect_surrounding_spaces); // idempotent - Transform.push_back(transform_names); // idempotent - Transform.push_back(resolve_ambiguous_calls); // idempotent - Transform.push_back(update_instruction_operations); // idempotent - Transform.push_back(transform_braces); // idempotent - Transform.push_back(transform_labels); // idempotent - // End Instruction Modifying Transforms - Transform.push_back(check_immutable_ingredients); // idempotent - // End Transforms - // Begin Checks - Transform.push_back(check_instruction); // idempotent - Transform.push_back(check_indirect_calls_against_header); // idempotent - Transform.push_back(check_calls_against_header); // idempotent - Transform.push_back(transform_new_to_allocate); // idempotent - Transform.push_back(check_merge_calls); - Transform.push_back(check_types_of_reply_instructions); - Transform.push_back(check_default_space); // idempotent - Transform.push_back(check_reply_instructions_against_header); // idempotent - // End Checks - - setup_types(); - setup_recipes(); - assert(MAX_PRIMITIVE_RECIPES < 200); // level 0 is primitives; until 199 - Next_recipe_ordinal = 200; - put(Recipe_ordinal, "main", Next_recipe_ordinal++); - put(Recipe_variants, "main", vector<recipe_ordinal>()); // since we manually added main to Recipe_ordinal - Literal_type_names.insert("number"); - Literal_type_names.insert("character"); - - load_permanently("core.mu"); - for (long long int t = 0; t < SIZE(Transform); ++t) { - for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) { - recipe& r = p->second; - if (r.steps.empty()) continue; - if (r.transformed_until != t-1) continue; - if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue; - (*Transform.at(t))(/*recipe_ordinal*/p->first); - r.transformed_until = t; - } - } - load( - "recipe new-editor [\n" - " local-scope\n" - " init:address:shared:list:character <- push 97/a, 0\n" - "]\n"); - cerr << "AAA " << contains_key(Type_ordinal, "_elem") << '\n'; - for (long long int t = 0; t < SIZE(Transform); ++t) { - cerr << "BBB " << t << ' ' << contains_key(Type_ordinal, "_elem") << '\n'; - for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) { - recipe& r = p->second; - if (r.steps.empty()) continue; - if (r.transformed_until != t-1) continue; - cerr << "CCC " << t << ' ' << p->second.name << ' ' << contains_key(Type_ordinal, "_elem") << '\n'; - if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue; - (*Transform.at(t))(/*recipe_ordinal*/p->first); - r.transformed_until = t; - } - } - parse_int_reagents(); // do this after all other transforms have run - check_container_field_types(); - test_replace_type_ingredients_entire(); - return 0; -} - -bool is_integer(const string& s) { - return s.find_first_not_of("0123456789-") == string::npos - && s.find('-', 1) == string::npos - && s.find_first_of("0123456789") != string::npos; -} - -long long int to_integer(string n) { - char* end = NULL; - // safe because string.c_str() is guaranteed to be null-terminated - long long int result = strtoll(n.c_str(), &end, /*any base*/0); - if (*end != '\0') cerr << "tried to convert " << n << " to number\n"; - assert(*end == '\0'); - return result; -} - -void test_is_integer() { - CHECK(is_integer("1234")); - CHECK(is_integer("-1")); - CHECK(!is_integer("234.0")); - CHECK(is_integer("-567")); - CHECK(!is_integer("89-0")); - CHECK(!is_integer("-")); - CHECK(!is_integer("1e3")); // not supported -} - - -void test_trace_check_compares() { - trace("test layer") << "foo" << end(); - CHECK_TRACE_CONTENTS("test layer: foo"); -} - -void test_trace_check_ignores_other_layers() { - trace("test layer 1") << "foo" << end(); - trace("test layer 2") << "bar" << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); - CHECK_TRACE_DOESNT_CONTAIN("test layer 2: foo"); -} - -void test_trace_check_ignores_leading_whitespace() { - trace("test layer 1") << " foo" << end(); - CHECK(trace_count("test layer 1", /*too little whitespace*/"foo") == 1); - CHECK(trace_count("test layer 1", /*too much whitespace*/" foo") == 1); -} - -void test_trace_check_ignores_other_lines() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "bar" << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); -} - -void test_trace_check_ignores_other_lines2() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "bar" << end(); - CHECK_TRACE_CONTENTS("test layer 1: bar"); -} - -void test_trace_ignores_trailing_whitespace() { - trace("test layer 1") << "foo\n" << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); -} - -void test_trace_ignores_trailing_whitespace2() { - trace("test layer 1") << "foo " << end(); - CHECK_TRACE_CONTENTS("test layer 1: foo"); -} - -void test_trace_orders_across_layers() { - trace("test layer 1") << "foo" << end(); - trace("test layer 2") << "bar" << end(); - trace("test layer 1") << "qux" << end(); - CHECK_TRACE_CONTENTS("test layer 1: footest layer 2: bartest layer 1: qux"); -} - -void test_trace_supports_count() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "foo" << end(); - CHECK_EQ(trace_count("test layer 1", "foo"), 2); -} - -void test_trace_supports_count2() { - trace("test layer 1") << "foo" << end(); - trace("test layer 1") << "bar" << end(); - CHECK_EQ(trace_count("test layer 1"), 2); -} - -void test_trace_count_ignores_trailing_whitespace() { - trace("test layer 1") << "foo\n" << end(); - CHECK(trace_count("test layer 1", "foo") == 1); -} - -// pending: DUMP tests -// pending: readable_contents() adds newline if necessary. -// pending: raise also prints to stderr. -// pending: raise doesn't print to stderr if Hide_errors is set. -// pending: raise doesn't have to be saved if Hide_errors is set, just printed. -// pending: raise prints to stderr if Trace_stream is NULL. -// pending: raise prints to stderr if Trace_stream is NULL even if Hide_errors is set. -// pending: raise << ... die() doesn't die if Hide_errors is set. - - - -// can't check trace because trace methods call 'split' - -void test_split_returns_at_least_one_elem() { - vector<string> result = split("", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result.at(0), ""); -} - -void test_split_returns_entire_input_when_no_delim() { - vector<string> result = split("abc", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result.at(0), "abc"); -} - -void test_split_works() { - vector<string> result = split("abc,def", ","); - CHECK_EQ(result.size(), 2); - CHECK_EQ(result.at(0), "abc"); - CHECK_EQ(result.at(1), "def"); -} - -void test_split_works2() { - vector<string> result = split("abc,def,ghi", ","); - CHECK_EQ(result.size(), 3); - CHECK_EQ(result.at(0), "abc"); - CHECK_EQ(result.at(1), "def"); - CHECK_EQ(result.at(2), "ghi"); -} - -void test_split_handles_multichar_delim() { - vector<string> result = split("abc,,def,,ghi", ",,"); - CHECK_EQ(result.size(), 3); - CHECK_EQ(result.at(0), "abc"); - CHECK_EQ(result.at(1), "def"); - CHECK_EQ(result.at(2), "ghi"); -} - -void test_trim() { - CHECK_EQ(trim(""), ""); - CHECK_EQ(trim(" "), ""); - CHECK_EQ(trim(" "), ""); - CHECK_EQ(trim("a"), "a"); - CHECK_EQ(trim(" a"), "a"); - CHECK_EQ(trim(" a"), "a"); - CHECK_EQ(trim(" ab"), "ab"); - CHECK_EQ(trim("a "), "a"); - CHECK_EQ(trim("a "), "a"); - CHECK_EQ(trim("ab "), "ab"); - CHECK_EQ(trim(" a "), "a"); - CHECK_EQ(trim(" a "), "a"); - CHECK_EQ(trim(" ab "), "ab"); -} - -void setup_types() { - Type.clear(); Type_ordinal.clear(); - put(Type_ordinal, "literal", 0); - Next_type_ordinal = 1; - // Mu Types Initialization - type_ordinal number = put(Type_ordinal, "number", Next_type_ordinal++); - put(Type_ordinal, "location", get(Type_ordinal, "number")); // wildcard type: either a pointer or a scalar - get_or_insert(Type, number).name = "number"; - type_ordinal address = put(Type_ordinal, "address", Next_type_ordinal++); - get_or_insert(Type, address).name = "address"; - type_ordinal boolean = put(Type_ordinal, "boolean", Next_type_ordinal++); - get_or_insert(Type, boolean).name = "boolean"; - type_ordinal character = put(Type_ordinal, "character", Next_type_ordinal++); - get_or_insert(Type, character).name = "character"; - // Array types are a special modifier to any other type. For example, - // array:number or array:address:boolean. - type_ordinal array = put(Type_ordinal, "array", Next_type_ordinal++); - get_or_insert(Type, array).name = "array"; - put(Type_ordinal, "literal-string", 0); - - put(Type_ordinal, "offset", 0); - - type_ordinal point = put(Type_ordinal, "point", Next_type_ordinal++); - get_or_insert(Type, point).size = 2; - get(Type, point).kind = CONTAINER; - get(Type, point).name = "point"; - get(Type, point).elements.push_back(reagent("x:number")); - get(Type, point).elements.push_back(reagent("y:number")); - - - // A more complex container, containing another container as one of its - // elements. - type_ordinal point_number = put(Type_ordinal, "point-number", Next_type_ordinal++); - get_or_insert(Type, point_number).size = 2; - get(Type, point_number).kind = CONTAINER; - get(Type, point_number).name = "point-number"; - get(Type, point_number).elements.push_back(reagent("xy:point")); - get(Type, point_number).elements.push_back(reagent("z:number")); - - { - type_ordinal tmp = put(Type_ordinal, "number-or-point", Next_type_ordinal++); - get_or_insert(Type, tmp).size = 2; - get(Type, tmp).kind = EXCLUSIVE_CONTAINER; - get(Type, tmp).name = "number-or-point"; - get(Type, tmp).elements.push_back(reagent("i:number")); - get(Type, tmp).elements.push_back(reagent("p:point")); - } - - put(Type_ordinal, "variant", 0); - - type_ordinal shared = put(Type_ordinal, "shared", Next_type_ordinal++); - get_or_insert(Type, shared).name = "shared"; - put(Type_ordinal, "type", 0); - - put(Type_ordinal, "label", 0); - - put(Type_ordinal, "recipe-literal", 0); - // 'recipe' variables can store recipe-literal - type_ordinal recipe = put(Type_ordinal, "recipe", Next_type_ordinal++); - get_or_insert(Type, recipe).name = "recipe"; - - // End Mu Types Initialization -} -void teardown_types() { - for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) { - for (long long int i = 0; i < SIZE(p->second.elements); ++i) - p->second.elements.clear(); - } - Type_ordinal.clear(); -} -void setup_recipes() { - Recipe.clear(); Recipe_ordinal.clear(); - put(Recipe_ordinal, "idle", IDLE); - // Primitive Recipe Numbers - put(Recipe_ordinal, "copy", COPY); - put(Recipe_ordinal, "add", ADD); - put(Recipe_ordinal, "subtract", SUBTRACT); - put(Recipe_ordinal, "multiply", MULTIPLY); - put(Recipe_ordinal, "divide", DIVIDE); - put(Recipe_ordinal, "divide-with-remainder", DIVIDE_WITH_REMAINDER); - put(Recipe_ordinal, "shift-left", SHIFT_LEFT); - put(Recipe_ordinal, "shift-right", SHIFT_RIGHT); - put(Recipe_ordinal, "and-bits", AND_BITS); - put(Recipe_ordinal, "or-bits", OR_BITS); - put(Recipe_ordinal, "xor-bits", XOR_BITS); - put(Recipe_ordinal, "flip-bits", FLIP_BITS); - put(Recipe_ordinal, "and", AND); - put(Recipe_ordinal, "or", OR); - put(Recipe_ordinal, "not", NOT); - put(Recipe_ordinal, "jump", JUMP); - put(Recipe_ordinal, "jump-if", JUMP_IF); - put(Recipe_ordinal, "jump-unless", JUMP_UNLESS); - put(Recipe_ordinal, "equal", EQUAL); - put(Recipe_ordinal, "greater-than", GREATER_THAN); - put(Recipe_ordinal, "lesser-than", LESSER_THAN); - put(Recipe_ordinal, "greater-or-equal", GREATER_OR_EQUAL); - put(Recipe_ordinal, "lesser-or-equal", LESSER_OR_EQUAL); - put(Recipe_ordinal, "trace", TRACE); - put(Recipe_ordinal, "stash", STASH); - put(Recipe_ordinal, "hide-errors", HIDE_ERRORS); - put(Recipe_ordinal, "show-errors", SHOW_ERRORS); - put(Recipe_ordinal, "trace-until", TRACE_UNTIL); - put(Recipe_ordinal, "$dump-trace", _DUMP_TRACE); - put(Recipe_ordinal, "$clear-trace", _CLEAR_TRACE); - put(Recipe_ordinal, "$save-trace", _SAVE_TRACE); - put(Recipe_ordinal, "assert", ASSERT); - put(Recipe_ordinal, "$print", _PRINT); - put(Recipe_ordinal, "$exit", _EXIT); - put(Recipe_ordinal, "$system", _SYSTEM); - put(Recipe_ordinal, "$dump-memory", _DUMP_MEMORY); - put(Recipe_ordinal, "$log", _LOG); - put(Recipe_ordinal, "get", GET); - put(Recipe_ordinal, "get-address", GET_ADDRESS); - put(Recipe_ordinal, "merge", MERGE); - put(Recipe_ordinal, "$dump", _DUMP); - put(Recipe_ordinal, "$foo", _FOO); - put(Recipe_ordinal, "create-array", CREATE_ARRAY); - put(Recipe_ordinal, "index", INDEX); - put(Recipe_ordinal, "index-address", INDEX_ADDRESS); - put(Recipe_ordinal, "length", LENGTH); - put(Recipe_ordinal, "maybe-convert", MAYBE_CONVERT); - put(Recipe_ordinal, "next-ingredient", NEXT_INGREDIENT); - put(Recipe_ordinal, "rewind-ingredients", REWIND_INGREDIENTS); - put(Recipe_ordinal, "ingredient", INGREDIENT); - put(Recipe_ordinal, "reply", REPLY); - put(Recipe_ordinal, "new", NEW); - put(Recipe_ordinal, "allocate", ALLOCATE); - put(Recipe_ordinal, "abandon", ABANDON); - put(Recipe_ordinal, "to-location-array", TO_LOCATION_ARRAY); - put(Recipe_ordinal, "break", BREAK); - put(Recipe_ordinal, "break-if", BREAK_IF); - put(Recipe_ordinal, "break-unless", BREAK_UNLESS); - put(Recipe_ordinal, "loop", LOOP); - put(Recipe_ordinal, "loop-if", LOOP_IF); - put(Recipe_ordinal, "loop-unless", LOOP_UNLESS); - put(Recipe_ordinal, "run", RUN); - put(Recipe_ordinal, "memory-should-contain", MEMORY_SHOULD_CONTAIN); - put(Recipe_ordinal, "trace-should-contain", TRACE_SHOULD_CONTAIN); - put(Recipe_ordinal, "trace-should-not-contain", TRACE_SHOULD_NOT_CONTAIN); - put(Recipe_ordinal, "check-trace-count-for-label", CHECK_TRACE_COUNT_FOR_LABEL); - put(Recipe_ordinal, "next-ingredient-without-typechecking", NEXT_INGREDIENT_WITHOUT_TYPECHECKING); - put(Recipe_ordinal, "call", CALL); - put(Recipe_ordinal, "start-running", START_RUNNING); - put(Recipe_ordinal, "routine-state", ROUTINE_STATE); - put(Recipe_ordinal, "restart", RESTART); - put(Recipe_ordinal, "stop", STOP); - put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES); - put(Recipe_ordinal, "limit-time", LIMIT_TIME); - put(Recipe_ordinal, "wait-for-location", WAIT_FOR_LOCATION); - put(Recipe_ordinal, "wait-for-routine", WAIT_FOR_ROUTINE); - put(Recipe_ordinal, "switch", SWITCH); - put(Recipe_ordinal, "random", RANDOM); - put(Recipe_ordinal, "make-random-nondeterministic", MAKE_RANDOM_NONDETERMINISTIC); - put(Recipe_ordinal, "round", ROUND); - put(Recipe_ordinal, "hash", HASH); - put(Recipe_ordinal, "hash_old", HASH_OLD); - put(Recipe_ordinal, "open-console", OPEN_CONSOLE); - put(Recipe_ordinal, "close-console", CLOSE_CONSOLE); - put(Recipe_ordinal, "clear-display", CLEAR_DISPLAY); - put(Recipe_ordinal, "sync-display", SYNC_DISPLAY); - put(Recipe_ordinal, "clear-line-on-display", CLEAR_LINE_ON_DISPLAY); - put(Recipe_ordinal, "print-character-to-display", PRINT_CHARACTER_TO_DISPLAY); - put(Recipe_ordinal, "cursor-position-on-display", CURSOR_POSITION_ON_DISPLAY); - put(Recipe_ordinal, "move-cursor-on-display", MOVE_CURSOR_ON_DISPLAY); - put(Recipe_ordinal, "move-cursor-down-on-display", MOVE_CURSOR_DOWN_ON_DISPLAY); - put(Recipe_ordinal, "move-cursor-up-on-display", MOVE_CURSOR_UP_ON_DISPLAY); - put(Recipe_ordinal, "move-cursor-right-on-display", MOVE_CURSOR_RIGHT_ON_DISPLAY); - put(Recipe_ordinal, "move-cursor-left-on-display", MOVE_CURSOR_LEFT_ON_DISPLAY); - put(Recipe_ordinal, "display-width", DISPLAY_WIDTH); - put(Recipe_ordinal, "display-height", DISPLAY_HEIGHT); - put(Recipe_ordinal, "hide-cursor-on-display", HIDE_CURSOR_ON_DISPLAY); - put(Recipe_ordinal, "show-cursor-on-display", SHOW_CURSOR_ON_DISPLAY); - put(Recipe_ordinal, "hide-display", HIDE_DISPLAY); - put(Recipe_ordinal, "show-display", SHOW_DISPLAY); - put(Recipe_ordinal, "wait-for-some-interaction", WAIT_FOR_SOME_INTERACTION); - put(Recipe_ordinal, "check-for-interaction", CHECK_FOR_INTERACTION); - put(Recipe_ordinal, "interactions-left?", INTERACTIONS_LEFT); - put(Recipe_ordinal, "clear-display-from", CLEAR_DISPLAY_FROM); - put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN); - put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR); - put(Recipe_ordinal, "$dump-screen", _DUMP_SCREEN); - put(Recipe_ordinal, "assume-console", ASSUME_CONSOLE); - put(Recipe_ordinal, "replace-in-console", REPLACE_IN_CONSOLE); - put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE); - put(Recipe_ordinal, "run-interactive", RUN_INTERACTIVE); - put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS); - put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS); - put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS); - put(Recipe_ordinal, "save-errors-warnings", SAVE_ERRORS_WARNINGS); - put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE); - put(Recipe_ordinal, "$cleanup-run-interactive", _CLEANUP_RUN_INTERACTIVE); - put(Recipe_ordinal, "reload", RELOAD); - put(Recipe_ordinal, "restore", RESTORE); - put(Recipe_ordinal, "save", SAVE); - // End Primitive Recipe Numbers -} -recipe::recipe() { - transformed_until = -1; - - has_header = false; - - // End recipe Constructor -} - -instruction::instruction() :is_label(false), operation(IDLE) { - tangle_done = false; - - // End instruction Constructor -} -void instruction::clear() { is_label=false; label.clear(); name.clear(); old_name.clear(); operation=IDLE; ingredients.clear(); products.clear(); original_string.clear(); } -bool instruction::is_empty() { return !is_label && name.empty(); } - -// Reagents have the form <name>:<type>:<type>:.../<property>/<property>/... -reagent::reagent(string s) :original_string(s), type(NULL), value(0), initialized(false) { - // Parsing reagent(string s) - if (s.at(0) == '{') { - assert(properties.empty()); - istringstream in(s); - in >> std::noskipws; - in.get(); // skip '{' - name = slurp_key(in); - if (name.empty()) { - raise_error << "invalid reagent " << s << " without a name\n"; - return; - } - if (name == "}") { - raise_error << "invalid empty reagent " << s << '\n'; - return; - } - { - string_tree* value = new string_tree(next_word(in)); - value = parse_string_tree(value); - - // End Parsing Reagent Type Property(value) - type = new_type_tree(value); - delete value; - } - while (has_data(in)) { - string key = slurp_key(in); - if (key.empty()) continue; - if (key == "}") continue; - string_tree* value = new string_tree(next_word(in)); - value = parse_string_tree(value); - // End Parsing Reagent Property(value) - properties.push_back(pair<string, string_tree*>(key, value)); - } - return; - } - - if (is_noninteger(s)) { - name = s; - type = new type_tree("literal-fractional-number", 0); - set_value(to_double(s)); - return; - } - - if (s.at(0) == '[') { - assert(*s.rbegin() == ']'); - // delete [] delimiters - s.erase(0, 1); - strip_last(s); - name = s; - type = new type_tree("literal-string", 0); - return; - } - - - istringstream in(s); - in >> std::noskipws; - // name and type - istringstream first_row(slurp_until(in, '/')); - first_row >> std::noskipws; - name = slurp_until(first_row, ':'); - string_tree* type_names = parse_property_list(first_row); - type = new_type_tree(type_names); - delete type_names; - // special cases - if (is_integer(name) && type == NULL) - type = new type_tree("literal", get(Type_ordinal, "literal")); - if (name == "_" && type == NULL) - type = new type_tree("literal", get(Type_ordinal, "literal")); - // other properties - while (has_data(in)) { - istringstream row(slurp_until(in, '/')); - row >> std::noskipws; - string key = slurp_until(row, ':'); - string_tree* value = parse_property_list(row); - properties.push_back(pair<string, string_tree*>(key, value)); - } - { - while (!name.empty() && name.at(0) == '*') { - name.erase(0, 1); - properties.push_back(pair<string, string_tree*>("lookup", NULL)); - } - if (name.empty()) - raise_error << "illegal name " << original_string << '\n' << end(); - } - - - // End Parsing reagent -} - -string_tree* parse_property_list(istream& in) { - skip_whitespace_but_not_newline(in); - if (!has_data(in)) return NULL; - string_tree* result = new string_tree(slurp_until(in, ':')); - result->right = parse_property_list(in); - return result; -} - -// TODO: delete -type_tree* new_type_tree(const string_tree* properties) { - if (!properties) return NULL; - type_tree* result = new type_tree("", 0); - if (!properties->value.empty()) { - const string& type_name = result->name = properties->value; - if (contains_key(Type_ordinal, type_name)) - result->value = get(Type_ordinal, type_name); - else if (is_integer(type_name)) // sometimes types will contain non-type tags, like numbers for the size of an array - result->value = 0; - else if (properties->value != "->") // used in recipe types - result->value = -1; // should never happen; will trigger errors later - } - result->left = new_type_tree(properties->left); - result->right = new_type_tree(properties->right); - return result; -} - - -reagent::reagent(const reagent& old) { - original_string = old.original_string; - name = old.name; - value = old.value; - initialized = old.initialized; - properties.clear(); - for (long long int i = 0; i < SIZE(old.properties); ++i) { - properties.push_back(pair<string, string_tree*>(old.properties.at(i).first, - old.properties.at(i).second ? new string_tree(*old.properties.at(i).second) : NULL)); - } - type = old.type ? new type_tree(*old.type) : NULL; -} - -type_tree::type_tree(const type_tree& old) { - name = old.name; - value = old.value; - left = old.left ? new type_tree(*old.left) : NULL; - right = old.right ? new type_tree(*old.right) : NULL; -} - -string_tree::string_tree(const string_tree& old) { // :value(old.value) { - value = old.value; - left = old.left ? new string_tree(*old.left) : NULL; - right = old.right ? new string_tree(*old.right) : NULL; -} - -reagent& reagent::operator=(const reagent& old) { - original_string = old.original_string; - properties.clear(); - for (long long int i = 0; i < SIZE(old.properties); ++i) - properties.push_back(pair<string, string_tree*>(old.properties.at(i).first, old.properties.at(i).second ? new string_tree(*old.properties.at(i).second) : NULL)); - name = old.name; - value = old.value; - initialized = old.initialized; - type = old.type ? new type_tree(*old.type) : NULL; - return *this; -} - -reagent::~reagent() { - 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; - delete right; -} -string_tree::~string_tree() { - delete left; - delete right; -} - -string slurp_until(istream& in, char delim) { - ostringstream out; - char c; - while (in >> c) { - if (c == delim) { - // drop the delim - break; - } - out << c; - } - return out.str(); -} - -bool has_property(reagent x, string name) { - for (long long int i = 0; i < SIZE(x.properties); ++i) { - if (x.properties.at(i).first == name) return true; - } - return false; -} - -string_tree* property(const reagent& r, const string& name) { - for (long long int p = 0; p != SIZE(r.properties); ++p) { - if (r.properties.at(p).first == name) - return r.properties.at(p).second; - } - return NULL; -} - -void skip_whitespace_but_not_newline(istream& in) { - while (true) { - if (!has_data(in)) break; - else if (in.peek() == '\n') break; - else if (isspace(in.peek())) in.get(); - else if (Ignore.find(in.peek()) != string::npos) in.get(); - else break; - } -} - -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'; - } -} - - -string to_string(const recipe& r) { - ostringstream out; - out << "recipe " << r.name << " [\n"; - for (long long int i = 0; i < SIZE(r.steps); ++i) - out << " " << to_string(r.steps.at(i)) << '\n'; - out << "]\n"; - return out.str(); -} - -string debug_string(const recipe& x) { - ostringstream out; - out << "- recipe " << x.name << '\n'; - // Begin debug_string(recipe x) - out << "ingredients:\n"; - for (long long int i = 0; i < SIZE(x.ingredients); ++i) - out << " " << debug_string(x.ingredients.at(i)) << '\n'; - out << "products:\n"; - for (long long int i = 0; i < SIZE(x.products); ++i) - out << " " << debug_string(x.products.at(i)) << '\n'; - - - for (long long int index = 0; index < SIZE(x.steps); ++index) { - const instruction& inst = x.steps.at(index); - out << "inst: " << to_string(inst) << '\n'; - out << " ingredients\n"; - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) - out << " " << debug_string(inst.ingredients.at(i)) << '\n'; - out << " products\n"; - for (long long int i = 0; i < SIZE(inst.products); ++i) - out << " " << debug_string(inst.products.at(i)) << '\n'; - } - return out.str(); -} - -string to_string(const instruction& inst) { - if (inst.is_label) return inst.label; - ostringstream out; - for (long long int i = 0; i < SIZE(inst.products); ++i) { - if (i > 0) out << ", "; - out << inst.products.at(i).original_string; - } - if (!inst.products.empty()) out << " <- "; - out << inst.name << ' '; - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (i > 0) out << ", "; - out << inst.ingredients.at(i).original_string; - } - return out.str(); -} - -string to_string(const reagent& r) { - if (is_literal_string(r)) - return emit_literal_string(r.name); - - ostringstream out; - out << r.name << ": " << names_to_string(r.type); - if (!r.properties.empty()) { - out << ", {"; - for (long long int i = 0; i < SIZE(r.properties); ++i) { - if (i > 0) out << ", "; - out << "\"" << r.properties.at(i).first << "\": " << to_string(r.properties.at(i).second); - } - out << "}"; - } - return out.str(); -} - -string debug_string(const reagent& x) { - ostringstream out; - out << x.name << ": " << x.value << ' ' << to_string(x.type) << " -- " << to_string(x); - return out.str(); -} - -string to_string(const string_tree* property) { - if (!property) return "()"; - ostringstream out; - if (!property->left && !property->right) - // abbreviate a single-node tree to just its contents - out << '"' << property->value << '"'; - else - dump(property, out); - return out.str(); -} - -void dump(const string_tree* x, ostream& out) { - if (!x->left && !x->right) { - out << x->value; - return; - } - out << '('; - for (const string_tree* curr = x; curr; curr = curr->right) { - if (curr != x) out << ' '; - if (curr->left) - dump(curr->left, out); - else - out << '"' << curr->value << '"'; - } - out << ')'; -} - -string to_string(const type_tree* type) { - // abbreviate a single-node tree to just its contents - if (!type) return "NULLNULLNULL"; // should never happen - ostringstream out; - dump(type, out); - return out.str(); -} - -void dump(const type_tree* x, ostream& out) { - if (!x->left && !x->right) { - out << x->value; - return; - } - out << '('; - for (const type_tree* curr = x; curr; curr = curr->right) { - if (curr != x) out << ' '; - if (curr->left) - dump(curr->left, out); - else - dump(curr->value, out); - } - out << ')'; -} - -void dump(type_ordinal type, ostream& out) { - if (contains_key(Type, type)) - out << get(Type, type).name; - else - out << "?" << type; -} - -string names_to_string(const type_tree* type) { - // abbreviate a single-node tree to just its contents - if (!type) return "()"; // should never happen - ostringstream out; - dump_names(type, out); - return out.str(); -} - -void dump_names(const type_tree* type, ostream& out) { - if (!type->left && !type->right) { - out << '"' << type->name << '"'; - return; - } - out << '('; - for (const type_tree* curr = type; curr; curr = curr->right) { - if (curr != type) out << ' '; - if (curr->left) - dump_names(curr->left, out); - else - out << '"' << curr->name << '"'; - } - out << ')'; -} - -string names_to_string_without_quotes(const type_tree* type) { - // abbreviate a single-node tree to just its contents - if (!type) return "NULLNULLNULL"; // should never happen - ostringstream out; - dump_names_without_quotes(type, out); - return out.str(); -} - -void dump_names_without_quotes(const type_tree* type, ostream& out) { - if (!type->left && !type->right) { - out << type->name; - return; - } - out << '('; - for (const type_tree* curr = type; curr; curr = curr->right) { - if (curr != type) out << ' '; - if (curr->left) - dump_names_without_quotes(curr->left, out); - else - out << curr->name; - } - out << ')'; -} - - -ostream& operator<<(ostream& os, no_scientific x) { - if (!isfinite(x.x)) { - // Infinity or NaN - os << x.x; - return os; - } - ostringstream tmp; - tmp << std::fixed << x.x; - os << trim_floating_point(tmp.str()); - return os; -} - -string trim_floating_point(const string& in) { - if (in.empty()) return ""; - long long int len = SIZE(in); - while (len > 1) { - if (in.at(len-1) != '0') break; - --len; - } - if (in.at(len-1) == '.') --len; - return in.substr(0, len); -} - -void test_trim_floating_point() { - CHECK_EQ("", trim_floating_point("")); - CHECK_EQ("0", trim_floating_point("000000000")); - CHECK_EQ("1.5", trim_floating_point("1.5000")); - CHECK_EQ("1.000001", trim_floating_point("1.000001")); - CHECK_EQ("23", trim_floating_point("23.000000")); - CHECK_EQ("23", trim_floating_point("23.0")); - CHECK_EQ("23", trim_floating_point("23.")); - CHECK_EQ("23", trim_floating_point("23")); - CHECK_EQ("3", trim_floating_point("3.000000")); - CHECK_EQ("3", trim_floating_point("3.0")); - CHECK_EQ("3", trim_floating_point("3.")); - CHECK_EQ("3", trim_floating_point("3")); -} - - -void test_first_recipe() { - Trace_file = "first_recipe"; - load("recipe main [\n 1:number <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 1: \"number\""); -} -vector<recipe_ordinal> load(string form) { - istringstream in(form); - in >> std::noskipws; - return load(in); -} - -vector<recipe_ordinal> load(istream& in) { - in >> std::noskipws; - vector<recipe_ordinal> result; - while (has_data(in)) { - skip_whitespace_and_comments(in); - if (!has_data(in)) break; - string command = next_word(in); - // Command Handlers - if (command == "recipe") { - result.push_back(slurp_recipe(in)); - } - else if (command == "recipe!") { - Disable_redefine_warnings = true; - result.push_back(slurp_recipe(in)); - Disable_redefine_warnings = false; - } - else if (command == "container") { - insert_container(command, CONTAINER, in); - } - - else if (command == "exclusive-container") { - insert_container(command, EXCLUSIVE_CONTAINER, in); - } - - - else if (command == "scenario") { - Scenarios.push_back(parse_scenario(in)); - } - - else if (command == "before") { - string label = next_word(in); - recipe tmp; - slurp_body(in, tmp); - if (is_waypoint(label)) - Before_fragments[label].steps.insert(Before_fragments[label].steps.end(), tmp.steps.begin(), tmp.steps.end()); - else - raise_error << "can't tangle before label " << label << '\n' << end(); - } - else if (command == "after") { - string label = next_word(in); - recipe tmp; - slurp_body(in, tmp); - if (is_waypoint(label)) - After_fragments[label].steps.insert(After_fragments[label].steps.begin(), tmp.steps.begin(), tmp.steps.end()); - else - raise_error << "can't tangle after label " << label << '\n' << end(); - } - - - // End Command Handlers - else { - raise_error << "unknown top-level command: " << command << '\n' << end(); - } - } - return result; -} - -long long int slurp_recipe(istream& in) { - recipe result; - result.name = next_word(in); - result.original_name = result.name; - - // End Load Recipe Name - skip_whitespace_but_not_newline(in); - if (in.peek() != '[') { - trace(9999, "parse") << "recipe has a header; parsing" << end(); - load_recipe_header(in, result); - } - - // End Recipe Refinements - if (result.name.empty()) - raise_error << "empty result.name\n" << end(); - trace(9991, "parse") << "--- defining " << result.name << end(); - if (!contains_key(Recipe_ordinal, result.name)) - put(Recipe_ordinal, result.name, Next_recipe_ordinal++); - if (Recipe.find(get(Recipe_ordinal, result.name)) != Recipe.end()) { - trace(9991, "parse") << "already exists" << end(); - if (warn_on_redefine(result.name)) - raise << "redefining recipe " << result.name << "\n" << end(); - Recipe.erase(get(Recipe_ordinal, result.name)); - } - slurp_body(in, result); - if (!result.has_header) { - result.has_header = true; - for (long long int i = 0; i < SIZE(result.steps); ++i) { - const instruction& inst = result.steps.at(i); - if ((inst.name == "reply" && !inst.ingredients.empty()) - || inst.name == "next-ingredient" - || inst.name == "ingredient" - || inst.name == "rewind-ingredients") { - result.has_header = false; - break; - } - } - } - if (result.has_header) { - trace(9999, "parse") << "recipe " << result.name << " has a header" << end(); - } - - - // End Recipe Body(result) - put(Recipe, get(Recipe_ordinal, result.name), result); - // track added recipes because we may need to undo them in tests; see below - Recently_added_recipes.push_back(get(Recipe_ordinal, result.name)); - return get(Recipe_ordinal, result.name); -} - -void slurp_body(istream& in, recipe& result) { - in >> std::noskipws; - skip_whitespace_but_not_newline(in); - if (in.get() != '[') - raise_error << "recipe body must begin with '['\n" << end(); - skip_whitespace_and_comments(in); // permit trailing comment after '[' - instruction curr; - while (next_instruction(in, &curr)) { - // rewrite `reply-if a, b, c, ...` to - // ``` - // jump-unless a, 1:offset - // reply b, c, ... - // ``` - if (curr.name == "reply-if") { - if (curr.products.empty()) { - curr.operation = get(Recipe_ordinal, "jump-unless"); - curr.name = "jump-unless"; - vector<reagent> results; - copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end())); - curr.ingredients.resize(1); - curr.ingredients.push_back(reagent("1:offset")); - result.steps.push_back(curr); - curr.clear(); - curr.operation = get(Recipe_ordinal, "reply"); - curr.name = "reply"; - curr.ingredients.swap(results); - } - else { - raise_error << "'reply-if' never yields any products\n" << end(); - } - } - // rewrite `reply-unless a, b, c, ...` to - // ``` - // jump-if a, 1:offset - // reply b, c, ... - // ``` - if (curr.name == "reply-unless") { - if (curr.products.empty()) { - curr.operation = get(Recipe_ordinal, "jump-if"); - curr.name = "jump-if"; - vector<reagent> results; - copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end())); - curr.ingredients.resize(1); - curr.ingredients.push_back(reagent("1:offset")); - result.steps.push_back(curr); - curr.clear(); - curr.operation = get(Recipe_ordinal, "reply"); - curr.name = "reply"; - curr.ingredients.swap(results); - } - else { - raise_error << "'reply-unless' never yields any products\n" << end(); - } - } - - // rewrite `new-default-space` to - // `default-space:address:shared:array:location <- new location:type, number-of-locals:literal` - // where N is Name[recipe][""] - if (curr.name == "new-default-space") { - rewrite_default_space_instruction(curr); - } - if (curr.name == "local-scope") { - rewrite_default_space_instruction(curr); - } - - if (curr.name == "load-ingredients") { - curr.clear(); - recipe_ordinal op = get(Recipe_ordinal, "next-ingredient-without-typechecking"); - for (long long int i = 0; i < SIZE(result.ingredients); ++i) { - curr.operation = op; - curr.name = "next-ingredient-without-typechecking"; - curr.products.push_back(result.ingredients.at(i)); - result.steps.push_back(curr); - curr.clear(); - } - } - - // rewrite `assume-screen width, height` to - // `screen:address:shared:screen <- new-fake-screen width, height` - if (curr.name == "assume-screen") { - curr.name = "new-fake-screen"; - assert(curr.products.empty()); - curr.products.push_back(reagent("screen:address:shared:screen")); - curr.products.at(0).set_value(SCREEN); - } - - // End Rewrite Instruction(curr, recipe result) - trace(9992, "load") << "after rewriting: " << to_string(curr) << end(); - if (!curr.is_empty()) { - curr.original_string = to_string(curr); - result.steps.push_back(curr); - } - } -} - -bool next_instruction(istream& in, instruction* curr) { - curr->clear(); - skip_whitespace_and_comments(in); - if (!has_data(in)) { - raise_error << "0: unbalanced '[' for recipe\n" << end(); - return false; - } - - vector<string> words; - while (has_data(in) && in.peek() != '\n') { - skip_whitespace_but_not_newline(in); - if (!has_data(in)) { - raise_error << "1: unbalanced '[' for recipe\n" << end(); - return false; - } - string word = next_word(in); - words.push_back(word); - skip_whitespace_but_not_newline(in); - } - skip_whitespace_and_comments(in); - if (SIZE(words) == 1 && words.at(0) == "]") - return false; // end of recipe - - if (SIZE(words) == 1 && !isalnum(words.at(0).at(0)) && words.at(0).at(0) != '$') { - curr->is_label = true; - curr->label = words.at(0); - trace(9993, "parse") << "label: " << curr->label << end(); - if (!has_data(in)) { - raise_error << "7: unbalanced '[' for recipe\n" << end(); - return false; - } - return true; - } - - vector<string>::iterator p = words.begin(); - if (find(words.begin(), words.end(), "<-") != words.end()) { - for (; *p != "<-"; ++p) - curr->products.push_back(reagent(*p)); - ++p; // skip <- - } - - if (p == words.end()) { - raise_error << "instruction prematurely ended with '<-'\n" << end(); - return false; - } - curr->old_name = curr->name = *p; p++; - // curr->operation will be set in a later layer - - for (; p != words.end(); ++p) - curr->ingredients.push_back(reagent(*p)); - - trace(9993, "parse") << "instruction: " << curr->name << end(); - trace(9993, "parse") << " number of ingredients: " << SIZE(curr->ingredients) << end(); - for (vector<reagent>::iterator p = curr->ingredients.begin(); p != curr->ingredients.end(); ++p) - trace(9993, "parse") << " ingredient: " << to_string(*p) << end(); - for (vector<reagent>::iterator p = curr->products.begin(); p != curr->products.end(); ++p) - trace(9993, "parse") << " product: " << to_string(*p) << end(); - if (!has_data(in)) { - raise_error << "9: unbalanced '[' for recipe\n" << end(); - return false; - } - return true; -} - -string next_word(istream& in) { - skip_whitespace_but_not_newline(in); - if (in.peek() == '[') { - string result = slurp_quoted(in); - skip_whitespace_and_comments_but_not_newline(in); - return result; - } - - 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); - - // End next_word Special-cases - ostringstream out; - slurp_word(in, out); - skip_whitespace_and_comments_but_not_newline(in); - return out.str(); -} - -void slurp_word(istream& in, ostream& out) { - char c; - if (has_data(in) && Terminators.find(in.peek()) != string::npos) { - in >> c; - out << c; - return; - } - while (in >> c) { - if (isspace(c) || Terminators.find(c) != string::npos || Ignore.find(c) != string::npos) { - in.putback(c); - break; - } - out << c; - } -} - -void skip_whitespace_and_comments(istream& in) { - while (true) { - if (!has_data(in)) break; - if (isspace(in.peek())) in.get(); - else if (Ignore.find(in.peek()) != string::npos) in.get(); - else if (in.peek() == '#') skip_comment(in); - else break; - } -} - -// confusing; move to the next line only to skip a comment, but never otherwise -void skip_whitespace_and_comments_but_not_newline(istream& in) { - while (true) { - if (!has_data(in)) break; - if (in.peek() == '\n') break; - if (isspace(in.peek())) in.get(); - else if (Ignore.find(in.peek()) != string::npos) in.get(); - else if (in.peek() == '#') skip_comment(in); - else break; - } -} - -void skip_comment(istream& in) { - if (has_data(in) && in.peek() == '#') { - in.get(); - while (has_data(in) && in.peek() != '\n') in.get(); - } -} - -bool warn_on_redefine(const string& recipe_name) { - if (recipe_name.find("scenario-") == 0) return true; - - - - if (Disable_redefine_warnings) return false; - return true; -} - -// for debugging -void show_rest_of_stream(istream& in) { - cerr << '^'; - char c; - while (in >> c) - cerr << c; - cerr << "$\n"; - exit(0); -} - -void clear_recently_added_recipes() { - for (long long int i = 0; i < SIZE(Recently_added_recipes); ++i) { - if (Recently_added_recipes.at(i) >= Reserved_for_tests // don't renumber existing recipes, like 'interactive' - && contains_key(Recipe, Recently_added_recipes.at(i))) // in case previous test had duplicate definitions - Recipe_ordinal.erase(get(Recipe, Recently_added_recipes.at(i)).name); - Recipe.erase(Recently_added_recipes.at(i)); - } - for (map<string, vector<recipe_ordinal> >::iterator p = Recipe_variants.begin(); p != Recipe_variants.end(); ++p) { - for (long long int i = 0; i < SIZE(p->second); ++i) { - if (find(Recently_added_recipes.begin(), Recently_added_recipes.end(), p->second.at(i)) != Recently_added_recipes.end()) - p->second.at(i) = -1; // just leave a ghost - } - } - - // Clear Other State For Recently_added_recipes - for (long long int i = 0; i < SIZE(Recently_added_recipes); ++i) { - Name.erase(Recently_added_recipes.at(i)); - } - - Recently_added_recipes.clear(); -} - -void test_parse_comment_outside_recipe() { - Trace_file = "parse_comment_outside_recipe"; - load("# this comment will be dropped by the tangler, so we need a dummy recipe to stop that\nrecipe f1 [ ]\n# this comment will go through to 'load'\nrecipe main [\n 1:number <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 1: \"number\""); -} -void test_parse_comment_amongst_instruction() { - Trace_file = "parse_comment_amongst_instruction"; - load("recipe main [\n # comment\n 1:number <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 1: \"number\""); -} -void test_parse_comment_amongst_instruction_2() { - Trace_file = "parse_comment_amongst_instruction_2"; - load("recipe main [\n # comment\n 1:number <- copy 23\n # comment\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 1: \"number\""); -} -void test_parse_comment_amongst_instruction_3() { - Trace_file = "parse_comment_amongst_instruction_3"; - load("recipe main [\n 1:number <- copy 23\n # comment\n 2:number <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 1: \"number\"parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 2: \"number\""); -} -void test_parse_comment_after_instruction() { - Trace_file = "parse_comment_after_instruction"; - load("recipe main [\n 1:number <- copy 23 # comment\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 1: \"number\""); -} -void test_parse_label() { - Trace_file = "parse_label"; - load("recipe main [\n +foo\n]\n"); - CHECK_TRACE_CONTENTS("parse: label: +foo"); -} -void test_parse_dollar_as_recipe_name() { - Trace_file = "parse_dollar_as_recipe_name"; - load("recipe main [\n $foo\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: $foo"); -} -void test_parse_multiple_properties() { - Trace_file = "parse_multiple_properties"; - load("recipe main [\n 1:number <- copy 23/foo:bar:baz\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\", {\"foo\": (\"bar\" \"baz\")}parse: product: 1: \"number\""); -} -void test_parse_multiple_products() { - Trace_file = "parse_multiple_products"; - load("recipe main [\n 1:number, 2:number <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: product: 1: \"number\"parse: product: 2: \"number\""); -} -void test_parse_multiple_ingredients() { - Trace_file = "parse_multiple_ingredients"; - load("recipe main [\n 1:number, 2:number <- copy 23, 4:number\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: ingredient: 4: \"number\"parse: product: 1: \"number\"parse: product: 2: \"number\""); -} -void test_parse_multiple_types() { - Trace_file = "parse_multiple_types"; - load("recipe main [\n 1:number, 2:address:number <- copy 23, 4:number\n]\n"); - CHECK_TRACE_CONTENTS("parse: instruction: copyparse: ingredient: 23: \"literal\"parse: ingredient: 4: \"number\"parse: product: 1: \"number\"parse: product: 2: (\"address\" \"number\")"); -} -void test_parse_properties() { - Trace_file = "parse_properties"; - load("recipe main [\n 1:address:number/lookup <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("parse: product: 1: (\"address\" \"number\"), {\"lookup\": ()}"); -} -void test_parse_comment_terminated_by_eof() { - Trace_file = "parse_comment_terminated_by_eof"; - load("recipe main [\n" - " a:number <- copy 34\n" - "]\n" - "# abc"); // no newline after comment - cerr << "."; // termination = success -} - -void test_warn_on_redefine() { - Trace_file = "warn_on_redefine"; - Hide_warnings = true; - load("recipe main [\n 1:number <- copy 23\n]\nrecipe main [\n 1:number <- copy 24\n]\n"); - CHECK_TRACE_CONTENTS("warn: redefining recipe main"); -} -void test_redefine_without_warning() { - Trace_file = "redefine_without_warning"; - Hide_warnings = true; - load("recipe main [\n 1:number <- copy 23\n]\nrecipe! main [\n 1:number <- copy 24\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("warn: redefining recipe main"); - CHECK_TRACE_COUNT("warn", 0); -} - -void transform_all() { - trace(9990, "transform") << "=== transform_all()" << end(); - for (long long int t = 0; t < SIZE(Transform); ++t) { -//? cerr << "transform " << t << '\n'; - for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) { - recipe& r = p->second; - if (r.steps.empty()) continue; - if (r.transformed_until != t-1) continue; - if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue; - - // End Transform Checks - (*Transform.at(t))(/*recipe_ordinal*/p->first); - r.transformed_until = t; - } - } -//? cerr << "wrapping up transform\n"; - parse_int_reagents(); // do this after all other transforms have run - check_container_field_types(); - - // End Transform All -} - -void parse_int_reagents() { - trace(9991, "transform") << "--- parsing any uninitialized reagents as integers" << end(); -//? cerr << "--- parsing any uninitialized reagents as integers" << '\n'; - for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) { - recipe& r = p->second; - if (r.steps.empty()) continue; - for (long long int index = 0; index < SIZE(r.steps); ++index) { - instruction& inst = r.steps.at(index); - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - populate_value(inst.ingredients.at(i)); - } - for (long long int i = 0; i < SIZE(inst.products); ++i) { - populate_value(inst.products.at(i)); - } - } - } -} - -void populate_value(reagent& r) { - if (r.initialized) return; - // End Reagent-parsing Exceptions - if (!is_integer(r.name)) return; - r.set_value(to_integer(r.name)); -} - - -void update_instruction_operations(recipe_ordinal r) { - trace(9991, "transform") << "--- compute instruction operations for recipe " << get(Recipe, r).name << end(); - recipe& caller = get(Recipe, r); -//? cerr << "--- compute instruction operations for recipe " << caller.name << '\n'; - for (long long int index = 0; index < SIZE(caller.steps); ++index) { - instruction& inst = caller.steps.at(index); - if (inst.is_label) continue; - if (!contains_key(Recipe_ordinal, inst.name)) { - raise_error << maybe(caller.name) << "instruction " << inst.name << " has no recipe\n" << end(); - return; - } - inst.operation = get(Recipe_ordinal, inst.name); - if (contains_key(Recipe, inst.operation) && inst.operation >= MAX_PRIMITIVE_RECIPES - && any_type_ingredient_in_header(inst.operation)) { - raise_error << maybe(caller.name) << "instruction " << inst.name << " has no valid specialization\n" << end(); - return; - } - - // End Instruction Operation Checks - } -} - -// hook to suppress inserting recipe name into errors and warnings (for later layers) -string maybe(string s) { - if (s == "interactive") return ""; - - return s + ": "; -} - -// temporarily suppress run -void transform(string form) { - load(form); - transform_all(); -} - - -void test_string_literal() { - Trace_file = "string_literal"; - load("recipe main [\n 1:address:array:character <- copy [abc def] # copy can't really take a string\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"abc def\": \"literal-string\""); -} -void test_string_literal_with_colons() { - Trace_file = "string_literal_with_colons"; - load("recipe main [\n 1:address:array:character <- copy [abc:def/ghi]\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"abc:def/ghi\": \"literal-string\""); -} -string slurp_quoted(istream& in) { - ostringstream out; - 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 - slurp_quoted_comment_oblivious(in, out); - return out.str(); -} - -// 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 (has_data(in)) { - char c = in.get(); - if (!isspace(c)) { - in.putback(c); - return false; - } - out << c; - if (c == '\n') { - return true; - } - } - return false; -} - -// Read a regular string. Regular strings can only contain other regular -// strings. -void slurp_quoted_comment_oblivious(istream& in, ostream& out) { - int brace_depth = 1; - while (has_data(in)) { - char c = in.get(); - if (c == '\\') { - out << static_cast<char>(in.get()); - continue; - } - out << c; - if (c == '[') ++brace_depth; - if (c == ']') --brace_depth; - if (brace_depth == 0) break; - } - if (!has_data(in) && brace_depth > 0) { - raise_error << "unbalanced '['\n" << end(); - out.clear(); - } -} - -// Read a code string. Code strings can contain either code or regular strings. -void slurp_quoted_comment_aware(istream& in, ostream& out) { - char c; - while (in >> c) { - if (c == '\\') { - out << static_cast<char>(in.get()); - continue; - } - if (c == '#') { - out << c; - while (has_data(in) && in.peek() != '\n') out << static_cast<char>(in.get()); - continue; - } - if (c == '[') { - in.putback(c); - // recurse - out << slurp_quoted(in); - continue; - } - out << c; - if (c == ']') return; - } - raise_error << "unbalanced '['\n" << end(); - out.clear(); -} - -bool is_literal_string(const reagent& x) { - return x.type && x.type->name == "literal-string"; -} - -string emit_literal_string(string name) { - size_t pos = 0; - while (pos != string::npos) - pos = replace(name, "\n", "\\n", pos); - return '"'+name+"\": \"literal-string\""; -} - -size_t replace(string& str, const string& from, const string& to, size_t n) { - size_t result = str.find(from, n); - if (result != string::npos) - str.replace(result, from.length(), to); - return result; -} - -void strip_last(string& s) { - if (!s.empty()) s.erase(SIZE(s)-1); -} - -void test_string_literal_nested() { - Trace_file = "string_literal_nested"; - load("recipe main [\n 1:address:array:character <- copy [abc [def]]\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"abc [def]\": \"literal-string\""); -} -void test_string_literal_escaped() { - Trace_file = "string_literal_escaped"; - load("recipe main [\n 1:address:array:character <- copy [abc \\[def]\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"abc [def\": \"literal-string\""); -} -void test_string_literal_escaped_comment_aware() { - Trace_file = "string_literal_escaped_comment_aware"; - load("recipe main [\n 1:address:array:character <- copy [\nabc \\\\\\[def]\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"\\nabc \\[def\": \"literal-string\""); -} -void test_string_literal_and_comment() { - Trace_file = "string_literal_and_comment"; - load("recipe main [\n 1:address:array:character <- copy [abc] # comment\n]\n"); - CHECK_TRACE_CONTENTS("parse: --- defining mainparse: instruction: copyparse: number of ingredients: 1parse: ingredient: \"abc\": \"literal-string\"parse: product: 1: (\"address\" \"array\" \"character\")"); -} -void test_string_literal_escapes_newlines_in_trace() { - Trace_file = "string_literal_escapes_newlines_in_trace"; - load("recipe main [\n copy [abc\ndef]\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"abc\\ndef\": \"literal-string\""); -} -void test_string_literal_can_skip_past_comments() { - Trace_file = "string_literal_can_skip_past_comments"; - load("recipe main [\n copy [\n # ']' inside comment\n bar\n ]\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"\\n # ']' inside comment\\n bar\\n \": \"literal-string\""); -} -void test_string_literal_empty() { - Trace_file = "string_literal_empty"; - load("recipe main [\n copy []\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: \"\": \"literal-string\""); -} - -void test_noninteger_literal() { - Trace_file = "noninteger_literal"; - load("recipe main [\n 1:number <- copy 3.14159\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: 3.14159: \"literal-fractional-number\""); -} -bool is_noninteger(const string& s) { - return s.find_first_not_of("0123456789-.") == string::npos - && s.find_first_of ("0123456789-") != string::npos - && std::count(s.begin(), s.end(), '.') == 1; -} - -double to_double(string n) { - char* end = NULL; - // safe because string.c_str() is guaranteed to be null-terminated - double result = strtod(n.c_str(), &end); - assert(*end == '\0'); - return result; -} - -void test_is_noninteger() { - CHECK(!is_noninteger("1234")); - CHECK(!is_noninteger("1a2")); - CHECK(is_noninteger("234.0")); - CHECK(!is_noninteger("...")); - CHECK(!is_noninteger(".")); - CHECK(is_noninteger("2.")); - CHECK(is_noninteger(".2")); -} - - -void test_copy_literal() { - Trace_file = "copy_literal"; - run("recipe main [\n 1:number <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:number <- copy 23mem: storing 23 in location 1"); -} -void test_copy() { - Trace_file = "copy"; - run("recipe main [\n 1:number <- copy 23\n 2:number <- copy 1:number\n]\n"); - CHECK_TRACE_CONTENTS("run: 2:number <- copy 1:numbermem: location 1 is 23mem: storing 23 in location 2"); -} -void test_copy_multiple() { - Trace_file = "copy_multiple"; - run("recipe main [\n 1:number, 2:number <- copy 23, 24\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 23 in location 1mem: storing 24 in location 2"); -} -void run(recipe_ordinal r) { - run(new routine(r)); -} - - -void run_current_routine(long long int time_slice) -{ // curly on a separate line, because later layers will modify header - long long int ninstrs = 0; - while (Current_routine->state == RUNNING && ninstrs < time_slice) - { - // when we reach the end of one call, we may reach the end of the one below - // it, and the one below that, and so on - while (current_step_index() >= SIZE(Current_routine->steps())) { - // Falling Through End Of Recipe - try_reclaim_locals(); - if (Trace_stream) { - trace(9999, "trace") << "fall-through: exiting " << current_recipe_name() << "; decrementing callstack depth from " << Trace_stream->callstack_depth << end(); - --Trace_stream->callstack_depth; - assert(Trace_stream->callstack_depth >= 0); - } - Current_routine->calls.pop_front(); - if (Current_routine->calls.empty()) return; - // Complete Call Fallthrough - // todo: fail if no products returned - ++current_step_index(); - } - - // Running One Instruction - ninstrs++; - - - if (Current_routine->calls.front().running_step_index == 0 - && any_type_ingredient_in_header(Current_routine->calls.front().running_recipe)) { - //? DUMP(""); - raise_error << "ran into unspecialized shape-shifting recipe " << current_recipe_name() << '\n' << end(); - } - - if (current_instruction().is_label) { ++current_step_index(); continue; } - trace(Initial_callstack_depth + Trace_stream->callstack_depth, "run") << to_string(current_instruction()) << end(); - if (get_or_insert(Memory, 0) != 0) { - raise_error << "something wrote to location 0; this should never happen\n" << end(); - put(Memory, 0, 0); - } - // 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) - ingredients.push_back(read_memory(current_instruction().ingredients.at(i))); - } - // instructions below will write to 'products' - vector<vector<double> > products; - switch (current_instruction().operation) { - // Primitive Recipe Implementations - case COPY: { - copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin())); - break; - } - case ADD: { - double result = 0; - for (long long int i = 0; i < SIZE(ingredients); ++i) { - result += ingredients.at(i).at(0); - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case SUBTRACT: { - double result = ingredients.at(0).at(0); - for (long long int i = 1; i < SIZE(ingredients); ++i) - result -= ingredients.at(i).at(0); - products.resize(1); - products.at(0).push_back(result); - break; - } - case MULTIPLY: { - double result = 1; - for (long long int i = 0; i < SIZE(ingredients); ++i) { - result *= ingredients.at(i).at(0); - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case DIVIDE: { - double result = ingredients.at(0).at(0); - for (long long int i = 1; i < SIZE(ingredients); ++i) - result /= ingredients.at(i).at(0); - products.resize(1); - products.at(0).push_back(result); - break; - } - - case DIVIDE_WITH_REMAINDER: { - products.resize(2); - long long int a = static_cast<long long int>(ingredients.at(0).at(0)); - long long int b = static_cast<long long int>(ingredients.at(1).at(0)); - if (b == 0) { - raise_error << maybe(current_recipe_name()) << "divide by zero in '" << to_string(current_instruction()) << "'\n" << end(); - products.resize(2); - products.at(0).push_back(0); - products.at(1).push_back(0); - break; - } - long long int quotient = a / b; - long long int remainder = a % b; - // very large integers will lose precision - products.at(0).push_back(quotient); - products.at(1).push_back(remainder); - break; - } - - case SHIFT_LEFT: { - // ingredients must be integers - long long int a = static_cast<long long int>(ingredients.at(0).at(0)); - long long int b = static_cast<long long int>(ingredients.at(1).at(0)); - products.resize(1); - if (b < 0) { - raise_error << maybe(current_recipe_name()) << "second ingredient can't be negative in '" << to_string(current_instruction()) << "'\n" << end(); - products.at(0).push_back(0); - break; - } - products.at(0).push_back(a<<b); - break; - } - - case SHIFT_RIGHT: { - // ingredients must be integers - long long int a = static_cast<long long int>(ingredients.at(0).at(0)); - long long int b = static_cast<long long int>(ingredients.at(1).at(0)); - products.resize(1); - if (b < 0) { - raise_error << maybe(current_recipe_name()) << "second ingredient can't be negative in '" << to_string(current_instruction()) << "'\n" << end(); - products.at(0).push_back(0); - break; - } - products.at(0).push_back(a>>b); - break; - } - - case AND_BITS: { - // ingredients must be integers - long long int a = static_cast<long long int>(ingredients.at(0).at(0)); - long long int b = static_cast<long long int>(ingredients.at(1).at(0)); - products.resize(1); - products.at(0).push_back(a&b); - break; - } - - case OR_BITS: { - // ingredients must be integers - long long int a = static_cast<long long int>(ingredients.at(0).at(0)); - long long int b = static_cast<long long int>(ingredients.at(1).at(0)); - products.resize(1); - products.at(0).push_back(a|b); - break; - } - - case XOR_BITS: { - // ingredients must be integers - long long int a = static_cast<long long int>(ingredients.at(0).at(0)); - long long int b = static_cast<long long int>(ingredients.at(1).at(0)); - products.resize(1); - products.at(0).push_back(a^b); - break; - } - - case FLIP_BITS: { - // ingredient must be integer - long long int a = static_cast<long long int>(ingredients.at(0).at(0)); - products.resize(1); - products.at(0).push_back(~a); - break; - } - - case AND: { - bool result = true; - for (long long int i = 0; i < SIZE(ingredients); ++i) - result = result && ingredients.at(i).at(0); - products.resize(1); - products.at(0).push_back(result); - break; - } - - case OR: { - bool result = false; - for (long long int i = 0; i < SIZE(ingredients); ++i) - result = result || ingredients.at(i).at(0); - products.resize(1); - products.at(0).push_back(result); - break; - } - - case NOT: { - products.resize(SIZE(ingredients)); - for (long long int i = 0; i < SIZE(ingredients); ++i) { - products.at(i).push_back(!ingredients.at(i).at(0)); - } - break; - } - - case JUMP: { - assert(current_instruction().ingredients.at(0).initialized); - current_step_index() += ingredients.at(0).at(0)+1; - trace(9998, "run") << "jumping to instruction " << current_step_index() << end(); - continue; // skip rest of this instruction - } - - case JUMP_IF: { - assert(current_instruction().ingredients.at(1).initialized); - if (!ingredients.at(0).at(0)) { - trace(9998, "run") << "jump-if fell through" << end(); - break; - } - current_step_index() += ingredients.at(1).at(0)+1; - trace(9998, "run") << "jumping to instruction " << current_step_index() << end(); - continue; // skip rest of this instruction - } - - case JUMP_UNLESS: { - assert(current_instruction().ingredients.at(1).initialized); - if (ingredients.at(0).at(0)) { - trace(9998, "run") << "jump-unless fell through" << end(); - break; - } - current_step_index() += ingredients.at(1).at(0)+1; - trace(9998, "run") << "jumping to instruction " << current_step_index() << end(); - continue; // skip rest of this instruction - } - - case EQUAL: { - vector<double>& exemplar = ingredients.at(0); - bool result = true; - for (long long int i = 1; i < SIZE(ingredients); ++i) { - if (!equal(ingredients.at(i).begin(), ingredients.at(i).end(), exemplar.begin())) { - result = false; - break; - } - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case GREATER_THAN: { - bool result = true; - for (long long int i = /**/1; i < SIZE(ingredients); ++i) { - if (ingredients.at(i-1).at(0) <= ingredients.at(i).at(0)) { - result = false; - } - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case LESSER_THAN: { - bool result = true; - for (long long int i = /**/1; i < SIZE(ingredients); ++i) { - if (ingredients.at(i-1).at(0) >= ingredients.at(i).at(0)) { - result = false; - } - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case GREATER_OR_EQUAL: { - bool result = true; - for (long long int i = /**/1; i < SIZE(ingredients); ++i) { - if (ingredients.at(i-1).at(0) < ingredients.at(i).at(0)) { - result = false; - } - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case LESSER_OR_EQUAL: { - bool result = true; - for (long long int i = /**/1; i < SIZE(ingredients); ++i) { - if (ingredients.at(i-1).at(0) > ingredients.at(i).at(0)) { - result = false; - } - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case TRACE: { - long long int depth = ingredients.at(0).at(0); - string label = current_instruction().ingredients.at(1).name; - ostringstream out; - for (long long int i = 2; i < SIZE(current_instruction().ingredients); ++i) { - out << print_mu(current_instruction().ingredients.at(i), ingredients.at(i)); - } - trace(depth, label) << out.str() << end(); - break; - } - - - case STASH: { - ostringstream out; - for (long long int i = 0; i < SIZE(current_instruction().ingredients); ++i) { - out << print_mu(current_instruction().ingredients.at(i), ingredients.at(i)); - } - trace(2, "app") << out.str() << end(); - break; - } - - case HIDE_ERRORS: { - Hide_errors = true; - Hide_warnings = true; - break; - } - - case SHOW_ERRORS: { - Hide_errors = false; - Hide_warnings = false; - break; - } - - case TRACE_UNTIL: { - if (Trace_stream) { - Trace_stream->collect_depth = ingredients.at(0).at(0); - } - break; - } - - case _DUMP_TRACE: { - if (ingredients.empty()) { - DUMP(""); - } - else { - DUMP(current_instruction().ingredients.at(0).name); - } - break; - } - - case _CLEAR_TRACE: { - if (Trace_stream) Trace_stream->past_lines.clear(); - break; - } - - case _SAVE_TRACE: { - if (!Trace_file.empty()) { - ofstream fout((Trace_dir+Trace_file).c_str()); - fout << Trace_stream->readable_contents(""); - fout.close(); - } - break; - } - - - case ASSERT: { - if (!ingredients.at(0).at(0)) { - raise_error << current_instruction().ingredients.at(1).name << '\n' << end(); - } - break; - } - - - case _PRINT: { - for (long long int i = 0; i < SIZE(ingredients); ++i) { - if (is_literal(current_instruction().ingredients.at(i))) { - trace(9998, "run") << "$print: " << current_instruction().ingredients.at(i).name << end(); - if (has_property(current_instruction().ingredients.at(i), "newline")) - cout << '\n'; - else - cout << current_instruction().ingredients.at(i).name; - } - else { - for (long long int j = 0; j < SIZE(ingredients.at(i)); ++j) { - trace(9998, "run") << "$print: " << ingredients.at(i).at(j) << end(); - if (j > 0) cout << " "; - cout << no_scientific(ingredients.at(i).at(j)); - } - } - } - cout.flush(); - break; - } - - case _EXIT: { - exit(0); - break; - } - - case _SYSTEM: { - if (Current_scenario) break; - - - int status = system(current_instruction().ingredients.at(0).name.c_str()); - products.resize(1); - products.at(0).push_back(status); - break; - } - - case _DUMP_MEMORY: { - dump_memory(); - break; - } - - - case _LOG: { - ostringstream out; - for (long long int i = 0; i < SIZE(current_instruction().ingredients); ++i) { - out << print_mu(current_instruction().ingredients.at(i), ingredients.at(i)); - } - LOG << out.str() << '\n'; - break; - } - - case GET: { - reagent base = current_instruction().ingredients.at(0); - // Update GET base in Run - canonize(base); - - long long int base_address = base.value; - if (base_address == 0) { - raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end(); - break; - } - type_ordinal base_type = base.type->value; - long long int offset = ingredients.at(1).at(0); - if (offset < 0 || offset >= SIZE(get(Type, base_type).elements)) break; // copied from Check above - long long int src = base_address; - for (long long int i = 0; i < offset; ++i) { - const type_tree* type = get(Type, base_type).elements.at(i).type; - if (type->value >= START_TYPE_INGREDIENTS) { - long long int size = size_of_type_ingredient(type, base.type->right); - if (!size) - raise_error << "illegal field type '" << to_string(type) << "' seems to be missing a type ingredient or three\n" << end(); - src += size; - continue; - } - - // End GET field Cases - src += size_of(element_type(base, i)); - } - trace(9998, "run") << "address to copy is " << src << end(); - reagent tmp = element_type(base, offset); - tmp.properties.push_back(pair<string, string_tree*>("raw", NULL)); - - - tmp.set_value(src); - trace(9998, "run") << "its type is " << to_string(tmp.type) << end(); - products.push_back(read_memory(tmp)); - break; - } - - case GET_ADDRESS: { - reagent base = current_instruction().ingredients.at(0); - // Update GET_ADDRESS base in Run - canonize(base); - - - long long int base_address = base.value; - if (base_address == 0) { - raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end(); - break; - } - type_ordinal base_type = base.type->value; - long long int offset = ingredients.at(1).at(0); - if (offset < 0 || offset >= SIZE(get(Type, base_type).elements)) break; // copied from Check above - long long int result = base_address; - for (long long int i = 0; i < offset; ++i) { - const type_tree* type = get(Type, base_type).elements.at(i).type; - if (type->value >= START_TYPE_INGREDIENTS) { - long long int size = size_of_type_ingredient(type, base.type->right); - if (!size) - raise_error << "illegal type '" << to_string(type) << "' seems to be missing a type ingredient or three\n" << end(); - result += size; - continue; - } - - - // End GET_ADDRESS field Cases - result += size_of(element_type(base, i)); - } - trace(9998, "run") << "address to copy is " << result << end(); - products.resize(1); - products.at(0).push_back(result); - break; - } - - case MERGE: { - products.resize(1); - for (long long int i = 0; i < SIZE(ingredients); ++i) - for (long long int j = 0; j < SIZE(ingredients.at(i)); ++j) - products.at(0).push_back(ingredients.at(i).at(j)); - break; - } - - - case _DUMP: { - reagent after_canonize = current_instruction().ingredients.at(0); - canonize(after_canonize); - cerr << maybe(current_recipe_name()) << current_instruction().ingredients.at(0).name << ' ' << no_scientific(current_instruction().ingredients.at(0).value) << " => " << no_scientific(after_canonize.value) << " => " << no_scientific(get_or_insert(Memory, after_canonize.value)) << '\n'; - break; - } - - case _FOO: { - if (current_instruction().ingredients.empty()) { - if (foo != -1) cerr << foo << ": " << no_scientific(get_or_insert(Memory, foo)) << '\n'; - else cerr << '\n'; - } - else { - reagent tmp = current_instruction().ingredients.at(0); - canonize(tmp); - foo = tmp.value; - } - break; - } - - case CREATE_ARRAY: { - reagent product = current_instruction().products.at(0); - canonize(product); - long long int base_address = product.value; - long long int array_size = to_integer(product.type->right->right->name); - // initialize array size, so that size_of will work - put(Memory, base_address, array_size); // in array elements - long long int size = size_of(product); // in locations - trace(9998, "run") << "creating array of size " << size << '\n' << end(); - // initialize array - for (long long int i = 1; i <= size_of(product); ++i) { - put(Memory, base_address+i, 0); - } - // dummy product; doesn't actually do anything - products.resize(1); - products.at(0).push_back(array_size); - break; - } - - case INDEX: { - reagent base = current_instruction().ingredients.at(0); - canonize(base); - long long int base_address = base.value; - trace(9998, "run") << "base address is " << base_address << end(); - if (base_address == 0) { - raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end(); - break; - } - reagent offset = current_instruction().ingredients.at(1); - canonize(offset); - vector<double> offset_val(read_memory(offset)); - type_tree* element_type = array_element(base.type); - if (offset_val.at(0) < 0 || offset_val.at(0) >= get_or_insert(Memory, base_address)) { - raise_error << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end(); - break; - } - long long int src = base_address + 1 + offset_val.at(0)*size_of(element_type); - trace(9998, "run") << "address to copy is " << src << end(); - trace(9998, "run") << "its type is " << get(Type, element_type->value).name << end(); - reagent tmp; - tmp.properties.push_back(pair<string, string_tree*>("raw", NULL)); - - - tmp.set_value(src); - tmp.type = new type_tree(*element_type); - products.push_back(read_memory(tmp)); - break; - } - - case INDEX_ADDRESS: { - reagent base = current_instruction().ingredients.at(0); - canonize(base); - long long int base_address = base.value; - if (base_address == 0) { - raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end(); - break; - } - reagent offset = current_instruction().ingredients.at(1); - canonize(offset); - vector<double> offset_val(read_memory(offset)); - type_tree* element_type = array_element(base.type); - if (offset_val.at(0) < 0 || offset_val.at(0) >= get_or_insert(Memory, base_address)) { - raise_error << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end(); - break; - } - long long int result = base_address + 1 + offset_val.at(0)*size_of(element_type); - products.resize(1); - products.at(0).push_back(result); - break; - } - - case LENGTH: { - reagent x = current_instruction().ingredients.at(0); - canonize(x); - if (x.value == 0) { - raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end(); - break; - } - products.resize(1); - products.at(0).push_back(get_or_insert(Memory, x.value)); - break; - } - - case MAYBE_CONVERT: { - reagent base = current_instruction().ingredients.at(0); - canonize(base); - long long int base_address = base.value; - if (base_address == 0) { - raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end(); - break; - } - long long int tag = current_instruction().ingredients.at(1).value; - long long int result; - if (tag == static_cast<long long int>(get_or_insert(Memory, base_address))) { - result = base_address+1; - } - else { - result = 0; - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - case NEXT_INGREDIENT: { - assert(!Current_routine->calls.empty()); - if (current_call().next_ingredient_to_process < SIZE(current_call().ingredient_atoms)) { - reagent product = current_instruction().products.at(0); - canonize_type(product); - if (current_recipe_name() == "main") { - // no ingredient types since the call might be implicit; assume ingredients are always strings - // todo: how to test this? - if (!is_mu_string(product)) - raise_error << "main: wrong type for ingredient " << product.original_string << '\n' << end(); - } - 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)); - assert(SIZE(products) == 1); products.resize(2); // push a new vector - products.at(1).push_back(1); - ++current_call().next_ingredient_to_process; - } - else { - if (SIZE(current_instruction().products) < 2) - raise_error << maybe(current_recipe_name()) << "no ingredient to save in " << current_instruction().products.at(0).original_string << '\n' << end(); - if (current_instruction().products.empty()) break; - products.resize(2); - // pad the first product with sufficient zeros to match its type - long long int size = size_of(current_instruction().products.at(0)); - for (long long int i = 0; i < size; ++i) - products.at(0).push_back(0); - products.at(1).push_back(0); - } - break; - } - - case REWIND_INGREDIENTS: { - current_call().next_ingredient_to_process = 0; - break; - } - - case INGREDIENT: { - if (static_cast<long long int>(ingredients.at(0).at(0)) < SIZE(current_call().ingredient_atoms)) { - current_call().next_ingredient_to_process = ingredients.at(0).at(0); - products.push_back( - current_call().ingredient_atoms.at(current_call().next_ingredient_to_process)); - assert(SIZE(products) == 1); products.resize(2); // push a new vector - products.at(1).push_back(1); - ++current_call().next_ingredient_to_process; - } - else { - if (SIZE(current_instruction().products) > 1) { - products.resize(2); - products.at(0).push_back(0); // todo: will fail noisily if we try to read a compound value - products.at(1).push_back(0); - } - } - break; - } - - case REPLY: { - // Starting Reply - try_reclaim_locals(); - - if (Trace_stream) { - trace(9999, "trace") << "reply: decrementing callstack depth from " << Trace_stream->callstack_depth << end(); - --Trace_stream->callstack_depth; - if (Trace_stream->callstack_depth < 0) { - Current_routine->calls.clear(); - goto stop_running_current_routine; - } - } - Current_routine->calls.pop_front(); - // just in case 'main' returns a value, drop it for now - if (Current_routine->calls.empty()) goto stop_running_current_routine; - const instruction& caller_instruction = current_instruction(); - for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) - trace(9998, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end(); - - // make reply products available to caller - copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin())); - // End Reply - break; // continue to process rest of *caller* instruction - } - - case ALLOCATE: { - // compute the space we need - long long int size = ingredients.at(0).at(0); - if (SIZE(ingredients) > 1) { - // array - trace(9999, "mem") << "array size is " << ingredients.at(1).at(0) << end(); - size = /*space for length*/1 + size*ingredients.at(1).at(0); - } - // include space for refcount - size++; - trace(9999, "mem") << "allocating size " << size << end(); - //? Total_alloc += size; - //? Num_alloc++; - // compute the region of memory to return - // really crappy at the moment - if (get_or_insert(Free_list, size)) { - trace(9999, "abandon") << "picking up space from free-list of size " << size << end(); - long long int result = get_or_insert(Free_list, size); - put(Free_list, size, get_or_insert(Memory, result)); - for (long long int curr = result+1; curr < result+size; ++curr) { - if (get_or_insert(Memory, curr) != 0) { - raise_error << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end(); - break; // always fatal - } - } - if (SIZE(current_instruction().ingredients) > 1) - put(Memory, result+/*skip refcount*/1, ingredients.at(1).at(0)); - else - put(Memory, result, 0); - products.resize(1); - products.at(0).push_back(result); - break; - } - - ensure_space(size); - const long long int result = Current_routine->alloc; - trace(9999, "mem") << "new alloc: " << result << end(); - // save result - products.resize(1); - products.at(0).push_back(result); - // initialize allocated space - for (long long int address = result; address < result+size; ++address) - put(Memory, address, 0); - // initialize array length - if (SIZE(current_instruction().ingredients) > 1) { - trace(9999, "mem") << "storing " << ingredients.at(1).at(0) << " in location " << result+/*skip refcount*/1 << end(); - put(Memory, result+/*skip refcount*/1, ingredients.at(1).at(0)); - } - // bump - Current_routine->alloc += size; - // no support for reclaiming memory - assert(Current_routine->alloc <= Current_routine->alloc_max); - break; - } - - case NEW: { - if (is_literal_string(current_instruction().ingredients.at(0))) { - products.resize(1); - products.at(0).push_back(new_mu_string(current_instruction().ingredients.at(0).name)); - break; - } - - raise << "no implementation for 'new'; why wasn't it translated to 'allocate'?\n" << end(); - break; - } - - //? :(before "End Globals") - //? long long int Total_alloc = 0; - //? long long int Num_alloc = 0; - //? long long int Total_free = 0; - //? long long int Num_free = 0; - //? :(before "End Setup") - //? Total_alloc = Num_alloc = Total_free = Num_free = 0; - //? :(before "End Teardown") - //? cerr << Total_alloc << "/" << Num_alloc - //? << " vs " << Total_free << "/" << Num_free << '\n'; - //? cerr << SIZE(Memory) << '\n'; - - case ABANDON: { - long long int address = ingredients.at(0).at(0); - trace(9999, "abandon") << "address to abandon is " << address << end(); - reagent types = current_instruction().ingredients.at(0); - trace(9999, "abandon") << "value of ingredient is " << types.value << end(); - canonize(types); - // lookup_memory without drop_one_lookup { - trace(9999, "abandon") << "value of ingredient after canonization is " << types.value << end(); - long long int address_location = types.value; - types.set_value(get_or_insert(Memory, types.value)+/*skip refcount*/1); - drop_from_type(types, "address"); - drop_from_type(types, "shared"); - // } - abandon(address, size_of(types)+/*refcount*/1); - // clear the address - trace(9999, "mem") << "resetting location " << address_location << end(); - put(Memory, address_location, 0); - break; - } - - case TO_LOCATION_ARRAY: { - long long int array_size = SIZE(ingredients.at(0)); - long long int allocation_size = array_size + /*refcount*/1 + /*length*/1; - ensure_space(allocation_size); - const long long int result = Current_routine->alloc; - products.resize(1); - products.at(0).push_back(result); - // initialize array refcount - put(Memory, result, 0); - // initialize array length - put(Memory, result+1, array_size); - // now copy over data - for (long long int i = 0; i < array_size; ++i) - put(Memory, result+2+i, ingredients.at(0).at(i)); - break; - } - - case RUN: { - assert(Name[Next_recipe_ordinal].empty()); - ostringstream tmp; - tmp << "recipe run_" << Next_recipe_ordinal << " [ " << current_instruction().ingredients.at(0).name << " ]"; - //? cerr << tmp.str() << '\n'; - //? 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(); - // There's a restriction on the number of variables 'run' can use, so that - // it can avoid colliding with the dynamic allocator in case it doesn't - // initialize a default-space. - assert(Name[tmp_recipe.at(0)][""] < Max_variables_in_scenarios); - - //? cerr << "end\n"; - if (Trace_stream) { - ++Trace_stream->callstack_depth; - trace(9998, "trace") << "run: incrementing callstack depth to " << Trace_stream->callstack_depth << end(); - assert(Trace_stream->callstack_depth < 9000); // 9998-101 plus cushion - } - Current_routine->calls.push_front(call(tmp_recipe.at(0))); - continue; // not done with caller; don't increment current_step_index() - } - - // Some variables for fake resources always get special addresses in - // scenarios. - case MEMORY_SHOULD_CONTAIN: { - if (!Passed) break; - check_memory(current_instruction().ingredients.at(0).name); - break; - } - - case TRACE_SHOULD_CONTAIN: { - if (!Passed) break; - check_trace(current_instruction().ingredients.at(0).name); - break; - } - - case TRACE_SHOULD_NOT_CONTAIN: { - if (!Passed) break; - check_trace_missing(current_instruction().ingredients.at(0).name); - break; - } - - case CHECK_TRACE_COUNT_FOR_LABEL: { - if (!Passed) break; - long long int expected_count = ingredients.at(0).at(0); - string label = current_instruction().ingredients.at(1).name; - long long int count = trace_count(label); - if (count != expected_count) { - if (Current_scenario && !Scenario_testing_scenario) { - // genuine test in a mu file - raise_error << "\nF - " << Current_scenario->name << ": " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace: "; - DUMP(label); - raise_error; - } - else { - // just testing scenario support - raise_error << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace\n" << end(); - } - if (!Scenario_testing_scenario) { - Passed = false; - ++Num_failures; - } - } - break; - } - - case NEXT_INGREDIENT_WITHOUT_TYPECHECKING: { - assert(!Current_routine->calls.empty()); - if (current_call().next_ingredient_to_process < SIZE(current_call().ingredient_atoms)) { - products.push_back( - current_call().ingredient_atoms.at(current_call().next_ingredient_to_process)); - assert(SIZE(products) == 1); products.resize(2); // push a new vector - products.at(1).push_back(1); - ++current_call().next_ingredient_to_process; - } - else { - products.resize(2); - // pad the first product with sufficient zeros to match its type - long long int size = size_of(current_instruction().products.at(0)); - for (long long int i = 0; i < size; ++i) - products.at(0).push_back(0); - products.at(1).push_back(0); - } - break; - } - - - case CALL: { - // Begin Call - if (Trace_stream) { - ++Trace_stream->callstack_depth; - trace("trace") << "indirect 'call': incrementing callstack depth to " << Trace_stream->callstack_depth << end(); - assert(Trace_stream->callstack_depth < 9000); // 9998-101 plus cushion - } - const instruction& caller_instruction = current_instruction(); - Current_routine->calls.push_front(call(ingredients.at(0).at(0))); - ingredients.erase(ingredients.begin()); // drop the callee - finish_call_housekeeping(caller_instruction, ingredients); - continue; - } - - - case START_RUNNING: { - routine* new_routine = new routine(ingredients.at(0).at(0)); - new_routine->parent_index = Current_routine_index; - // populate ingredients - for (long long int i = 1; i < SIZE(current_instruction().ingredients); ++i) { - 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().ingredients.push_back(ingredient); - } - Routines.push_back(new_routine); - products.resize(1); - products.at(0).push_back(new_routine->id); - break; - } - - case ROUTINE_STATE: { - long long int id = ingredients.at(0).at(0); - long long int result = -1; - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->id == id) { - result = Routines.at(i)->state; - break; - } - } - products.resize(1); - products.at(0).push_back(result); - break; - } - - - case RESTART: { - long long int id = ingredients.at(0).at(0); - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->id == id) { - Routines.at(i)->state = RUNNING; - break; - } - } - break; - } - - case STOP: { - long long int id = ingredients.at(0).at(0); - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->id == id) { - Routines.at(i)->state = COMPLETED; - break; - } - } - break; - } - - case _DUMP_ROUTINES: { - for (long long int i = 0; i < SIZE(Routines); ++i) { - cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n'; - } - break; - } - - - case LIMIT_TIME: { - long long int id = ingredients.at(0).at(0); - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->id == id) { - Routines.at(i)->limit = ingredients.at(1).at(0); - break; - } - } - break; - } - - - case WAIT_FOR_LOCATION: { - reagent loc = current_instruction().ingredients.at(0); - canonize(loc); - Current_routine->state = WAITING; - Current_routine->waiting_on_location = loc.value; - Current_routine->old_value_of_waiting_location = get_or_insert(Memory, loc.value); - trace(9998, "run") << "waiting for location " << loc.value << " to change from " << no_scientific(get_or_insert(Memory, loc.value)) << end(); - break; - } - - - case WAIT_FOR_ROUTINE: { - if (ingredients.at(0).at(0) == Current_routine->id) { - raise_error << maybe(current_recipe_name()) << "routine can't wait for itself! " << to_string(current_instruction()) << '\n' << end(); - break; - } - Current_routine->state = WAITING; - Current_routine->waiting_on_routine = ingredients.at(0).at(0); - trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << end(); - break; - } - - case SWITCH: { - long long int id = some_other_running_routine(); - if (id) { - assert(id != Current_routine->id); - Current_routine->state = WAITING; - Current_routine->waiting_on_routine = id; - } - break; - } - - case RANDOM: { - // todo: limited range of numbers, might be imperfectly random - // todo: thread state in extra ingredients and products - products.resize(1); - products.at(0).push_back(rand()); - break; - } - - case MAKE_RANDOM_NONDETERMINISTIC: { - srand(time(NULL)); - break; - } - - case ROUND: { - products.resize(1); - products.at(0).push_back(rint(ingredients.at(0).at(0))); - break; - } - - case HASH: { - reagent input = current_instruction().ingredients.at(0); // copy - products.resize(1); - products.at(0).push_back(hash(0, input)); - break; - } - - - case HASH_OLD: { - string input = read_mu_string(ingredients.at(0).at(0)); - size_t h = 0 ; - - for (long long int i = 0; i < SIZE(input); ++i) { - h += static_cast<size_t>(input.at(i)); - h += (h<<10); - h ^= (h>>6); - - h += (h<<3); - h ^= (h>>11); - h += (h<<15); - } - - products.resize(1); - products.at(0).push_back(h); - break; - } - - case OPEN_CONSOLE: { - tb_init(); - Display_row = Display_column = 0; - long long int width = tb_width(); - long long int height = tb_height(); - if (width > 222 || height > 222) tb_shutdown(); - if (width > 222) - raise_error << "sorry, mu doesn't support windows wider than 222 characters. Please resize your window.\n" << end(); - if (height > 222) - raise_error << "sorry, mu doesn't support windows taller than 222 characters. Please resize your window.\n" << end(); - break; - } - - case CLOSE_CONSOLE: { - tb_shutdown(); - break; - } - - case CLEAR_DISPLAY: { - tb_clear(); - Display_row = Display_column = 0; - break; - } - - case SYNC_DISPLAY: { - tb_sync(); - break; - } - - case CLEAR_LINE_ON_DISPLAY: { - long long int width = tb_width(); - for (long long int x = Display_column; x < width; ++x) { - tb_change_cell(x, Display_row, ' ', TB_WHITE, TB_BLACK); - } - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - break; - } - - case PRINT_CHARACTER_TO_DISPLAY: { - int h=tb_height(), w=tb_width(); - long long int height = (h >= 0) ? h : 0; - long long int width = (w >= 0) ? w : 0; - long long int c = ingredients.at(0).at(0); - int color = TB_BLACK; - if (SIZE(ingredients) > 1) { - color = ingredients.at(1).at(0); - } - int bg_color = TB_BLACK; - if (SIZE(ingredients) > 2) { - bg_color = ingredients.at(2).at(0); - if (bg_color == 0) bg_color = TB_BLACK; - } - tb_change_cell(Display_column, Display_row, c, color, bg_color); - if (c == '\n' || c == '\r') { - if (Display_row < height-1) { - Display_column = 0; - ++Display_row; - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - } - break; - } - if (c == '\b') { - if (Display_column > 0) { - tb_change_cell(Display_column-1, Display_row, ' ', color, bg_color); - --Display_column; - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - } - break; - } - if (Display_column < width-1) { - ++Display_column; - tb_set_cursor(Display_column, Display_row); - } - if (Autodisplay) tb_present(); - break; - } - - case CURSOR_POSITION_ON_DISPLAY: { - products.resize(2); - products.at(0).push_back(Display_row); - products.at(1).push_back(Display_column); - break; - } - - case MOVE_CURSOR_ON_DISPLAY: { - Display_row = ingredients.at(0).at(0); - Display_column = ingredients.at(1).at(0); - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - break; - } - - case MOVE_CURSOR_DOWN_ON_DISPLAY: { - int h=tb_height(); - long long int height = (h >= 0) ? h : 0; - if (Display_row < height-1) { - Display_row++; - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - } - break; - } - - case MOVE_CURSOR_UP_ON_DISPLAY: { - if (Display_row > 0) { - Display_row--; - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - } - break; - } - - case MOVE_CURSOR_RIGHT_ON_DISPLAY: { - int w=tb_width(); - long long int width = (w >= 0) ? w : 0; - if (Display_column < width-1) { - Display_column++; - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - } - break; - } - - case MOVE_CURSOR_LEFT_ON_DISPLAY: { - if (Display_column > 0) { - Display_column--; - tb_set_cursor(Display_column, Display_row); - if (Autodisplay) tb_present(); - } - break; - } - - case DISPLAY_WIDTH: { - products.resize(1); - products.at(0).push_back(tb_width()); - break; - } - - case DISPLAY_HEIGHT: { - products.resize(1); - products.at(0).push_back(tb_height()); - break; - } - - case HIDE_CURSOR_ON_DISPLAY: { - tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR); - break; - } - - case SHOW_CURSOR_ON_DISPLAY: { - tb_set_cursor(Display_row, Display_column); - break; - } - - case HIDE_DISPLAY: { - Autodisplay = false; - break; - } - - case SHOW_DISPLAY: { - Autodisplay = true; - tb_present(); - break; - } - - - case WAIT_FOR_SOME_INTERACTION: { - tb_event event; - tb_poll_event(&event); - break; - } - - case CHECK_FOR_INTERACTION: { - products.resize(2); // result and status - tb_event event; - int event_type = tb_peek_event(&event, 5/*ms*/); - if (event_type == TB_EVENT_KEY && event.ch) { - products.at(0).push_back(/*text event*/0); - products.at(0).push_back(event.ch); - products.at(0).push_back(0); - products.at(0).push_back(0); - products.at(1).push_back(/*found*/true); - break; - } - // treat keys within ascii as unicode characters - if (event_type == TB_EVENT_KEY && event.key < 0xff) { - products.at(0).push_back(/*text event*/0); - if (event.key == TB_KEY_CTRL_C) { - tb_shutdown(); - exit(1); - } - if (event.key == TB_KEY_BACKSPACE2) event.key = TB_KEY_BACKSPACE; - if (event.key == TB_KEY_CARRIAGE_RETURN) event.key = TB_KEY_NEWLINE; - products.at(0).push_back(event.key); - products.at(0).push_back(0); - products.at(0).push_back(0); - products.at(1).push_back(/*found*/true); - break; - } - // keys outside ascii aren't unicode characters but arbitrary termbox inventions - if (event_type == TB_EVENT_KEY) { - products.at(0).push_back(/*keycode event*/1); - products.at(0).push_back(event.key); - products.at(0).push_back(0); - products.at(0).push_back(0); - products.at(1).push_back(/*found*/true); - break; - } - if (event_type == TB_EVENT_MOUSE) { - products.at(0).push_back(/*touch event*/2); - products.at(0).push_back(event.key); // which button, etc. - products.at(0).push_back(event.y); // row - products.at(0).push_back(event.x); // column - products.at(1).push_back(/*found*/true); - break; - } - if (event_type == TB_EVENT_RESIZE) { - products.at(0).push_back(/*resize event*/3); - products.at(0).push_back(event.w); // width - products.at(0).push_back(event.h); // height - products.at(0).push_back(0); - products.at(1).push_back(/*found*/true); - break; - } - assert(event_type == 0); - products.at(0).push_back(0); - products.at(0).push_back(0); - products.at(0).push_back(0); - products.at(0).push_back(0); - products.at(1).push_back(/*found*/false); - break; - } - - case INTERACTIONS_LEFT: { - products.resize(1); - products.at(0).push_back(tb_event_ready()); - break; - } - - - case CLEAR_DISPLAY_FROM: { - // todo: error checking - int row = ingredients.at(0).at(0); - int column = ingredients.at(1).at(0); - int left = ingredients.at(2).at(0); - int right = ingredients.at(3).at(0); - int height=tb_height(); - for (; row < height; ++row, column=left) { // start column from left in every inner loop except first - for (; column <= right; ++column) { - tb_change_cell(column, row, ' ', TB_WHITE, TB_BLACK); - } - } - if (Autodisplay) tb_present(); - break; - } - - 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; - assert(scalar(ingredients.at(0))); - check_screen(current_instruction().ingredients.at(0).name, -1); - break; - } - - case SCREEN_SHOULD_CONTAIN_IN_COLOR: { - if (!Passed) break; - assert(scalar(ingredients.at(0))); - assert(scalar(ingredients.at(1))); - check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0)); - break; - } - - case _DUMP_SCREEN: { - dump_screen(); - break; - } - - case ASSUME_CONSOLE: { - // create a temporary recipe just for parsing; it won't contain valid instructions - istringstream in("[" + current_instruction().ingredients.at(0).name + "]"); - recipe r; - slurp_body(in, r); - long long int num_events = count_events(r); - // initialize the events like in new-fake-console - long long int size = /*space for refcount*/1 + /*space for length*/1 + num_events*size_of_event(); - ensure_space(size); - long long int event_data_address = Current_routine->alloc; - // store length - put(Memory, Current_routine->alloc+/*skip refcount*/1, num_events); - Current_routine->alloc += /*skip refcount and length*/2; - for (long long int i = 0; i < SIZE(r.steps); ++i) { - const instruction& curr = r.steps.at(i); - if (curr.name == "left-click") { - trace(9999, "mem") << "storing 'left-click' event starting at " << Current_routine->alloc << end(); - put(Memory, Current_routine->alloc, /*tag for 'touch-event' variant of 'event' exclusive-container*/2); - put(Memory, Current_routine->alloc+1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT); - put(Memory, Current_routine->alloc+1+/*offset of 'row' in 'mouse-event'*/1, to_integer(curr.ingredients.at(0).name)); - put(Memory, Current_routine->alloc+1+/*offset of 'column' in 'mouse-event'*/2, to_integer(curr.ingredients.at(1).name)); - Current_routine->alloc += size_of_event(); - } - else if (curr.name == "press") { - trace(9999, "mem") << "storing 'press' event starting at " << Current_routine->alloc << end(); - string key = curr.ingredients.at(0).name; - if (is_integer(key)) - put(Memory, Current_routine->alloc+1, to_integer(key)); - else if (contains_key(Key, key)) - put(Memory, Current_routine->alloc+1, Key[key]); - else - raise_error << "assume-console: can't press " << key << '\n' << end(); - if (get_or_insert(Memory, Current_routine->alloc+1) < 256) - // these keys are in ascii - put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0); - else { - // distinguish from unicode - put(Memory, Current_routine->alloc, /*tag for 'keycode' variant of 'event' exclusive-container*/1); - } - Current_routine->alloc += size_of_event(); - } - // End Event Handlers - else { - // keyboard input - assert(curr.name == "type"); - trace(9999, "mem") << "storing 'type' event starting at " << Current_routine->alloc << end(); - const string& contents = curr.ingredients.at(0).name; - const char* raw_contents = contents.c_str(); - long long int num_keyboard_events = unicode_length(contents); - long long int curr = 0; - for (long long int i = 0; i < num_keyboard_events; ++i) { - trace(9999, "mem") << "storing 'text' tag at " << Current_routine->alloc << end(); - put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0); - uint32_t curr_character; - assert(curr < SIZE(contents)); - tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]); - trace(9999, "mem") << "storing character " << curr_character << " at " << Current_routine->alloc+1 << end(); - put(Memory, Current_routine->alloc+/*skip exclusive container tag*/1, curr_character); - curr += tb_utf8_char_length(raw_contents[curr]); - Current_routine->alloc += size_of_event(); - } - } - } - assert(Current_routine->alloc == event_data_address+size); - // wrap the array of events in a console object - ensure_space(size_of_console()); - put(Memory, CONSOLE, Current_routine->alloc); - trace(9999, "mem") << "storing console in " << Current_routine->alloc << end(); - Current_routine->alloc += size_of_console(); - long long int console_address = get_or_insert(Memory, CONSOLE); - trace(9999, "mem") << "storing console data in " << console_address+2 << end(); - put(Memory, console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1, event_data_address); - break; - } - - case REPLACE_IN_CONSOLE: { - assert(scalar(ingredients.at(0))); - if (!get_or_insert(Memory, CONSOLE)) { - raise_error << "console not initialized\n" << end(); - break; - } - long long int console_address = get_or_insert(Memory, CONSOLE); - long long int console_data = get_or_insert(Memory, console_address+1); - long long int size = get_or_insert(Memory, console_data); // array size - for (long long int i = 0, curr = console_data+1; i < size; ++i, curr+=size_of_event()) { - if (get_or_insert(Memory, curr) != /*text*/0) continue; - if (get_or_insert(Memory, curr+1) != ingredients.at(0).at(0)) continue; - for (long long int n = 0; n < size_of_event(); ++n) - put(Memory, curr+n, ingredients.at(1).at(n)); - } - break; - } - - case _BROWSE_TRACE: { - start_trace_browser(); - break; - } - - case RUN_INTERACTIVE: { - bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0)); - if (!new_code_pushed_to_stack) { - products.resize(5); - products.at(0).push_back(0); - products.at(1).push_back(trace_error_warning_contents()); - products.at(2).push_back(0); - products.at(3).push_back(trace_app_contents()); - products.at(4).push_back(1); // completed - run_code_end(); - break; // done with this instruction - } - else { - continue; // not done with caller; don't increment current_step_index() - } - } - - case _START_TRACKING_PRODUCTS: { - Track_most_recent_products = true; - break; - } - - case _STOP_TRACKING_PRODUCTS: { - Track_most_recent_products = false; - break; - } - - case _MOST_RECENT_PRODUCTS: { - products.resize(1); - products.at(0).push_back(new_mu_string(Most_recent_products)); - break; - } - - case SAVE_ERRORS_WARNINGS: { - products.resize(1); - products.at(0).push_back(trace_error_warning_contents()); - break; - } - - case SAVE_APP_TRACE: { - products.resize(1); - products.at(0).push_back(trace_app_contents()); - break; - } - - case _CLEANUP_RUN_INTERACTIVE: { - run_code_end(); - break; - } - - case RELOAD: { - //? cerr << "== reload\n"; - // clear any containers in advance - for (long long int i = 0; i < SIZE(Recently_added_types); ++i) { - if (!contains_key(Type, Recently_added_types.at(i))) continue; - 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) { - 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(/*snapshot_recently_added_recipes*/false); - routine* save_current_routine = Current_routine; - Current_routine = NULL; - vector<recipe_ordinal> recipes_reloaded = load(code); - // 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; - } - - case RESTORE: { - string filename; - if (is_literal_string(current_instruction().ingredients.at(0))) { - filename = current_instruction().ingredients.at(0).name; - } - else if (is_mu_string(current_instruction().ingredients.at(0))) { - filename = read_mu_string(ingredients.at(0).at(0)); - } - if (Current_scenario) { - // do nothing in tests - products.resize(1); - products.at(0).push_back(0); - break; - } - string contents = slurp("lesson/"+filename); - products.resize(1); - if (contents.empty()) - products.at(0).push_back(0); - else - products.at(0).push_back(new_mu_string(contents)); - break; - } - - case SAVE: { - if (Current_scenario) break; // do nothing in tests - string filename; - if (is_literal_string(current_instruction().ingredients.at(0))) { - filename = current_instruction().ingredients.at(0).name; - } - else if (is_mu_string(current_instruction().ingredients.at(0))) { - filename = read_mu_string(ingredients.at(0).at(0)); - } - ofstream fout(("lesson/"+filename).c_str()); - string contents = read_mu_string(ingredients.at(1).at(0)); - fout << contents; - fout.close(); - if (!exists("lesson/.git")) break; - // bug in git: git diff -q messes up --exit-code - // explicitly say '--all' for git 1.9 - int status = system("cd lesson; git add --all .; git diff HEAD --exit-code >/dev/null || git commit -a -m . >/dev/null"); - if (status != 0) - raise_error << "error in commit: contents " << contents << '\n' << end(); - break; - } - - // End Primitive Recipe Implementations - default: { - const instruction& call_instruction = current_instruction(); - if (Recipe.find(current_instruction().operation) == Recipe.end()) { // duplicate from Checks - // stop running this instruction immediately - ++current_step_index(); - continue; - } - // not a primitive; look up the book of recipes - if (Trace_stream) { - ++Trace_stream->callstack_depth; - trace(9999, "trace") << "incrementing callstack depth to " << Trace_stream->callstack_depth << end(); - assert(Trace_stream->callstack_depth < 9000); // 9998-101 plus cushion - } - Current_routine->calls.push_front(call(current_instruction().operation)); - finish_call_housekeeping(call_instruction, ingredients); - continue; // not done with caller; don't increment step_index of caller - } - } - if (SIZE(products) < SIZE(current_instruction().products)) { - raise_error << SIZE(products) << " vs " << SIZE(current_instruction().products) << ": failed to write to all products! " << to_string(current_instruction()) << '\n' << end(); - } - else { - for (long long int i = 0; i < SIZE(current_instruction().products); ++i) { - write_memory(current_instruction().products.at(i), products.at(i)); - } - } - if (Track_most_recent_products) { - track_most_recent_products(current_instruction(), products); - } - // End of Instruction - ++current_step_index(); - } - stop_running_current_routine:; -} - -bool should_copy_ingredients() { - recipe_ordinal r = current_instruction().operation; - if (r == CREATE_ARRAY || r == INDEX || r == INDEX_ADDRESS || r == LENGTH) - return false; - - // End should_copy_ingredients Special-cases - return true; -} - - -inline long long int& current_step_index() { - assert(!Current_routine->calls.empty()); - return current_call().running_step_index; -} - -inline const string& current_recipe_name() { - assert(!Current_routine->calls.empty()); - return get(Recipe, current_call().running_recipe).name; -} - -inline const instruction& current_instruction() { - assert(!Current_routine->calls.empty()); - return to_instruction(current_call()); -} - -inline bool routine::completed() const { - return calls.empty(); -} - -inline const vector<instruction>& routine::steps() const { - assert(!calls.empty()); - return get(Recipe, calls.front().running_recipe).steps; -} - - - -void run_main(int argc, char* argv[]) { - recipe_ordinal r = get(Recipe_ordinal, "main"); - assert(r); - routine* main_routine = new routine(r); - // pass in commandline args as ingredients to main - // todo: test this - Current_routine = main_routine; - for (long long int i = 1; i < argc; ++i) { - vector<double> arg; - arg.push_back(new_mu_string(argv[i])); - current_call().ingredient_atoms.push_back(arg); - } - run(main_routine); -} - - - -void dump_profile() { - for (map<string, long long int>::iterator p = Instructions_running.begin(); p != Instructions_running.end(); ++p) { - cerr << p->first << ": " << p->second << '\n'; - } - cerr << "== locations read\n"; - for (map<string, long long int>::iterator p = Locations_read.begin(); p != Locations_read.end(); ++p) { - cerr << p->first << ": " << p->second << '\n'; - } - cerr << "== locations read by instruction\n"; - for (map<string, long long int>::iterator p = Locations_read_by_instruction.begin(); p != Locations_read_by_instruction.end(); ++p) { - cerr << p->first << ": " << p->second << '\n'; - } -} -void cleanup_main() { - if (!Trace_file.empty() && Trace_stream) { - ofstream fout((Trace_dir+Trace_file).c_str()); - fout << Trace_stream->readable_contents(""); - fout.close(); - } -} -void load_permanently(string filename) { - if (is_directory(filename)) { - load_all_permanently(filename); - return; - } - ifstream fin(filename.c_str()); - fin.peek(); - if (!fin) { - raise_error << "no such file " << filename << '\n' << end(); - return; - } - fin >> std::noskipws; - trace(9990, "load") << "=== " << filename << end(); - load(fin); - fin.close(); - // freeze everything so it doesn't get cleared by tests - Recently_added_recipes.clear(); - Recently_added_types.clear(); - // End load_permanently. -} - -bool is_directory(string path) { - struct stat info; - if (stat(path.c_str(), &info)) return false; // error - return info.st_mode & S_IFDIR; -} - -void load_all_permanently(string dir) { - dirent** files; - int num_files = scandir(dir.c_str(), &files, NULL, alphasort); - for (int i = 0; i < num_files; ++i) { - string curr_file = files[i]->d_name; - if (!isdigit(curr_file.at(0))) continue; - load_permanently(dir+'/'+curr_file); - free(files[i]); - files[i] = NULL; - } - free(files); -} -vector<double> read_memory(reagent x) { - if (x.name == "number-of-locals") { - vector<double> result; - result.push_back(Name[get(Recipe_ordinal, current_recipe_name())][""]); - if (result.back() == 0) - raise_error << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names?\n" << end(); - return result; - } - if (x.name == "default-space") { - vector<double> result; - result.push_back(current_call().default_space); - return result; - } - - - vector<double> result; - if (is_literal(x)) { - result.push_back(x.value); - return result; - } - canonize(x); - - long long int base = x.value; - long long int size = size_of(x); - for (long long int offset = 0; offset < size; ++offset) { - double val = get_or_insert(Memory, base+offset); - trace(9999, "mem") << "location " << base+offset << " is " << no_scientific(val) << end(); - result.push_back(val); - } - return result; -} - -void write_memory(reagent x, vector<double> data) { - if (x.name == "global-space") { - if (!scalar(data) - || !x.type - || x.type->value != get(Type_ordinal, "address") - || !x.type->right - || x.type->right->value != get(Type_ordinal, "shared") - || !x.type->right->right - || x.type->right->right->value != get(Type_ordinal, "array") - || !x.type->right->right->right - || x.type->right->right->right->value != get(Type_ordinal, "location") - || x.type->right->right->right->right) { - raise_error << maybe(current_recipe_name()) << "'global-space' should be of type address:shared:array:location, but tried to write " << to_string(data) << '\n' << end(); - } - if (Current_routine->global_space) - raise_error << "routine already has a global-space; you can't over-write your globals" << end(); - Current_routine->global_space = data.at(0); - return; - } - - if (x.name == "number-of-locals") { - raise_error << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end(); - return; - } - - - if (x.name == "default-space") { - if (!scalar(data) - || !x.type - || x.type->value != get(Type_ordinal, "address") - || !x.type->right - || x.type->right->value != get(Type_ordinal, "shared") - || !x.type->right->right - || x.type->right->right->value != get(Type_ordinal, "array") - || !x.type->right->right->right - || x.type->right->right->right->value != get(Type_ordinal, "location") - || x.type->right->right->right->right) { - raise_error << maybe(current_recipe_name()) << "'default-space' should be of type address:shared:array:location, but tried to write " << to_string(data) << '\n' << end(); - } - current_call().default_space = data.at(0); - return; - } - - if (!x.type) { - raise_error << "can't write to " << to_string(x) << "; no type\n" << end(); - return; - } - if (is_dummy(x)) return; - if (is_literal(x)) return; - canonize(x); - if (x.value == 0) { - raise_error << "can't write to location 0 in '" << to_string(current_instruction()) << "'\n" << end(); - return; - } - - long long int base = x.value; - if (base == 0) return; - if (size_mismatch(x, data)) { - raise_error << maybe(current_recipe_name()) << "size mismatch in storing to " << x.original_string << " (" << size_of(x.type) << " vs " << SIZE(data) << ") at '" << to_string(current_instruction()) << "'\n" << end(); - return; - } - if (x.type->value == get(Type_ordinal, "address") - && x.type->right - && x.type->right->value == get(Type_ordinal, "shared")) { - // compute old address of x, as well as new address we want to write in - long long int old_address = get_or_insert(Memory, x.value); - assert(scalar(data)); - long long int new_address = data.at(0); - // decrement refcount of old address - if (old_address) { - long long int old_refcount = get_or_insert(Memory, old_address); - trace(9999, "mem") << "decrementing refcount of " << old_address << ": " << old_refcount << " -> " << (old_refcount-1) << end(); - put(Memory, old_address, old_refcount-1); - } - // perform the write - trace(9999, "mem") << "storing " << no_scientific(data.at(0)) << " in location " << base << end(); - put(Memory, base, new_address); - // increment refcount of new address - if (new_address) { - long long int new_refcount = get_or_insert(Memory, new_address); - assert(new_refcount >= 0); // == 0 only when new_address == old_address - trace(9999, "mem") << "incrementing refcount of " << new_address << ": " << new_refcount << " -> " << (new_refcount+1) << end(); - put(Memory, new_address, new_refcount+1); - } - // abandon old address if necessary - // do this after all refcount updates are done just in case old and new are identical - assert(get_or_insert(Memory, old_address) >= 0); - if (old_address && get_or_insert(Memory, old_address) == 0) { - // lookup_memory without drop_one_lookup { - trace(9999, "mem") << "automatically abandoning " << old_address << end(); - trace(9999, "mem") << "computing size to abandon at " << x.value << end(); - x.set_value(get_or_insert(Memory, x.value)+/*skip refcount*/1); - drop_from_type(x, "address"); - drop_from_type(x, "shared"); - // } - abandon(old_address, size_of(x)+/*refcount*/1); - } - return; - } - - // End write_memory(reagent x, long long int base) Special-cases - for (long long int offset = 0; offset < SIZE(data); ++offset) { - assert(base+offset > 0); - trace(9999, "mem") << "storing " << no_scientific(data.at(offset)) << " in location " << base+offset << end(); - put(Memory, base+offset, data.at(offset)); - } -} - -long long int size_of(const reagent& r) { - if (r.type == NULL) return 0; - if (r.type && r.type->value == get(Type_ordinal, "array")) { - if (!r.type->right) { - raise_error << maybe(current_recipe_name()) << "'" << r.original_string << "' is an array of what?\n" << end(); - return 1; - } - //? trace(9999, "mem") << "computing size of array starting at " << r.value << end(); - return 1 + get_or_insert(Memory, r.value)*size_of(array_element(r.type)); - } - - - // End size_of(reagent) Cases - return size_of(r.type); -} -long long int size_of(const type_tree* type) { - if (type == NULL) return 0; - if (type->value == -1) { - // error value, but we'll raise it elsewhere - return 1; - } - if (type->value == 0) { - assert(!type->left && !type->right); - return 1; - } - if (!contains_key(Type, type->value)) { - raise_error << "no such type " << type->value << '\n' << end(); - return 0; - } - type_info t = get(Type, type->value); - if (t.kind == CONTAINER) { - // size of a container is the sum of the sizes of its elements - long long int result = 0; - for (long long int i = 0; i < SIZE(t.elements); ++i) { - // todo: strengthen assertion to disallow mutual type recursion - if (t.elements.at(i).type->value == type->value) { - raise_error << "container " << t.name << " can't include itself as a member\n" << end(); - return 0; - } - reagent tmp; - tmp.type = new type_tree(*type); - result += size_of(element_type(tmp, i)); - } - return result; - } - - if (t.kind == EXCLUSIVE_CONTAINER) { - // size of an exclusive container is the size of its largest variant - // (So like containers, it can't contain arrays.) - long long int result = 0; - for (long long int i = 0; i < t.size; ++i) { - reagent tmp; - tmp.type = new type_tree(*type); - long long int size = size_of(variant_type(tmp, i)); - if (size > result) result = size; - } - // ...+1 for its tag. - return result+1; - } - - - // End size_of(type) Cases - return 1; -} - -bool size_mismatch(const reagent& x, const vector<double>& data) { - if (x.type == NULL) return true; - if (x.type && x.type->value == get(Type_ordinal, "array")) return false; - if (current_instruction().operation == MERGE - && !current_instruction().products.empty() - && current_instruction().products.at(0).type) { - reagent x = current_instruction().products.at(0); - canonize(x); - if (get(Type, x.type->value).kind == EXCLUSIVE_CONTAINER) { - return size_of(x) < SIZE(data); - } - } - - // End size_mismatch(x) Cases -//? if (size_of(x) != SIZE(data)) cerr << size_of(x) << " vs " << SIZE(data) << '\n'; - return size_of(x) != SIZE(data); -} - -inline bool is_dummy(const reagent& x) { - return x.name == "_"; -} - -inline bool is_literal(const reagent& r) { - if (!r.type) return false; - if (r.type->value == 0) - assert(!r.type->left && !r.type->right); - return r.type->value == 0; -} - -inline bool scalar(const vector<long long int>& x) { - return SIZE(x) == 1; -} -inline bool scalar(const vector<double>& x) { - return SIZE(x) == 1; -} - -// helper for tests -void run(string form) { - vector<recipe_ordinal> tmp = load(form); - transform_all(); - if (tmp.empty()) return; - if (trace_count("error") > 0) return; - run(tmp.front()); -} - -void test_run_label() { - Trace_file = "run_label"; - run("recipe main [\n +foo\n 1:number <- copy 23\n 2:number <- copy 1:number\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:number <- copy 23run: 2:number <- copy 1:number"); - CHECK_TRACE_DOESNT_CONTAIN("run: +foo"); -} -void test_run_dummy() { - Trace_file = "run_dummy"; - run("recipe main [\n _ <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("run: _ <- copy 0"); -} -void test_write_to_0_disallowed() { - Trace_file = "write_to_0_disallowed"; - Hide_errors = true; - run("recipe main [\n 0:number <- copy 34\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 0"); -} -void test_comma_without_space() { - Trace_file = "comma_without_space"; - run("recipe main [\n 1:number, 2:number <- copy 2,2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_space_without_comma() { - Trace_file = "space_without_comma"; - run("recipe main [\n 1:number, 2:number <- copy 2 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_comma_before_space() { - Trace_file = "comma_before_space"; - run("recipe main [\n 1:number, 2:number <- copy 2, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_comma_after_space() { - Trace_file = "comma_after_space"; - run("recipe main [\n 1:number, 2:number <- copy 2 ,2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} - -void check_instruction(const recipe_ordinal r) { - trace(9991, "transform") << "--- perform checks for recipe " << get(Recipe, r).name << end(); -//? cerr << "--- perform checks for recipe " << get(Recipe, r).name << '\n'; - map<string, vector<type_ordinal> > metadata; - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - instruction& inst = get(Recipe, r).steps.at(i); - if (inst.is_label) continue; - switch (inst.operation) { - // Primitive Recipe Checks - case COPY: { - if (SIZE(inst.products) != SIZE(inst.ingredients)) { - raise_error << "ingredients and products should match in '" << to_string(inst) << "'\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++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; - } - } - break; - } - case ADD: { - // primary goal of these checks is to forbid address arithmetic - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'add' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'add' yields exactly one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'add' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - break; - } - break; - } - case SUBTRACT: { - if (inst.ingredients.empty()) { - raise_error << maybe(get(Recipe, r).name) << "'subtract' has no ingredients\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (is_raw(inst.ingredients.at(i))) continue; // permit address offset computations in tests - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'subtract' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'subtract' yields exactly one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'subtract' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - break; - } - break; - } - case MULTIPLY: { - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'multiply' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'multiply' yields exactly one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'multiply' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - break; - } - break; - } - case DIVIDE: { - if (inst.ingredients.empty()) { - raise_error << maybe(get(Recipe, r).name) << "'divide' has no ingredients\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'divide' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'divide' yields exactly one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'divide' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - break; - } - break; - } - case DIVIDE_WITH_REMAINDER: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' requires number ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (SIZE(inst.products) > 2) { - raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' yields two products in '" << to_string(inst) << "'\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.products); ++i) { - if (!is_dummy(inst.products.at(i)) && !is_mu_number(inst.products.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' should yield a number, but got " << inst.products.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case SHIFT_LEFT: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'shift-left' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'shift-left' requires number ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'shift-left' yields one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'shift-left' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - goto finish_checking_instruction; - } - break; - } - case SHIFT_RIGHT: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'shift-right' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'shift-right' requires number ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'shift-right' yields one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'shift-right' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - goto finish_checking_instruction; - } - break; - } - case AND_BITS: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'and-bits' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'and-bits' requires number ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'and-bits' yields one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'and-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - goto finish_checking_instruction; - } - break; - } - case OR_BITS: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'or-bits' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'or-bits' requires number ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'or-bits' yields one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'or-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - goto finish_checking_instruction; - } - break; - } - case XOR_BITS: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'xor-bits' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'xor-bits' requires number ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'xor-bits' yields one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'xor-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - goto finish_checking_instruction; - } - break; - } - case FLIP_BITS: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'flip-bits' requires exactly one ingredient, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'flip-bits' requires a number ingredient, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (SIZE(inst.products) > 1) { - raise_error << maybe(get(Recipe, r).name) << "'flip-bits' yields one product in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'flip-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end(); - goto finish_checking_instruction; - } - break; - } - case AND: { - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_scalar(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case OR: { - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_scalar(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case NOT: { - if (SIZE(inst.products) > SIZE(inst.ingredients)) { - raise_error << maybe(get(Recipe, r).name) << "'not' cannot have fewer ingredients than products in '" << to_string(inst) << "'\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_scalar(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'not' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case JUMP: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'jump' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_scalar(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'jump' should be a label or offset, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case JUMP_IF: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'jump-if' requires exactly two ingredients, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_scalar(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'jump-if' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (!is_mu_scalar(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'jump-if' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case JUMP_UNLESS: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'jump-unless' requires exactly two ingredients, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_scalar(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'jump-unless' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (!is_mu_scalar(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'jump-unless' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case EQUAL: { - if (SIZE(inst.ingredients) <= 1) { - raise_error << maybe(get(Recipe, r).name) << "'equal' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end(); - break; - } - break; - } - case GREATER_THAN: { - if (SIZE(inst.ingredients) <= 1) { - raise_error << maybe(get(Recipe, r).name) << "'greater-than' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'greater-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case LESSER_THAN: { - if (SIZE(inst.ingredients) <= 1) { - raise_error << maybe(get(Recipe, r).name) << "'lesser-than' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'lesser-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case GREATER_OR_EQUAL: { - if (SIZE(inst.ingredients) <= 1) { - raise_error << maybe(get(Recipe, r).name) << "'greater-or-equal' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'greater-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case LESSER_OR_EQUAL: { - if (SIZE(inst.ingredients) <= 1) { - raise_error << maybe(get(Recipe, r).name) << "'lesser-or-equal' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end(); - break; - } - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_number(inst.ingredients.at(i))) { - raise_error << maybe(get(Recipe, r).name) << "'lesser-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end(); - goto finish_checking_instruction; - } - } - break; - } - case TRACE: { - if (SIZE(inst.ingredients) < 3) { - raise_error << maybe(get(Recipe, r).name) << "'trace' takes three or more ingredients rather than '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'trace' should be a number (depth), but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (!is_literal_string(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'trace' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - break; - } - case STASH: { - break; - } - case HIDE_ERRORS: { - break; - } - case SHOW_ERRORS: { - break; - } - case TRACE_UNTIL: { - break; - } - case _DUMP_TRACE: { - break; - } - case _CLEAR_TRACE: { - break; - } - case _SAVE_TRACE: { - break; - } - case ASSERT: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'assert' takes exactly two ingredients rather than '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_scalar(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'assert' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (!is_literal_string(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "'assert' requires a literal string for its second ingredient, but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - break; - } - case _PRINT: { - break; - } - case _EXIT: { - break; - } - case _SYSTEM: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'$system' requires exactly one ingredient, but got none\n" << end(); - break; - } - break; - } - case _DUMP_MEMORY: { - break; - } - case _LOG: { - break; - } - case GET: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'get' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end(); - break; - } - reagent base = inst.ingredients.at(0); // new copy for every invocation - // Update GET base in Check - if (!canonize_type(base)) break; - if (!base.type || !base.type->value || !contains_key(Type, base.type->value) || get(Type, base.type->value).kind != CONTAINER) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - type_ordinal base_type = base.type->value; - reagent offset = inst.ingredients.at(1); - if (!is_literal(offset) || !is_mu_scalar(offset)) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - long long int offset_value = 0; - if (is_integer(offset.name)) // later layers permit non-integer offsets - offset_value = to_integer(offset.name); - else - offset_value = offset.value; - if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type).elements)) { - 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 - if (!canonize_type(product)) break; - const reagent element = element_type(base, offset_value); - if (!types_coercible(product, element)) { - raise_error << maybe(get(Recipe, r).name) << "'get " << base.original_string << ", " << offset.original_string << "' should write to " << names_to_string_without_quotes(element.type) << " but " << product.name << " has type " << names_to_string_without_quotes(product.type) << '\n' << end(); - break; - } - break; - } - case GET_ADDRESS: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'get-address' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end(); - break; - } - reagent base = inst.ingredients.at(0); - // Update GET_ADDRESS base in Check - if (!canonize_type(base)) break; - if (!base.type || !base.type->value || !contains_key(Type, base.type->value) || get(Type, base.type->value).kind != CONTAINER) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'get-address' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - type_ordinal base_type = base.type->value; - reagent offset = inst.ingredients.at(1); - if (!is_literal(offset) || !is_mu_scalar(offset)) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - long long int offset_value = 0; - if (is_integer(offset.name)) { // later layers permit non-integer offsets - offset_value = to_integer(offset.name); - if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type).elements)) { - raise_error << maybe(get(Recipe, r).name) << "invalid offset " << offset_value << " for " << get(Type, base_type).name << '\n' << end(); - break; - } - } - else { - offset_value = offset.value; - } - reagent product = inst.products.at(0); - // Update GET_ADDRESS product in Check - if (!canonize_type(base)) break; - // same type as for GET.. - reagent element = element_type(base, offset_value); - // ..except for an address at the start - element.type = new type_tree("address", get(Type_ordinal, "address"), element.type); - if (!types_coercible(product, element)) { - raise_error << maybe(get(Recipe, r).name) << "'get-address " << base.original_string << ", " << offset.original_string << "' should write to " << names_to_string_without_quotes(element.type) << " but " << product.name << " has type " << names_to_string_without_quotes(product.type) << '\n' << end(); - break; - } - break; - } - case MERGE: { - // type-checking in a separate transform below - break; - } - case CREATE_ARRAY: { - if (inst.products.empty()) { - raise_error << maybe(get(Recipe, r).name) << "'create-array' needs one product and no ingredients but got '" << to_string(inst) << '\n' << end(); - break; - } - reagent product = inst.products.at(0); - canonize_type(product); - if (!is_mu_array(product)) { - raise_error << maybe(get(Recipe, r).name) << "'create-array' cannot create non-array " << product.original_string << '\n' << end(); - break; - } - if (!product.type->right) { - raise_error << maybe(get(Recipe, r).name) << "create array of what? " << to_string(inst) << '\n' << end(); - break; - } - // 'create-array' will need to check properties rather than types - if (!product.type->right->right) { - raise_error << maybe(get(Recipe, r).name) << "create array of what size? " << to_string(inst) << '\n' << end(); - break; - } - if (!is_integer(product.type->right->right->name)) { - raise_error << maybe(get(Recipe, r).name) << "'create-array' product should specify size of array after its element type, but got " << product.type->right->right->name << '\n' << end(); - break; - } - break; - } - case INDEX: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'index' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end(); - break; - } - reagent base = inst.ingredients.at(0); - canonize_type(base); - if (!is_mu_array(base)) { - raise_error << maybe(get(Recipe, r).name) << "'index' on a non-array " << base.original_string << '\n' << end(); - break; - } - if (inst.products.empty()) break; - reagent product = inst.products.at(0); - canonize_type(product); - reagent element; - element.type = new type_tree(*array_element(base.type)); - 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 " << names_to_string_without_quotes(element.type) << '\n' << end(); - break; - } - break; - } - case INDEX_ADDRESS: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'index-address' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end(); - break; - } - reagent base = inst.ingredients.at(0); - canonize_type(base); - if (!is_mu_array(base)) { - raise_error << maybe(get(Recipe, r).name) << "'index-address' on a non-array " << base.original_string << '\n' << end(); - break; - } - if (inst.products.empty()) break; - reagent product = inst.products.at(0); - canonize_type(product); - reagent element; - element.type = new type_tree("address", get(Type_ordinal, "address"), - new type_tree(*array_element(base.type))); - 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 " << names_to_string_without_quotes(element.type) << '\n' << end(); - break; - } - break; - } - case LENGTH: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'length' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end(); - break; - } - reagent x = inst.ingredients.at(0); - canonize_type(x); - if (!is_mu_array(x)) { - raise_error << "tried to calculate length of non-array " << x.original_string << '\n' << end(); - break; - } - break; - } - case MAYBE_CONVERT: { - const recipe& caller = get(Recipe, r); - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(caller.name) << "'maybe-convert' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end(); - break; - } - reagent base = inst.ingredients.at(0); - canonize_type(base); - if (!base.type || !base.type->value || get(Type, base.type->value).kind != EXCLUSIVE_CONTAINER) { - raise_error << maybe(caller.name) << "first ingredient of 'maybe-convert' should be an exclusive-container, but got " << base.original_string << '\n' << end(); - break; - } - if (!is_literal(inst.ingredients.at(1))) { - raise_error << maybe(caller.name) << "second ingredient of 'maybe-convert' should have type 'variant', but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - if (inst.products.empty()) break; - reagent product = inst.products.at(0); - if (!canonize_type(product)) break; - reagent& offset = inst.ingredients.at(1); - populate_value(offset); - if (offset.value >= SIZE(get(Type, base.type->value).elements)) { - raise_error << maybe(caller.name) << "invalid tag " << offset.value << " in '" << to_string(inst) << '\n' << end(); - break; - } - reagent variant = variant_type(base, offset.value); - variant.type = new type_tree("address", get(Type_ordinal, "address"), variant.type); - if (!types_coercible(product, variant)) { - raise_error << maybe(caller.name) << "'maybe-convert " << base.original_string << ", " << inst.ingredients.at(1).original_string << "' should write to " << to_string(variant.type) << " but " << product.name << " has type " << to_string(product.type) << '\n' << end(); - break; - } - break; - } - case NEXT_INGREDIENT: { - if (!inst.ingredients.empty()) { - raise_error << maybe(get(Recipe, r).name) << "'next-ingredient' didn't expect any ingredients in '" << to_string(inst) << "'\n" << end(); - break; - } - break; - } - case REWIND_INGREDIENTS: { - break; - } - case INGREDIENT: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'ingredient' expects exactly one ingredient, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_literal(inst.ingredients.at(0)) && !is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'ingredient' expects a literal ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case REPLY: { - break; // checks will be performed by a transform below - } - case NEW: { - const recipe& caller = get(Recipe, r); - if (inst.ingredients.empty() || SIZE(inst.ingredients) > 2) { - raise_error << maybe(caller.name) << "'new' requires one or two ingredients, but got " << to_string(inst) << '\n' << end(); - break; - } - if (is_literal_string(inst.ingredients.at(0))) break; - // End NEW Check Special-cases - reagent type = inst.ingredients.at(0); - if (!is_mu_type_literal(type)) { - raise_error << maybe(caller.name) << "first ingredient of 'new' should be a type, but got " << type.original_string << '\n' << end(); - break; - } - if (inst.products.empty()) { - raise_error << maybe(caller.name) << "result of 'new' should never be ignored\n" << end(); - break; - } - if (!product_of_new_is_valid(inst)) { - raise_error << maybe(caller.name) << "product of 'new' has incorrect type: " << to_string(inst) << '\n' << end(); - break; - } - break; - } - case ALLOCATE: { - raise << "never call 'allocate' directly'; always use 'new'\n" << end(); - break; - } - - case ABANDON: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'abandon' requires one ingredient, but got '" << to_string(inst) << "'\n" << end(); - break; - } - reagent types = inst.ingredients.at(0); - canonize_type(types); - if (!types.type || types.type->value != get(Type_ordinal, "address") || types.type->right->value != get(Type_ordinal, "shared")) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'abandon' should be an address:shared:___, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case TO_LOCATION_ARRAY: { - const recipe& caller = get(Recipe, r); - if (!is_shared_address_of_array_of_numbers(inst.products.at(0))) { - raise_error << maybe(caller.name) << "product of 'to-location-array' has incorrect type: " << to_string(inst) << '\n' << end(); - break; - } - break; - } - case BREAK: break; - case BREAK_IF: break; - case BREAK_UNLESS: break; - case LOOP: break; - case LOOP_IF: break; - case LOOP_UNLESS: break; - - case RUN: { - break; - } - case MEMORY_SHOULD_CONTAIN: { - break; - } - case TRACE_SHOULD_CONTAIN: { - break; - } - case TRACE_SHOULD_NOT_CONTAIN: { - break; - } - case CHECK_TRACE_COUNT_FOR_LABEL: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'check-trace-for-label' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'check-trace-for-label' should be a number (count), but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (!is_literal_string(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'check-trace-for-label' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - break; - } - case NEXT_INGREDIENT_WITHOUT_TYPECHECKING: { - break; - } - case CALL: { - if (inst.ingredients.empty()) { - raise_error << maybe(get(Recipe, r).name) << "'call' requires at least one ingredient (the recipe to call)\n" << end(); - break; - } - if (!is_mu_recipe(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'call' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case START_RUNNING: { - if (inst.ingredients.empty()) { - raise_error << maybe(get(Recipe, r).name) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end(); - break; - } - if (!is_mu_recipe(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'start-running' should be a recipe, but got " << to_string(inst.ingredients.at(0)) << '\n' << end(); - break; - } - break; - } - case ROUTINE_STATE: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case RESTART: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'restart' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restart' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case STOP: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'stop' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case _DUMP_ROUTINES: { - break; - } - case LIMIT_TIME: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'limit-time' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'limit-time' should be a number (of instructions to run for), but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - break; - } - case WAIT_FOR_LOCATION: { - break; - } - case WAIT_FOR_ROUTINE: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'wait-for-routine' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'wait-for-routine' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case SWITCH: { - break; - } - case RANDOM: { - break; - } - case MAKE_RANDOM_NONDETERMINISTIC: { - break; - } - case ROUND: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'round' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'round' should be a number, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case HASH: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'hash' takes exactly one ingredient rather than '" << to_string(inst) << "'\n" << end(); - break; - } - break; - } - case HASH_OLD: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'hash_old' takes exactly one ingredient rather than '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_mu_string(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "'hash_old' currently only supports strings (address:shared:array:character), but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case OPEN_CONSOLE: { - break; - } - case CLOSE_CONSOLE: { - break; - } - case CLEAR_DISPLAY: { - break; - } - case SYNC_DISPLAY: { - break; - } - case CLEAR_LINE_ON_DISPLAY: { - break; - } - case PRINT_CHARACTER_TO_DISPLAY: { - if (inst.ingredients.empty()) { - raise_error << maybe(get(Recipe, r).name) << "'print-character-to-display' requires at least one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'print-character-to-display' should be a character, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (SIZE(inst.ingredients) > 1) { - if (!is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'print-character-to-display' should be a foreground color number, but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - } - if (SIZE(inst.ingredients) > 2) { - if (!is_mu_number(inst.ingredients.at(2))) { - raise_error << maybe(get(Recipe, r).name) << "third ingredient of 'print-character-to-display' should be a background color number, but got " << inst.ingredients.at(2).original_string << '\n' << end(); - break; - } - } - break; - } - case CURSOR_POSITION_ON_DISPLAY: { - break; - } - case MOVE_CURSOR_ON_DISPLAY: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'move-cursor-on-display' requires two ingredients, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'move-cursor-on-display' should be a row number, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'move-cursor-on-display' should be a column number, but got " << inst.ingredients.at(1).original_string << '\n' << end(); - break; - } - break; - } - case MOVE_CURSOR_DOWN_ON_DISPLAY: { - break; - } - case MOVE_CURSOR_UP_ON_DISPLAY: { - break; - } - case MOVE_CURSOR_RIGHT_ON_DISPLAY: { - break; - } - case MOVE_CURSOR_LEFT_ON_DISPLAY: { - break; - } - case DISPLAY_WIDTH: { - break; - } - case DISPLAY_HEIGHT: { - break; - } - case HIDE_CURSOR_ON_DISPLAY: { - break; - } - case SHOW_CURSOR_ON_DISPLAY: { - break; - } - case HIDE_DISPLAY: { - break; - } - case SHOW_DISPLAY: { - break; - } - case WAIT_FOR_SOME_INTERACTION: { - break; - } - case CHECK_FOR_INTERACTION: { - break; - } - case INTERACTIONS_LEFT: { - break; - } - case CLEAR_DISPLAY_FROM: { - break; - } - case SCREEN_SHOULD_CONTAIN: { - break; - } - case SCREEN_SHOULD_CONTAIN_IN_COLOR: { - break; - } - case _DUMP_SCREEN: { - break; - } - case ASSUME_CONSOLE: { - break; - } - case REPLACE_IN_CONSOLE: { - break; - } - case _BROWSE_TRACE: { - break; - } - case RUN_INTERACTIVE: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'run-interactive' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_string(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'run-interactive' should be a string, but got " << to_string(inst.ingredients.at(0)) << '\n' << end(); - break; - } - break; - } - case _START_TRACKING_PRODUCTS: { - break; - } - case _STOP_TRACKING_PRODUCTS: { - break; - } - case _MOST_RECENT_PRODUCTS: { - break; - } - case SAVE_ERRORS_WARNINGS: { - break; - } - case SAVE_APP_TRACE: { - break; - } - case _CLEANUP_RUN_INTERACTIVE: { - break; - } - case RELOAD: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - if (!is_mu_string(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; - } - case RESTORE: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'restore' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); - break; - } - string filename; - if (is_literal_string(inst.ingredients.at(0))) { - ; - } - else if (is_mu_string(inst.ingredients.at(0))) { - ; - } - else { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restore' should be a string, but got " << to_string(inst.ingredients.at(0)) << '\n' << end(); - break; - } - break; - } - case SAVE: { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "'save' requires exactly two ingredients, but got " << to_string(inst) << '\n' << end(); - break; - } - if (is_literal_string(inst.ingredients.at(0))) { - ; - } - else if (is_mu_string(inst.ingredients.at(0))) { - ; - } - else { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'save' should be a string, but got " << to_string(inst.ingredients.at(0)) << '\n' << end(); - break; - } - if (!is_mu_string(inst.ingredients.at(1))) { - raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'save' should be an address:array:character, but got " << to_string(inst.ingredients.at(1)) << '\n' << end(); - break; - } - break; - } - // End Primitive Recipe Checks - default: { - // Defined Recipe Checks - // not a primitive; check that it's present in the book of recipes - if (!contains_key(Recipe, inst.operation)) { - raise_error << maybe(get(Recipe, r).name) << "undefined operation in '" << to_string(inst) << "'\n" << end(); - break; - } - // End Defined Recipe Checks - } - } - finish_checking_instruction:; - } -} - -void test_copy_checks_reagent_count() { - Trace_file = "copy_checks_reagent_count"; - Hide_errors = true; - run("recipe main [\n 1:number <- copy 34, 35\n]\n"); - CHECK_TRACE_CONTENTS("error: ingredients and products should match in '1:number <- copy 34, 35'"); -} -void test_write_scalar_to_array_disallowed() { - Trace_file = "write_scalar_to_array_disallowed"; - Hide_errors = true; - run("recipe main [\n 1:array:number <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("error: main: can't copy 34 to 1:array:number; types don't match"); -} -void test_write_scalar_to_array_disallowed_2() { - Trace_file = "write_scalar_to_array_disallowed_2"; - Hide_errors = true; - run("recipe main [\n 1:number, 2:array:number <- copy 34, 35\n]\n"); - CHECK_TRACE_CONTENTS("error: main: can't copy 35 to 2:array:number; types don't match"); -} -void test_write_scalar_to_address_disallowed() { - Trace_file = "write_scalar_to_address_disallowed"; - Hide_errors = true; - run("recipe main [\n 1:address:number <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("error: main: can't copy 34 to 1:address:number; types don't match"); -} -void test_write_address_to_number_allowed() { - Trace_file = "write_address_to_number_allowed"; - Hide_errors = true; - run("recipe main [\n 1:address:number <- copy 12/unsafe\n 2:number <- copy 1:address:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 12 in location 2"); - CHECK_TRACE_COUNT("error", 0); -} -void test_write_boolean_to_number_allowed() { - Trace_file = "write_boolean_to_number_allowed"; - Hide_errors = true; - run("recipe main [\n 1:boolean <- copy 1/true\n 2:number <- copy 1:boolean\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 2"); - CHECK_TRACE_COUNT("error", 0); -} -// types_match with some leniency -bool types_coercible(const reagent& to, const reagent& from) { - if (types_match(to, from)) return true; - if (is_mu_address(from) && is_mu_number(to)) return true; - if (is_mu_boolean(from) && is_mu_number(to)) return true; - // End types_coercible Special-cases - return false; -} - -bool types_match(const reagent& to, const reagent& from) { - // to sidestep type-checking, use /unsafe in the source. - // this will be highlighted in red inside vim. just for setting up some tests. - if (is_unsafe(from)) return true; - if (is_literal(from)) { - if (is_mu_array(to)) return false; - if (contains_type_ingredient_name(to)) return false; - - if (is_mu_recipe(to)) { - if (!contains_key(Recipe, from.value)) { - raise_error << "trying to store recipe " << from.name << " into " << to_string(to) << " but there's no such recipe\n" << end(); - return false; - } - const recipe& rrhs = get(Recipe, from.value); - const recipe& rlhs = from_reagent(to); - for (long int i = 0; i < min(SIZE(rlhs.ingredients), SIZE(rrhs.ingredients)); ++i) { - if (!types_match(rlhs.ingredients.at(i), rrhs.ingredients.at(i))) - return false; - } - for (long int i = 0; i < min(SIZE(rlhs.products), SIZE(rrhs.products)); ++i) { - if (!types_match(rlhs.products.at(i), rrhs.products.at(i))) - return false; - } - return true; - } - - // End Matching Types For Literal(to) - // allow writing 0 to any address - if (is_mu_address(to)) return from.name == "0"; - if (!to.type) return false; - if (to.type->value == get(Type_ordinal, "boolean")) - return boolean_matches_literal(to, from); - return size_of(to) == 1; // literals are always scalars - } - return types_strictly_match(to, from); -} - -bool types_strictly_match_except_literal_against_boolean(const reagent& to, const reagent& from) { - // to sidestep type-checking, use /unsafe in the source. - // this will be highlighted in red inside vim. just for setting up some tests. - if (is_literal(from) - && to.type && to.type->value == get(Type_ordinal, "boolean")) - return boolean_matches_literal(to, from); - return types_strictly_match(to, from); -} - -bool boolean_matches_literal(const reagent& to, const reagent& from) { - if (!is_literal(from)) return false; - if (!to.type) return false; - if (to.type->value != get(Type_ordinal, "boolean")) return false; - return from.name == "0" || from.name == "1"; -} - -// copy arguments because later layers will want to make changes to them -// without perturbing the caller -bool types_strictly_match(reagent to, reagent from) { - if (!canonize_type(to)) return false; - if (!canonize_type(from)) return false; - - if (is_literal(from) && to.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(from)) return true; - // '_' never raises type error - if (is_dummy(to)) return true; - if (!to.type) return !from.type; - return types_strictly_match(to.type, from.type); -} - -// two types match if the second begins like the first -// (trees perform the same check recursively on each subtree) -bool types_strictly_match(type_tree* to, type_tree* from) { - if (!to) return true; - if (!from) return to->value == 0; - if (to->value != from->value) return false; - return types_strictly_match(to->left, from->left) && types_strictly_match(to->right, from->right); -} - -bool is_unsafe(const reagent& r) { - return has_property(r, "unsafe"); -} - -bool is_mu_array(reagent r) { - if (!canonize_type(r)) return false; - - if (!r.type) return false; - if (is_literal(r)) return false; - return r.type->value == get(Type_ordinal, "array"); -} - -bool is_mu_address(reagent r) { - if (!canonize_type(r)) return false; - - if (!r.type) return false; - if (is_literal(r)) return false; - return r.type->value == get(Type_ordinal, "address"); -} - -bool is_mu_boolean(reagent r) { - if (!r.type) return false; - if (is_literal(r)) return false; - return r.type->value == get(Type_ordinal, "boolean"); -} - -bool is_mu_number(reagent r) { - if (!canonize_type(r)) return false; - - if (!r.type) return false; - if (is_literal(r)) { - if (!r.type) return false; - return r.type->name == "literal-fractional-number" - || r.type->name == "literal"; - } - if (r.type->value == get(Type_ordinal, "character")) return true; // permit arithmetic on unicode code points - return r.type->value == get(Type_ordinal, "number"); -} - -bool is_mu_scalar(reagent r) { - if (!r.type) return false; - if (is_literal(r)) - return !r.type || r.type->name != "literal-string"; - if (is_mu_array(r)) return false; - return size_of(r) == 1; -} - - -void test_add_literal() { - Trace_file = "add_literal"; - run("recipe main [\n 1:number <- add 23, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 57 in location 1"); -} -void test_add() { - Trace_file = "add"; - run("recipe main [\n 1:number <- copy 23\n 2:number <- copy 34\n 3:number <- add 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 57 in location 3"); -} -void test_add_multiple() { - Trace_file = "add_multiple"; - run("recipe main [\n 1:number <- add 3, 4, 5\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 12 in location 1"); -} -void test_add_checks_type() { - Trace_file = "add_checks_type"; - Hide_errors = true; - run("recipe main [\n 1:number <- add 2:boolean, 1\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'add' requires number ingredients, but got 2:boolean"); -} -void test_add_checks_return_type() { - Trace_file = "add_checks_return_type"; - Hide_errors = true; - run("recipe main [\n 1:address:number <- add 2, 2\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'add' should yield a number, but got 1:address:number"); -} -bool is_raw(const reagent& r) { - return has_property(r, "raw"); -} - -void test_subtract_literal() { - Trace_file = "subtract_literal"; - run("recipe main [\n 1:number <- subtract 5, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 1"); -} -void test_subtract() { - Trace_file = "subtract"; - run("recipe main [\n 1:number <- copy 23\n 2:number <- copy 34\n 3:number <- subtract 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing -11 in location 3"); -} -void test_subtract_multiple() { - Trace_file = "subtract_multiple"; - run("recipe main [\n 1:number <- subtract 6, 3, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_multiply_literal() { - Trace_file = "multiply_literal"; - run("recipe main [\n 1:number <- multiply 2, 3\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 6 in location 1"); -} -void test_multiply() { - Trace_file = "multiply"; - run("recipe main [\n 1:number <- copy 4\n 2:number <- copy 6\n 3:number <- multiply 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 24 in location 3"); -} -void test_multiply_multiple() { - Trace_file = "multiply_multiple"; - run("recipe main [\n 1:number <- multiply 2, 3, 4\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 24 in location 1"); -} -void test_divide_literal() { - Trace_file = "divide_literal"; - run("recipe main [\n 1:number <- divide 8, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 1"); -} -void test_divide() { - Trace_file = "divide"; - run("recipe main [\n 1:number <- copy 27\n 2:number <- copy 3\n 3:number <- divide 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 9 in location 3"); -} -void test_divide_multiple() { - Trace_file = "divide_multiple"; - run("recipe main [\n 1:number <- divide 12, 3, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_divide_with_remainder_literal() { - Trace_file = "divide_with_remainder_literal"; - run("recipe main [\n 1:number, 2:number <- divide-with-remainder 9, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 1mem: storing 1 in location 2"); -} -void test_divide_with_remainder() { - Trace_file = "divide_with_remainder"; - run("recipe main [\n 1:number <- copy 27\n 2:number <- copy 11\n 3:number, 4:number <- divide-with-remainder 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 3mem: storing 5 in location 4"); -} -void test_divide_with_decimal_point() { - Trace_file = "divide_with_decimal_point"; - run("recipe main [\n 1:number <- divide 5, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2.5 in location 1"); -} -void test_divide_by_zero() { - Trace_file = "divide_by_zero"; - run("recipe main [\n 1:number <- divide 4, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing inf in location 1"); -} -void test_divide_by_zero_2() { - Trace_file = "divide_by_zero_2"; - Hide_errors = true; - run("recipe main [\n 1:number <- divide-with-remainder 4, 0\n]\n# integer division can't return floating-point infinity\n"); - CHECK_TRACE_CONTENTS("error: main: divide by zero in '1:number <- divide-with-remainder 4, 0'"); -} -void test_shift_left_by_zero() { - Trace_file = "shift_left_by_zero"; - run("recipe main [\n 1:number <- shift-left 1, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_shift_left_1() { - Trace_file = "shift_left_1"; - run("recipe main [\n 1:number <- shift-left 1, 4\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 16 in location 1"); -} -void test_shift_left_2() { - Trace_file = "shift_left_2"; - run("recipe main [\n 1:number <- shift-left 3, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 12 in location 1"); -} -void test_shift_left_by_negative() { - Trace_file = "shift_left_by_negative"; - Hide_errors = true; - run("recipe main [\n 1:number <- shift-left 3, -1\n]\n"); - CHECK_TRACE_CONTENTS("error: main: second ingredient can't be negative in '1:number <- shift-left 3, -1'"); -} -void test_shift_left_ignores_fractional_part() { - Trace_file = "shift_left_ignores_fractional_part"; - run("recipe main [\n 1:number <- divide 3, 2\n 2:number <- shift-left 1:number, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 2"); -} -void test_shift_right_by_zero() { - Trace_file = "shift_right_by_zero"; - run("recipe main [\n 1:number <- shift-right 1, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_shift_right_1() { - Trace_file = "shift_right_1"; - run("recipe main [\n 1:number <- shift-right 1024, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 512 in location 1"); -} -void test_shift_right_2() { - Trace_file = "shift_right_2"; - run("recipe main [\n 1:number <- shift-right 3, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_shift_right_by_negative() { - Trace_file = "shift_right_by_negative"; - Hide_errors = true; - run("recipe main [\n 1:number <- shift-right 4, -1\n]\n"); - CHECK_TRACE_CONTENTS("error: main: second ingredient can't be negative in '1:number <- shift-right 4, -1'"); -} -void test_shift_right_ignores_fractional_part() { - Trace_file = "shift_right_ignores_fractional_part"; - run("recipe main [\n 1:number <- divide 3, 2\n 2:number <- shift-right 1:number, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 2"); -} -void test_and_bits_1() { - Trace_file = "and_bits_1"; - run("recipe main [\n 1:number <- and-bits 8, 3\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_and_bits_2() { - Trace_file = "and_bits_2"; - run("recipe main [\n 1:number <- and-bits 3, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_and_bits_3() { - Trace_file = "and_bits_3"; - run("recipe main [\n 1:number <- and-bits 14, 3\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_and_bits_negative() { - Trace_file = "and_bits_negative"; - run("recipe main [\n 1:number <- and-bits -3, 4\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 1"); -} -void test_or_bits_1() { - Trace_file = "or_bits_1"; - run("recipe main [\n 1:number <- or-bits 3, 8\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 11 in location 1"); -} -void test_or_bits_2() { - Trace_file = "or_bits_2"; - run("recipe main [\n 1:number <- or-bits 3, 10\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 11 in location 1"); -} -void test_or_bits_3() { - Trace_file = "or_bits_3"; - run("recipe main [\n 1:number <- or-bits 4, 6\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 6 in location 1"); -} -void test_xor_bits_1() { - Trace_file = "xor_bits_1"; - run("recipe main [\n 1:number <- xor-bits 3, 8\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 11 in location 1"); -} -void test_xor_bits_2() { - Trace_file = "xor_bits_2"; - run("recipe main [\n 1:number <- xor-bits 3, 10\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 9 in location 1"); -} -void test_xor_bits_3() { - Trace_file = "xor_bits_3"; - run("recipe main [\n 1:number <- xor-bits 4, 6\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_flip_bits_zero() { - Trace_file = "flip_bits_zero"; - run("recipe main [\n 1:number <- flip-bits 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing -1 in location 1"); -} -void test_flip_bits_negative() { - Trace_file = "flip_bits_negative"; - run("recipe main [\n 1:number <- flip-bits -1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_flip_bits_1() { - Trace_file = "flip_bits_1"; - run("recipe main [\n 1:number <- flip-bits 3\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing -4 in location 1"); -} -void test_flip_bits_2() { - Trace_file = "flip_bits_2"; - run("recipe main [\n 1:number <- flip-bits 12\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing -13 in location 1"); -} - -void test_and() { - Trace_file = "and"; - run("recipe main [\n 1:boolean <- copy 1\n 2:boolean <- copy 0\n 3:boolean <- and 1:boolean, 2:boolean\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} -void test_and_2() { - Trace_file = "and_2"; - run("recipe main [\n 1:boolean <- and 1, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_and_multiple() { - Trace_file = "and_multiple"; - run("recipe main [\n 1:boolean <- and 1, 1, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_and_multiple_2() { - Trace_file = "and_multiple_2"; - run("recipe main [\n 1:boolean <- and 1, 1, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_or() { - Trace_file = "or"; - run("recipe main [\n 1:boolean <- copy 1\n 2:boolean <- copy 0\n 3:boolean <- or 1:boolean, 2:boolean\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_or_2() { - Trace_file = "or_2"; - run("recipe main [\n 1:boolean <- or 0, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_or_multiple() { - Trace_file = "or_multiple"; - run("recipe main [\n 1:boolean <- and 0, 0, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_or_multiple_2() { - Trace_file = "or_multiple_2"; - run("recipe main [\n 1:boolean <- or 0, 0, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_not() { - Trace_file = "not"; - run("recipe main [\n 1:boolean <- copy 1\n 2:boolean <- not 1:boolean\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 2"); -} -void test_not_multiple() { - Trace_file = "not_multiple"; - run("recipe main [\n 1:boolean, 2:boolean, 3:boolean <- not 1, 0, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 1 in location 2mem: storing 0 in location 3"); -} - -void test_jump_can_skip_instructions() { - Trace_file = "jump_can_skip_instructions"; - run("recipe main [\n jump 1:offset\n 1:number <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("run: jump 1:offset"); - CHECK_TRACE_DOESNT_CONTAIN("run: 1:number <- copy 1"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 1 in location 1"); -} -void test_jump_backward() { - Trace_file = "jump_backward"; - run("recipe main [\n jump 1:offset # 0 -+\n jump 3:offset # | +-+ 1\n # \\/ /\\ |\n jump -2:offset # 2 +-->+ |\n] # \\/ 3\n"); - CHECK_TRACE_CONTENTS("run: jump 1:offsetrun: jump -2:offsetrun: jump 3:offset"); -} -void test_jump_if() { - Trace_file = "jump_if"; - run("recipe main [\n jump-if 999, 1:offset\n 123:number <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("run: jump-if 999, 1:offsetrun: jumping to instruction 2"); - CHECK_TRACE_DOESNT_CONTAIN("run: 1:number <- copy 1"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 1 in location 123"); -} -void test_jump_if_fallthrough() { - Trace_file = "jump_if_fallthrough"; - run("recipe main [\n jump-if 0, 1:offset\n 123:number <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("run: jump-if 0, 1:offsetrun: jump-if fell throughrun: 123:number <- copy 1mem: storing 1 in location 123"); -} -void test_jump_unless() { - Trace_file = "jump_unless"; - run("recipe main [\n jump-unless 0, 1:offset\n 123:number <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("run: jump-unless 0, 1:offsetrun: jumping to instruction 2"); - CHECK_TRACE_DOESNT_CONTAIN("run: 123:number <- copy 1"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 1 in location 123"); -} -void test_jump_unless_fallthrough() { - Trace_file = "jump_unless_fallthrough"; - run("recipe main [\n jump-unless 999, 1:offset\n 123:number <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("run: jump-unless 999, 1:offsetrun: jump-unless fell throughrun: 123:number <- copy 1mem: storing 1 in location 123"); -} - -void test_equal() { - Trace_file = "equal"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 33\n 3:number <- equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: location 1 is 34mem: location 2 is 33mem: storing 0 in location 3"); -} -void test_equal_2() { - Trace_file = "equal_2"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 34\n 3:number <- equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: location 1 is 34mem: location 2 is 34mem: storing 1 in location 3"); -} -void test_equal_multiple() { - Trace_file = "equal_multiple"; - run("recipe main [\n 1:number <- equal 34, 34, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_equal_multiple_2() { - Trace_file = "equal_multiple_2"; - run("recipe main [\n 1:number <- equal 34, 34, 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_greater_than() { - Trace_file = "greater_than"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 33\n 3:boolean <- greater-than 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_greater_than_2() { - Trace_file = "greater_than_2"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 34\n 3:boolean <- greater-than 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} -void test_greater_than_multiple() { - Trace_file = "greater_than_multiple"; - run("recipe main [\n 1:boolean <- greater-than 36, 35, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_greater_than_multiple_2() { - Trace_file = "greater_than_multiple_2"; - run("recipe main [\n 1:boolean <- greater-than 36, 35, 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_lesser_than() { - Trace_file = "lesser_than"; - run("recipe main [\n 1:number <- copy 32\n 2:number <- copy 33\n 3:boolean <- lesser-than 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_lesser_than_2() { - Trace_file = "lesser_than_2"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 33\n 3:boolean <- lesser-than 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} -void test_lesser_than_multiple() { - Trace_file = "lesser_than_multiple"; - run("recipe main [\n 1:boolean <- lesser-than 34, 35, 36\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_lesser_than_multiple_2() { - Trace_file = "lesser_than_multiple_2"; - run("recipe main [\n 1:boolean <- lesser-than 34, 35, 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_greater_or_equal() { - Trace_file = "greater_or_equal"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 33\n 3:boolean <- greater-or-equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_greater_or_equal_2() { - Trace_file = "greater_or_equal_2"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 34\n 3:boolean <- greater-or-equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_greater_or_equal_3() { - Trace_file = "greater_or_equal_3"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 35\n 3:boolean <- greater-or-equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} -void test_greater_or_equal_multiple() { - Trace_file = "greater_or_equal_multiple"; - run("recipe main [\n 1:boolean <- greater-or-equal 36, 35, 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_greater_or_equal_multiple_2() { - Trace_file = "greater_or_equal_multiple_2"; - run("recipe main [\n 1:boolean <- greater-or-equal 36, 35, 36\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} -void test_lesser_or_equal() { - Trace_file = "lesser_or_equal"; - run("recipe main [\n 1:number <- copy 32\n 2:number <- copy 33\n 3:boolean <- lesser-or-equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_lesser_or_equal_2() { - Trace_file = "lesser_or_equal_2"; - run("recipe main [\n 1:number <- copy 33\n 2:number <- copy 33\n 3:boolean <- lesser-or-equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_lesser_or_equal_3() { - Trace_file = "lesser_or_equal_3"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 33\n 3:boolean <- lesser-or-equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} -void test_lesser_or_equal_multiple() { - Trace_file = "lesser_or_equal_multiple"; - run("recipe main [\n 1:boolean <- lesser-or-equal 34, 35, 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1"); -} -void test_lesser_or_equal_multiple_2() { - Trace_file = "lesser_or_equal_multiple_2"; - run("recipe main [\n 1:boolean <- lesser-or-equal 34, 35, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); -} - -void test_trace() { - Trace_file = "trace"; - run("recipe main [\n trace 1, [foo], [this is a trace in mu]\n]\n"); - CHECK_TRACE_CONTENTS("foo: this is a trace in mu"); -} -void test_stash_literal_string() { - Trace_file = "stash_literal_string"; - run("recipe main [\n stash [foo]\n]\n"); - CHECK_TRACE_CONTENTS("app: foo"); -} -void test_stash_literal_number() { - Trace_file = "stash_literal_number"; - run("recipe main [\n stash [foo:], 4\n]\n"); - CHECK_TRACE_CONTENTS("app: foo: 4"); -} -void test_stash_number() { - Trace_file = "stash_number"; - run("recipe main [\n 1:number <- copy 34\n stash [foo:], 1:number\n]\n"); - CHECK_TRACE_CONTENTS("app: foo: 34"); -} -string print_mu(const reagent& r, const vector<double>& data) { - if (is_literal(r)) - return r.name+' '; - if (is_mu_string(r)) { - assert(scalar(data)); - return read_mu_string(data.at(0))+' '; - } - - // End print Special-cases(reagent r, data) - ostringstream out; - for (long long i = 0; i < SIZE(data); ++i) - out << no_scientific(data.at(i)) << ' '; - return out.str(); -} - -void test_assert() { - Trace_file = "assert"; - Hide_errors = true; // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line. - run("recipe main [\n assert 0, [this is an assert in mu]\n]\n"); - CHECK_TRACE_CONTENTS("error: this is an assert in mu"); -} - -void test_copy_multiple_locations() { - Trace_file = "copy_multiple_locations"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 35\n 3:point <- copy 1:point/unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 3mem: storing 35 in location 4"); -} -void test_copy_checks_size() { - Trace_file = "copy_checks_size"; - Hide_errors = true; - run("recipe main [\n 2:point <- copy 1:number\n]\n"); - CHECK_TRACE_CONTENTS("error: main: can't copy 1:number to 2:point; types don't match"); -} -void test_copy_handles_nested_container_elements() { - Trace_file = "copy_handles_nested_container_elements"; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 14:number <- copy 36\n 15:point-number <- copy 12:point-number/unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 36 in location 17"); -} -void test_compare_multiple_locations() { - Trace_file = "compare_multiple_locations"; - run("recipe main [\n 1:number <- copy 34 # first\n 2:number <- copy 35\n 3:number <- copy 36\n 4:number <- copy 34 # second\n 5:number <- copy 35\n 6:number <- copy 36\n 7:boolean <- equal 1:point-number/raw, 4:point-number/unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 7"); -} -void test_compare_multiple_locations_2() { - Trace_file = "compare_multiple_locations_2"; - run("recipe main [\n 1:number <- copy 34 # first\n 2:number <- copy 35\n 3:number <- copy 36\n 4:number <- copy 34 # second\n 5:number <- copy 35\n 6:number <- copy 37 # different\n 7:boolean <- equal 1:point-number/raw, 4:point-number/unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 7"); -} -void test_stash_container() { - Trace_file = "stash_container"; - run("recipe main [\n 1:number <- copy 34 # first\n 2:number <- copy 35\n 3:number <- copy 36\n stash [foo:], 1:point-number/raw\n]\n"); - CHECK_TRACE_CONTENTS("app: foo: 34 35 36"); -} -void test_get() { - Trace_file = "get"; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 15:number <- get 12:point/raw, 1:offset # unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 15"); -} -const reagent element_type(const reagent& canonized_base, long long int offset_value) { - assert(offset_value >= 0); - assert(contains_key(Type, canonized_base.type->value)); - assert(!get(Type, canonized_base.type->value).name.empty()); - const type_info& info = get(Type, canonized_base.type->value); - assert(info.kind == CONTAINER); - reagent element = info.elements.at(offset_value); - if (contains_type_ingredient(element)) { - if (!canonized_base.type->right) - raise_error << "illegal type " << names_to_string(canonized_base.type) << " seems to be missing a type ingredient or three\n" << end(); - replace_type_ingredients(element.type, canonized_base.type->right, info); - } - - // End element_type Special-cases - return element; -} - -void test_get_handles_nested_container_elements() { - Trace_file = "get_handles_nested_container_elements"; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 14:number <- copy 36\n 15:number <- get 12:point-number/raw, 1:offset # unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 36 in location 15"); -} -void test_get_out_of_bounds() { - Trace_file = "get_out_of_bounds"; - Hide_errors = true; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 14:number <- copy 36\n get 12:point-number/raw, 2:offset # point-number occupies 3 locations but has only 2 fields; out of bounds\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid offset 2 for point-number"); -} -void test_get_out_of_bounds_2() { - Trace_file = "get_out_of_bounds_2"; - Hide_errors = true; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 14:number <- copy 36\n get 12:point-number/raw, -1:offset\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid offset -1 for point-number"); -} -void test_get_product_type_mismatch() { - Trace_file = "get_product_type_mismatch"; - Hide_errors = true; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 14:number <- copy 36\n 15:address:number <- get 12:point-number/raw, 1:offset\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'get 12:point-number/raw, 1:offset' should write to number but 15 has type (address number)"); -} -void test_get_without_product() { - Trace_file = "get_without_product"; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n get 12:point/raw, 1:offset # unsafe\n]\n# just don't die\n"); -} -void test_get_address() { - Trace_file = "get_address"; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 15:address:number <- get-address 12:point/raw, 1:offset # unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 13 in location 15"); -} -void test_get_address_out_of_bounds() { - Trace_file = "get_address_out_of_bounds"; - Hide_errors = true; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 14:number <- copy 36\n get-address 12:point-number/raw, 2:offset # point-number occupies 3 locations but has only 2 fields; out of bounds\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid offset 2 for point-number"); -} -void test_get_address_out_of_bounds_2() { - Trace_file = "get_address_out_of_bounds_2"; - Hide_errors = true; - run("recipe main [\n 12:number <- copy 34\n 13:number <- copy 35\n 14:number <- copy 36\n get-address 12:point-number/raw, -1:offset\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid offset -1 for point-number"); -} -void test_get_address_product_type_mismatch() { - Trace_file = "get_address_product_type_mismatch"; - Hide_errors = true; - run("container boolbool [\n x:boolean\n y:boolean\n]\nrecipe main [\n 12:boolean <- copy 1\n 13:boolean <- copy 0\n 15:boolean <- get-address 12:boolbool, 1:offset\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'get-address 12:boolbool, 1:offset' should write to (address boolean) but 15 has type boolean"); -} -void test_container() { - Trace_file = "container"; - load("container foo [\n x:number\n y:number\n]\n"); - CHECK_TRACE_CONTENTS("parse: --- defining container fooparse: element: x: \"number\"parse: element: y: \"number\""); -} -void test_container_use_before_definition() { - Trace_file = "container_use_before_definition"; - load("container foo [\n x:number\n y:bar\n]\ncontainer bar [\n x:number\n y:number\n]\n"); - CHECK_TRACE_CONTENTS("parse: --- defining container fooparse: type number: 1000parse: element: x: \"number\"parse: element: y: \"bar\"parse: --- defining container barparse: type number: 1001parse: element: x: \"number\"parse: element: y: \"number\""); -} -void insert_container(const string& command, kind_of_type kind, istream& in) { - skip_whitespace_but_not_newline(in); - string name = next_word(in); - if (name.find(':') != string::npos) { - trace(9999, "parse") << "container has type ingredients; parsing" << end(); - read_type_ingredients(name); - } - - // End container Name Refinements - trace(9991, "parse") << "--- defining " << command << ' ' << name << end(); - if (!contains_key(Type_ordinal, name) - || get(Type_ordinal, name) == 0) { - put(Type_ordinal, name, Next_type_ordinal++); - } - trace(9999, "parse") << "type number: " << get(Type_ordinal, name) << end(); - skip_bracket(in, "'container' must begin with '['"); - type_info& info = get_or_insert(Type, get(Type_ordinal, name)); - Recently_added_types.push_back(get(Type_ordinal, name)); - info.name = name; - info.kind = kind; - while (has_data(in)) { - skip_whitespace_and_comments(in); - string element = next_word(in); - if (element == "]") break; - info.elements.push_back(reagent(element)); - replace_unknown_types_with_unique_ordinals(info.elements.back().type, info); - trace(9993, "parse") << " element: " << to_string(info.elements.back()) << end(); - { - const type_tree* type = info.elements.back().type; - if (type->name == "array") { - if (!type->right) { - raise_error << "container '" << name << "' doesn't specify type of array elements for " << info.elements.back().name << '\n' << end(); - continue; - } - if (!type->right->right) { // array has no length - raise_error << "container '" << name << "' cannot determine size of element " << info.elements.back().name << '\n' << end(); - continue; - } - } - } - - - // End Load Container Element Definition - } - info.size = SIZE(info.elements); -} - -void replace_unknown_types_with_unique_ordinals(type_tree* type, const type_info& info) { - if (!type) return; - if (!type->name.empty()) { - if (contains_key(Type_ordinal, type->name)) { - type->value = get(Type_ordinal, type->name); - } - else if (is_integer(type->name)) { // sometimes types will contain non-type tags, like numbers for the size of an array - type->value = 0; - } - // check for use of type ingredients - else if (is_type_ingredient_name(type->name)) { - type->value = get(info.type_ingredient_names, type->name); - } - // End insert_container Special-cases - else if (type->name != "->") { // used in recipe types - put(Type_ordinal, type->name, Next_type_ordinal++); - type->value = get(Type_ordinal, type->name); - } - } - replace_unknown_types_with_unique_ordinals(type->left, info); - replace_unknown_types_with_unique_ordinals(type->right, info); -} - -void skip_bracket(istream& in, string message) { - skip_whitespace_and_comments(in); - if (in.get() != '[') - raise_error << message << '\n' << end(); -} - -void test_container_define_twice() { - Trace_file = "container_define_twice"; - run("container foo [\n x:number\n]\ncontainer foo [\n y:number\n]\nrecipe main [\n 1:number <- copy 34\n 2:number <- copy 35\n 3:number <- get 1:foo, 0:offset\n 4:number <- get 1:foo, 1:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 3mem: storing 35 in location 4"); -} -void test_run_complains_on_unknown_types() { - Trace_file = "run_complains_on_unknown_types"; - Hide_errors = true; - run("recipe main [\n # integer is not a type\n 1:integer <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("error: main: unknown type integer in '1:integer <- copy 0'"); -} -void test_run_allows_type_definition_after_use() { - Trace_file = "run_allows_type_definition_after_use"; - Hide_errors = true; - run("recipe main [\n 1:bar <- copy 0/unsafe\n]\ncontainer bar [\n x:number\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void check_or_set_invalid_types(const recipe_ordinal r) { - recipe& caller = get(Recipe, r); - trace(9991, "transform") << "--- check for invalid types in recipe " << caller.name << end(); - for (long long int index = 0; index < SIZE(caller.steps); ++index) { - instruction& inst = caller.steps.at(index); - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) - check_or_set_invalid_types(inst.ingredients.at(i).type, maybe(caller.name), "'"+to_string(inst)+"'"); - for (long long int i = 0; i < SIZE(inst.products); ++i) - check_or_set_invalid_types(inst.products.at(i).type, maybe(caller.name), "'"+to_string(inst)+"'"); - } - for (long long int i = 0; i < SIZE(caller.ingredients); ++i) - check_or_set_invalid_types(caller.ingredients.at(i).type, maybe(caller.name), "recipe header ingredient"); - for (long long int i = 0; i < SIZE(caller.products); ++i) - check_or_set_invalid_types(caller.products.at(i).type, maybe(caller.name), "recipe header product"); - - // End check_or_set_invalid_types -} - -void check_or_set_invalid_types(type_tree* type, const string& block, const string& name) { - if (!type) return; // will throw a more precise error elsewhere - if (type->value >= START_TYPE_INGREDIENTS - && (type->value - START_TYPE_INGREDIENTS) < SIZE(get(Type, type->value).type_ingredient_names)) - return; - // End Container Type Checks - if (type->value == 0) return; - if (!contains_key(Type, type->value)) { - assert(!type->name.empty()); - if (contains_key(Type_ordinal, type->name)) - type->value = get(Type_ordinal, type->name); - else - raise_error << block << "unknown type " << type->name << " in " << name << '\n' << end(); - } - check_or_set_invalid_types(type->left, block, name); - check_or_set_invalid_types(type->right, block, name); -} - -void test_container_unknown_field() { - Trace_file = "container_unknown_field"; - Hide_errors = true; - run("container foo [\n x:number\n y:bar\n]\n"); - CHECK_TRACE_CONTENTS("error: foo: unknown type in y"); -} -void test_read_container_with_bracket_in_comment() { - Trace_file = "read_container_with_bracket_in_comment"; - run("container foo [\n x:number\n # ']' in comment\n y:number\n]\n"); - CHECK_TRACE_CONTENTS("parse: --- defining container fooparse: element: x: \"number\"parse: element: y: \"number\""); -} -void check_container_field_types() { - for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) { - const type_info& info = p->second; - if (!info.type_ingredient_names.empty()) continue; - - // Check Container Field Types(info) - for (long long int i = 0; i < SIZE(info.elements); ++i) - check_invalid_types(info.elements.at(i).type, maybe(info.name), info.elements.at(i).name); - } -} - -void check_invalid_types(const type_tree* type, const string& block, const string& name) { - if (!type) return; // will throw a more precise error elsewhere - if (type->value >= START_TYPE_INGREDIENTS - && (type->value - START_TYPE_INGREDIENTS) < SIZE(get(Type, type->value).type_ingredient_names)) - return; - - // End Container Type Checks2 - if (type->value == 0) { - assert(!type->left && !type->right); - return; - } - if (!contains_key(Type, type->value)) - raise_error << block << "unknown type in " << name << '\n' << end(); - check_invalid_types(type->left, block, name); - check_invalid_types(type->right, block, name); -} - - -void test_merge() { - Trace_file = "merge"; - run("container foo [\n x:number\n y:number\n]\nrecipe main [\n 1:foo <- merge 3, 4\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 1mem: storing 4 in location 2"); -} -void test_merge_check() { - Trace_file = "merge_check"; - Hide_errors = true; - run("recipe main [\n 1:point <- merge 3, 4\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_missing_element() { - Trace_file = "merge_check_missing_element"; - Hide_errors = true; - run("recipe main [\n 1:point <- merge 3\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:point <- merge 3'"); -} -void test_merge_check_extra_element() { - Trace_file = "merge_check_extra_element"; - Hide_errors = true; - run("recipe main [\n 1:point <- merge 3, 4, 5\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too many ingredients in '1:point <- merge 3, 4, 5'"); -} -void test_merge_check_recursive_containers() { - Trace_file = "merge_check_recursive_containers"; - Hide_errors = true; - run("recipe main [\n 1:point <- merge 3, 4\n 1:point-number <- merge 1:point, 5\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_recursive_containers_2() { - Trace_file = "merge_check_recursive_containers_2"; - Hide_errors = true; - run("recipe main [\n 1:point <- merge 3, 4\n 2:point-number <- merge 1:point\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too few ingredients in '2:point-number <- merge 1:point'"); -} -void test_merge_check_recursive_containers_3() { - Trace_file = "merge_check_recursive_containers_3"; - Hide_errors = true; - run("recipe main [\n 1:point-number <- merge 3, 4, 5\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_recursive_containers_4() { - Trace_file = "merge_check_recursive_containers_4"; - Hide_errors = true; - run("recipe main [\n 1:point-number <- merge 3, 4\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:point-number <- merge 3, 4'"); -} -void check_merge_calls(const recipe_ordinal r) { - const recipe& caller = get(Recipe, r); - trace(9991, "transform") << "--- type-check merge instructions in recipe " << caller.name << end(); - for (long long int i = 0; i < SIZE(caller.steps); ++i) { - const instruction& inst = caller.steps.at(i); - if (inst.name != "merge") continue; - if (SIZE(inst.products) != 1) { - raise_error << maybe(caller.name) << "'merge' should yield a single product in '" << to_string(inst) << "'\n" << end(); - continue; - } - reagent product = inst.products.at(0); - // Update product While Type-checking Merge - if (!canonize_type(product)) continue; - - type_ordinal product_type = product.type->value; - if (product_type == 0 || !contains_key(Type, product_type)) { - raise_error << maybe(caller.name) << "'merge' should yield a container in '" << to_string(inst) << "'\n" << end(); - continue; - } - const type_info& info = get(Type, product_type); - if (info.kind != CONTAINER && info.kind != EXCLUSIVE_CONTAINER) { - raise_error << maybe(caller.name) << "'merge' should yield a container in '" << to_string(inst) << "'\n" << end(); - continue; - } - check_merge_call(inst.ingredients, product, caller, inst); - } -} - -void check_merge_call(const vector<reagent>& ingredients, const reagent& product, const recipe& caller, const instruction& inst) { - long long int ingredient_index = 0; - merge_check_state state; - state.data.push(merge_check_point(product, 0)); - while (true) { - assert(!state.data.empty()); - trace(9999, "transform") << ingredient_index << " vs " << SIZE(ingredients) << end(); - if (ingredient_index >= SIZE(ingredients)) { - raise_error << maybe(caller.name) << "too few ingredients in '" << to_string(inst) << "'\n" << end(); - return; - } - reagent& container = state.data.top().container; - type_info& container_info = get(Type, container.type->value); - switch (container_info.kind) { - case CONTAINER: { - reagent expected_ingredient = element_type(container, state.data.top().container_element_index); - trace(9999, "transform") << "checking container " << to_string(container) << " || " << to_string(expected_ingredient) << " vs ingredient " << ingredient_index << end(); - // if the current element is the ingredient we expect, move on to the next element/ingredient - if (types_coercible(expected_ingredient, ingredients.at(ingredient_index))) { - ++ingredient_index; - ++state.data.top().container_element_index; - while (state.data.top().container_element_index >= SIZE(get(Type, state.data.top().container.type->value).elements)) { - state.data.pop(); - if (state.data.empty()) { - if (ingredient_index < SIZE(ingredients)) - raise_error << maybe(caller.name) << "too many ingredients in '" << to_string(inst) << "'\n" << end(); - return; - } - ++state.data.top().container_element_index; - } - } - // if not, maybe it's a field of the current element - else { - // no change to ingredient_index - state.data.push(merge_check_point(expected_ingredient, 0)); - } - break; - } - case EXCLUSIVE_CONTAINER: { - assert(state.data.top().container_element_index == 0); - trace(9999, "transform") << "checking exclusive container " << to_string(container) << " vs ingredient " << ingredient_index << end(); - if (!is_literal(ingredients.at(ingredient_index))) { - raise_error << maybe(caller.name) << "ingredient " << ingredient_index << " of 'merge' should be a literal, for the tag of exclusive-container " << container_info.name << '\n' << end(); - return; - } - reagent ingredient = ingredients.at(ingredient_index); // unnecessary copy just to keep this function from modifying caller - populate_value(ingredient); - if (ingredient.value >= SIZE(container_info.elements)) { - raise_error << maybe(caller.name) << "invalid tag at " << ingredient_index << " for " << container_info.name << " in '" << to_string(inst) << '\n' << end(); - return; - } - reagent variant = variant_type(container, ingredient.value); - trace(9999, "transform") << "tag: " << ingredient.value << end(); - // replace union with its variant - state.data.pop(); - state.data.push(merge_check_point(variant, 0)); - ++ingredient_index; - break; - } - - // End valid_merge Cases - default: { - if (!types_coercible(container, ingredients.at(ingredient_index))) { - raise_error << maybe(caller.name) << "incorrect type of ingredient " << ingredient_index << " in '" << to_string(inst) << "'\n" << end(); - cerr << " expected " << debug_string(container) << '\n'; - cerr << " got " << debug_string(ingredients.at(ingredient_index)) << '\n'; - return; - } - ++ingredient_index; - // ++state.data.top().container_element_index; // unnecessary, but wouldn't do any harm - do { - state.data.pop(); - if (state.data.empty()) { - if (ingredient_index < SIZE(ingredients)) - raise_error << maybe(caller.name) << "too many ingredients in '" << to_string(inst) << "'\n" << end(); - return; - } - ++state.data.top().container_element_index; - } while (state.data.top().container_element_index >= SIZE(get(Type, state.data.top().container.type->value).elements)); - } - } - } - // never gets here - assert(false); -} - -void test_merge_check_product() { - Trace_file = "merge_check_product"; - Hide_errors = true; - run("recipe main [\n 1:number <- merge 3\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'merge' should yield a container in '1:number <- merge 3'"); -} - -void test_copy_indirect() { - Trace_file = "copy_indirect"; - run("recipe main [\n 1:address:number <- copy 2/unsafe\n 2:number <- copy 34\n # This loads location 1 as an address and looks up *that* location.\n 3:number <- copy 1:address:number/lookup\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 3"); -} -void test_store_indirect() { - Trace_file = "store_indirect"; - run("recipe main [\n 1:address:number <- copy 2/unsafe\n 1:address:number/lookup <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 2"); -} -void test_store_to_0_fails() { - Trace_file = "store_to_0_fails"; - Hide_errors = true; - run("recipe main [\n 1:address:number <- copy 0\n 1:address:number/lookup <- copy 34\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 0"); - run(""); - CHECK_TRACE_CONTENTS("error: can't write to location 0 in '1:address:number/lookup <- copy 34'"); -} -void canonize(reagent& x) { - if (is_literal(x)) return; - absolutize(x); - // End canonize(x) Special-cases - while (has_property(x, "lookup")) - lookup_memory(x); -} - -void lookup_memory(reagent& x) { - if (!x.type || x.type->value != get(Type_ordinal, "address")) { - raise_error << maybe(current_recipe_name()) << "tried to /lookup " << x.original_string << " but it isn't an address\n" << end(); - return; - } - // compute value - if (x.value == 0) { - raise_error << maybe(current_recipe_name()) << "tried to /lookup 0\n" << end(); - return; - } - trace(9999, "mem") << "location " << x.value << " is " << no_scientific(get_or_insert(Memory, x.value)) << end(); - x.set_value(get_or_insert(Memory, x.value)); - drop_from_type(x, "address"); - if (x.type->name == "shared") { - trace(9999, "mem") << "skipping refcount at " << x.value << end(); - x.set_value(x.value+1); // skip refcount - drop_from_type(x, "shared"); - } - // End Drop Address In lookup_memory(x) - drop_one_lookup(x); -} - -void test_canonize_non_pointer_fails_without_crashing() { - Trace_file = "canonize_non_pointer_fails_without_crashing"; - Hide_errors = true; - run("recipe foo [\n 1:address:number <- get-address *p, x:offset\n]\n# don't crash\n"); -} -bool canonize_type(reagent& r) { - while (has_property(r, "lookup")) { - if (!r.type || r.type->value != get(Type_ordinal, "address")) { - raise_error << "can't lookup non-address: " << to_string(r) << ": " << to_string(r.type) << '\n' << end(); - return false; - } - drop_from_type(r, "address"); - if (r.type->name == "shared") { - drop_from_type(r, "shared"); - } - - // End Drop Address In canonize_type(r) - drop_one_lookup(r); - } - return true; -} - -void drop_from_type(reagent& r, string expected_type) { - if (!r.type) { - raise_error << "can't drop " << expected_type << " from " << to_string(r) << '\n' << end(); - return; - } - if (r.type->name != expected_type) { - raise_error << "can't drop2 " << expected_type << " from " << to_string(r) << '\n' << end(); - return; - } - type_tree* tmp = r.type; - r.type = tmp->right; - tmp->right = NULL; - delete tmp; -} - -void drop_one_lookup(reagent& r) { - for (vector<pair<string, string_tree*> >::iterator p = r.properties.begin(); p != r.properties.end(); ++p) { - if (p->first == "lookup") { - r.properties.erase(p); - return; - } - } - assert(false); -} - -void test_get_indirect() { - Trace_file = "get_indirect"; - run("recipe main [\n 1:number <- copy 2\n 2:number <- copy 34\n 3:number <- copy 35\n 4:number <- get 1:address:point/lookup, 0:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 4"); -} -void test_get_indirect2() { - Trace_file = "get_indirect2"; - run("recipe main [\n 1:number <- copy 2\n 2:number <- copy 34\n 3:number <- copy 35\n 4:address:number <- copy 5/unsafe\n *4:address:number <- get 1:address:point/lookup, 0:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 5"); -} -void test_include_nonlookup_properties() { - Trace_file = "include_nonlookup_properties"; - run("recipe main [\n 1:number <- copy 2\n 2:number <- copy 34\n 3:number <- copy 35\n 4:number <- get 1:address:point/lookup/foo, 0:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 4"); -} -void test_get_address_indirect() { - Trace_file = "get_address_indirect"; - run("# 'get' can read from container address\nrecipe main [\n 1:number <- copy 2\n 2:number <- copy 34\n 3:number <- copy 35\n 4:address:number <- get-address 1:address:point/lookup, 0:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 4"); -} -void test_lookup_abbreviation() { - Trace_file = "lookup_abbreviation"; - run("recipe main [\n 1:address:number <- copy 2/unsafe\n 2:number <- copy 34\n 3:number <- copy *1:address:number\n]\n"); - CHECK_TRACE_CONTENTS("parse: ingredient: 1: (\"address\" \"number\"), {\"lookup\": ()}mem: storing 34 in location 3"); -} - -void test_create_array() { - Trace_file = "create_array"; - run("recipe main [\n # create an array occupying locations 1 (for the size) and 2-4 (for the elements)\n 1:array:number:3 <- create-array\n]\n"); - CHECK_TRACE_CONTENTS("run: creating array of size 4"); -} -void test_copy_array() { - Trace_file = "copy_array"; - run("# Arrays can be copied around with a single instruction just like numbers,\n# no matter how large they are.\nrecipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:array:number <- copy 1:array:number:3\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 5mem: storing 14 in location 6mem: storing 15 in location 7mem: storing 16 in location 8"); -} -void test_copy_array_indirect() { - Trace_file = "copy_array_indirect"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:address:array:number <- copy 1/unsafe\n 6:array:number <- copy *5:address:array:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 6mem: storing 14 in location 7mem: storing 15 in location 8mem: storing 16 in location 9"); -} -void test_stash_array() { - Trace_file = "stash_array"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n stash [foo:], 1:array:number:3\n]\n"); - CHECK_TRACE_CONTENTS("app: foo: 3 14 15 16"); -} -void test_container_contains_array() { - Trace_file = "container_contains_array"; - Hide_errors = true; - run("container foo [\n x:array:number:3\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_container_warns_on_dynamic_array_element() { - Trace_file = "container_warns_on_dynamic_array_element"; - Hide_errors = true; - run("container foo [\n x:array:number\n]\n"); - CHECK_TRACE_CONTENTS("error: container 'foo' cannot determine size of element x"); -} -void test_index() { - Trace_file = "index"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- index 1:array:number:3, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 14 in location 5"); -} -void test_index_direct_offset() { - Trace_file = "index_direct_offset"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- copy 0\n 6:number <- index 1:array:number, 5:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 14 in location 6"); -} -type_tree* array_element(const type_tree* type) { - return type->right; -} - -void test_index_indirect() { - Trace_file = "index_indirect"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:address:array:number <- copy 1/unsafe\n 6:number <- index *5:address:array:number, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 15 in location 6"); -} -void test_index_out_of_bounds() { - Trace_file = "index_out_of_bounds"; - Hide_errors = true; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- copy 14\n 6:number <- copy 15\n 7:number <- copy 16\n 8:address:array:point <- copy 1/unsafe\n index *8:address:array:point, 4 # less than size of array in locations, but larger than its length in elements\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid index 4"); -} -void test_index_out_of_bounds_2() { - Trace_file = "index_out_of_bounds_2"; - Hide_errors = true; - run("recipe main [\n 1:array:point:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- copy 14\n 6:number <- copy 15\n 7:number <- copy 16\n 8:address:array:point <- copy 1/unsafe\n index *8:address:array:point, -1\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid index -1"); -} -void test_index_product_type_mismatch() { - Trace_file = "index_product_type_mismatch"; - Hide_errors = true; - run("recipe main [\n 1:array:point:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- copy 14\n 6:number <- copy 15\n 7:number <- copy 16\n 8:address:array:point <- copy 1/unsafe\n 9:number <- index *8:address:array:point, 0\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'index' on *8:address:array:point can't be saved in 9:number; type should be point"); -} -void test_index_without_product() { - Trace_file = "index_without_product"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n index 1:array:number:3, 0\n]\n# just don't die\n"); -} -void test_index_address() { - Trace_file = "index_address"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:address:number <- index-address 1:array:number, 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 5"); -} -void test_index_address_out_of_bounds() { - Trace_file = "index_address_out_of_bounds"; - Hide_errors = true; - run("recipe main [\n 1:array:point:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- copy 14\n 6:number <- copy 15\n 7:number <- copy 16\n 8:address:array:point <- copy 1/unsafe\n index-address *8:address:array:point, 4 # less than size of array in locations, but larger than its length in elements\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid index 4"); -} -void test_index_address_out_of_bounds_2() { - Trace_file = "index_address_out_of_bounds_2"; - Hide_errors = true; - run("recipe main [\n 1:array:point:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- copy 14\n 6:number <- copy 15\n 7:number <- copy 16\n 8:address:array:point <- copy 1/unsafe\n index-address *8:address:array:point, -1\n]\n"); - CHECK_TRACE_CONTENTS("error: main: invalid index -1"); -} -void test_index_address_product_type_mismatch() { - Trace_file = "index_address_product_type_mismatch"; - Hide_errors = true; - run("recipe main [\n 1:array:point:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- copy 14\n 6:number <- copy 15\n 7:number <- copy 16\n 8:address:array:point <- copy 1/unsafe\n 9:address:number <- index-address *8:address:array:point, 0\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'index' on *8:address:array:point can't be saved in 9:address:number; type should be (address point)"); -} -void test_array_length() { - Trace_file = "array_length"; - run("recipe main [\n 1:array:number:3 <- create-array\n 2:number <- copy 14\n 3:number <- copy 15\n 4:number <- copy 16\n 5:number <- length 1:array:number:3\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 5"); -} -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, "shared") - && x.type->right->right - && x.type->right->right->value == get(Type_ordinal, "array") - && x.type->right->right->right - && x.type->right->right->right->value == get(Type_ordinal, "character") - && x.type->right->right->right->right == NULL; -} - - -void test_copy_exclusive_container() { - Trace_file = "copy_exclusive_container"; - run("# Copying exclusive containers copies all their contents and an extra location for the tag.\nrecipe main [\n 1:number <- copy 1 # 'point' variant\n 2:number <- copy 34\n 3:number <- copy 35\n 4:number-or-point <- copy 1:number-or-point/unsafe\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 4mem: storing 34 in location 5mem: storing 35 in location 6"); -} -void test_maybe_convert() { - Trace_file = "maybe_convert"; - run("recipe main [\n 12:number <- copy 1\n 13:number <- copy 35\n 14:number <- copy 36\n 20:address:point <- maybe-convert 12:number-or-point/unsafe, 1:variant\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 13 in location 20"); -} -void test_maybe_convert_fail() { - Trace_file = "maybe_convert_fail"; - run("recipe main [\n 12:number <- copy 1\n 13:number <- copy 35\n 14:number <- copy 36\n 20:address:number <- maybe-convert 12:number-or-point/unsafe, 0:variant\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 20"); -} -const reagent variant_type(const reagent& canonized_base, long long int tag) { - assert(tag >= 0); - assert(contains_key(Type, canonized_base.type->value)); - assert(!get(Type, canonized_base.type->value).name.empty()); - const type_info& info = get(Type, canonized_base.type->value); - assert(info.kind == EXCLUSIVE_CONTAINER); - reagent element = info.elements.at(tag); - if (contains_type_ingredient(element)) { - if (!canonized_base.type->right) - raise_error << "illegal type '" << to_string(canonized_base.type) << "' seems to be missing a type ingredient or three\n" << end(); - replace_type_ingredients(element.type, canonized_base.type->right, info); - } - - // End variant_type Special-cases - return element; -} - -void test_maybe_convert_product_type_mismatch() { - Trace_file = "maybe_convert_product_type_mismatch"; - Hide_errors = true; - run("recipe main [\n 12:number <- copy 1\n 13:number <- copy 35\n 14:number <- copy 36\n 20:address:number <- maybe-convert 12:number-or-point/unsafe, 1:variant\n]\n"); - CHECK_TRACE_CONTENTS("error: main: 'maybe-convert 12:number-or-point/unsafe, 1:variant' should write to (address point) but 20 has type (address number)"); -} -void test_exclusive_container() { - Trace_file = "exclusive_container"; - run("exclusive-container foo [\n x:number\n y:number\n]\n"); - CHECK_TRACE_CONTENTS("parse: --- defining exclusive-container fooparse: element: x: \"number\"parse: element: y: \"number\""); -} -void test_exclusive_container_contains_array() { - Trace_file = "exclusive_container_contains_array"; - Hide_errors = true; - run("exclusive-container foo [\n x:array:number:3\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_exclusive_container_warns_on_dynamic_array_element() { - Trace_file = "exclusive_container_warns_on_dynamic_array_element"; - Hide_errors = true; - run("exclusive-container foo [\n x:array:number\n]\n"); - CHECK_TRACE_CONTENTS("error: container 'foo' cannot determine size of element x"); -} -void test_lift_to_exclusive_container() { - Trace_file = "lift_to_exclusive_container"; - run("exclusive-container foo [\n x:number\n y:number\n]\nrecipe main [\n 1:number <- copy 34\n 2:foo <- merge 0/x, 1:number # tag must be a literal when merging exclusive containers\n 4:foo <- merge 1/y, 1:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 2mem: storing 34 in location 3mem: storing 1 in location 4mem: storing 34 in location 5"); -} -void test_merge_handles_exclusive_container() { - Trace_file = "merge_handles_exclusive_container"; - Hide_errors = true; - run("exclusive-container foo [\n x:number\n y:bar\n]\ncontainer bar [\n z:number\n]\nrecipe main [\n 1:foo <- merge 0/x, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 34 in location 2"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_requires_literal_tag_for_exclusive_container() { - Trace_file = "merge_requires_literal_tag_for_exclusive_container"; - Hide_errors = true; - run("exclusive-container foo [\n x:number\n y:bar\n]\ncontainer bar [\n z:number\n]\nrecipe main [\n local-scope\n 1:number <- copy 0\n 2:foo <- merge 1:number, 34\n]\n"); - CHECK_TRACE_CONTENTS("error: main: ingredient 0 of 'merge' should be a literal, for the tag of exclusive-container foo"); -} -void test_merge_check_container_containing_exclusive_container() { - Trace_file = "merge_check_container_containing_exclusive_container"; - Hide_errors = true; - run("container foo [\n x:number\n y:bar\n]\nexclusive-container bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo <- merge 23, 1/y, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 23 in location 1mem: storing 1 in location 2mem: storing 34 in location 3"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_container_containing_exclusive_container_2() { - Trace_file = "merge_check_container_containing_exclusive_container_2"; - Hide_errors = true; - run("container foo [\n x:number\n y:bar\n]\nexclusive-container bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo <- merge 23, 1/y, 34, 35\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too many ingredients in '1:foo <- merge 23, 1/y, 34, 35'"); -} -void test_merge_check_exclusive_container_containing_container() { - Trace_file = "merge_check_exclusive_container_containing_container"; - Hide_errors = true; - run("exclusive-container foo [\n x:number\n y:bar\n]\ncontainer bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo <- merge 1/y, 23, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1mem: storing 23 in location 2mem: storing 34 in location 3"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_exclusive_container_containing_container_2() { - Trace_file = "merge_check_exclusive_container_containing_container_2"; - Hide_errors = true; - run("exclusive-container foo [\n x:number\n y:bar\n]\ncontainer bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo <- merge 0/x, 23\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_exclusive_container_containing_container_3() { - Trace_file = "merge_check_exclusive_container_containing_container_3"; - Hide_errors = true; - run("exclusive-container foo [\n x:number\n y:bar\n]\ncontainer bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo <- merge 1/y, 23\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:foo <- merge 1/y, 23'"); -} -void test_merge_exclusive_container_with_mismatched_sizes() { - Trace_file = "merge_exclusive_container_with_mismatched_sizes"; - run("container foo [\n x:number\n y:number\n]\nexclusive-container bar [\n x:number\n y:foo\n]\nrecipe main [\n 1:number <- copy 34\n 2:number <- copy 35\n 3:bar <- merge 0/x, 1:number\n 6:bar <- merge 1/foo, 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3mem: storing 34 in location 4mem: storing 1 in location 6mem: storing 34 in location 7mem: storing 35 in location 8"); -} - -void test_calling_recipe() { - Trace_file = "calling_recipe"; - run("recipe main [\n f\n]\nrecipe f [\n 3:number <- add 2, 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 3"); -} -void test_return_on_fallthrough() { - Trace_file = "return_on_fallthrough"; - run("recipe main [\n f\n 1:number <- copy 0\n 2:number <- copy 0\n 3:number <- copy 0\n]\nrecipe f [\n 4:number <- copy 0\n 5:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("run: frun: 4:number <- copy 0run: 5:number <- copy 0run: 1:number <- copy 0run: 2:number <- copy 0run: 3:number <- copy 0"); -} -routine::routine(recipe_ordinal r) { - if (Trace_stream) { - ++Trace_stream->callstack_depth; - trace(9999, "trace") << "new routine; incrementing callstack depth to " << Trace_stream->callstack_depth << end(); - assert(Trace_stream->callstack_depth < 9000); // 9998-101 plus cushion - } - calls.push_front(call(r)); - alloc = Memory_allocated_until; - Memory_allocated_until += Initial_memory_per_routine; - alloc_max = Memory_allocated_until; - trace(9999, "new") << "routine allocated memory from " << alloc << " to " << alloc_max << end(); - - global_space = 0; - state = RUNNING; - - id = Next_routine_id; - Next_routine_id++; - - parent_index = -1; - - limit = -1; /* no limit */ - - waiting_on_location = old_value_of_waiting_location = 0; - - - waiting_on_routine = 0; - - // End routine Constructor -} - -inline call& current_call() { - return Current_routine->calls.front(); -} - - -inline const instruction& to_instruction(const call& call) { - return get(Recipe, call.running_recipe).steps.at(call.running_step_index); -} - -void finish_call_housekeeping(const instruction& call_instruction, const vector<vector<double> >& ingredients) { - for (long long int i = 0; i < SIZE(ingredients); ++i) { - current_call().ingredient_atoms.push_back(ingredients.at(i)); - reagent ingredient = call_instruction.ingredients.at(i); - canonize_type(ingredient); - current_call().ingredients.push_back(ingredient); - } - - // End Call Housekeeping -} - -void test_calling_undefined_recipe_fails() { - Trace_file = "calling_undefined_recipe_fails"; - Hide_errors = true; - run("recipe main [\n foo\n]\n"); - CHECK_TRACE_CONTENTS("error: main: undefined operation in 'foo '"); -} -void test_calling_undefined_recipe_handles_missing_result() { - Trace_file = "calling_undefined_recipe_handles_missing_result"; - Hide_errors = true; - run("recipe main [\n x:number <- foo\n]\n"); - CHECK_TRACE_CONTENTS("error: main: undefined operation in 'x:number <- foo '"); -} - -void test_next_ingredient() { - Trace_file = "next_ingredient"; - run("recipe main [\n f 2\n]\nrecipe f [\n 12:number <- next-ingredient\n 13:number <- add 1, 12:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 13"); -} -void test_next_ingredient_missing() { - Trace_file = "next_ingredient_missing"; - run("recipe main [\n f\n]\nrecipe f [\n _, 12:number <- next-ingredient\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 12"); -} -void test_next_ingredient_fail_on_missing() { - Trace_file = "next_ingredient_fail_on_missing"; - Hide_errors = true; - run("recipe main [\n f\n]\nrecipe f [\n 11:number <- next-ingredient\n]\n"); - CHECK_TRACE_CONTENTS("error: f: no ingredient to save in 11:number"); -} -void test_rewind_ingredients() { - Trace_file = "rewind_ingredients"; - run("recipe main [\n f 2\n]\nrecipe f [\n 12:number <- next-ingredient # consume ingredient\n _, 1:boolean <- next-ingredient # will not find any ingredients\n rewind-ingredients\n 13:number, 2:boolean <- next-ingredient # will find ingredient again\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 12mem: storing 0 in location 1mem: storing 2 in location 13mem: storing 1 in location 2"); -} -void test_ingredient() { - Trace_file = "ingredient"; - run("recipe main [\n f 1, 2\n]\nrecipe f [\n 12:number <- ingredient 1 # consume second ingredient first\n 13:number, 1:boolean <- next-ingredient # next-ingredient tries to scan past that\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 12mem: storing 0 in location 1"); -} - -void test_reply() { - Trace_file = "reply"; - run("recipe main [\n 1:number, 2:number <- f 34\n]\nrecipe f [\n 12:number <- next-ingredient\n 13:number <- add 1, 12:number\n reply 12:number, 13:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1mem: storing 35 in location 2"); -} -void test_reply_container() { - Trace_file = "reply_container"; - run("recipe main [\n 3:point <- f 2\n]\nrecipe f [\n 12:number <- next-ingredient\n 13:number <- copy 35\n reply 12:point/raw\n]\n"); - CHECK_TRACE_CONTENTS("run: result 0 is [2, 35]mem: storing 2 in location 3mem: storing 35 in location 4"); -} -void check_types_of_reply_instructions(recipe_ordinal r) { - const recipe& caller = get(Recipe, r); - trace(9991, "transform") << "--- check types of reply instructions in recipe " << caller.name << end(); - for (long long int i = 0; i < SIZE(caller.steps); ++i) { - const instruction& caller_instruction = caller.steps.at(i); - if (caller_instruction.is_label) continue; - if (caller_instruction.products.empty()) continue; - if (caller_instruction.operation < MAX_PRIMITIVE_RECIPES) continue; - const recipe& callee = get(Recipe, caller_instruction.operation); - for (long long int i = 0; i < SIZE(callee.steps); ++i) { - const instruction& reply_inst = callee.steps.at(i); - if (reply_inst.operation != REPLY) continue; - // check types with the caller - if (SIZE(caller_instruction.products) > SIZE(reply_inst.ingredients)) { - raise_error << maybe(caller.name) << "too few values replied from " << callee.name << '\n' << end(); - break; - } - for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) { - if (!types_coercible(caller_instruction.products.at(i), reply_inst.ingredients.at(i))) { - raise_error << maybe(callee.name) << "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 << to_string(lhs.type) << " vs " << to_string(rhs.type) << '\n' << end(); - goto finish_reply_check; - } - } - // check that any reply ingredients with /same-as-ingredient connect up - // the corresponding ingredient and product in the caller. - for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) { - if (has_property(reply_inst.ingredients.at(i), "same-as-ingredient")) { - string_tree* tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient"); - if (!tmp || tmp->right) { - raise_error << maybe(caller.name) << "'same-as-ingredient' metadata should take exactly one value in " << to_string(reply_inst) << '\n' << end(); - goto finish_reply_check; - } - long long int ingredient_index = to_integer(tmp->value); - if (ingredient_index >= SIZE(caller_instruction.ingredients)) { - raise_error << maybe(caller.name) << "too few ingredients in '" << to_string(caller_instruction) << "'\n" << end(); - goto finish_reply_check; - } - if (!is_dummy(caller_instruction.products.at(i)) && !is_literal(caller_instruction.ingredients.at(ingredient_index)) && caller_instruction.products.at(i).name != caller_instruction.ingredients.at(ingredient_index).name) { - raise_error << maybe(caller.name) << "'" << to_string(caller_instruction) << "' should write to " << caller_instruction.ingredients.at(ingredient_index).original_string << " rather than " << caller_instruction.products.at(i).original_string << '\n' << end(); - } - } - } - finish_reply_check:; - } - } -} - -void test_reply_type_mismatch() { - Trace_file = "reply_type_mismatch"; - Hide_errors = true; - run("recipe main [\n 3:number <- f 2\n]\nrecipe f [\n 12:number <- next-ingredient\n 13:number <- copy 35\n 14:point <- copy 12:point/raw\n reply 14:point\n]\n"); - CHECK_TRACE_CONTENTS("error: f: reply ingredient 14:point can't be saved in 3:number"); -} -void test_reply_same_as_ingredient() { - Trace_file = "reply_same_as_ingredient"; - Hide_errors = true; - run("recipe main [\n 1:number <- copy 0\n 2:number <- test1 1:number # call with different ingredient and product\n]\nrecipe test1 [\n 10:number <- next-ingredient\n reply 10:number/same-as-ingredient:0\n]\n"); - CHECK_TRACE_CONTENTS("error: main: '2:number <- test1 1:number' should write to 1:number rather than 2:number"); -} -void test_reply_same_as_ingredient_dummy() { - Trace_file = "reply_same_as_ingredient_dummy"; - run("# % Hide_errors = true;\nrecipe main [\n 1:number <- copy 0\n _ <- test1 1:number # call with different ingredient and product\n]\nrecipe test1 [\n 10:number <- next-ingredient\n reply 10:number/same-as-ingredient:0\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -string to_string(const vector<double>& in) { - if (in.empty()) return "[]"; - ostringstream out; - if (SIZE(in) == 1) { - out << no_scientific(in.at(0)); - return out.str(); - } - out << "["; - for (long long int i = 0; i < SIZE(in); ++i) { - if (i > 0) out << ", "; - out << no_scientific(in.at(i)); - } - out << "]"; - return out.str(); -} - - -void test_reply_if() { - Trace_file = "reply_if"; - run("recipe main [\n 1:number <- test1\n]\nrecipe test1 [\n reply-if 0, 34\n reply 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 1"); -} -void test_reply_if_2() { - Trace_file = "reply_if_2"; - run("recipe main [\n 1:number <- test1\n]\nrecipe test1 [\n reply-if 1, 34\n reply 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1"); -} - - -void test_new() { - Trace_file = "new"; - run("# call new two times with identical arguments; you should get back different results\nrecipe main [\n 1:address:shared:number/raw <- new number:type\n 2:address:shared:number/raw <- new number:type\n 3:boolean/raw <- equal 1:address:shared:number/raw, 2:address:shared:number/raw\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} -bool product_of_new_is_valid(const instruction& inst) { - reagent product = inst.products.at(0); - canonize_type(product); - if (!product.type || product.type->value != get(Type_ordinal, "address")) return false; - drop_from_type(product, "address"); - if (!product.type || product.type->value != get(Type_ordinal, "shared")) return false; - drop_from_type(product, "shared"); - if (SIZE(inst.ingredients) > 1) { - // array allocation - if (!product.type || product.type->value != get(Type_ordinal, "array")) return false; - drop_from_type(product, "array"); - } - reagent expected_product("x:"+inst.ingredients.at(0).name); - { - string_tree* tmp_type_names = parse_string_tree(expected_product.type->name); - delete expected_product.type; - expected_product.type = new_type_tree(tmp_type_names); - delete tmp_type_names; - } - // End Post-processing(expected_product) When Checking 'new' - return types_strictly_match(product, expected_product); -} - -void transform_new_to_allocate(const recipe_ordinal r) { - trace(9991, "transform") << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << end(); - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - instruction& inst = get(Recipe, r).steps.at(i); - if (inst.name == "new" && is_literal_string(inst.ingredients.at(0))) continue; - // Convert 'new' To 'allocate' - if (inst.name == "new") { - inst.operation = ALLOCATE; - string_tree* type_name = new string_tree(inst.ingredients.at(0).name); - type_name = parse_string_tree(type_name); - - // End Post-processing(type_name) When Converting 'new' - type_tree* type = new_type_tree(type_name); - inst.ingredients.at(0).set_value(size_of(type)); - trace(9992, "new") << "size of " << to_string(type_name) << " is " << inst.ingredients.at(0).value << end(); - delete type; - delete type_name; - } - } -} - - -void ensure_space(long long int size) { - if (size > Initial_memory_per_routine) { - tb_shutdown(); - cerr << "can't allocate " << size << " locations, that's too much compared to " << Initial_memory_per_routine << ".\n"; - exit(0); - } - if (Current_routine->alloc + size > Current_routine->alloc_max) { - // waste the remaining space and create a new chunk - Current_routine->alloc = Memory_allocated_until; - Memory_allocated_until += Initial_memory_per_routine; - Current_routine->alloc_max = Memory_allocated_until; - trace(9999, "new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max << end(); - } -} - -void test_new_initializes() { - Trace_file = "new_initializes"; - Memory_allocated_until = 10; - put(Memory, Memory_allocated_until, 1); - run("recipe main [\n 1:address:shared:number <- new number:type\n 2:number <- copy *1:address:shared:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 2"); -} -void test_new_error() { - Trace_file = "new_error"; - Hide_errors = true; - run("recipe main [\n 1:address:number/raw <- new number:type\n]\n"); - CHECK_TRACE_CONTENTS("error: main: product of 'new' has incorrect type: 1:address:number/raw <- new number:type"); -} -void test_new_array() { - Trace_file = "new_array"; - run("recipe main [\n 1:address:shared:array:number/raw <- new number:type, 5\n 2:address:shared:number/raw <- new number:type\n 3:number/raw <- subtract 2:address:shared:number/raw, 1:address:shared:array:number/raw\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:address:shared:array:number/raw <- new number:type, 5mem: array size is 5mem: storing 7 in location 3"); -} -void test_new_empty_array() { - Trace_file = "new_empty_array"; - run("recipe main [\n 1:address:shared:array:number/raw <- new number:type, 0\n 2:address:shared:number/raw <- new number:type\n 3:number/raw <- subtract 2:address:shared:number/raw, 1:address:shared:array:number/raw\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:address:shared:array:number/raw <- new number:type, 0mem: array size is 0mem: storing 2 in location 3"); -} -void test_new_overflow() { - Trace_file = "new_overflow"; - Initial_memory_per_routine = 3; // barely enough room for point allocation below - run("recipe main [\n 1:address:shared:number/raw <- new number:type\n 2:address:shared:point/raw <- new point:type # not enough room in initial page\n]\n"); - CHECK_TRACE_CONTENTS("new: routine allocated memory from 1000 to 1003new: routine allocated memory from 1003 to 1006"); -} -void test_new_reclaim() { - Trace_file = "new_reclaim"; - run("recipe main [\n 1:address:shared:number <- new number:type\n 2:address:shared:number <- copy 1:address:shared:number # because 1 will get reset during abandon below\n abandon 1:address:shared:number # unsafe\n 3:address:shared:number <- new number:type # must be same size as abandoned memory to reuse\n 4:boolean <- equal 2:address:shared:number, 3:address:shared:number\n]\n# both allocations should have returned the same address\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 4"); -} -void abandon(long long int address, long long int size) { - trace(9999, "abandon") << "saving in free-list of size " << size << end(); -//? Total_free += size; -//? Num_free++; -//? cerr << "abandon: " << size << '\n'; - // clear memory - for (long long int curr = address; curr < address+size; ++curr) - put(Memory, curr, 0); - // append existing free list to address - put(Memory, address, get_or_insert(Free_list, size)); - put(Free_list, size, address); -} - -void test_new_differing_size_no_reclaim() { - Trace_file = "new_differing_size_no_reclaim"; - run("recipe main [\n 1:address:shared:number <- new number:type\n 2:address:shared:number <- copy 1:address:shared:number\n abandon 1:address:shared:number\n 3:address:shared:array:number <- new number:type, 2 # different size\n 4:boolean <- equal 2:address:shared:number, 3:address:shared:array:number\n]\n# no reuse\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 4"); -} -void test_new_reclaim_array() { - Trace_file = "new_reclaim_array"; - run("recipe main [\n 1:address:shared:array:number <- new number:type, 2\n 2:address:shared:array:number <- copy 1:address:shared:array:number\n abandon 1:address:shared:array:number # unsafe\n 3:address:shared:array:number <- new number:type, 2\n 4:boolean <- equal 2:address:shared:array:number, 3:address:shared:array:number\n]\n# reuse\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 4"); -} -void test_reset_on_abandon() { - Trace_file = "reset_on_abandon"; - run("recipe main [\n 1:address:shared:number <- new number:type\n abandon 1:address:shared:number\n]\n# reuse\n"); - CHECK_TRACE_CONTENTS("run: abandon 1:address:shared:numbermem: resetting location 1"); -} -void test_refcounts() { - Trace_file = "refcounts"; - run("recipe main [\n 1:address:shared:number <- copy 1000/unsafe\n 2:address:shared:number <- copy 1:address:shared:number\n 1:address:shared:number <- copy 0\n 2:address:shared:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- copy 1000/unsafemem: incrementing refcount of 1000: 0 -> 1run: 2:address:shared:number <- copy 1:address:shared:numbermem: incrementing refcount of 1000: 1 -> 2run: 1:address:shared:number <- copy 0mem: decrementing refcount of 1000: 2 -> 1run: 2:address:shared:number <- copy 0mem: decrementing refcount of 1000: 1 -> 0mem: automatically abandoning 1000"); -} -void test_refcounts_2() { - Trace_file = "refcounts_2"; - run("recipe main [\n 1:address:shared:number <- new number:type\n # over-writing one allocation with another\n 1:address:shared:number <- new number:type\n 1:address:shared:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 1:address:shared:number <- new number:typemem: automatically abandoning 1000"); -} -void test_refcounts_3() { - Trace_file = "refcounts_3"; - run("recipe main [\n 1:address:shared:number <- new number:type\n # passing in addresses to recipes increments refcount\n foo 1:address:shared:number\n 1:address:shared:number <- copy 0\n]\nrecipe foo [\n 2:address:shared:number <- next-ingredient\n # return does NOT yet decrement refcount; memory must be explicitly managed\n 2:address:shared:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 2:address:shared:number <- next-ingredientmem: incrementing refcount of 1000: 1 -> 2run: 2:address:shared:number <- copy 0mem: decrementing refcount of 1000: 2 -> 1run: 1:address:shared:number <- copy 0mem: decrementing refcount of 1000: 1 -> 0mem: automatically abandoning 1000"); -} -void test_refcounts_4() { - Trace_file = "refcounts_4"; - run("recipe main [\n 1:address:shared:number <- new number:type\n # idempotent copies leave refcount unchanged\n 1:address:shared:number <- copy 1:address:shared:number\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 1:address:shared:number <- copy 1:address:shared:numbermem: decrementing refcount of 1000: 1 -> 0mem: incrementing refcount of 1000: 0 -> 1"); -} -void test_refcounts_5() { - Trace_file = "refcounts_5"; - run("recipe main [\n 1:address:shared:number <- new number:type\n # passing in addresses to recipes increments refcount\n foo 1:address:shared:number\n # return does NOT yet decrement refcount; memory must be explicitly managed\n 1:address:shared:number <- new number:type\n]\nrecipe foo [\n 2:address:shared:number <- next-ingredient\n]\n"); - CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 2:address:shared:number <- next-ingredientmem: incrementing refcount of 1000: 1 -> 2run: 1:address:shared:number <- new number:typemem: decrementing refcount of 1000: 2 -> 1"); -} -void test_new_string() { - Trace_file = "new_string"; - run("recipe main [\n 1:address:shared:array:character <- new [abc def]\n 2:character <- index *1:address:shared:array:character, 5\n]\n# number code for 'e'\n"); - CHECK_TRACE_CONTENTS("mem: storing 101 in location 2"); -} -void test_new_string_handles_unicode() { - Trace_file = "new_string_handles_unicode"; - run("recipe main [\n 1:address:shared:array:character <- new [a«c]\n 2:number <- length *1:address:shared:array:character\n 3:character <- index *1:address:shared:array:character, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 2mem: storing 171 in location 3"); -} -long long int new_mu_string(const string& contents) { - // allocate an array just large enough for it - long long int string_length = unicode_length(contents); -//? Total_alloc += string_length+1; -//? Num_alloc++; - ensure_space(string_length+1); // don't forget the extra location for array size - // initialize string - long long int result = Current_routine->alloc; - // initialize refcount - put(Memory, Current_routine->alloc++, 0); - // store length - put(Memory, Current_routine->alloc++, string_length); - long long int curr = 0; - const char* raw_contents = contents.c_str(); - for (long long int i = 0; i < string_length; ++i) { - uint32_t curr_character; - assert(curr < SIZE(contents)); - tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]); - put(Memory, Current_routine->alloc, curr_character); - curr += tb_utf8_char_length(raw_contents[curr]); - ++Current_routine->alloc; - } - // mu strings are not null-terminated in memory - return result; -} - - -void test_stash_string() { - Trace_file = "stash_string"; - run("recipe main [\n 1:address:shared:array:character <- new [abc]\n stash [foo:], 1:address:shared:array:character\n]\n"); - CHECK_TRACE_CONTENTS("app: foo: abc"); -} -void test_unicode_string() { - Trace_file = "unicode_string"; - run("recipe main [\n 1:address:shared:array:character <- new [♠]\n stash [foo:], 1:address:shared:array:character\n]\n"); - CHECK_TRACE_CONTENTS("app: foo: ♠"); -} -void test_stash_space_after_string() { - Trace_file = "stash_space_after_string"; - run("recipe main [\n 1:address:shared:array:character <- new [abc]\n stash 1:address:shared:array:character, [foo]\n]\n"); - CHECK_TRACE_CONTENTS("app: abc foo"); -} -void test_new_string_overflow() { - Trace_file = "new_string_overflow"; - Initial_memory_per_routine = 2; - run("recipe main [\n 1:address:shared:number/raw <- new number:type\n 2:address:shared:array:character/raw <- new [a] # not enough room in initial page, if you take the array size into account\n]\n"); - CHECK_TRACE_CONTENTS("new: routine allocated memory from 1000 to 1002new: routine allocated memory from 1002 to 1004"); -} -long long int unicode_length(const string& s) { - const char* in = s.c_str(); - long long int result = 0; - long long int curr = 0; - while (curr < SIZE(s)) { // carefully bounds-check on the string - // before accessing its raw pointer - ++result; - curr += tb_utf8_char_length(in[curr]); - } - return result; -} - -string read_mu_string(long long int address) { - if (address == 0) return ""; - address++; // skip refcount - long long int size = get_or_insert(Memory, address); - if (size == 0) return ""; - ostringstream tmp; - for (long long int curr = address+1; curr <= address+size; ++curr) { - tmp << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr))); - } - return tmp.str(); -} - -bool is_mu_type_literal(reagent r) { - return is_literal(r) && r.type && r.type->name == "type"; -} - -bool is_shared_address_of_array_of_numbers(reagent product) { - canonize_type(product); - if (!product.type || product.type->value != get(Type_ordinal, "address")) return false; - drop_from_type(product, "address"); - if (!product.type || product.type->value != get(Type_ordinal, "shared")) return false; - drop_from_type(product, "shared"); - if (!product.type || product.type->value != get(Type_ordinal, "array")) return false; - drop_from_type(product, "array"); - if (!product.type || product.type->value != get(Type_ordinal, "number")) return false; - return true; -} - -void test_brace_conversion() { - Trace_file = "brace_conversion"; - transform("recipe main [\n {\n break\n 1:number <- copy 0\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: jump 1:offsettransform: copy ..."); -} -void transform_braces(const recipe_ordinal r) { - const int OPEN = 0, CLOSE = 1; - // use signed integer for step index because we'll be doing arithmetic on it - list<pair<int/*OPEN/CLOSE*/, /*step*/long long int> > braces; - trace(9991, "transform") << "--- transform braces for recipe " << get(Recipe, r).name << end(); -//? cerr << "--- transform braces for recipe " << get(Recipe, r).name << '\n'; - for (long long int index = 0; index < SIZE(get(Recipe, r).steps); ++index) { - const instruction& inst = get(Recipe, r).steps.at(index); - if (inst.label == "{") { - trace(9993, "transform") << maybe(get(Recipe, r).name) << "push (open, " << index << ")" << end(); - braces.push_back(pair<int,long long int>(OPEN, index)); - } - if (inst.label == "}") { - trace(9993, "transform") << "push (close, " << index << ")" << end(); - braces.push_back(pair<int,long long int>(CLOSE, index)); - } - } - stack</*step*/long long int> open_braces; - for (long long int index = 0; index < SIZE(get(Recipe, r).steps); ++index) { - instruction& inst = get(Recipe, r).steps.at(index); - if (inst.label == "{") { - open_braces.push(index); - continue; - } - if (inst.label == "}") { - if (open_braces.empty()) { - raise << "missing '{' in " << get(Recipe, r).name << '\n'; - return; - } - open_braces.pop(); - continue; - } - if (inst.is_label) continue; - if (inst.old_name != "loop" - && inst.old_name != "loop-if" - && inst.old_name != "loop-unless" - && inst.old_name != "break" - && inst.old_name != "break-if" - && inst.old_name != "break-unless") { - trace(9992, "transform") << inst.old_name << " ..." << end(); - continue; - } - // check for errors - if (inst.old_name.find("-if") != string::npos || inst.old_name.find("-unless") != string::npos) { - if (inst.ingredients.empty()) { - raise_error << inst.old_name << " expects 1 or 2 ingredients, but got none\n" << end(); - continue; - } - } - // update instruction operation - if (inst.old_name.find("-if") != string::npos) { - inst.name = "jump-if"; - inst.operation = JUMP_IF; - } - else if (inst.old_name.find("-unless") != string::npos) { - inst.name = "jump-unless"; - inst.operation = JUMP_UNLESS; - } - else { - inst.name = "jump"; - inst.operation = JUMP; - } - // check for explicitly provided targets - if (inst.old_name.find("-if") != string::npos || inst.old_name.find("-unless") != string::npos) { - // conditional branches check arg 1 - if (SIZE(inst.ingredients) > 1 && is_literal(inst.ingredients.at(1))) { - trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(1).name << ":offset" << end(); - continue; - } - } - else { - // unconditional branches check arg 0 - if (!inst.ingredients.empty() && is_literal(inst.ingredients.at(0))) { - trace(9992, "transform") << "jump " << inst.ingredients.at(0).name << ":offset" << end(); - continue; - } - } - // if implicit, compute target - reagent target; - target.type = new type_tree("offset", get(Type_ordinal, "offset")); - target.set_value(0); - if (open_braces.empty()) - raise_error << inst.old_name << " needs a '{' before\n" << end(); - else if (inst.old_name.find("loop") != string::npos) - target.set_value(open_braces.top()-index); - else // break instruction - target.set_value(matching_brace(open_braces.top(), braces, r) - index - 1); - inst.ingredients.push_back(target); - // log computed target - if (inst.name == "jump") - trace(9992, "transform") << "jump " << no_scientific(target.value) << ":offset" << end(); - else - trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(0).name << ", " << no_scientific(target.value) << ":offset" << end(); - } -} - -// returns a signed integer not just so that we can return -1 but also to -// enable future signed arithmetic -long long int matching_brace(long long int index, const list<pair<int, long long int> >& braces, recipe_ordinal r) { - int stacksize = 0; - for (list<pair<int, long long int> >::const_iterator p = braces.begin(); p != braces.end(); ++p) { - if (p->second < index) continue; - stacksize += (p->first ? 1 : -1); - if (stacksize == 0) return p->second; - } - raise_error << maybe(get(Recipe, r).name) << "unbalanced '{'\n" << end(); - return SIZE(get(Recipe, r).steps); // exit current routine -} - -void test_loop() { - Trace_file = "loop"; - transform("recipe main [\n 1:number <- copy 0\n 2:number <- copy 0\n {\n 3:number <- copy 0\n loop\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: copy ...transform: jump -2:offset"); -} -void test_break_empty_block() { - Trace_file = "break_empty_block"; - transform("recipe main [\n 1:number <- copy 0\n {\n break\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: jump 0:offset"); -} -void test_break_cascading() { - Trace_file = "break_cascading"; - transform("recipe main [\n 1:number <- copy 0\n {\n break\n }\n {\n break\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: jump 0:offsettransform: jump 0:offset"); -} -void test_break_cascading_2() { - Trace_file = "break_cascading_2"; - transform("recipe main [\n 1:number <- copy 0\n 2:number <- copy 0\n {\n break\n 3:number <- copy 0\n }\n {\n break\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump 1:offsettransform: copy ...transform: jump 0:offset"); -} -void test_break_if() { - Trace_file = "break_if"; - transform("recipe main [\n 1:number <- copy 0\n 2:number <- copy 0\n {\n break-if 2:number\n 3:number <- copy 0\n }\n {\n break\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump-if 2, 1:offsettransform: copy ...transform: jump 0:offset"); -} -void test_break_nested() { - Trace_file = "break_nested"; - transform("recipe main [\n 1:number <- copy 0\n {\n 2:number <- copy 0\n break\n {\n 3:number <- copy 0\n }\n 4:number <- copy 0\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: jump 4:offset"); -} -void test_break_nested_degenerate() { - Trace_file = "break_nested_degenerate"; - transform("recipe main [\n 1:number <- copy 0\n {\n 2:number <- copy 0\n break\n {\n }\n 4:number <- copy 0\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: jump 3:offset"); -} -void test_break_nested_degenerate_2() { - Trace_file = "break_nested_degenerate_2"; - transform("recipe main [\n 1:number <- copy 0\n {\n 2:number <- copy 0\n break\n {\n }\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: jump 2:offset"); -} -void test_break_label() { - Trace_file = "break_label"; - Hide_errors = true; - transform("recipe main [\n 1:number <- copy 0\n {\n break +foo:offset\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: jump +foo:offset"); -} -void test_break_unless() { - Trace_file = "break_unless"; - transform("recipe main [\n 1:number <- copy 0\n 2:number <- copy 0\n {\n break-unless 2:number\n 3:number <- copy 0\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump-unless 2, 1:offsettransform: copy ..."); -} -void test_loop_unless() { - Trace_file = "loop_unless"; - transform("recipe main [\n 1:number <- copy 0\n 2:number <- copy 0\n {\n loop-unless 2:number\n 3:number <- copy 0\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump-unless 2, -1:offsettransform: copy ..."); -} -void test_loop_nested() { - Trace_file = "loop_nested"; - transform("recipe main [\n 1:number <- copy 0\n {\n 2:number <- copy 0\n {\n 3:number <- copy 0\n }\n loop-if 4:boolean\n 5:number <- copy 0\n }\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: jump-if 4, -5:offset"); -} -void test_loop_label() { - Trace_file = "loop_label"; - transform("recipe main [\n 1:number <- copy 0\n +foo\n 2:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ..."); -} -void test_brace_conversion_and_run() { - Trace_file = "brace_conversion_and_run"; - run("recipe test-factorial [\n 1:number <- copy 5\n 2:number <- copy 1\n {\n 3:boolean <- equal 1:number, 1\n break-if 3:boolean\n# $print 1:number\n 2:number <- multiply 2:number, 1:number\n 1:number <- subtract 1:number, 1\n loop\n }\n 4:number <- copy 2:number # trigger a read\n]\n"); - CHECK_TRACE_CONTENTS("mem: location 2 is 120"); -} -void test_break_outside_braces_fails() { - Trace_file = "break_outside_braces_fails"; - Hide_errors = true; - run("recipe main [\n break\n]\n"); - CHECK_TRACE_CONTENTS("error: break needs a '{' before"); -} -void test_break_conditional_without_ingredient_fails() { - Trace_file = "break_conditional_without_ingredient_fails"; - Hide_errors = true; - run("recipe main [\n {\n break-if\n }\n]\n"); - CHECK_TRACE_CONTENTS("error: break-if expects 1 or 2 ingredients, but got none"); -} - -void test_jump_to_label() { - Trace_file = "jump_to_label"; - run("recipe main [\n jump +target:label\n 1:number <- copy 0\n +target\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1"); -} -void transform_labels(const recipe_ordinal r) { - map<string, long long int> offset; - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - const instruction& inst = get(Recipe, r).steps.at(i); - if (!inst.label.empty() && inst.label.at(0) == '+') { - if (!contains_key(offset, inst.label)) { - put(offset, inst.label, i); - } - else { - raise_error << maybe(get(Recipe, r).name) << "duplicate label '" << inst.label << "'" << end(); - // have all jumps skip some random but noticeable and deterministic amount of code - put(offset, inst.label, 9999); - } - } - } - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - instruction& inst = get(Recipe, r).steps.at(i); - if (inst.name == "jump") { - replace_offset(inst.ingredients.at(0), offset, i, r); - } - if (inst.name == "jump-if" || inst.name == "jump-unless") { - replace_offset(inst.ingredients.at(1), offset, i, r); - } - if ((inst.name == "loop" || inst.name == "break") - && SIZE(inst.ingredients) == 1) { - replace_offset(inst.ingredients.at(0), offset, i, r); - } - if ((inst.name == "loop-if" || inst.name == "loop-unless" - || inst.name == "break-if" || inst.name == "break-unless") - && SIZE(inst.ingredients) == 2) { - replace_offset(inst.ingredients.at(1), offset, i, r); - } - } -} - -void replace_offset(reagent& x, /*const*/ map<string, long long int>& offset, const long long int current_offset, const recipe_ordinal r) { - if (!is_literal(x)) { - raise_error << maybe(get(Recipe, r).name) << "jump target must be offset or label but is " << x.original_string << '\n' << end(); - x.set_value(0); // no jump by default - return; - } - if (x.initialized) return; - if (is_integer(x.name)) return; // non-labels will be handled like other number operands - if (!is_jump_target(x.name)) { - raise_error << maybe(get(Recipe, r).name) << "can't jump to label " << x.name << '\n' << end(); - x.set_value(0); // no jump by default - return; - } - if (!contains_key(offset, x.name)) { - raise_error << maybe(get(Recipe, r).name) << "can't find label " << x.name << '\n' << end(); - x.set_value(0); // no jump by default - return; - } - x.set_value(get(offset, x.name) - current_offset); -} - -bool is_jump_target(string label) { - return label.at(0) == '+'; -} - -void test_break_to_label() { - Trace_file = "break_to_label"; - run("recipe main [\n {\n {\n break +target:label\n 1:number <- copy 0\n }\n }\n +target\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1"); -} -void test_jump_if_to_label() { - Trace_file = "jump_if_to_label"; - run("recipe main [\n {\n {\n jump-if 1, +target:label\n 1:number <- copy 0\n }\n }\n +target\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1"); -} -void test_loop_unless_to_label() { - Trace_file = "loop_unless_to_label"; - run("recipe main [\n {\n {\n loop-unless 0, +target:label # loop/break with a label don't care about braces\n 1:number <- copy 0\n }\n }\n +target\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1"); -} -void test_jump_runs_code_after_label() { - Trace_file = "jump_runs_code_after_label"; - run("recipe main [\n # first a few lines of padding to exercise the offset computation\n 1:number <- copy 0\n 2:number <- copy 0\n 3:number <- copy 0\n jump +target:label\n 4:number <- copy 0\n +target\n 5:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 5"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 4"); -} -void test_recipe_fails_on_duplicate_jump_target() { - Trace_file = "recipe_fails_on_duplicate_jump_target"; - Hide_errors = true; - run("recipe main [\n +label\n 1:number <- copy 0\n +label\n 2:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("error: main: duplicate label '+label'"); -} -void test_jump_ignores_nontarget_label() { - Trace_file = "jump_ignores_nontarget_label"; - Hide_errors = true; - run("recipe main [\n # first a few lines of padding to exercise the offset computation\n 1:number <- copy 0\n 2:number <- copy 0\n 3:number <- copy 0\n jump $target:label\n 4:number <- copy 0\n $target\n 5:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("error: main: can't jump to label $target"); -} - -void test_transform_names() { - Trace_file = "transform_names"; - run("recipe main [\n x:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("name: assign x 1mem: storing 0 in location 1"); -} -void test_transform_names_fails_on_use_before_define() { - Trace_file = "transform_names_fails_on_use_before_define"; - Hide_errors = true; - transform("recipe main [\n x:number <- copy y:number\n]\n"); - CHECK_TRACE_CONTENTS("error: main: use before set: y"); -} -void transform_names(const recipe_ordinal r) { - 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(caller.steps); ++i) { - instruction& inst = caller.steps.at(i); - // replace element names of containers with offsets - if (inst.name == "get" || inst.name == "get-address") { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "exactly 2 ingredients expected in '" << to_string(inst) << "'\n" << end(); - break; - } - if (!is_literal(inst.ingredients.at(1))) - raise_error << maybe(get(Recipe, r).name) << "expected ingredient 1 of " << (inst.name == "get" ? "'get'" : "'get-address'") << " to have type 'offset'; got " << inst.ingredients.at(1).original_string << '\n' << end(); - 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); - 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(); - } - } - } - - // convert variant names of exclusive containers - if (inst.name == "maybe-convert") { - if (SIZE(inst.ingredients) != 2) { - raise_error << maybe(get(Recipe, r).name) << "exactly 2 ingredients expected in '" << to_string(inst) << "'\n" << end(); - break; - } - assert(is_literal(inst.ingredients.at(1))); - 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); - 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(); - } - } - } - - // End transform_names(inst) Special-cases - // map names to addresses - for (long long int in = 0; in < SIZE(inst.ingredients); ++in) { - if (is_disqualified(inst.ingredients.at(in), inst, caller.name)) continue; - if (is_numeric_location(inst.ingredients.at(in))) numeric_locations_used = true; - if (is_named_location(inst.ingredients.at(in))) names_used = true; - if (is_integer(inst.ingredients.at(in).name)) continue; - if (!already_transformed(inst.ingredients.at(in), names)) { - 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_disqualified(inst.products.at(out), inst, caller.name)) continue; - if (is_numeric_location(inst.products.at(out))) numeric_locations_used = true; - if (is_named_location(inst.products.at(out))) names_used = true; - if (is_integer(inst.products.at(out).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; - curr_idx += size_of(inst.products.at(out)); - } - inst.products.at(out).set_value(lookup_name(inst.products.at(out), r)); - } - } - if (names_used && numeric_locations_used) - raise_error << maybe(caller.name) << "mixing variable names and numeric addresses\n" << end(); -} - -bool is_disqualified(/*mutable*/ reagent& x, const instruction& inst, const string& recipe_name) { - if (!x.type) { - if (!x.type && contains_key(Recipe_ordinal, x.name)) { - x.type = new type_tree("recipe-literal", get(Type_ordinal, "recipe-literal")); - x.set_value(get(Recipe_ordinal, x.name)); - return true; - } - - // End Null-type is_disqualified Exceptions - raise_error << maybe(recipe_name) << "missing type for " << x.original_string << " in '" << to_string(inst) << "'\n" << end(); - return true; - } - if (is_raw(x)) return true; - if (is_literal(x)) return true; - if (x.name == "default-space") - x.initialized = true; - if (x.name == "number-of-locals") - x.initialized = true; - if (x.name == "global-space") - x.initialized = true; - // End is_disqualified Cases - if (x.initialized) return true; - return false; -} - -bool already_transformed(const reagent& r, const map<string, long long int>& names) { - if (has_property(r, "space")) { - string_tree* p = property(r, "space"); - if (!p || p->right) { - raise_error << "/space property should have exactly one (non-negative integer) value in " << r.original_string << '\n' << end(); - return false; - } - if (p->value != "0") return true; - } - return contains_key(names, r.name); -} - - -long long int lookup_name(const reagent& x, const recipe_ordinal default_recipe) { - if (!has_property(x, "space")) { - if (Name[default_recipe].empty()) raise_error << "name not found: " << x.name << '\n' << end(); - return Name[default_recipe][x.name]; - } - string_tree* p = property(x, "space"); - if (!p || p->right) raise_error << "/space property should have exactly one (non-negative integer) value\n" << end(); - long long int n = to_integer(p->value); - assert(n >= 0); - recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n); - set<recipe_ordinal> done; - vector<recipe_ordinal> path; - return lookup_name(x, surrounding_recipe, done, path); -} - -// If the recipe we need to lookup this name in doesn't have names done yet, -// recursively call transform_names on it. -long long int lookup_name(const reagent& x, const recipe_ordinal r, set<recipe_ordinal>& done, vector<recipe_ordinal>& path) { - if (!Name[r].empty()) return Name[r][x.name]; - if (contains_key(done, r)) { - raise_error << "can't compute address of " << to_string(x) << " because " << end(); - for (long long int i = 1; i < SIZE(path); ++i) { - raise_error << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end(); - } - raise_error << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end(); - return 0; - } - done.insert(r); - path.push_back(r); - transform_names(r); // Not passing 'done' through. Might this somehow cause an infinite loop? - assert(!Name[r].empty()); - return Name[r][x.name]; -} - -recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, long long int n) { - if (n == 0) return r; - if (!contains_key(Surrounding_space, r)) { - raise_error << "don't know surrounding recipe of " << get(Recipe, r).name << '\n' << end(); - return 0; - } - assert(contains_key(Surrounding_space, r)); - return lookup_surrounding_recipe(get(Surrounding_space, r), n-1); -} - - -type_ordinal skip_addresses(type_tree* type, const string& recipe_name) { - type_ordinal address = get(Type_ordinal, "address"); - type_ordinal shared = get(Type_ordinal, "shared"); - for (; type; type = type->right) { - if (type->value != address && type->value != shared) - return type->value; - } - raise_error << maybe(recipe_name) << "expected a container" << '\n' << end(); - return -1; -} - -int find_element_name(const type_ordinal t, const string& name, const string& recipe_name) { - const type_info& container = get(Type, t); - for (long long int i = 0; i < SIZE(container.elements); ++i) - if (container.elements.at(i).name == name) return i; - raise_error << maybe(recipe_name) << "unknown element " << name << " in container " << get(Type, t).name << '\n' << end(); - return -1; -} - -bool is_numeric_location(const reagent& x) { - if (is_global(x)) return false; - - - if (is_literal(x)) return false; - if (is_raw(x)) return false; - if (x.name == "0") return false; // used for chaining lexical scopes - return is_integer(x.name); -} - -bool is_named_location(const reagent& x) { - if (is_literal(x)) return false; - if (is_raw(x)) return false; - if (is_special_name(x.name)) return false; - return !is_integer(x.name); -} - -bool is_special_name(const string& s) { - if (s == "_") return true; - if (s == "0") return true; - if (s == "default-space") return true; - - if (s == "number-of-locals") return true; - - if (s == "global-space") return true; - - if (s == "screen") return true; - - if (s == "console") return true; - - // End is_special_name Cases - return false; -} - -void test_transform_names_passes_dummy() { - Trace_file = "transform_names_passes_dummy"; - transform("# _ is just a dummy result that never gets consumed\nrecipe main [\n _, x:number <- copy 0, 1\n]\n"); - CHECK_TRACE_CONTENTS("name: assign x 1"); - CHECK_TRACE_DOESNT_CONTAIN("name: assign _ 1"); -} -void test_transform_names_passes_raw() { - Trace_file = "transform_names_passes_raw"; - Hide_errors = true; - run("recipe main [\n x:number/raw <- copy 0\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("name: assign x 1"); - run(""); - CHECK_TRACE_CONTENTS("error: can't write to location 0 in 'x:number/raw <- copy 0'"); -} -void test_transform_names_fails_when_mixing_names_and_numeric_locations() { - Trace_file = "transform_names_fails_when_mixing_names_and_numeric_locations"; - Hide_errors = true; - transform("recipe main [\n x:number <- copy 1:number\n]\n"); - CHECK_TRACE_CONTENTS("error: main: mixing variable names and numeric addresses"); -} -void test_transform_names_fails_when_mixing_names_and_numeric_locations_2() { - Trace_file = "transform_names_fails_when_mixing_names_and_numeric_locations_2"; - Hide_errors = true; - transform("recipe main [\n x:number <- copy 1\n 1:number <- copy x:number\n]\n"); - CHECK_TRACE_CONTENTS("error: main: mixing variable names and numeric addresses"); -} -void test_transform_names_does_not_fail_when_mixing_names_and_raw_locations() { - Trace_file = "transform_names_does_not_fail_when_mixing_names_and_raw_locations"; - Hide_errors = true; - transform("recipe main [\n x:number <- copy 1:number/raw\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("error: main: mixing variable names and numeric addresses"); - CHECK_TRACE_COUNT("error", 0); -} -void test_transform_names_does_not_fail_when_mixing_names_and_literals() { - Trace_file = "transform_names_does_not_fail_when_mixing_names_and_literals"; - Hide_errors = true; - transform("recipe main [\n x:number <- copy 1\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("error: main: mixing variable names and numeric addresses"); - CHECK_TRACE_COUNT("error", 0); -} -void test_transform_names_transforms_container_elements() { - Trace_file = "transform_names_transforms_container_elements"; - transform("recipe main [\n p:address:point <- copy 0\n a:number <- get *p:address:point, y:offset\n b:number <- get *p:address:point, x:offset\n]\n"); - CHECK_TRACE_CONTENTS("name: element y of type point is at offset 1name: element x of type point is at offset 0"); -} -void test_transform_names_handles_containers() { - Trace_file = "transform_names_handles_containers"; - transform("recipe main [\n a:point <- copy 0/unsafe\n b:number <- copy 0/unsafe\n]\n"); - CHECK_TRACE_CONTENTS("name: assign a 1name: assign b 3"); -} -void test_transform_names_handles_exclusive_containers() { - Trace_file = "transform_names_handles_exclusive_containers"; - run("recipe main [\n 12:number <- copy 1\n 13:number <- copy 35\n 14:number <- copy 36\n 20:address:point <- maybe-convert 12:number-or-point/unsafe, p:variant\n]\n"); - CHECK_TRACE_CONTENTS("name: variant p of type number-or-point has tag 1mem: storing 13 in location 20"); -} - -void test_set_default_space() { - Trace_file = "set_default_space"; - run("# if default-space is 10, and if an array of 5 locals lies from location 12 to 16 (inclusive),\n# then local 0 is really location 12, local 1 is really location 13, and so on.\nrecipe main [\n # pretend shared:array:location; in practice we'll use new\n 10:number <- copy 0 # refcount\n 11:number <- copy 5 # length\n default-space:address:shared:array:location <- copy 10/unsafe\n 1:number <- copy 23\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 23 in location 13"); -} -void test_lookup_sidesteps_default_space() { - Trace_file = "lookup_sidesteps_default_space"; - run("recipe main [\n # pretend pointer from outside\n 3:number <- copy 34\n # pretend shared:array:location; in practice we'll use new\n 1000:number <- copy 0 # refcount\n 1001:number <- copy 5 # length\n # actual start of this recipe\n default-space:address:shared:array:location <- copy 1000/unsafe\n 1:address:number <- copy 3/unsafe\n 8:number/raw <- copy *1:address:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 8"); -} -void test_convert_names_passes_default_space() { - Trace_file = "convert_names_passes_default_space"; - Hide_errors = true; - run("recipe main [\n default-space:number, x:number <- copy 0, 1\n]\n"); - CHECK_TRACE_CONTENTS("name: assign x 1"); - CHECK_TRACE_DOESNT_CONTAIN("name: assign default-space 1"); -} -void absolutize(reagent& x) { - if (is_raw(x) || is_dummy(x)) return; - if (x.name == "default-space") return; - if (!x.initialized) { - raise_error << to_string(current_instruction()) << ": reagent not initialized: " << x.original_string << '\n' << end(); - } - x.set_value(address(x.value, space_base(x))); - x.properties.push_back(pair<string, string_tree*>("raw", NULL)); - assert(is_raw(x)); -} - -long long int space_base(const reagent& x) { - if (is_global(x)) { - if (!Current_routine->global_space) - raise_error << "routine has no global space\n" << end(); - return Current_routine->global_space + /*skip refcount*/1; - } - - - long long int base = current_call().default_space ? (current_call().default_space+/*skip refcount*/1) : 0; - return space_base(x, space_index(x), base); -} - -long long int space_base(const reagent& x, long long int space_index, long long int base) { -//? trace(9999, "space") << "space-base: " << space_index << " " << base << end(); - if (space_index == 0) { - return base; - } - long long int result = space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1))+/*skip refcount*/1; -//? trace(9999, "space") << "space-base: " << space_index << " " << base << " => " << result << end(); - return result; -} - -long long int space_index(const reagent& x) { - for (long long int i = 0; i < SIZE(x.properties); ++i) { - if (x.properties.at(i).first == "space") { - if (!x.properties.at(i).second || x.properties.at(i).second->right) - raise_error << maybe(current_recipe_name()) << "/space metadata should take exactly one value in " << x.original_string << '\n' << end(); - return to_integer(x.properties.at(i).second->value); - } - } - return 0; -} - - -long long int address(long long int offset, long long int base) { - if (base == 0) return offset; // raw - long long int size = get_or_insert(Memory, base); - if (offset >= size) { - // todo: test - raise_error << "location " << offset << " is out of bounds " << size << " at " << base << '\n' << end(); - return 0; - } - return base + /*skip length*/1 + offset; -} - - -void test_get_default_space() { - Trace_file = "get_default_space"; - run("recipe main [\n default-space:address:shared:array:location <- copy 10/unsafe\n 1:address:shared:array:location/raw <- copy default-space:address:shared:array:location\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 10 in location 1"); -} -void test_lookup_sidesteps_default_space_in_get() { - Trace_file = "lookup_sidesteps_default_space_in_get"; - run("recipe main [\n # pretend pointer to container from outside\n 12:number <- copy 34\n 13:number <- copy 35\n # pretend shared:array:location; in practice we'll use new\n 1000:number <- copy 0 # refcount\n 1001:number <- copy 5 # length\n # actual start of this recipe\n default-space:address:shared:array:location <- copy 1000/unsafe\n 1:address:point <- copy 12/unsafe\n 9:number/raw <- get *1:address:point, 1:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 9"); -} -void test_lookup_sidesteps_default_space_in_index() { - Trace_file = "lookup_sidesteps_default_space_in_index"; - run("recipe main [\n # pretend pointer to array from outside\n 12:number <- copy 2\n 13:number <- copy 34\n 14:number <- copy 35\n # pretend shared:array:location; in practice we'll use new\n 1000:number <- copy 0 # refcount\n 1001:number <- copy 5 # length\n # actual start of this recipe\n default-space:address:shared:array:location <- copy 1000/unsafe\n 1:address:array:number <- copy 12/unsafe\n 9:number/raw <- index *1:address:array:number, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 9"); -} -void test_new_default_space() { - Trace_file = "new_default_space"; - run("recipe main [\n new-default-space\n x:number <- copy 0\n y:number <- copy 3\n]\n# allocate space for x and y, as well as the chaining slot at 0\n"); - CHECK_TRACE_CONTENTS("mem: array size is 3"); -} -void test_local_scope() { - Trace_file = "local_scope"; - run("recipe main [\n 1:address <- foo\n 2:address <- foo\n 3:boolean <- equal 1:address, 2:address\n]\nrecipe foo [\n local-scope\n x:number <- copy 34\n reply default-space:address:shared:array:location\n]\n# both calls to foo should have received the same default-space\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void try_reclaim_locals() { - // only reclaim routines starting with 'local-scope' - const recipe_ordinal r = get(Recipe_ordinal, current_recipe_name()); - if (get(Recipe, r).steps.empty()) return; - const instruction& inst = get(Recipe, r).steps.at(0); - if (inst.old_name != "local-scope") return; - abandon(current_call().default_space, - /*refcount*/1 + /*array length*/1 + /*number-of-locals*/Name[r][""]); -} - -void rewrite_default_space_instruction(instruction& curr) { - if (!curr.ingredients.empty()) - raise_error << to_string(curr) << " can't take any ingredients\n" << end(); - curr.name = "new"; - curr.ingredients.push_back(reagent("location:type")); - curr.ingredients.push_back(reagent("number-of-locals:literal")); - if (!curr.products.empty()) - raise_error << "new-default-space can't take any results\n" << end(); - curr.products.push_back(reagent("default-space:address:shared:array:location")); -} - - -void check_default_space(const recipe_ordinal r) { - if (!Warn_on_missing_default_space) return; // skip previous core tests; this is only for mu code - const recipe& caller = get(Recipe, r); - // skip scenarios (later layer) - // user code should never create recipes with underscores in their names - if (caller.name.find("scenario_") == 0) return; // skip mu scenarios which will use raw memory locations - if (caller.name.find("run_") == 0) return; // skip calls to 'run', which should be in scenarios and will also use raw memory locations - // assume recipes with only numeric addresses know what they're doing (usually tests) - if (!contains_non_special_name(r)) return; - trace(9991, "transform") << "--- check that recipe " << caller.name << " sets default-space" << end(); - if (caller.steps.empty()) return; - if (caller.steps.at(0).products.empty() - || caller.steps.at(0).products.at(0).name != "default-space") { - raise << maybe(caller.name) << " does not seem to start with default-space or local-scope\n" << end(); -//? cerr << maybe(caller.name) << " does not seem to start with default-space or local-scope\n" << '\n'; - } -} -bool contains_non_special_name(const recipe_ordinal r) { - for (map<string, long long int>::iterator p = Name[r].begin(); p != Name[r].end(); ++p) { - if (p->first.empty()) continue; - if (p->first.find("stash_") == 0) continue; // generated by rewrite_stashes_to_text (cross-layer) - if (!is_special_name(p->first)) { -//? cerr << " " << get(Recipe, r).name << ": " << p->first << '\n'; - return true; - } - } - return false; -} - - -void test_surrounding_space() { - Trace_file = "surrounding_space"; - run("# location 1 in space 1 refers to the space surrounding the default space, here 20.\nrecipe main [\n # pretend shared:array:location; in practice we'll use new\n 10:number <- copy 0 # refcount\n 11:number <- copy 5 # length\n # pretend shared:array:location; in practice we'll use new\n 20:number <- copy 0 # refcount\n 21:number <- copy 5 # length\n # actual start of this recipe\n default-space:address:shared:array:location <- copy 10/unsafe\n 0:address:shared:array:location/names:dummy <- copy 20/unsafe # later layers will explain the /names: property\n 1:number <- copy 32\n 1:number/space:1 <- copy 33\n]\nrecipe dummy [ # just for the /names: property above\n]\n# chain space: 10 + /*skip refcount*/1 + /*skip length*/1\n"); - CHECK_TRACE_CONTENTS("mem: storing 20 in location 12mem: storing 32 in location 13mem: storing 33 in location 23"); -} -void test_permit_space_as_variable_name() { - Trace_file = "permit_space_as_variable_name"; - run("recipe main [\n space:number <- copy 0\n]\n"); -} - -void test_closure() { - Trace_file = "closure"; - run("recipe main [\n default-space:address:shared:array:location <- new location:type, 30\n 1:address:shared:array:location/names:new-counter <- new-counter\n 2:number/raw <- increment-counter 1:address:shared:array:location/names:new-counter\n 3:number/raw <- increment-counter 1:address:shared:array:location/names:new-counter\n]\nrecipe new-counter [\n default-space:address:shared:array:location <- new location:type, 30\n x:number <- copy 23\n y:number <- copy 3 # variable that will be incremented\n reply default-space:address:shared:array:location\n]\nrecipe increment-counter [\n default-space:address:shared:array:location <- new location:type, 30\n 0:address:shared:array:location/names:new-counter <- next-ingredient # outer space must be created by 'new-counter' above\n y:number/space:1 <- add y:number/space:1, 1 # increment\n y:number <- copy 234 # dummy\n reply y:number/space:1\n]\n"); - CHECK_TRACE_CONTENTS("name: lexically surrounding space for recipe increment-counter comes from new-countermem: storing 5 in location 3"); -} -void collect_surrounding_spaces(const recipe_ordinal r) { - trace(9991, "transform") << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << end(); -//? cerr << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << '\n'; - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - const instruction& inst = get(Recipe, r).steps.at(i); - if (inst.is_label) continue; - for (long long int j = 0; j < SIZE(inst.products); ++j) { - if (is_literal(inst.products.at(j))) continue; - if (inst.products.at(j).name != "0") continue; - type_tree* type = inst.products.at(j).type; - if (!type - || type->value != get(Type_ordinal, "address") - || !type->right - || type->right->value != get(Type_ordinal, "shared") - || !type->right->right - || type->right->right->value != get(Type_ordinal, "array") - || !type->right->right->right - || type->right->right->right->value != get(Type_ordinal, "location") - || type->right->right->right->right) { - raise_error << "slot 0 should always have type address:shared:array:location, but is " << to_string(inst.products.at(j)) << '\n' << end(); - continue; - } - string_tree* s = property(inst.products.at(j), "names"); - if (!s) { - raise_error << "slot 0 requires a /names property in recipe " << get(Recipe, r).name << end(); - continue; - } - if (s->right) raise_error << "slot 0 should have a single value in /names, but got " << to_string(inst.products.at(j)) << '\n' << end(); - const string& surrounding_recipe_name = s->value; - if (surrounding_recipe_name.empty()) { - raise_error << "slot 0 doesn't initialize its /names property in recipe " << get(Recipe, r).name << end(); - continue; - } - if (contains_key(Surrounding_space, r) - && get(Surrounding_space, r) != get(Recipe_ordinal, surrounding_recipe_name)) { - raise_error << "recipe " << get(Recipe, r).name << " can have only one 'surrounding' recipe but has " << get(Recipe, get(Surrounding_space, r)).name << " and " << surrounding_recipe_name << '\n' << end(); - continue; - } - trace(9993, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end(); -//? cerr << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << '\n'; - if (!contains_key(Recipe_ordinal, surrounding_recipe_name)) { - raise << "can't find recipe providing surrounding space for " << get(Recipe, r).name << ": " << surrounding_recipe_name << '\n' << end(); - continue; - } - put(Surrounding_space, r, get(Recipe_ordinal, surrounding_recipe_name)); - } - } -} - - -// So far we have local variables, and we can nest local variables of short -// lifetimes inside longer ones. Now let's support 'global' variables that -// last for the life of a routine. If we create multiple routines they won't -// have access to each other's globals. - -void test_global_space() { - Trace_file = "global_space"; - run("recipe main [\n # pretend shared:array:location; in practice we'll use new\n 10:number <- copy 0 # refcount\n 11:number <- copy 5 # length\n # pretend shared:array:location; in practice we'll use new\n 20:number <- copy 0 # refcount\n 21:number <- copy 5 # length\n # actual start of this recipe\n global-space:address:shared:array:location <- copy 20/unsafe\n default-space:address:shared:array:location <- copy 10/unsafe\n 1:number <- copy 23\n 1:number/space:global <- copy 24\n]\n# store to default space: 10 + /*skip refcount*/1 + /*skip length*/1 + /*index*/1\n"); - CHECK_TRACE_CONTENTS("mem: storing 23 in location 13mem: storing 24 in location 23"); -} -void test_global_space_with_names() { - Trace_file = "global_space_with_names"; - Hide_errors = true; - run("recipe main [\n global-space:address:shared:array:location <- new location:type, 10\n x:number <- copy 23\n 1:number/space:global <- copy 24\n]\n# don't complain about mixing numeric addresses and names\n"); - CHECK_TRACE_COUNT("error", 0); -} -bool is_global(const reagent& x) { - for (long long int i = 0; i < SIZE(x.properties); ++i) { - if (x.properties.at(i).first == "space") - return x.properties.at(i).second && x.properties.at(i).second->value == "global"; - } - return false; -} - - -void test_transform_fails_on_reusing_name_with_different_type() { - Trace_file = "transform_fails_on_reusing_name_with_different_type"; - Hide_errors = true; - run("recipe main [\n x:number <- copy 1\n x:boolean <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("error: main: x used with multiple types"); -} -void check_or_set_types_by_name(const recipe_ordinal r) { - trace(9991, "transform") << "--- deduce types for recipe " << get(Recipe, r).name << end(); - map<string, type_tree*> type; - 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 in = 0; in < SIZE(inst.ingredients); ++in) { - deduce_missing_type(type, inst.ingredients.at(in)); - check_type(type, inst.ingredients.at(in), r); - } - for (long long int out = 0; out < SIZE(inst.products); ++out) { - deduce_missing_type(type, inst.products.at(out)); - check_type(type, inst.products.at(out), r); - } - } -} - -void deduce_missing_type(map<string, type_tree*>& type, reagent& x) { - if (x.type) return; - if (!contains_key(type, x.name)) return; - x.type = new type_tree(*get(type, x.name)); - trace(9992, "transform") << x.name << " <= " << names_to_string(x.type) << end(); -} - -void check_type(map<string, type_tree*>& type, const reagent& x, const recipe_ordinal r) { - if (is_literal(x)) return; - if (is_integer(x.name)) return; // if you use raw locations you're probably doing something unsafe - if (!x.type) return; // might get filled in by other logic later - if (!contains_key(type, x.name)) { - trace(9992, "transform") << x.name << " => " << names_to_string(x.type) << end(); - put(type, x.name, x.type); - } - if (!types_strictly_match(get(type, x.name), x.type)) { - raise_error << maybe(get(Recipe, r).name) << x.name << " used with multiple types\n" << end(); - return; - } - if (get(type, x.name)->name == "array") { - if (!get(type, x.name)->right) { - raise_error << maybe(get(Recipe, r).name) << x.name << " can't be just an array. What is it an array of?\n" << end(); - return; - } - if (!get(type, x.name)->right->right) { - raise_error << get(Recipe, r).name << " can't determine the size of array variable " << x.name << ". Either allocate it separately and make the type of " << x.name << " address:shared:..., or specify the length of the array in the type of " << x.name << ".\n" << end(); - return; - } - } -} - -void test_transform_fills_in_missing_types() { - Trace_file = "transform_fills_in_missing_types"; - run("recipe main [\n x:number <- copy 1\n y:number <- add x, 1\n]\n"); -} -void test_transform_fills_in_missing_types_in_product() { - Trace_file = "transform_fills_in_missing_types_in_product"; - run("recipe main [\n x:number <- copy 1\n x <- copy 2\n]\n"); -} -void test_transform_fills_in_missing_types_in_product_and_ingredient() { - Trace_file = "transform_fills_in_missing_types_in_product_and_ingredient"; - run("recipe main [\n x:number <- copy 1\n x <- add x, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_transform_fails_on_missing_types_in_first_mention() { - Trace_file = "transform_fails_on_missing_types_in_first_mention"; - Hide_errors = true; - run("recipe main [\n x <- copy 1\n x:number <- copy 2\n]\n"); - CHECK_TRACE_CONTENTS("error: main: missing type for x in 'x <- copy 1'"); -} -void test_typo_in_address_type_fails() { - Trace_file = "typo_in_address_type_fails"; - Hide_errors = true; - run("recipe main [\n y:address:shared:charcter <- new character:type\n *y <- copy 67\n]\n"); - CHECK_TRACE_CONTENTS("error: main: unknown type charcter in 'y:address:shared:charcter <- new character:type'"); -} -void test_array_type_without_size_fails() { - Trace_file = "array_type_without_size_fails"; - Hide_errors = true; - run("recipe main [\n x:array:number <- merge 2, 12, 13\n]\n"); - CHECK_TRACE_CONTENTS("error: main can't determine the size of array variable x. Either allocate it separately and make the type of x address:shared:..., or specify the length of the array in the type of x."); -} - -scenario parse_scenario(istream& in) { - scenario result; - result.name = next_word(in); - if (contains_key(Scenario_names, result.name)) - raise_error << "duplicate scenario name: " << result.name << '\n' << end(); - Scenario_names.insert(result.name); - skip_whitespace_and_comments(in); - assert(in.peek() == '['); - // scenarios are take special 'code' strings so we need to ignore brackets - // inside comments - result.to_run = slurp_quoted(in); - // delete [] delimiters - assert(result.to_run.at(0) == '['); - result.to_run.erase(0, 1); - assert(result.to_run.at(SIZE(result.to_run)-1) == ']'); - result.to_run.erase(SIZE(result.to_run)-1); - return result; -} - -void test_warn_on_redefine_scenario() { - Trace_file = "warn_on_redefine_scenario"; - Hide_warnings = true; - Disable_redefine_warnings = true; - run("recipe scenario-foo [\n 1:number <- copy 34\n]\nrecipe scenario-foo [\n 1:number <- copy 35\n]\n"); - CHECK_TRACE_CONTENTS("warn: redefining recipe scenario-foo"); -} -void test_run() { - Trace_file = "run"; - run("recipe main [\n run [\n 1:number <- copy 13\n ]\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 13 in location 1"); -} -void bind_special_scenario_names(recipe_ordinal r) { - // Special Scenario Variable Names(r) - Name[r]["screen"] = SCREEN; - - Name[r]["console"] = CONSOLE; - - // End Special Scenario Variable Names(r) -} - -void test_run_multiple() { - Trace_file = "run_multiple"; - run("recipe main [\n run [\n 1:number <- copy 13\n ]\n run [\n 2:number <- copy 13\n ]\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 13 in location 1mem: storing 13 in location 2"); -} -void test_memory_check() { - Trace_file = "memory_check"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n memory-should-contain [\n 1 <- 13\n ]\n]\n"); - CHECK_TRACE_CONTENTS("run: checking location 1error: expected location 1 to contain 13 but saw 0"); -} -void check_memory(const string& s) { - istringstream in(s); - in >> std::noskipws; - set<long long int> locations_checked; - while (true) { - skip_whitespace_and_comments(in); - if (!has_data(in)) break; - string lhs = next_word(in); - if (!is_integer(lhs)) { - check_type(lhs, in); - continue; - } - long long int address = to_integer(lhs); - skip_whitespace_and_comments(in); - string _assign; in >> _assign; assert(_assign == "<-"); - skip_whitespace_and_comments(in); - double value = 0; in >> value; - if (contains_key(locations_checked, address)) - raise_error << "duplicate expectation for location " << address << '\n' << end(); - trace(9999, "run") << "checking location " << address << end(); - if (get_or_insert(Memory, address) != value) { - if (Current_scenario && !Scenario_testing_scenario) { - // genuine test in a mu file - raise_error << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end(); - } - else { - // just testing scenario support - raise_error << "expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end(); - } - if (!Scenario_testing_scenario) { - Passed = false; - ++Num_failures; - } - return; - } - locations_checked.insert(address); - } -} - -void check_type(const string& lhs, istream& in) { - reagent x(lhs); - if (x.type->name == "array" - && x.type->right && x.type->right->name == "character" - && !x.type->right->right) { - x.set_value(to_integer(x.name)); - skip_whitespace_and_comments(in); - string _assign = next_word(in); - assert(_assign == "<-"); - skip_whitespace_and_comments(in); - string literal = next_word(in); - long long int address = x.value; - // exclude quoting brackets - assert(*literal.begin() == '['); literal.erase(literal.begin()); - assert(*--literal.end() == ']'); literal.erase(--literal.end()); - check_string(address, literal); - return; - } - raise_error << "don't know how to check memory for " << lhs << '\n' << end(); -} - -void check_string(long long int address, const string& literal) { - trace(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)) << " (" << 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) { - Passed = false; - ++Num_failures; - } - return; - } - ++address; // now skip length - for (long long int i = 0; i < SIZE(literal); ++i) { - trace(9999, "run") << "checking location " << address+i << end(); - if (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)) << " ('" << static_cast<char>(get_or_insert(Memory, address+i)) << "')\n" << end(); - } - else { - // just testing scenario support - raise_error << "expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << '\n' << end(); - } - if (!Scenario_testing_scenario) { - Passed = false; - ++Num_failures; - } - return; - } - } -} - -void test_memory_check_multiple() { - Trace_file = "memory_check_multiple"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n memory-should-contain [\n 1 <- 0\n 1 <- 0\n ]\n]\n"); - CHECK_TRACE_CONTENTS("error: duplicate expectation for location 1"); -} -void test_memory_check_string_length() { - Trace_file = "memory_check_string_length"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n 1:number <- copy 3\n 2:number <- copy 97 # 'a'\n 3:number <- copy 98 # 'b'\n 4:number <- copy 99 # 'c'\n memory-should-contain [\n 1:array:character <- [ab]\n ]\n]\n"); - CHECK_TRACE_CONTENTS("error: expected location 1 to contain length 2 of string [ab] but saw 3"); -} -void test_memory_check_string() { - Trace_file = "memory_check_string"; - run("recipe main [\n 1:number <- copy 3\n 2:number <- copy 97 # 'a'\n 3:number <- copy 98 # 'b'\n 4:number <- copy 99 # 'c'\n memory-should-contain [\n 1:array:character <- [abc]\n ]\n]\n"); - CHECK_TRACE_CONTENTS("run: checking string length at 1run: checking location 2run: checking location 3run: checking location 4"); -} -// Like runs of contiguous '+' lines, order is important. The trace checks -// that the lines are present *and* in the specified sequence. (There can be -// other lines in between.) - -void test_trace_check_fails() { - Trace_file = "trace_check_fails"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n trace-should-contain [\n a: b\n a: d\n ]\n]\n"); - CHECK_TRACE_CONTENTS("error: missing [b] in trace with label a"); -} -// simplified version of check_trace_contents() that emits errors rather -// than just printing to stderr -void check_trace(const string& expected) { - Trace_stream->newline(); - vector<trace_line> expected_lines = parse_trace(expected); - 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; - } - - 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; -} - -vector<trace_line> parse_trace(const string& expected) { - vector<string> buf = split(expected, "\n"); - vector<trace_line> result; - for (long long int i = 0; i < SIZE(buf); ++i) { - buf.at(i) = trim(buf.at(i)); - if (buf.at(i).empty()) continue; - long long int delim = buf.at(i).find(": "); - result.push_back(trace_line(trim(buf.at(i).substr(0, delim)), trim(buf.at(i).substr(delim+2)))); - } - return result; -} - -void test_trace_check_fails_in_nonfirst_line() { - Trace_file = "trace_check_fails_in_nonfirst_line"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n run [\n trace 1, [a], [b]\n ]\n trace-should-contain [\n a: b\n a: d\n ]\n]\n"); - CHECK_TRACE_CONTENTS("error: missing [d] in trace with label a"); -} -void test_trace_check_passes_silently() { - Trace_file = "trace_check_passes_silently"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n run [\n trace 1, [a], [b]\n ]\n trace-should-contain [\n a: b\n ]\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("error: missing [b] in trace with label a"); - CHECK_TRACE_COUNT("error", 0); -} -void test_trace_negative_check_fails() { - Trace_file = "trace_negative_check_fails"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n run [\n trace 1, [a], [b]\n ]\n trace-should-not-contain [\n a: b\n ]\n]\n"); - CHECK_TRACE_CONTENTS("error: unexpected [b] in trace with label a"); -} -// simplified version of check_trace_contents() that emits errors rather -// than just printing to stderr -bool check_trace_missing(const string& in) { - Trace_stream->newline(); - vector<trace_line> lines = parse_trace(in); - for (long long int i = 0; i < SIZE(lines); ++i) { - if (trace_count(lines.at(i).label, lines.at(i).contents) != 0) { - raise_error << "unexpected [" << lines.at(i).contents << "] in trace with label " << lines.at(i).label << '\n' << end(); - Passed = false; - return false; - } - } - return true; -} - -void test_trace_negative_check_passes_silently() { - Trace_file = "trace_negative_check_passes_silently"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n trace-should-not-contain [\n a: b\n ]\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("error: unexpected [b] in trace with label a"); - CHECK_TRACE_COUNT("error", 0); -} -void test_trace_negative_check_fails_on_any_unexpected_line() { - Trace_file = "trace_negative_check_fails_on_any_unexpected_line"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n run [\n trace 1, [a], [d]\n ]\n trace-should-not-contain [\n a: b\n a: d\n ]\n]\n"); - CHECK_TRACE_CONTENTS("error: unexpected [d] in trace with label a"); -} -void test_trace_count_check() { - Trace_file = "trace_count_check"; - run("recipe main [\n run [\n trace 1, [a], [foo]\n ]\n check-trace-count-for-label 1, [a]\n]\n"); -} -void test_trace_count_check_2() { - Trace_file = "trace_count_check_2"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n run [\n trace 1, [a], [foo]\n ]\n check-trace-count-for-label 2, [a]\n]\n"); - CHECK_TRACE_CONTENTS("error: main: expected 2 lines in trace with label a in trace"); -} - -void test_tangle_before() { - Trace_file = "tangle_before"; - run("recipe main [\n 1:number <- copy 0\n <label1>\n 3:number <- copy 0\n]\nbefore <label1> [\n 2:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3"); - CHECK_TRACE_COUNT("mem", 3); -} -void insert_fragments(const recipe_ordinal r) { - bool made_progress = true; - long long int pass = 0; - while (made_progress) { - made_progress = false; - // create a new vector because insertions invalidate iterators - vector<instruction> result; - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - const instruction& inst = get(Recipe, r).steps.at(i); - if (!inst.is_label || !is_waypoint(inst.label) || inst.tangle_done) { - result.push_back(inst); - continue; - } - inst.tangle_done = true; - made_progress = true; - Fragments_used.insert(inst.label); - ostringstream prefix; - prefix << '+' << get(Recipe, r).name << '_' << pass << '_' << i; - // ok to use contains_key even though Before_fragments uses [], - // because appending an empty recipe is a noop - if (contains_key(Before_fragments, inst.label)) - append_fragment(result, Before_fragments[inst.label].steps, prefix.str()); - result.push_back(inst); - if (contains_key(After_fragments, inst.label)) - append_fragment(result, After_fragments[inst.label].steps, prefix.str()); - } - get(Recipe, r).steps.swap(result); - ++pass; - } -} - -void append_fragment(vector<instruction>& base, const vector<instruction>& patch, const string prefix) { - // append 'patch' to 'base' while keeping 'base' oblivious to any new jump - // targets in 'patch' oblivious to 'base' by prepending 'prefix' to them. - // we might tangle the same fragment at multiple points in a single recipe, - // and we need to avoid duplicate jump targets. - // so we'll keep jump targets local to the specific before/after fragment - // that introduces them. - set<string> jump_targets; - for (long long int i = 0; i < SIZE(patch); ++i) { - const instruction& inst = patch.at(i); - if (inst.is_label && is_jump_target(inst.label)) - jump_targets.insert(inst.label); - } - for (long long int i = 0; i < SIZE(patch); ++i) { - instruction inst = patch.at(i); - if (inst.is_label) { - if (contains_key(jump_targets, inst.label)) - inst.label = prefix+inst.label; - base.push_back(inst); - continue; - } - for (long long int j = 0; j < SIZE(inst.ingredients); ++j) { - reagent& x = inst.ingredients.at(j); - if (!is_literal(x)) continue; - if (x.type->name == "label" && contains_key(jump_targets, x.name)) - x.name = prefix+x.name; - } - base.push_back(inst); - } -} - -bool is_waypoint(string label) { - return *label.begin() == '<' && *label.rbegin() == '>'; -} - -void check_insert_fragments(unused recipe_ordinal) { - if (Transform_check_insert_fragments_Ran) return; - Transform_check_insert_fragments_Ran = true; - for (map<string, recipe>::iterator p = Before_fragments.begin(); p != Before_fragments.end(); ++p) { - if (!contains_key(Fragments_used, p->first)) - raise_error << "could not locate insert before " << p->first << '\n' << end(); - } - for (map<string, recipe>::iterator p = After_fragments.begin(); p != After_fragments.end(); ++p) { - if (!contains_key(Fragments_used, p->first)) - raise_error << "could not locate insert after " << p->first << '\n' << end(); - } -} - -void test_tangle_before_and_after() { - Trace_file = "tangle_before_and_after"; - run("recipe main [\n 1:number <- copy 0\n <label1>\n 4:number <- copy 0\n]\nbefore <label1> [\n 2:number <- copy 0\n]\nafter <label1> [\n 3:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3mem: storing 0 in location 4"); - CHECK_TRACE_COUNT("mem", 4); -} -void test_tangle_ignores_jump_target() { - Trace_file = "tangle_ignores_jump_target"; - Hide_errors = true; - run("recipe main [\n 1:number <- copy 0\n +label1\n 4:number <- copy 0\n]\nbefore +label1 [\n 2:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("error: can't tangle before label +label1"); -} -void test_tangle_keeps_labels_separate() { - Trace_file = "tangle_keeps_labels_separate"; - run("recipe main [\n 1:number <- copy 0\n <label1>\n <label2>\n 6:number <- copy 0\n]\nbefore <label1> [\n 2:number <- copy 0\n]\nafter <label1> [\n 3:number <- copy 0\n]\nbefore <label2> [\n 4:number <- copy 0\n]\nafter <label2> [\n 5:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3mem: storing 0 in location 4mem: storing 0 in location 5mem: storing 0 in location 6"); - CHECK_TRACE_COUNT("mem", 6); -} -void test_tangle_stacks_multiple_fragments() { - Trace_file = "tangle_stacks_multiple_fragments"; - run("recipe main [\n 1:number <- copy 0\n <label1>\n 6:number <- copy 0\n]\nbefore <label1> [\n 2:number <- copy 0\n]\nafter <label1> [\n 3:number <- copy 0\n]\nbefore <label1> [\n 4:number <- copy 0\n]\nafter <label1> [\n 5:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 4mem: storing 0 in location 5mem: storing 0 in location 3mem: storing 0 in location 6"); - CHECK_TRACE_COUNT("mem", 6); -} -void test_tangle_supports_fragments_with_multiple_instructions() { - Trace_file = "tangle_supports_fragments_with_multiple_instructions"; - run("recipe main [\n 1:number <- copy 0\n <label1>\n 6:number <- copy 0\n]\nbefore <label1> [\n 2:number <- copy 0\n 3:number <- copy 0\n]\nafter <label1> [\n 4:number <- copy 0\n 5:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3mem: storing 0 in location 4mem: storing 0 in location 5mem: storing 0 in location 6"); - CHECK_TRACE_COUNT("mem", 6); -} -void test_tangle_tangles_into_all_labels_with_same_name() { - Trace_file = "tangle_tangles_into_all_labels_with_same_name"; - run("recipe main [\n 1:number <- copy 10\n <label1>\n 4:number <- copy 10\n recipe2\n]\nrecipe recipe2 [\n 1:number <- copy 11\n <label1>\n 4:number <- copy 11\n]\nbefore <label1> [\n 2:number <- copy 12\n]\nafter <label1> [\n 3:number <- copy 12\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 10 in location 4mem: storing 11 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 11 in location 4"); - CHECK_TRACE_COUNT("mem", 8); -} -void test_tangle_tangles_into_all_labels_with_same_name_2() { - Trace_file = "tangle_tangles_into_all_labels_with_same_name_2"; - run("recipe main [\n 1:number <- copy 10\n <label1>\n <label1>\n 4:number <- copy 10\n]\nbefore <label1> [\n 2:number <- copy 12\n]\nafter <label1> [\n 3:number <- copy 12\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 10 in location 4"); - CHECK_TRACE_COUNT("mem", 6); -} -void test_tangle_tangles_into_all_labels_with_same_name_3() { - Trace_file = "tangle_tangles_into_all_labels_with_same_name_3"; - run("recipe main [\n 1:number <- copy 10\n <label1>\n <foo>\n 4:number <- copy 10\n]\nbefore <label1> [\n 2:number <- copy 12\n]\nafter <label1> [\n 3:number <- copy 12\n]\nafter <foo> [\n <label1>\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 10 in location 4"); - CHECK_TRACE_COUNT("mem", 6); -} -void test_tangle_handles_jump_target_inside_fragment() { - Trace_file = "tangle_handles_jump_target_inside_fragment"; - run("recipe main [\n 1:number <- copy 10\n <label1>\n 4:number <- copy 10\n]\nbefore <label1> [\n jump +label2:label\n 2:number <- copy 12\n +label2\n 3:number <- copy 12\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 3mem: storing 10 in location 4"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in label 2"); - CHECK_TRACE_COUNT("mem", 3); -} -void test_tangle_renames_jump_target() { - Trace_file = "tangle_renames_jump_target"; - run("recipe main [\n 1:number <- copy 10\n <label1>\n +label2\n 4:number <- copy 10\n]\nbefore <label1> [\n jump +label2:label\n 2:number <- copy 12\n +label2 # renamed\n 3:number <- copy 12\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 3mem: storing 10 in location 4"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in label 2"); - CHECK_TRACE_COUNT("mem", 3); -} -void test_tangle_jump_to_base_recipe() { - Trace_file = "tangle_jump_to_base_recipe"; - run("recipe main [\n 1:number <- copy 10\n <label1>\n +label2\n 4:number <- copy 10\n]\nbefore <label1> [\n jump +label2:label\n 2:number <- copy 12\n 3:number <- copy 12\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 10 in location 4"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in label 2"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in location 3"); - CHECK_TRACE_COUNT("mem", 2); -} - -void rewrite_stashes_to_text(recipe_ordinal r) { - recipe& caller = get(Recipe, r); - trace(9991, "transform") << "--- rewrite 'stash' instructions in recipe " << caller.name << end(); - if (contains_named_locations(caller)) - rewrite_stashes_to_text_named(caller); - // in recipes without named locations, 'stash' is still not extensible -} - -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:shared: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); -} - - -void test_dilated_reagent() { - Trace_file = "dilated_reagent"; - load("recipe main [\n {1: number, foo: bar} <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("parse: product: 1: \"number\", {\"foo\": \"bar\"}"); -} -void test_load_trailing_space_after_curly_bracket() { - Trace_file = "load_trailing_space_after_curly_bracket"; - load("recipe main [\n # line below has a space at the end\n { \n]\n# successfully parsed\n"); -} -void test_dilated_reagent_with_comment() { - Trace_file = "dilated_reagent_with_comment"; - Hide_errors = true; - run("recipe main [\n {1: number, foo: bar} <- copy 34 # test comment\n]\n"); - CHECK_TRACE_CONTENTS("parse: product: 1: \"number\", {\"foo\": \"bar\"}"); - CHECK_TRACE_COUNT("error", 0); -} -void test_dilated_reagent_with_comment_immediately_following() { - Trace_file = "dilated_reagent_with_comment_immediately_following"; - Hide_errors = true; - run("recipe main [\n 1:number <- copy {34: literal} # test comment\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -// A curly is considered a label if it's the last thing on a line. Dilated -// reagents should remain all on one line. -bool start_of_dilated_reagent(istream& in) { - if (in.peek() != '{') return false; - long long int pos = in.tellg(); - in.get(); // slurp '{' - skip_whitespace_but_not_newline(in); - char next = in.peek(); - in.seekg(pos); - return next != '\n'; -} - -// Assume the first letter is an open bracket, and read everything until the -// matching close bracket. -// We balance {} () and []. And we skip one character after '\'. -string slurp_balanced_bracket(istream& in) { - ostringstream result; - char c; - list<char> open_brackets; - while (in >> c) { - if (c == '\\') { - // always silently skip the next character - result << c; - if (!(in >> c)) break; - result << c; - continue; - } - if (c == '(') open_brackets.push_back(c); - if (c == ')') { - assert(open_brackets.back() == '('); - open_brackets.pop_back(); - } - if (c == '[') open_brackets.push_back(c); - if (c == ']') { - assert(open_brackets.back() == '['); - open_brackets.pop_back(); - } - if (c == '{') open_brackets.push_back(c); - if (c == '}') { - assert(open_brackets.back() == '{'); - open_brackets.pop_back(); - } - result << c; - if (open_brackets.empty()) break; - } - skip_whitespace_and_comments_but_not_newline(in); - return result.str(); -} - -string slurp_key(istream& in) { - string result = next_word(in); - while (!result.empty() && *result.rbegin() == ':') - strip_last(result); - while (isspace(in.peek()) || in.peek() == ':') - in.get(); - return result; -} - -// So far instructions can only contain linear lists of properties. Now we add -// support for more complex trees of properties in dilated reagents. This will -// come in handy later for expressing complex types, like "a dictionary from -// (address to array of charaters) to (list of numbers)". - -void test_dilated_reagent_with_nested_brackets() { - Trace_file = "dilated_reagent_with_nested_brackets"; - run("recipe main [\n {1: number, foo: (bar (baz quux))} <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("parse: product: 1: \"number\", {\"foo\": (\"bar\" (\"baz\" \"quux\"))}"); -} -string_tree* parse_string_tree(string_tree* s) { - assert(!s->left && !s->right); - if (s->value.at(0) != '(') return s; - string_tree* result = parse_string_tree(s->value); - delete s; - return result; -} - -string_tree* parse_string_tree(const string& s) { - istringstream in(s); - in >> std::noskipws; - return parse_string_tree(in); -} - -string_tree* parse_string_tree(istream& in) { - skip_whitespace_but_not_newline(in); - if (!has_data(in)) return NULL; - if (in.peek() == ')') { - in.get(); - return NULL; - } - if (in.peek() != '(') { - string_tree* result = new string_tree(next_word(in)); - return result; - } - in.get(); // skip '(' - string_tree* result = NULL; - string_tree** curr = &result; - while (in.peek() != ')') { - assert(has_data(in)); - *curr = new string_tree(""); - skip_whitespace_but_not_newline(in); - if (in.peek() == '(') - (*curr)->left = parse_string_tree(in); - else - (*curr)->value = next_word(in); - curr = &(*curr)->right; - } - in.get(); // skip ')' - return result; -} - -void test_dilated_reagent_with_type_tree() { - Trace_file = "dilated_reagent_with_type_tree"; - Hide_errors = true; // 'map' isn't defined yet - run("recipe main [\n {1: (foo (address array character) (bar number))} <- copy 34\n]\n# just to avoid errors\ncontainer foo [\n]\ncontainer bar [\n]\n"); - CHECK_TRACE_CONTENTS("parse: product: 1: (\"foo\" (\"address\" \"array\" \"character\") (\"bar\" \"number\"))"); -} -void test_dilated_reagent_with_new() { - Trace_file = "dilated_reagent_with_new"; - run("recipe main [\n x:address:shared:address:number <- new {(address number): type}\n]\n"); - CHECK_TRACE_CONTENTS("new: size of (\"address\" \"number\") is 1"); -} - -void test_recipe_with_header() { - Trace_file = "recipe_with_header"; - run("recipe main [\n 1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n local-scope\n load-ingredients\n z:number <- add x, y\n reply z\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 8 in location 1"); -} -void load_recipe_header(istream& in, recipe& result) { - result.has_header = true; - while (has_data(in) && in.peek() != '[' && in.peek() != '\n') { - string s = next_word(in); - if (s == "->") break; - result.ingredients.push_back(reagent(s)); - trace(9999, "parse") << "header ingredient: " << result.ingredients.back().original_string << end(); - skip_whitespace_but_not_newline(in); - } - while (has_data(in) && in.peek() != '[' && in.peek() != '\n') { - string s = next_word(in); - result.products.push_back(reagent(s)); - trace(9999, "parse") << "header product: " << result.products.back().original_string << end(); - skip_whitespace_but_not_newline(in); - } - // there can only ever be one variant for main - if (result.name != "main" && contains_key(Recipe_ordinal, result.name)) { - const recipe_ordinal r = get(Recipe_ordinal, result.name); - //? cerr << result.name << ": " << contains_key(Recipe, r) << (contains_key(Recipe, r) ? get(Recipe, r).has_header : 0) << matching_variant_name(result) << '\n'; - if (!contains_key(Recipe, r) || get(Recipe, r).has_header) { - string new_name = matching_variant_name(result); - if (new_name.empty()) { - // variant doesn't already exist - new_name = next_unused_recipe_name(result.name); - put(Recipe_ordinal, new_name, Next_recipe_ordinal++); - get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, new_name)); - } - trace(9999, "load") << "switching " << result.name << " to " << new_name << end(); - result.name = new_name; - //? cerr << "=> " << new_name << '\n'; - } - } - else { - // save first variant - put(Recipe_ordinal, result.name, Next_recipe_ordinal++); - get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, result.name)); - } - - // End Load Recipe Header(result) -} - -void test_recipe_handles_stray_comma() { - Trace_file = "recipe_handles_stray_comma"; - run("recipe main [\n 1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number, [\n local-scope\n load-ingredients\n z:number <- add x, y\n reply z\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 8 in location 1"); -} -void test_recipe_handles_stray_comma_2() { - Trace_file = "recipe_handles_stray_comma_2"; - run("recipe main [\n foo\n]\nrecipe foo, [\n 1:number/raw <- add 2, 2\n]\nrecipe bar [\n 1:number/raw <- add 2, 3\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 1"); -} -void test_recipe_handles_missing_bracket() { - Trace_file = "recipe_handles_missing_bracket"; - Hide_errors = true; - run("recipe main\n]\n"); - CHECK_TRACE_CONTENTS("error: recipe body must begin with '['"); -} -void test_recipe_handles_missing_bracket_2() { - Trace_file = "recipe_handles_missing_bracket_2"; - Hide_errors = true; - run("recipe main\n local-scope\n {\n }\n]\n# doesn't overflow line when reading header\n"); - CHECK_TRACE_DOESNT_CONTAIN("parse: header ingredient: local-scope"); - run(""); - CHECK_TRACE_CONTENTS("error: recipe body must begin with '['"); -} -void test_recipe_handles_missing_bracket_3() { - Trace_file = "recipe_handles_missing_bracket_3"; - Hide_errors = true; - run("recipe main # comment\n local-scope\n {\n }\n]\n# doesn't overflow line when reading header\n"); - CHECK_TRACE_DOESNT_CONTAIN("parse: header ingredient: local-scope"); - run(""); - CHECK_TRACE_CONTENTS("error: recipe body must begin with '['"); -} -void test_recipe_without_ingredients_or_products_has_header() { - Trace_file = "recipe_without_ingredients_or_products_has_header"; - run("recipe test [\n 1:number <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("parse: recipe test has a header"); -} -void test_show_clear_error_on_bad_call() { - Trace_file = "show_clear_error_on_bad_call"; - Hide_errors = true; - run("recipe main [\n 1:number <- foo 34\n]\nrecipe foo x:boolean -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\n"); - CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '1:number <- foo 34'"); -} -void test_show_clear_error_on_bad_call_2() { - Trace_file = "show_clear_error_on_bad_call_2"; - Hide_errors = true; - run("recipe main [\n 1:boolean <- foo 34\n]\nrecipe foo x:number -> y:number [\n local-scope\n load-ingredients\n reply x\n]\n"); - CHECK_TRACE_CONTENTS("error: main: product 0 has the wrong type at '1:boolean <- foo 34'"); -} -void check_calls_against_header(const recipe_ordinal r) { - trace(9991, "transform") << "--- type-check calls inside recipe " << get(Recipe, r).name << end(); - const recipe& caller = get(Recipe, r); - for (long long int i = 0; i < SIZE(caller.steps); ++i) { - const instruction& inst = caller.steps.at(i); - if (inst.operation < MAX_PRIMITIVE_RECIPES) continue; - const recipe& callee = get(Recipe, inst.operation); - if (!callee.has_header) continue; - for (long int i = 0; i < min(SIZE(inst.ingredients), SIZE(callee.ingredients)); ++i) { - // ingredients coerced from call to callee - if (!types_coercible(callee.ingredients.at(i), inst.ingredients.at(i))) - raise_error << maybe(caller.name) << "ingredient " << i << " has the wrong type at '" << to_string(inst) << "'\n" << end(); - if (is_unique_address(inst.ingredients.at(i))) - raise << maybe(caller.name) << "try to avoid passing non-shared addresses into calls, like ingredient " << i << " at '" << to_string(inst) << "'\n" << end(); - } - for (long int i = 0; i < min(SIZE(inst.products), SIZE(callee.products)); ++i) { - if (is_dummy(inst.products.at(i))) continue; - // products coerced from callee to call - if (!types_coercible(inst.products.at(i), callee.products.at(i))) - raise_error << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_string(inst) << "'\n" << end(); - if (is_unique_address(inst.products.at(i))) - raise << maybe(caller.name) << "try to avoid getting non-shared addresses out of calls, like product " << i << " at '" << to_string(inst) << "'\n" << end(); - } - } -} - -bool is_unique_address(reagent x) { - if (!canonize_type(x)) return false; - if (!x.type) return false; - if (x.type->value != get(Type_ordinal, "address")) return false; - if (!x.type->right) return true; - return x.type->right->value != get(Type_ordinal, "shared"); -} - - -void test_warn_on_calls_with_addresses() { - Trace_file = "warn_on_calls_with_addresses"; - Hide_warnings= true; - run("recipe main [\n 1:address:number <- copy 3/unsafe\n foo 1:address:number\n]\nrecipe foo x:address:number [\n local-scope\n load-ingredients\n]\n"); - CHECK_TRACE_CONTENTS("warn: main: try to avoid passing non-shared addresses into calls, like ingredient 0 at 'foo 1:address:number'"); -} -void test_warn_on_calls_with_addresses_2() { - Trace_file = "warn_on_calls_with_addresses_2"; - Hide_warnings= true; - run("recipe main [\n 1:address:number <- foo\n]\nrecipe foo -> x:address:number [\n local-scope\n load-ingredients\n x <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("warn: main: try to avoid getting non-shared addresses out of calls, like product 0 at '1:address:number <- foo '"); -} -void test_recipe_headers_are_checked() { - Trace_file = "recipe_headers_are_checked"; - Hide_errors = true; - transform("recipe add2 x:number, y:number -> z:number [\n local-scope\n load-ingredients\n z:address:number <- copy 0/unsafe\n reply z\n]\n"); - CHECK_TRACE_CONTENTS("error: add2: replied with the wrong type at 'reply z'"); -} -void check_reply_instructions_against_header(const recipe_ordinal r) { - const recipe& caller_recipe = get(Recipe, r); - if (!caller_recipe.has_header) return; - trace(9991, "transform") << "--- checking reply instructions against header for " << caller_recipe.name << end(); - 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) << "replied with the wrong number of products at '" << to_string(inst) << "'\n" << end(); - continue; - } - for (long long int i = 0; i < SIZE(caller_recipe.products); ++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 '" << to_string(inst) << "'\n" << end(); - } - } -} - -void test_recipe_headers_are_checked_2() { - Trace_file = "recipe_headers_are_checked_2"; - Hide_errors = true; - transform("recipe add2 x:number, y:number [\n local-scope\n load-ingredients\n z:address:number <- copy 0/unsafe\n reply z\n]\n"); - CHECK_TRACE_CONTENTS("error: add2: replied with the wrong number of products at 'reply z'"); -} -void test_recipe_headers_check_for_duplicate_names() { - Trace_file = "recipe_headers_check_for_duplicate_names"; - Hide_errors = true; - transform("recipe add2 x:number, x:number -> z:number [\n local-scope\n load-ingredients\n reply z\n]\n"); - CHECK_TRACE_CONTENTS("error: add2: x can't repeat in the ingredients"); -} -void check_header_ingredients(const recipe_ordinal r) { - recipe& caller_recipe = get(Recipe, r); - if (caller_recipe.products.empty()) return; - caller_recipe.ingredient_index.clear(); - trace(9991, "transform") << "--- checking reply instructions against header for " << caller_recipe.name << end(); - for (long long int i = 0; i < SIZE(caller_recipe.ingredients); ++i) { - if (contains_key(caller_recipe.ingredient_index, caller_recipe.ingredients.at(i).name)) - raise_error << maybe(caller_recipe.name) << caller_recipe.ingredients.at(i).name << " can't repeat in the ingredients\n" << end(); - put(caller_recipe.ingredient_index, caller_recipe.ingredients.at(i).name, i); - } -} - - -void test_deduce_instruction_types_from_recipe_header() { - Trace_file = "deduce_instruction_types_from_recipe_header"; - run("recipe main [\n 1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n local-scope\n load-ingredients\n z <- add x, y # no type for z\n reply z\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 8 in location 1"); -} -void deduce_types_from_header(const recipe_ordinal r) { - recipe& caller_recipe = get(Recipe, r); - if (caller_recipe.products.empty()) return; - trace(9991, "transform") << "--- deduce types from header for " << caller_recipe.name << end(); - map<string, const type_tree*> header_type; - for (long long int i = 0; i < SIZE(caller_recipe.ingredients); ++i) { - put(header_type, caller_recipe.ingredients.at(i).name, caller_recipe.ingredients.at(i).type); - //TODO: replace to_string with names_to_string - trace(9993, "transform") << "type of " << caller_recipe.ingredients.at(i).name << " is " << to_string(caller_recipe.ingredients.at(i).type) << end(); - } - for (long long int i = 0; i < SIZE(caller_recipe.products); ++i) { - put(header_type, caller_recipe.products.at(i).name, caller_recipe.products.at(i).type); - trace(9993, "transform") << "type of " << caller_recipe.products.at(i).name << " is " << to_string(caller_recipe.products.at(i).type) << end(); - } - for (long long int i = 0; i < SIZE(caller_recipe.steps); ++i) { - instruction& inst = caller_recipe.steps.at(i); - trace(9992, "transform") << "instruction: " << to_string(inst) << end(); - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (inst.ingredients.at(i).type) continue; - if (header_type.find(inst.ingredients.at(i).name) == header_type.end()) - continue; - if (!inst.ingredients.at(i).type) - inst.ingredients.at(i).type = new type_tree(*get(header_type, inst.ingredients.at(i).name)); - trace(9993, "transform") << "type of " << inst.ingredients.at(i).name << " is " << to_string(inst.ingredients.at(i).type) << end(); - } - for (long long int i = 0; i < SIZE(inst.products); ++i) { - trace(9993, "transform") << " product: " << to_string(inst.products.at(i)) << end(); - if (inst.products.at(i).type) continue; - if (header_type.find(inst.products.at(i).name) == header_type.end()) - continue; - if (!inst.products.at(i).type) - inst.products.at(i).type = new type_tree(*get(header_type, inst.products.at(i).name)); - trace(9993, "transform") << "type of " << inst.products.at(i).name << " is " << to_string(inst.products.at(i).type) << end(); - } - } -} - - -void test_reply_based_on_header() { - Trace_file = "reply_based_on_header"; - run("recipe main [\n 1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n local-scope\n load-ingredients\n z <- add x, y\n reply\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 8 in location 1"); -} -void fill_in_reply_ingredients(recipe_ordinal r) { - recipe& caller_recipe = get(Recipe, r); - if (!caller_recipe.has_header) return; - trace(9991, "transform") << "--- fill in reply ingredients from header for recipe " << caller_recipe.name << end(); - for (long long int i = 0; i < SIZE(caller_recipe.steps); ++i) { - instruction& inst = caller_recipe.steps.at(i); - if (inst.name == "reply") - add_header_products(inst, caller_recipe); - } - // fall through reply - if (caller_recipe.steps.at(SIZE(caller_recipe.steps)-1).name != "reply") { - instruction inst; - inst.name = "reply"; - add_header_products(inst, caller_recipe); - caller_recipe.steps.push_back(inst); - } -} - -void add_header_products(instruction& inst, const recipe& caller_recipe) { - assert(inst.name == "reply"); - // collect any products with the same names as ingredients - for (long long int i = 0; i < SIZE(caller_recipe.products); ++i) { - // if the ingredient is missing, add it from the header - if (SIZE(inst.ingredients) == i) - inst.ingredients.push_back(caller_recipe.products.at(i)); - // if it's missing /same_as_ingredient, try to fill it in - if (contains_key(caller_recipe.ingredient_index, caller_recipe.products.at(i).name) && !has_property(inst.ingredients.at(i), "same_as_ingredient")) { - ostringstream same_as_ingredient; - same_as_ingredient << get(caller_recipe.ingredient_index, caller_recipe.products.at(i).name); - inst.ingredients.at(i).properties.push_back(pair<string, string_tree*>("same-as-ingredient", new string_tree(same_as_ingredient.str()))); - } - } -} - -void test_explicit_reply_ignores_header() { - Trace_file = "explicit_reply_ignores_header"; - run("recipe main [\n 1:number/raw, 2:number/raw <- add2 3, 5\n]\nrecipe add2 a:number, b:number -> y:number, z:number [\n local-scope\n load-ingredients\n y <- add a, b\n z <- subtract a, b\n reply a, z\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 3 in location 1mem: storing -2 in location 2"); -} -void test_reply_on_fallthrough_based_on_header() { - Trace_file = "reply_on_fallthrough_based_on_header"; - run("recipe main [\n 1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n local-scope\n load-ingredients\n z <- add x, y\n]\n"); - CHECK_TRACE_CONTENTS("transform: instruction: reply z:numbermem: storing 8 in location 1"); -} -void test_reply_on_fallthrough_already_exists() { - Trace_file = "reply_on_fallthrough_already_exists"; - run("recipe main [\n 1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n local-scope\n load-ingredients\n z <- add x, y # no type for z\n reply z\n]\n"); - CHECK_TRACE_CONTENTS("transform: instruction: reply z"); - CHECK_TRACE_DOESNT_CONTAIN("transform: instruction: reply z:number"); - run(""); - CHECK_TRACE_CONTENTS("mem: storing 8 in location 1"); -} -void test_recipe_headers_perform_same_ingredient_check() { - Trace_file = "recipe_headers_perform_same_ingredient_check"; - Hide_errors = true; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 34\n 3:number <- add2 1:number, 2:number\n]\nrecipe add2 x:number, y:number -> x:number [\n local-scope\n load-ingredients\n]\n"); - CHECK_TRACE_CONTENTS("error: main: '3:number <- add2 1:number, 2:number' should write to 1:number rather than 3:number"); -} - -void test_static_dispatch() { - Trace_file = "static_dispatch"; - run("recipe main [\n 7:number/raw <- test 3\n]\nrecipe test a:number -> z:number [\n z <- copy 1\n]\nrecipe test a:number, b:number -> z:number [\n z <- copy 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 7"); -} -string matching_variant_name(const recipe& rr) { - 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))) continue; - const recipe& candidate = get(Recipe, variants.at(i)); - if (!all_reagents_match(rr, candidate)) continue; - return candidate.name; - } - return ""; -} - -bool all_reagents_match(const recipe& r1, const recipe& r2) { - if (SIZE(r1.ingredients) != SIZE(r2.ingredients)) return false; - if (SIZE(r1.products) != SIZE(r2.products)) return false; - for (long long int i = 0; i < SIZE(r1.ingredients); ++i) { - if (!deeply_equal_type_names(r1.ingredients.at(i), r2.ingredients.at(i))) { - return false; - } - } - for (long long int i = 0; i < SIZE(r1.products); ++i) { - if (!deeply_equal_type_names(r1.products.at(i), r2.products.at(i))) { - return false; - } - } - return true; -} - -bool deeply_equal_type_names(const reagent& a, const reagent& b) { - return deeply_equal_type_names(a.type, b.type); -} -bool deeply_equal_type_names(const type_tree* a, const type_tree* b) { - if (!a) return !b; - if (!b) return !a; - if (a->name == "literal" && b->name == "literal") - return true; - if (a->name == "literal") - return Literal_type_names.find(b->name) != Literal_type_names.end(); - if (b->name == "literal") - return Literal_type_names.find(a->name) != Literal_type_names.end(); - return a->name == b->name - && deeply_equal_type_names(a->left, b->left) - && deeply_equal_type_names(a->right, b->right); -} - -string next_unused_recipe_name(const string& recipe_name) { - for (long long int i = 2; ; ++i) { - ostringstream out; - out << recipe_name << '_' << i; - if (!contains_key(Recipe_ordinal, out.str())) - return out.str(); - } -} - - -void test_static_dispatch_picks_most_similar_variant() { - Trace_file = "static_dispatch_picks_most_similar_variant"; - run("recipe main [\n 7:number/raw <- test 3, 4, 5\n]\nrecipe test a:number -> z:number [\n z <- copy 1\n]\nrecipe test a:number, b:number -> z:number [\n z <- copy 2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 7"); -} -void resolve_ambiguous_calls(recipe_ordinal r) { - cerr << "DDD " << get(Recipe, r).name << ": " << contains_key(Type_ordinal, "_elem") << '\n'; - recipe& caller_recipe = get(Recipe, r); - trace(9991, "transform") << "--- resolve ambiguous calls for recipe " << caller_recipe.name << end(); - 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 (non_ghost_size(get_or_insert(Recipe_variants, inst.name)) == 0) continue; - trace(9992, "transform") << "instruction " << inst.original_string << end(); - resolve_stack.push_front(call(r)); - resolve_stack.front().running_step_index = index; - cerr << "EEE " << contains_key(Type_ordinal, "_elem") << '\n'; - string new_name = best_variant(inst, caller_recipe); - if (!new_name.empty()) - inst.name = new_name; - assert(resolve_stack.front().running_recipe == r); - assert(resolve_stack.front().running_step_index == index); - resolve_stack.pop_front(); - } - if (contains_key(Type_ordinal, "_elem")) { - cerr << "ZZZ " << get(Recipe, r).name << '\n'; - exit(0); - } -} - -string best_variant(instruction& inst, const recipe& caller_recipe) { - cerr << "FFF " << contains_key(Type_ordinal, "_elem") << '\n'; - vector<recipe_ordinal>& variants = get(Recipe_variants, inst.name); - vector<recipe_ordinal> candidates; - - // Static Dispatch Phase 1 - candidates = strictly_matching_variants(inst, variants); - if (!candidates.empty()) return best_variant(inst, candidates).name; - - // Static Dispatch Phase 2 (shape-shifting recipes in a later layer) - candidates = strictly_matching_shape_shifting_variants(inst, variants); - if (!candidates.empty()) { - recipe_ordinal exemplar = best_shape_shifting_variant(inst, candidates); - trace(9992, "transform") << "found variant to specialize: " << exemplar << ' ' << get(Recipe, exemplar).name << end(); - cerr << "GGG " << contains_key(Type_ordinal, "_elem") << '\n'; - recipe_ordinal new_recipe_ordinal = new_variant(exemplar, inst, caller_recipe); - cerr << "XXX " << contains_key(Type_ordinal, "_elem") << '\n'; - if (new_recipe_ordinal == 0) goto skip_shape_shifting_variants; - variants.push_back(new_recipe_ordinal); // side-effect - recipe& variant = get(Recipe, new_recipe_ordinal); - // perform all transforms on the new specialization - cerr << "YYY " << contains_key(Type_ordinal, "_elem") << '\n'; - if (!variant.steps.empty()) { - trace(9992, "transform") << "transforming new specialization: " << variant.name << end(); - for (long long int t = 0; t < SIZE(Transform); ++t) { - (*Transform.at(t))(new_recipe_ordinal); - } - } - variant.transformed_until = SIZE(Transform)-1; - trace(9992, "transform") << "new specialization: " << variant.name << end(); - return variant.name; - } - skip_shape_shifting_variants:; - - - // End Static Dispatch Phase 2 - - // Static Dispatch Phase 3 - candidates = strictly_matching_variants_except_literal_against_boolean(inst, variants); - if (!candidates.empty()) return best_variant(inst, candidates).name; - - // Static Dispatch Phase 4 - candidates = matching_variants(inst, variants); - if (!candidates.empty()) return best_variant(inst, candidates).name; - - // error messages - if (get(Recipe_ordinal, inst.name) >= MAX_PRIMITIVE_RECIPES) { // we currently don't check types for primitive variants - raise_error << maybe(caller_recipe.name) << "failed to find a matching call for '" << to_string(inst) << "'\n" << end(); - for (list<call>::iterator p = /*skip*/++resolve_stack.begin(); p != resolve_stack.end(); ++p) { - const recipe& specializer_recipe = get(Recipe, p->running_recipe); - const instruction& specializer_inst = specializer_recipe.steps.at(p->running_step_index); - if (specializer_recipe.name != "interactive") - raise_error << " (from '" << to_string(specializer_inst) << "' in " << specializer_recipe.name << ")\n" << end(); - else - raise_error << " (from '" << to_string(specializer_inst) << "')\n" << end(); - // One special-case to help with the rewrite_stash transform. (cross-layer) - if (specializer_inst.products.at(0).name.find("stash_") == 0) { - instruction stash_inst; - if (next_stash(*p, &stash_inst)) { - if (specializer_recipe.name != "interactive") - raise_error << " (part of '" << stash_inst.original_string << "' in " << specializer_recipe.name << ")\n" << end(); - else - raise_error << " (part of '" << stash_inst.original_string << "')\n" << end(); - } - } - } - } - return ""; -} - -// phase 1 -vector<recipe_ordinal> strictly_matching_variants(const instruction& inst, vector<recipe_ordinal>& variants) { - vector<recipe_ordinal> result; - for (long long int i = 0; i < SIZE(variants); ++i) { - if (variants.at(i) == -1) continue; - trace(9992, "transform") << "checking variant (strict) " << i << ": " << header_label(variants.at(i)) << end(); - if (all_header_reagents_strictly_match(inst, get(Recipe, variants.at(i)))) - result.push_back(variants.at(i)); - } - return result; -} - -bool all_header_reagents_strictly_match(const instruction& inst, const recipe& variant) { - for (long long int i = 0; i < min(SIZE(inst.ingredients), SIZE(variant.ingredients)); ++i) { - if (!types_strictly_match(variant.ingredients.at(i), inst.ingredients.at(i))) { - trace(9993, "transform") << "strict match failed: ingredient " << i << end(); - return false; - } - } - for (long long int i = 0; i < min(SIZE(inst.products), SIZE(variant.products)); ++i) { - if (is_dummy(inst.products.at(i))) continue; - if (!types_strictly_match(variant.products.at(i), inst.products.at(i))) { - trace(9993, "transform") << "strict match failed: product " << i << end(); - return false; - } - } - return true; -} - -// phase 3 -vector<recipe_ordinal> strictly_matching_variants_except_literal_against_boolean(const instruction& inst, vector<recipe_ordinal>& variants) { - vector<recipe_ordinal> result; - for (long long int i = 0; i < SIZE(variants); ++i) { - if (variants.at(i) == -1) continue; - trace(9992, "transform") << "checking variant (strict except literals-against-booleans) " << i << ": " << header_label(variants.at(i)) << end(); - if (all_header_reagents_strictly_match_except_literal_against_boolean(inst, get(Recipe, variants.at(i)))) - result.push_back(variants.at(i)); - } - return result; -} - -bool all_header_reagents_strictly_match_except_literal_against_boolean(const instruction& inst, const recipe& variant) { - for (long long int i = 0; i < min(SIZE(inst.ingredients), SIZE(variant.ingredients)); ++i) { - if (!types_strictly_match_except_literal_against_boolean(variant.ingredients.at(i), inst.ingredients.at(i))) { - trace(9993, "transform") << "strict match failed: ingredient " << i << end(); - return false; - } - } - for (long long int i = 0; i < min(SIZE(variant.products), SIZE(inst.products)); ++i) { - if (is_dummy(inst.products.at(i))) continue; - if (!types_strictly_match_except_literal_against_boolean(variant.products.at(i), inst.products.at(i))) { - trace(9993, "transform") << "strict match failed: product " << i << end(); - return false; - } - } - return true; -} - -// phase 4 -vector<recipe_ordinal> matching_variants(const instruction& inst, vector<recipe_ordinal>& variants) { - vector<recipe_ordinal> result; - for (long long int i = 0; i < SIZE(variants); ++i) { - if (variants.at(i) == -1) continue; - trace(9992, "transform") << "checking variant " << i << ": " << header_label(variants.at(i)) << end(); - if (all_header_reagents_match(inst, get(Recipe, variants.at(i)))) - result.push_back(variants.at(i)); - } - return result; -} - -bool all_header_reagents_match(const instruction& inst, const recipe& variant) { - for (long long int i = 0; i < min(SIZE(inst.ingredients), SIZE(variant.ingredients)); ++i) { - if (!types_match(variant.ingredients.at(i), inst.ingredients.at(i))) { - trace(9993, "transform") << "strict match failed: ingredient " << i << end(); - return false; - } - } - for (long long int i = 0; i < min(SIZE(variant.products), SIZE(inst.products)); ++i) { - if (is_dummy(inst.products.at(i))) continue; - if (!types_match(variant.products.at(i), inst.products.at(i))) { - trace(9993, "transform") << "strict match failed: product " << i << end(); - return false; - } - } - return true; -} - -// tie-breaker for each phase -const recipe& best_variant(const instruction& inst, vector<recipe_ordinal>& candidates) { - assert(!candidates.empty()); - long long int min_score = 999; - long long int min_index = 0; - for (long long int i = 0; i < SIZE(candidates); ++i) { - const recipe& candidate = get(Recipe, candidates.at(i)); - long long int score = abs(SIZE(candidate.products)-SIZE(inst.products)) - + abs(SIZE(candidate.ingredients)-SIZE(inst.ingredients)); - assert(score < 999); - if (score < min_score) { - min_score = score; - min_index = i; - } - } - return get(Recipe, candidates.at(min_index)); -} - -long long int non_ghost_size(vector<recipe_ordinal>& variants) { - long long int result = 0; - for (long long int i = 0; i < SIZE(variants); ++i) - if (variants.at(i) != -1) ++result; - return result; -} - -bool next_stash(const call& c, instruction* stash_inst) { - const recipe& specializer_recipe = get(Recipe, c.running_recipe); - long long int index = c.running_step_index; - for (++index; index < SIZE(specializer_recipe.steps); ++index) { - const instruction& inst = specializer_recipe.steps.at(index); - if (inst.name == "stash") { - *stash_inst = inst; - return true; - } - } - return false; -} - -void test_static_dispatch_disabled_in_recipe_without_variants() { - Trace_file = "static_dispatch_disabled_in_recipe_without_variants"; - run("recipe main [\n 1:number <- test 3\n]\nrecipe test [\n 2:number <- next-ingredient # ensure no header\n reply 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1"); -} -void test_static_dispatch_disabled_on_headerless_definition() { - Trace_file = "static_dispatch_disabled_on_headerless_definition"; - Hide_warnings = true; - run("recipe test a:number -> z:number [\n z <- copy 1\n]\nrecipe test [\n reply 34\n]\n"); - CHECK_TRACE_CONTENTS("warn: redefining recipe test"); -} -void test_static_dispatch_disabled_on_headerless_definition_2() { - Trace_file = "static_dispatch_disabled_on_headerless_definition_2"; - Hide_warnings = true; - run("recipe test [\n reply 34\n]\nrecipe test a:number -> z:number [\n z <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("warn: redefining recipe test"); -} -void test_static_dispatch_on_primitive_names() { - Trace_file = "static_dispatch_on_primitive_names"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- copy 34\n 3:boolean <- equal 1:number, 2:number\n 4:boolean <- copy 0/false\n 5:boolean <- copy 0/false\n 6:boolean <- equal 4:boolean, 5:boolean\n]\n# temporarily hardcode number equality to always fail\nrecipe equal x:number, y:number -> z:boolean [\n local-scope\n load-ingredients\n z <- copy 0/false\n]\n# comparing numbers used overload\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3mem: storing 1 in location 6"); -} -void test_static_dispatch_works_with_dummy_results_for_containers() { - Trace_file = "static_dispatch_works_with_dummy_results_for_containers"; - Hide_errors = true; - run("recipe main [\n _ <- test 3, 4\n]\nrecipe test a:number -> z:point [\n local-scope\n load-ingredients\n z <- merge a, 0\n]\nrecipe test a:number, b:number -> z:point [\n local-scope\n load-ingredients\n z <- merge a, b\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_static_dispatch_works_with_compound_type_containing_container_defined_after_first_use() { - Trace_file = "static_dispatch_works_with_compound_type_containing_container_defined_after_first_use"; - Hide_errors = true; - run("recipe main [\n x:address:shared:foo <- new foo:type\n test x\n]\ncontainer foo [\n x:number\n]\nrecipe test a:address:shared:foo -> z:number [\n local-scope\n load-ingredients\n z:number <- get *a, x:offset\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_static_dispatch_works_with_compound_type_containing_container_defined_after_second_use() { - Trace_file = "static_dispatch_works_with_compound_type_containing_container_defined_after_second_use"; - Hide_errors = true; - run("recipe main [\n x:address:shared:foo <- new foo:type\n test x\n]\nrecipe test a:address:shared:foo -> z:number [\n local-scope\n load-ingredients\n z:number <- get *a, x:offset\n]\ncontainer foo [\n x:number\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_static_dispatch_prefers_literals_to_be_numbers_rather_than_addresses() { - Trace_file = "static_dispatch_prefers_literals_to_be_numbers_rather_than_addresses"; - run("recipe main [\n 1:number <- foo 0\n]\nrecipe foo x:address:number -> y:number [\n reply 34\n]\nrecipe foo x:number -> y:number [\n reply 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 1"); -} -void test_static_dispatch_on_non_literal_character_ignores_variant_with_numbers() { - Trace_file = "static_dispatch_on_non_literal_character_ignores_variant_with_numbers"; - Hide_errors = true; - run("recipe main [\n local-scope\n x:character <- copy 10/newline\n 1:number/raw <- foo x\n]\nrecipe foo x:number -> y:number [\n load-ingredients\n reply 34\n]\n"); - CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '1:number/raw <- foo x'"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 1"); -} -void test_static_dispatch_dispatches_literal_to_boolean_before_character() { - Trace_file = "static_dispatch_dispatches_literal_to_boolean_before_character"; - run("recipe main [\n 1:number/raw <- foo 0 # valid literal for boolean\n]\nrecipe foo x:character -> y:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo x:boolean -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\n# boolean variant is preferred\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 1"); -} -void test_static_dispatch_dispatches_literal_to_character_when_out_of_boolean_range() { - Trace_file = "static_dispatch_dispatches_literal_to_character_when_out_of_boolean_range"; - run("recipe main [\n 1:number/raw <- foo 97 # not a valid literal for boolean\n]\nrecipe foo x:character -> y:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo x:boolean -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\n# character variant is preferred\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1"); -} -void test_static_dispatch_dispatches_literal_to_number_if_at_all_possible() { - Trace_file = "static_dispatch_dispatches_literal_to_number_if_at_all_possible"; - run("recipe main [\n 1:number/raw <- foo 97\n]\nrecipe foo x:character -> y:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo x:number -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\n# number variant is preferred\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 1"); -} -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 << ' ' << to_string(caller.ingredients.at(i)); - if (!caller.products.empty()) out << " ->"; - for (long long int i = 0; i < SIZE(caller.products); ++i) - out << ' ' << to_string(caller.products.at(i)); - return out.str(); -} - -void test_reload_variant_retains_other_variants() { - Trace_file = "reload_variant_retains_other_variants"; - run("recipe main [\n 1:number <- copy 34\n 2:number <- foo 1:number\n]\nrecipe foo x:number -> y:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo x:address:number -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\nrecipe! foo x:address:number -> y:number [\n local-scope\n load-ingredients\n reply 36\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 2"); - CHECK_TRACE_COUNT("error", 0); - run(""); - CHECK_TRACE_COUNT("warn", 0); -} -void test_dispatch_errors_come_after_unknown_name_errors() { - Trace_file = "dispatch_errors_come_after_unknown_name_errors"; - Hide_errors = true; - run("recipe main [\n y:number <- foo x\n]\nrecipe foo a:number -> b:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo a:boolean -> b:number [\n local-scope\n load-ingredients\n reply 35\n]\n"); - CHECK_TRACE_CONTENTS("error: main: missing type for x in 'y:number <- foo x'error: main: failed to find a matching call for 'y:number <- foo x'"); -} - -void test_size_of_shape_shifting_container() { - Trace_file = "size_of_shape_shifting_container"; - run("container foo:_t [\n x:_t\n y:number\n]\nrecipe main [\n 1:foo:number <- merge 12, 13\n 3:foo:point <- merge 14, 15, 16\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 12 in location 1mem: storing 13 in location 2mem: storing 14 in location 3mem: storing 15 in location 4mem: storing 16 in location 5"); -} -void test_size_of_shape_shifting_container_2() { - Trace_file = "size_of_shape_shifting_container_2"; - Hide_errors = true; - run("# multiple type ingredients\ncontainer foo:_a:_b [\n x:_a\n y:_b\n]\nrecipe main [\n 1:foo:number:boolean <- merge 34, 1/true\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_size_of_shape_shifting_container_3() { - Trace_file = "size_of_shape_shifting_container_3"; - Hide_errors = true; - run("container foo:_a:_b [\n x:_a\n y:_b\n]\nrecipe main [\n 1:address:shared:array:character <- new [abc]\n # compound types for type ingredients\n {2: (foo number (address shared array character))} <- merge 34/x, 1:address:shared:array:character/y\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_size_of_shape_shifting_container_4() { - Trace_file = "size_of_shape_shifting_container_4"; - Hide_errors = true; - run("container foo:_a:_b [\n x:_a\n y:_b\n]\ncontainer bar:_a:_b [\n # dilated element\n {data: (foo _a (address shared _b))}\n]\nrecipe main [\n 1:address:shared:array:character <- new [abc]\n 2:bar:number:array:character <- merge 34/x, 1:address:shared:array:character/y\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void read_type_ingredients(string& name) { - string save_name = name; - istringstream in(save_name); - name = slurp_until(in, ':'); - if (!contains_key(Type_ordinal, name) || get(Type_ordinal, name) == 0) - put(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 (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(); - return; - } - put(info.type_ingredient_names, curr, next_type_ordinal++); - } -} - -bool is_type_ingredient_name(const string& type) { - return !type.empty() && type.at(0) == '_'; -} - -void test_size_of_shape_shifting_exclusive_container() { - Trace_file = "size_of_shape_shifting_exclusive_container"; - run("exclusive-container foo:_t [\n x:_t\n y:number\n]\nrecipe main [\n 1:foo:number <- merge 0/x, 34\n 3:foo:point <- merge 0/x, 15, 16\n 6:foo:point <- merge 1/y, 23\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 34 in location 2mem: storing 0 in location 3mem: storing 15 in location 4mem: storing 16 in location 5mem: storing 1 in location 6mem: storing 23 in location 7"); - CHECK_TRACE_COUNT("mem", 7); -} -// shape-shifting version of size_of -long long int size_of_type_ingredient(const type_tree* element_template, const type_tree* rest_of_use) { - type_tree* element_type = type_ingredient(element_template, rest_of_use); - if (!element_type) return 0; - long long int result = size_of(element_type); - delete element_type; - return result; -} - -type_tree* type_ingredient(const type_tree* element_template, const type_tree* rest_of_use) { - long long int type_ingredient_index = element_template->value - START_TYPE_INGREDIENTS; - const type_tree* curr = rest_of_use; - if (!curr) return NULL; - while (type_ingredient_index > 0) { - --type_ingredient_index; - curr = curr->right; - if (!curr) return NULL; - } - assert(curr); - if (curr->left) curr = curr->left; - assert(curr->value > 0); - trace(9999, "type") << "type deduced to be " << get(Type, curr->value).name << "$" << end(); - return new type_tree(*curr); -} - -void test_get_on_shape_shifting_container() { - Trace_file = "get_on_shape_shifting_container"; - run("container foo:_t [\n x:_t\n y:number\n]\nrecipe main [\n 1:foo:point <- merge 14, 15, 16\n 2:number <- get 1:foo:point, y:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 16 in location 2"); -} -void test_get_on_shape_shifting_container_2() { - Trace_file = "get_on_shape_shifting_container_2"; - run("container foo:_t [\n x:_t\n y:number\n]\nrecipe main [\n 1:foo:point <- merge 14, 15, 16\n 2:point <- get 1:foo:point, x:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 14 in location 2mem: storing 15 in location 3"); -} -void test_get_on_shape_shifting_container_3() { - Trace_file = "get_on_shape_shifting_container_3"; - run("container foo:_t [\n x:_t\n y:number\n]\nrecipe main [\n 1:foo:address:point <- merge 34/unsafe, 48\n 2:address:point <- get 1:foo:address:point, x:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 2"); -} -void test_get_on_shape_shifting_container_inside_container() { - Trace_file = "get_on_shape_shifting_container_inside_container"; - run("container foo:_t [\n x:_t\n y:number\n]\ncontainer bar [\n x:foo:point\n y:number\n]\nrecipe main [\n 1:bar <- merge 14, 15, 16, 17\n 2:number <- get 1:bar, 1:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 17 in location 2"); -} -void test_get_on_complex_shape_shifting_container() { - Trace_file = "get_on_complex_shape_shifting_container"; - run("container foo:_a:_b [\n x:_a\n y:_b\n]\nrecipe main [\n 1:address:shared:array:character <- new [abc]\n {2: (foo number (address shared array character))} <- merge 34/x, 1:address:shared:array:character/y\n 3:address:shared:array:character <- get {2: (foo number (address shared array character))}, y:offset\n 4:boolean <- equal 1:address:shared:array:character, 3:address:shared:array:character\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 4"); -} -bool contains_type_ingredient(const reagent& x) { - return contains_type_ingredient(x.type); -} - -bool contains_type_ingredient(const type_tree* type) { - if (!type) return false; - if (type->value >= START_TYPE_INGREDIENTS) return true; - return contains_type_ingredient(type->left) || contains_type_ingredient(type->right); -} - -// todo: too complicated and likely incomplete; maybe avoid replacing in place? Maybe process element_type and element_type_name in separate functions? -void replace_type_ingredients(type_tree* element_type, const type_tree* callsite_type, const type_info& container_info) { - if (!callsite_type) return; // error but it's already been raised above - if (!element_type) return; - - // A. recurse first to avoid nested replaces (which I can't reason about yet) - replace_type_ingredients(element_type->left, callsite_type, container_info); - replace_type_ingredients(element_type->right, callsite_type, container_info); - if (element_type->value < START_TYPE_INGREDIENTS) return; - - const long long int type_ingredient_index = element_type->value-START_TYPE_INGREDIENTS; - if (!has_nth_type(callsite_type, type_ingredient_index)) { - raise_error << "illegal type " << names_to_string(callsite_type) << " seems to be missing a type ingredient or three\n" << end(); - return; - } - - // B. replace the current location - const type_tree* replacement = NULL; - bool splice_right = true ; - { - const type_tree* curr = callsite_type; - for (long long int i = 0; i < type_ingredient_index; ++i) - curr = curr->right; - if (curr && curr->left) { - replacement = curr->left; - } - else { - // We want foo:_t to be used like foo:number, which expands to {foo: number} - // rather than {foo: (number)} - // We'd also like to use it with multiple types: foo:address:number. - replacement = curr; - if (!final_type_ingredient(type_ingredient_index, container_info)) { - splice_right = false; - } - } - } - element_type->name = replacement->name; - element_type->value = replacement->value; - assert(!element_type->left); // since value is set - element_type->left = replacement->left ? new type_tree(*replacement->left) : NULL; - if (splice_right) { - type_tree* old_right = element_type->right; - element_type->right = replacement->right ? new type_tree(*replacement->right) : NULL; - append(element_type->right, old_right); - } -} - -bool final_type_ingredient(long long int type_ingredient_index, const type_info& container_info) { - for (map<string, type_ordinal>::const_iterator p = container_info.type_ingredient_names.begin(); - p != container_info.type_ingredient_names.end(); - ++p) { - if (p->second > START_TYPE_INGREDIENTS+type_ingredient_index) return false; - } - return true; -} - -void append(type_tree*& base, type_tree* extra) { - if (!base) { - base = extra; - return; - } - type_tree* curr = base; - while (curr->right) curr = curr->right; - curr->right = extra; -} - -void append(string_tree*& base, string_tree* extra) { - if (!base) { - base = extra; - return; - } - string_tree* curr = base; - while (curr->right) curr = curr->right; - curr->right = extra; -} - -void test_replace_type_ingredients_entire() { - run("container foo:_elem [\n" - " x:_elem\n" - " y:number\n" - "]\n"); - reagent callsite("x:foo:point"); - reagent element = element_type(callsite, 0); - cerr << debug_string(element) << '\n'; -//? DUMP(""); - CHECK_EQ(element.name, "x"); - cerr << get(Type_ordinal, "point") << '\n'; - cerr << contains_key(Type, 23) << '\n'; - for (map<string, type_ordinal>::iterator p = Type_ordinal.begin(); p != Type_ordinal.end(); ++p) - if (p->second == 23) cerr << p->first << '\n'; - CHECK_EQ(element.type->name, "point"); - CHECK(!element.type->right); -} - -void test_replace_type_ingredients_tail() { - exit(0); - run("container foo:_elem [\n" - " x:_elem\n" - "]\n" - "container bar:_elem [\n" - " x:foo:_elem\n" - "]\n"); - reagent callsite("x:bar:point"); - reagent element = element_type(callsite, 0); - CHECK_EQ(element.name, "x"); - CHECK_EQ(element.type->name, "foo"); - CHECK_EQ(element.type->right->name, "point"); - CHECK(!element.type->right->right); -} - -void test_replace_type_ingredients_head_tail_multiple() { - run("container foo:_elem [\n" - " x:_elem\n" - "]\n" - "container bar:_elem [\n" - " x:foo:_elem\n" - "]\n"); - reagent callsite("x:bar:address:shared:array:character"); - reagent element = element_type(callsite, 0); - CHECK_EQ(element.name, "x"); - CHECK_EQ(element.type->name, "foo"); - CHECK_EQ(element.type->right->name, "address"); - CHECK_EQ(element.type->right->right->name, "shared"); - CHECK_EQ(element.type->right->right->right->name, "array"); - CHECK_EQ(element.type->right->right->right->right->name, "character"); - CHECK(!element.type->right->right->right->right->right); -} - -void test_replace_type_ingredients_head_middle() { - run("container foo:_elem [\n" - " x:_elem\n" - "]\n" - "container bar:_elem [\n" - " x:foo:_elem:number\n" - "]\n"); - reagent callsite("x:bar:address"); - reagent element = element_type(callsite, 0); - CHECK_EQ(element.name, "x"); - CHECK(element.type) - CHECK_EQ(element.type->name, "foo"); - CHECK(element.type->right) - CHECK_EQ(element.type->right->name, "address"); - CHECK(element.type->right->right) - CHECK_EQ(element.type->right->right->name, "number"); - CHECK(!element.type->right->right->right); -} - -void test_replace_last_type_ingredient_with_multiple() { - run("container foo:_a:_b [\n" - " x:_a\n" - " y:_b\n" - "]\n"); - reagent callsite("{f: (foo number (address shared array character))}"); - reagent element1 = element_type(callsite, 0); - CHECK_EQ(element1.name, "x"); - CHECK_EQ(element1.type->name, "number"); - CHECK(!element1.type->right); - reagent element2 = element_type(callsite, 1); - CHECK_EQ(element2.name, "y"); - CHECK_EQ(element2.type->name, "address"); - CHECK_EQ(element2.type->right->name, "shared"); - CHECK_EQ(element2.type->right->right->name, "array"); - CHECK_EQ(element2.type->right->right->right->name, "character"); - CHECK(!element2.type->right->right->right->right); -} - -void test_replace_middle_type_ingredient_with_multiple() { - run("container foo:_a:_b:_c [\n" - " x:_a\n" - " y:_b\n" - " z:_c\n" - "]\n"); - reagent callsite("{f: (foo number (address shared array character) boolean)}"); - reagent element1 = element_type(callsite, 0); - CHECK_EQ(element1.name, "x"); - CHECK_EQ(element1.type->name, "number"); - CHECK(!element1.type->right); - reagent element2 = element_type(callsite, 1); - CHECK_EQ(element2.name, "y"); - CHECK_EQ(element2.type->name, "address"); - CHECK_EQ(element2.type->right->name, "shared"); - CHECK_EQ(element2.type->right->right->name, "array"); - CHECK_EQ(element2.type->right->right->right->name, "character"); - CHECK(!element2.type->right->right->right->right); - reagent element3 = element_type(callsite, 2); - CHECK_EQ(element3.name, "z"); - CHECK_EQ(element3.type->name, "boolean"); - CHECK(!element3.type->right); -} - -bool has_nth_type(const type_tree* base, long long int n) { - assert(n >= 0); - if (base == NULL) return false; - if (n == 0) return true; - return has_nth_type(base->right, n-1); -} - -void test_get_on_shape_shifting_container_error() { - Trace_file = "get_on_shape_shifting_container_error"; - Hide_errors = true; - run("container foo:_t [\n x:_t\n y:number\n]\nrecipe main [\n 10:foo:point <- merge 14, 15, 16\n 1:number <- get 10:foo, 1:offset\n]\n"); - CHECK_TRACE_CONTENTS("error: illegal type \"foo\" seems to be missing a type ingredient or three"); -} -void test_get_address_on_shape_shifting_container() { - Trace_file = "get_address_on_shape_shifting_container"; - run("container foo:_t [\n x:_t\n y:number\n]\nrecipe main [\n 10:foo:point <- merge 14, 15, 16\n 1:address:number <- get-address 10:foo:point, 1:offset\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 12 in location 1"); -} -void test_merge_check_shape_shifting_container_containing_exclusive_container() { - Trace_file = "merge_check_shape_shifting_container_containing_exclusive_container"; - Hide_errors = true; - run("container foo:_elem [\n x:number\n y:_elem\n]\nexclusive-container bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo:bar <- merge 23, 1/y, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 23 in location 1mem: storing 1 in location 2mem: storing 34 in location 3"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_shape_shifting_container_containing_exclusive_container_2() { - Trace_file = "merge_check_shape_shifting_container_containing_exclusive_container_2"; - Hide_errors = true; - run("container foo:_elem [\n x:number\n y:_elem\n]\nexclusive-container bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo:bar <- merge 23, 1/y, 34, 35\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35'"); -} -void test_merge_check_shape_shifting_exclusive_container_containing_container() { - Trace_file = "merge_check_shape_shifting_exclusive_container_containing_container"; - Hide_errors = true; - run("exclusive-container foo:_elem [\n x:number\n y:_elem\n]\ncontainer bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo:bar <- merge 1/y, 23, 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 1mem: storing 23 in location 2mem: storing 34 in location 3"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_shape_shifting_exclusive_container_containing_container_2() { - Trace_file = "merge_check_shape_shifting_exclusive_container_containing_container_2"; - Hide_errors = true; - run("exclusive-container foo:_elem [\n x:number\n y:_elem\n]\ncontainer bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo:bar <- merge 0/x, 23\n]\n"); - CHECK_TRACE_COUNT("error", 0); -} -void test_merge_check_shape_shifting_exclusive_container_containing_container_3() { - Trace_file = "merge_check_shape_shifting_exclusive_container_containing_container_3"; - Hide_errors = true; - run("exclusive-container foo:_elem [\n x:number\n y:_elem\n]\ncontainer bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo:bar <- merge 1/y, 23\n]\n"); - CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23'"); -} - -void test_shape_shifting_recipe() { - Trace_file = "shape_shifting_recipe"; - run("recipe main [\n 10:point <- merge 14, 15\n 11:point <- foo 10:point\n]\n# non-matching variant\nrecipe foo a:number -> result:number [\n local-scope\n load-ingredients\n result <- copy 34\n]\n# matching shape-shifting variant\nrecipe foo a:_t -> result:_t [\n local-scope\n load-ingredients\n result <- copy a\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 14 in location 11mem: storing 15 in location 12"); -} -// phase 2 of static dispatch -vector<recipe_ordinal> strictly_matching_shape_shifting_variants(const instruction& inst, vector<recipe_ordinal>& variants) { - vector<recipe_ordinal> result; - for (long long int i = 0; i < SIZE(variants); ++i) { - if (variants.at(i) == -1) continue; - if (!any_type_ingredient_in_header(variants.at(i))) continue; - if (all_concrete_header_reagents_strictly_match(inst, get(Recipe, variants.at(i)))) - result.push_back(variants.at(i)); - } - return result; -} - -bool all_concrete_header_reagents_strictly_match(const instruction& inst, const recipe& variant) { - if (SIZE(inst.ingredients) < SIZE(variant.ingredients)) { - trace(9993, "transform") << "too few ingredients" << end(); - return false; - } - if (SIZE(variant.products) < SIZE(inst.products)) { - trace(9993, "transform") << "too few products" << end(); - return false; - } - for (long long int i = 0; i < SIZE(variant.ingredients); ++i) { - if (!concrete_type_names_strictly_match(variant.ingredients.at(i), inst.ingredients.at(i))) { - trace(9993, "transform") << "concrete-type match failed: ingredient " << i << end(); - return false; - } - } - for (long long int i = 0; i < SIZE(inst.products); ++i) { - if (is_dummy(inst.products.at(i))) continue; - if (!concrete_type_names_strictly_match(variant.products.at(i), inst.products.at(i))) { - trace(9993, "transform") << "strict match failed: product " << i << end(); - return false; - } - } - return true; -} - -// tie-breaker for phase 2 -recipe_ordinal best_shape_shifting_variant(const instruction& inst, vector<recipe_ordinal>& candidates) { - assert(!candidates.empty()); - // primary score - long long int max_score = -1; - for (long long int i = 0; i < SIZE(candidates); ++i) { - long long int score = number_of_concrete_type_names(candidates.at(i)); - assert(score > -1); - if (score > max_score) max_score = score; - } - // break any ties at max_score by a secondary score - long long int min_score2 = 999; - long long int best_index = 0; - for (long long int i = 0; i < SIZE(candidates); ++i) { - long long int score1 = number_of_concrete_type_names(candidates.at(i)); - assert(score1 <= max_score); - if (score1 != max_score) continue; - const recipe& candidate = get(Recipe, candidates.at(i)); - long long int score2 = (SIZE(candidate.products)-SIZE(inst.products)) - + (SIZE(inst.ingredients)-SIZE(candidate.ingredients)); - assert(score2 < 999); - if (score2 < min_score2) { - min_score2 = score2; - best_index = i; - } - } - return candidates.at(best_index); -} - -bool any_type_ingredient_in_header(recipe_ordinal variant) { - 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(caller.products); ++i) { - if (contains_type_ingredient_name(caller.products.at(i))) - return true; - } - return false; -} - -bool concrete_type_names_strictly_match(reagent to, reagent from) { - canonize_type(to); - canonize_type(from); - return concrete_type_names_strictly_match(to.type, from.type, from); -} - -long long int number_of_concrete_type_names(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_type_names(caller.ingredients.at(i)); - for (long long int i = 0; i < SIZE(caller.products); ++i) - result += number_of_concrete_type_names(caller.products.at(i)); - return result; -} - -long long int number_of_concrete_type_names(const reagent& r) { - return number_of_concrete_type_names(r.type); -} - -long long int number_of_concrete_type_names(const type_tree* type) { - if (!type) return 0; - long long int result = 0; - if (!type->name.empty() && !is_type_ingredient_name(type->name)) - result++; - result += number_of_concrete_type_names(type->left); - result += number_of_concrete_type_names(type->right); - return result; -} - -bool concrete_type_names_strictly_match(const type_tree* to, const type_tree* from, const reagent& rhs_reagent) { - if (!to) return !from; - if (!from) return !to; - if (is_type_ingredient_name(to->name)) return true; // type ingredient matches anything - if (to->name == "literal" && from->name == "literal") - return true; - if (to->name == "literal" - && Literal_type_names.find(from->name) != Literal_type_names.end()) - return true; - if (from->name == "literal" - && Literal_type_names.find(to->name) != Literal_type_names.end()) - return true; - if (from->name == "literal" && to->name == "address") - return rhs_reagent.name == "0"; - return to->name == from->name - && concrete_type_names_strictly_match(to->left, from->left, rhs_reagent) - && concrete_type_names_strictly_match(to->right, from->right, rhs_reagent); -} - -bool contains_type_ingredient_name(const reagent& x) { - return contains_type_ingredient_name(x.type); -} - -bool contains_type_ingredient_name(const type_tree* type) { - if (!type) return false; - if (is_type_ingredient_name(type->name)) return true; - return contains_type_ingredient_name(type->left) || contains_type_ingredient_name(type->right); -} - -recipe_ordinal new_variant(recipe_ordinal exemplar, const instruction& inst, const recipe& caller_recipe) { - cerr << "HHH " << contains_key(Type_ordinal, "_elem") << '\n'; - string new_name = next_unused_recipe_name(inst.name); - assert(!contains_key(Recipe_ordinal, new_name)); - recipe_ordinal new_recipe_ordinal = put(Recipe_ordinal, new_name, Next_recipe_ordinal++); - // make a copy - 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; - trace(9993, "transform") << "switching " << inst.name << " to specialized " << header_label(new_recipe_ordinal) << end(); - // Since the exemplar never ran any transforms, we have to redo some of the - // work of the check_types_by_name transform while supporting type-ingredients. - compute_type_names(new_recipe); - // that gives enough information to replace type-ingredients with concrete types - { - map<string, const type_tree*> mappings; - bool error = false; - cerr << "III " << contains_key(Type_ordinal, "_elem") << '\n'; - compute_type_ingredient_mappings(get(Recipe, exemplar), inst, mappings, caller_recipe, &error); - cerr << "JJJ " << contains_key(Type_ordinal, "_elem") << '\n'; - if (!error) replace_type_ingredients(new_recipe, mappings); - cerr << "VVV " << contains_key(Type_ordinal, "_elem") << '\n'; -//? if (!is_type_ingredient_name(x.type->name) && contains_key(Type_ordinal, x.type->name)) { -//? x.type->value = get(Type_ordinal, x.type->name); -//? return; -//? } - for (map<string, const type_tree*>::iterator p = mappings.begin(); p != mappings.end(); ++p) - delete p->second; - if (error) return 0; // todo: delete new_recipe_ordinal from Recipes and other global state - } - ensure_all_concrete_types(new_recipe, get(Recipe, exemplar)); - cerr << "WWW " << contains_key(Type_ordinal, "_elem") << '\n'; - return new_recipe_ordinal; -} - -void compute_type_names(recipe& variant) { - trace(9993, "transform") << "compute type names: " << variant.name << end(); - map<string, type_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, variant); - for (long long int i = 0; i < SIZE(variant.products); ++i) - 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: " << to_string(inst) << end(); - for (long long int in = 0; in < SIZE(inst.ingredients); ++in) - 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, variant); - } -} - -void save_or_deduce_type_name(reagent& x, map<string, type_tree*>& type, const recipe& variant) { - trace(9994, "transform") << " checking " << to_string(x) << ": " << names_to_string(x.type) << end(); - if (!x.type && contains_key(type, x.name)) { - x.type = new type_tree(*get(type, x.name)); - trace(9994, "transform") << " deducing type to " << names_to_string(x.type) << end(); - return; - } - if (!x.type) { - raise_error << maybe(variant.original_name) << "unknown type for " << x.original_string << " (check the name for typos)\n" << end(); - return; - } - if (contains_key(type, x.name)) return; - if (x.type->name == "offset" || x.type->name == "variant") return; // special-case for container-access instructions - put(type, x.name, x.type); - trace(9993, "transform") << "type of " << x.name << " is " << names_to_string(x.type) << end(); -} - -void compute_type_ingredient_mappings(const recipe& exemplar, const instruction& inst, map<string, const type_tree*>& mappings, const recipe& caller_recipe, bool* error) { - long long int limit = min(SIZE(inst.ingredients), SIZE(exemplar.ingredients)); - for (long long int i = 0; i < limit; ++i) { - const reagent& exemplar_reagent = exemplar.ingredients.at(i); - reagent ingredient = inst.ingredients.at(i); - canonize_type(ingredient); - if (is_mu_address(exemplar_reagent) && ingredient.name == "0") continue; // assume it matches - accumulate_type_ingredients(exemplar_reagent, ingredient, mappings, exemplar, inst, caller_recipe, error); - } - limit = min(SIZE(inst.products), SIZE(exemplar.products)); - for (long long int i = 0; i < limit; ++i) { - const reagent& exemplar_reagent = exemplar.products.at(i); - reagent product = inst.products.at(i); - canonize_type(product); - accumulate_type_ingredients(exemplar_reagent, product, mappings, exemplar, inst, caller_recipe, error); - } -} - -inline long long int min(long long int a, long long int b) { - return (a < b) ? a : b; -} - -void accumulate_type_ingredients(const reagent& exemplar_reagent, reagent& refinement, map<string, const type_tree*>& mappings, const recipe& exemplar, const instruction& call_instruction, const recipe& caller_recipe, bool* error) { - assert(refinement.type); - accumulate_type_ingredients(exemplar_reagent.type, refinement.type, mappings, exemplar, exemplar_reagent, call_instruction, caller_recipe, error); -} - -void accumulate_type_ingredients(const type_tree* exemplar_type, const type_tree* refinement_type, map<string, const type_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; - } - if (is_type_ingredient_name(exemplar_type->name)) { - assert(!refinement_type->name.empty()); - if (exemplar_type->right) { - raise_error << "type_ingredients in non-last position not currently supported\n" << end(); - return; - } - if (!contains_key(mappings, exemplar_type->name)) { - trace(9993, "transform") << "adding mapping from " << exemplar_type->name << " to " << to_string(refinement_type) << end(); - put(mappings, exemplar_type->name, new type_tree(*refinement_type)); - } - else { - if (!deeply_equal_type_names(get(mappings, exemplar_type->name), refinement_type)) { - raise_error << maybe(caller_recipe.name) << "no call found for '" << to_string(call_instruction) << "'\n" << end(); - *error = true; - return; - } - if (get(mappings, exemplar_type->name)->name == "literal") { - delete get(mappings, exemplar_type->name); - put(mappings, exemplar_type->name, new type_tree(*refinement_type)); - } - } - } - else { - accumulate_type_ingredients(exemplar_type->left, refinement_type->left, mappings, exemplar, exemplar_reagent, call_instruction, caller_recipe, error); - } - accumulate_type_ingredients(exemplar_type->right, refinement_type->right, mappings, exemplar, exemplar_reagent, call_instruction, caller_recipe, error); -} - -void replace_type_ingredients(recipe& new_recipe, const map<string, const type_tree*>& mappings) { - cerr << "KKK " << contains_key(Type_ordinal, "_elem") << '\n'; - // update its header - if (mappings.empty()) return; - cerr << "LLL " << contains_key(Type_ordinal, "_elem") << '\n'; - 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, new_recipe); - cerr << "MMM " << contains_key(Type_ordinal, "_elem") << '\n'; - 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, new_recipe); - // update its body - cerr << "NNN " << contains_key(Type_ordinal, "_elem") << '\n'; - for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) { - cerr << "OOO " << i << ' ' << contains_key(Type_ordinal, "_elem") << ' ' << to_string(new_recipe.steps.at(i)) << '\n'; - instruction& inst = new_recipe.steps.at(i); - trace(9993, "transform") << "replacing in instruction '" << to_string(inst) << "'" << end(); - for (long long int j = 0; j < SIZE(inst.ingredients); ++j) - 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, new_recipe); - // special-case for new: replace type ingredient in first ingredient *value* - if (inst.name == "new" && inst.ingredients.at(0).type->name != "literal-string") { - cerr << "PPP " << contains_key(Type_ordinal, "_elem") << '\n'; - type_tree* type = parse_type_tree(inst.ingredients.at(0).name); - cerr << "QQQ " << contains_key(Type_ordinal, "_elem") << '\n'; - replace_type_ingredients(type, mappings); - cerr << "RRR " << contains_key(Type_ordinal, "_elem") << '\n'; - inst.ingredients.at(0).name = inspect(type); - delete type; - cerr << "SSS " << contains_key(Type_ordinal, "_elem") << '\n'; - } - } - cerr << "TTT " << contains_key(Type_ordinal, "_elem") << '\n'; -} - -void replace_type_ingredients(reagent& x, const map<string, const type_tree*>& mappings, const recipe& caller) { - string before = to_string(x); - trace(9993, "transform") << "replacing in ingredient " << x.original_string << end(); - if (!x.type) { - raise_error << "specializing " << caller.original_name << ": missing type for " << x.original_string << '\n' << end(); - return; - } - replace_type_ingredients(x.type, mappings); -} - -void replace_type_ingredients(type_tree* type, const map<string, const type_tree*>& mappings) { - if (!type) return; - if (contains_key(Type_ordinal, type->name)) - type->value = get(Type_ordinal, type->name); - if (is_type_ingredient_name(type->name) && contains_key(mappings, type->name)) { - const type_tree* replacement = get(mappings, type->name); - //TODO names_to_string - trace(9993, "transform") << type->name << " => " << to_string(replacement) << end(); - if (replacement->name == "literal") { - type->name = "number"; - type->value = get(Type_ordinal, "number"); - } - else { - type->name = replacement->name; - type->value = replacement->value; - } - if (replacement->left) type->left = new type_tree(*replacement->left); - if (replacement->right) type->right = new type_tree(*replacement->right); - } - replace_type_ingredients(type->left, mappings); - replace_type_ingredients(type->right, mappings); -} - -type_tree* parse_type_tree(const string& s) { - istringstream in(s); - in >> std::noskipws; - return parse_type_tree(in); -} - -type_tree* parse_type_tree(istream& in) { - skip_whitespace_but_not_newline(in); - if (!has_data(in)) return NULL; - if (in.peek() == ')') { - in.get(); - return NULL; - } - if (in.peek() != '(') { - string type_name = next_word(in); - if (!contains_key(Type_ordinal, type_name)) - put(Type_ordinal, type_name, Next_type_ordinal++); - type_tree* result = new type_tree(type_name, get(Type_ordinal, type_name)); - return result; - } - in.get(); // skip '(' - type_tree* result = NULL; - type_tree** curr = &result; - while (in.peek() != ')') { - assert(has_data(in)); - *curr = new type_tree("", 0); - skip_whitespace_but_not_newline(in); - if (in.peek() == '(') - (*curr)->left = parse_type_tree(in); - else { - (*curr)->name = next_word(in); - if (!is_type_ingredient_name((*curr)->name)) { - if (!contains_key(Type_ordinal, (*curr)->name)) { - cerr << "RRR aaa " << contains_key(Type_ordinal, "_elem") << '\n'; - put(Type_ordinal, (*curr)->name, Next_type_ordinal++); - cerr << ">>> RRR zzz " << contains_key(Type_ordinal, "_elem") << '\n'; - } - (*curr)->value = get(Type_ordinal, (*curr)->name); - } - } - curr = &(*curr)->right; - } - in.get(); // skip ')' - return result; -} - -string inspect(const type_tree* x) { - ostringstream out; - dump_inspect(x, out); - return out.str(); -} - -void dump_inspect(const type_tree* x, ostream& out) { - if (!x->left && !x->right) { - out << x->name; - return; - } - out << '('; - for (const type_tree* curr = x; curr; curr = curr->right) { - if (curr != x) out << ' '; - if (curr->left) - dump_inspect(curr->left, out); - else - out << curr->name; - } - out << ')'; -} - -void ensure_all_concrete_types(/*const*/ recipe& new_recipe, const recipe& exemplar) { - for (long long int i = 0; i < SIZE(new_recipe.ingredients); ++i) - ensure_all_concrete_types(new_recipe.ingredients.at(i), exemplar); - for (long long int i = 0; i < SIZE(new_recipe.products); ++i) - ensure_all_concrete_types(new_recipe.products.at(i), exemplar); - for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) { - instruction& inst = new_recipe.steps.at(i); - for (long long int j = 0; j < SIZE(inst.ingredients); ++j) - ensure_all_concrete_types(inst.ingredients.at(j), exemplar); - for (long long int j = 0; j < SIZE(inst.products); ++j) - ensure_all_concrete_types(inst.products.at(j), exemplar); - } -} - -void ensure_all_concrete_types(/*const*/ reagent& x, const recipe& exemplar) { - if (!x.type || contains_type_ingredient_name(x.type)) { - raise_error << maybe(exemplar.name) << "failed to map a type to " << x.original_string << '\n' << end(); - x.type = new type_tree("", 0); // just to prevent crashes later - return; - } - if (x.type->value == -1) { - raise_error << maybe(exemplar.name) << "failed to map a type to the unknown " << x.original_string << '\n' << end(); - return; - } -} - -void test_shape_shifting_recipe_2() { - Trace_file = "shape_shifting_recipe_2"; - run("recipe main [\n 10:point <- merge 14, 15\n 11:point <- foo 10:point\n]\n# non-matching shape-shifting variant\nrecipe foo a:_t, b:_t -> result:number [\n local-scope\n load-ingredients\n result <- copy 34\n]\n# matching shape-shifting variant\nrecipe foo a:_t -> result:_t [\n local-scope\n load-ingredients\n result <- copy a\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 14 in location 11mem: storing 15 in location 12"); -} -void test_shape_shifting_recipe_nonroot() { - Trace_file = "shape_shifting_recipe_nonroot"; - run("recipe main [\n 10:foo:point <- merge 14, 15, 16\n 20:point/raw <- bar 10:foo:point\n]\n# shape-shifting recipe with type ingredient following some other type\nrecipe bar a:foo:_t -> result:_t [\n local-scope\n load-ingredients\n result <- get a, x:offset\n]\ncontainer foo:_t [\n x:_t\n y:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 14 in location 20mem: storing 15 in location 21"); -} -void test_shape_shifting_recipe_type_deduction_ignores_offsets() { - Trace_file = "shape_shifting_recipe_type_deduction_ignores_offsets"; - run("recipe main [\n 10:foo:point <- merge 14, 15, 16\n 20:point/raw <- bar 10:foo:point\n]\nrecipe bar a:foo:_t -> result:_t [\n local-scope\n load-ingredients\n x:number <- copy 1\n result <- get a, x:offset # shouldn't collide with other variable\n]\ncontainer foo:_t [\n x:_t\n y:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 14 in location 20mem: storing 15 in location 21"); -} -void test_shape_shifting_recipe_empty() { - Trace_file = "shape_shifting_recipe_empty"; - run("recipe main [\n foo 1\n]\n# shape-shifting recipe with no body\nrecipe foo a:_t [\n]\n# shouldn't crash\n"); -} -void test_shape_shifting_recipe_handles_shape_shifting_new_ingredient() { - Trace_file = "shape_shifting_recipe_handles_shape_shifting_new_ingredient"; - run("recipe main [\n 1:address:shared:foo:point <- bar 3\n 11:foo:point <- copy *1:address:shared:foo:point\n]\ncontainer foo:_t [\n x:_t\n y:number\n]\nrecipe bar x:number -> result:address:shared:foo:_t [\n local-scope\n load-ingredients\n # new refers to _t in its ingredient *value*\n result <- new {(foo _t) : type}\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 11mem: storing 0 in location 12mem: storing 0 in location 13"); -} -void test_shape_shifting_recipe_handles_shape_shifting_new_ingredient_2() { - Trace_file = "shape_shifting_recipe_handles_shape_shifting_new_ingredient_2"; - run("recipe main [\n 1:address:shared:foo:point <- bar 3\n 11:foo:point <- copy *1:address:shared:foo:point\n]\nrecipe bar x:number -> result:address:shared:foo:_t [\n local-scope\n load-ingredients\n # new refers to _t in its ingredient *value*\n result <- new {(foo _t) : type}\n]\n# container defined after use\ncontainer foo:_t [\n x:_t\n y:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 11mem: storing 0 in location 12mem: storing 0 in location 13"); -} -void test_shape_shifting_recipe_supports_compound_types() { - Trace_file = "shape_shifting_recipe_supports_compound_types"; - run("recipe main [\n 1:address:shared:point <- new point:type\n 2:address:number <- get-address *1:address:shared:point, y:offset\n *2:address:number <- copy 34\n 3:address:shared:point <- bar 1:address:shared:point # specialize _t to address:shared:point\n 4:point <- copy *3:address:shared:point\n]\nrecipe bar a:_t -> result:_t [\n local-scope\n load-ingredients\n result <- copy a\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 5"); -} -void test_shape_shifting_recipe_error() { - Trace_file = "shape_shifting_recipe_error"; - Hide_errors = true; - run("recipe main [\n a:number <- copy 3\n b:address:shared:number <- foo a\n]\nrecipe foo a:_t -> b:_t [\n load-ingredients\n b <- copy a\n]\n"); - CHECK_TRACE_CONTENTS("error: main: no call found for 'b:address:shared:number <- foo a'"); -} -void test_specialize_inside_recipe_without_header() { - Trace_file = "specialize_inside_recipe_without_header"; - run("recipe main [\n foo 3\n]\nrecipe foo [\n local-scope\n x:number <- next-ingredient # ensure no header\n 1:number/raw <- bar x # call a shape-shifting recipe\n]\nrecipe bar x:_elem -> y:_elem [\n local-scope\n load-ingredients\n y <- add x, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 1"); -} -void test_specialize_with_literal() { - Trace_file = "specialize_with_literal"; - run("recipe main [\n local-scope\n # permit literal to map to number\n 1:number/raw <- foo 3\n]\nrecipe foo x:_elem -> y:_elem [\n local-scope\n load-ingredients\n y <- add x, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 1"); -} -void test_specialize_with_literal_2() { - Trace_file = "specialize_with_literal_2"; - run("recipe main [\n local-scope\n # permit literal to map to character\n 1:character/raw <- foo 3\n]\nrecipe foo x:_elem -> y:_elem [\n local-scope\n load-ingredients\n y <- add x, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 1"); -} -void test_specialize_with_literal_3() { - Trace_file = "specialize_with_literal_3"; - Hide_errors = true; - run("recipe main [\n local-scope\n # permit '0' to map to address to shape-shifting type-ingredient\n 1:address:shared:character/raw <- foo 0\n]\nrecipe foo x:address:_elem -> y:address:_elem [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 1"); - CHECK_TRACE_COUNT("error", 0); -} -void test_specialize_with_literal_4() { - Trace_file = "specialize_with_literal_4"; - Hide_errors = true; - run("recipe main [\n local-scope\n # ambiguous call: what's the type of its ingredient?!\n foo 0\n]\nrecipe foo x:address:_elem -> y:address:_elem [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("error: foo: failed to map a type to xerror: foo: failed to map a type to y"); -} -void test_specialize_with_literal_5() { - Trace_file = "specialize_with_literal_5"; - run("recipe main [\n foo 3, 4 # recipe mapping two variables to literals\n]\nrecipe foo x:_elem, y:_elem [\n local-scope\n load-ingredients\n 1:number/raw <- add x, y\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 7 in location 1"); -} -void test_multiple_shape_shifting_variants() { - Trace_file = "multiple_shape_shifting_variants"; - run("# try to call two different shape-shifting recipes with the same name\nrecipe main [\n e1:d1:number <- merge 3\n e2:d2:number <- merge 4, 5\n 1:number/raw <- foo e1\n 2:number/raw <- foo e2\n]\n# the two shape-shifting definitions\nrecipe foo a:d1:_elem -> b:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo a:d2:_elem -> b:number [\n local-scope\n load-ingredients\n reply 35\n]\n# the shape-shifting containers they use\ncontainer d1:_elem [\n x:_elem\n]\ncontainer d2:_elem [\n x:number\n y:_elem\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1mem: storing 35 in location 2"); -} -void test_multiple_shape_shifting_variants_2() { - Trace_file = "multiple_shape_shifting_variants_2"; - run("# static dispatch between shape-shifting variants, _including pointer lookups_\nrecipe main [\n e1:d1:number <- merge 3\n e2:address:shared:d2:number <- new {(d2 number): type}\n 1:number/raw <- foo e1\n 2:number/raw <- foo *e2 # different from previous scenario\n]\nrecipe foo a:d1:_elem -> b:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo a:d2:_elem -> b:number [\n local-scope\n load-ingredients\n reply 35\n]\ncontainer d1:_elem [\n x:_elem\n]\ncontainer d2:_elem [\n x:number\n y:_elem\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1mem: storing 35 in location 2"); -} -void test_missing_type_in_shape_shifting_recipe() { - Trace_file = "missing_type_in_shape_shifting_recipe"; - Hide_errors = true; - run("recipe main [\n a:d1:number <- merge 3\n foo a\n]\nrecipe foo a:d1:_elem -> b:number [\n local-scope\n load-ingredients\n copy e # no such variable\n reply 34\n]\ncontainer d1:_elem [\n x:_elem\n]\n"); - CHECK_TRACE_CONTENTS("error: foo: unknown type for e (check the name for typos)error: specializing foo: missing type for e"); -} -void test_missing_type_in_shape_shifting_recipe_2() { - Trace_file = "missing_type_in_shape_shifting_recipe_2"; - Hide_errors = true; - run("recipe main [\n a:d1:number <- merge 3\n foo a\n]\nrecipe foo a:d1:_elem -> b:number [\n local-scope\n load-ingredients\n get e, x:offset # unknown variable in a 'get', which does some extra checking\n reply 34\n]\ncontainer d1:_elem [\n x:_elem\n]\n"); - CHECK_TRACE_CONTENTS("error: foo: unknown type for e (check the name for typos)error: specializing foo: missing type for e"); -} -void test_specialize_recursive_shape_shifting_recipe() { - Trace_file = "specialize_recursive_shape_shifting_recipe"; - transform("recipe main [\n 1:number <- copy 34\n 2:number <- foo 1:number\n]\nrecipe foo x:_elem -> y:number [\n local-scope\n load-ingredients\n {\n break\n y:number <- foo x\n }\n reply y\n]\n"); - CHECK_TRACE_CONTENTS("transform: new specialization: foo_2"); -} -void test_specialize_most_similar_variant() { - Trace_file = "specialize_most_similar_variant"; - run("recipe main [\n 1:address:shared:number <- new number:type\n 2:number <- foo 1:address:shared:number\n]\nrecipe foo x:_elem -> y:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo x:address:shared:_elem -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 35 in location 2"); -} -void test_specialize_most_similar_variant_2() { - Trace_file = "specialize_most_similar_variant_2"; - run("# version with headers padded with lots of unrelated concrete types\nrecipe main [\n 1:number <- copy 23\n 2:address:shared:array:number <- copy 0\n 3:number <- foo 2:address:shared:array:number, 1:number\n]\n# variant with concrete type\nrecipe foo dummy:address:shared:array:number, x:number -> y:number, dummy:address:shared:array:number [\n local-scope\n load-ingredients\n reply 34\n]\n# shape-shifting variant\nrecipe foo dummy:address:shared:array:number, x:_elem -> y:number, dummy:address:shared:array:number [\n local-scope\n load-ingredients\n reply 35\n]\n# prefer the concrete variant\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 3"); -} -void test_specialize_most_similar_variant_3() { - Trace_file = "specialize_most_similar_variant_3"; - run("recipe main [\n 1:address:shared:array:character <- new [abc]\n foo 1:address:shared:array:character\n]\nrecipe foo x:address:shared:array:character [\n 2:number <- copy 34\n]\nrecipe foo x:address:_elem [\n 2:number <- copy 35\n]\n# make sure the more precise version was used\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 2"); -} -void test_specialize_literal_as_number() { - Trace_file = "specialize_literal_as_number"; - run("recipe main [\n 1:number <- foo 23\n]\nrecipe foo x:_elem -> y:number [\n local-scope\n load-ingredients\n reply 34\n]\nrecipe foo x:character -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1"); -} -void test_specialize_literal_as_number_2() { - Trace_file = "specialize_literal_as_number_2"; - run("# version calling with literal\nrecipe main [\n 1:number <- foo 0\n]\n# variant with concrete type\nrecipe foo x:number -> y:number [\n local-scope\n load-ingredients\n reply 34\n]\n# shape-shifting variant\nrecipe foo x:address:shared:_elem -> y:number [\n local-scope\n load-ingredients\n reply 35\n]\n# prefer the concrete variant, ignore concrete types in scoring the shape-shifting variant\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1"); -} - -void test_can_modify_value_ingredients() { - Trace_file = "can_modify_value_ingredients"; - Hide_warnings = true; - run("recipe main [\n local-scope\n p:address:shared:point <- new point:type\n foo *p\n]\nrecipe foo p:point [\n local-scope\n load-ingredients\n x:address:number <- get-address p, x:offset\n *x <- copy 34\n]\n"); - CHECK_TRACE_COUNT("warn", 0); -} -void test_can_modify_ingredients_that_are_also_products() { - Trace_file = "can_modify_ingredients_that_are_also_products"; - Hide_warnings = true; - run("recipe main [\n local-scope\n p:address:shared:point <- new point:type\n p <- foo p\n]\nrecipe foo p:address:shared:point -> p:address:shared:point [\n local-scope\n load-ingredients\n x:address:number <- get-address *p, x:offset\n *x <- copy 34\n]\n"); - CHECK_TRACE_COUNT("warn", 0); -} -void test_ignore_literal_ingredients_for_immutability_checks() { - Trace_file = "ignore_literal_ingredients_for_immutability_checks"; - Hide_warnings = true; - run("recipe main [\n local-scope\n p:address:shared:d1 <- new d1:type\n q:number <- foo p\n]\nrecipe foo p:address:shared:d1 -> q:number [\n local-scope\n load-ingredients\n x:address:shared:d1 <- new d1:type\n y:address:number <- get-address *x, p:offset # ignore this 'p'\n q <- copy 34\n]\ncontainer d1 [\n p:number\n q:number\n]\n"); - CHECK_TRACE_COUNT("warn", 0); -} -void test_cannot_take_address_inside_immutable_ingredients() { - Trace_file = "cannot_take_address_inside_immutable_ingredients"; - Hide_warnings = true; - run("recipe main [\n local-scope\n p:address:shared:point <- new point:type\n foo p\n]\nrecipe foo p:address:shared:point [\n local-scope\n load-ingredients\n x:address:number <- get-address *p, x:offset\n *x <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("warn: foo: cannot modify ingredient p after instruction 'x:address:number <- get-address *p, x:offset' because it's not also a product of foo"); -} -void test_cannot_call_mutating_recipes_on_immutable_ingredients() { - Trace_file = "cannot_call_mutating_recipes_on_immutable_ingredients"; - Hide_warnings = true; - run("recipe main [\n local-scope\n p:address:shared:point <- new point:type\n foo p\n]\nrecipe foo p:address:shared:point [\n local-scope\n load-ingredients\n bar p\n]\nrecipe bar p:address:shared:point -> p:address:shared:point [\n local-scope\n load-ingredients\n x:address:number <- get-address *p, x:offset\n *x <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("warn: foo: cannot modify ingredient p at instruction 'bar p' because it's not also a product of foo"); -} -void test_cannot_modify_copies_of_immutable_ingredients() { - Trace_file = "cannot_modify_copies_of_immutable_ingredients"; - Hide_warnings = true; - run("recipe main [\n local-scope\n p:address:shared:point <- new point:type\n foo p\n]\nrecipe foo p:address:shared:point [\n local-scope\n load-ingredients\n q:address:shared:point <- copy p\n x:address:number <- get-address *q, x:offset\n]\n"); - CHECK_TRACE_CONTENTS("warn: foo: cannot modify q after instruction 'x:address:number <- get-address *q, x:offset' because that would modify ingredient p which is not also a product of foo"); -} -void test_can_traverse_immutable_ingredients() { - Trace_file = "can_traverse_immutable_ingredients"; - Hide_warnings = true; - run("container test-list [\n next:address:shared:test-list\n]\nrecipe main [\n local-scope\n p:address:shared:test-list <- new test-list:type\n foo p\n]\nrecipe foo p:address:shared:test-list [\n local-scope\n load-ingredients\n p2:address:shared:test-list <- bar p\n]\nrecipe bar x:address:shared:test-list -> y:address:shared:test-list [\n local-scope\n load-ingredients\n y <- get *x, next:offset\n]\n"); - CHECK_TRACE_COUNT("warn", 0); -} -void test_handle_optional_ingredients_in_immutability_checks() { - Trace_file = "handle_optional_ingredients_in_immutability_checks"; - Hide_warnings = true; - run("recipe main [\n k:address:shared:number <- new number:type\n test k\n]\n# recipe taking an immutable address ingredient\nrecipe test k:address:shared:number [\n local-scope\n load-ingredients\n foo k\n]\n# ..calling a recipe with an optional address ingredient\nrecipe foo -> [\n local-scope\n load-ingredients\n k:address:shared:number, found?:boolean <- next-ingredient\n]\n"); - CHECK_TRACE_COUNT("warn", 0); -} -void check_immutable_ingredients(recipe_ordinal r) { - // to ensure a reagent isn't modified, it suffices to show that we never - // call get-address or index-address with it, and that any non-primitive - // recipe calls in the body aren't returning it as a product. - const recipe& caller = get(Recipe, r); - trace(9991, "transform") << "--- check mutability of ingredients in recipe " << caller.name << end(); - if (!caller.has_header) return; // skip check for old-style recipes calling next-ingredient directly - for (long long int i = 0; i < SIZE(caller.ingredients); ++i) { - const reagent& current_ingredient = caller.ingredients.at(i); - if (!is_mu_address(current_ingredient)) continue; // will be copied - if (is_present_in_products(caller, current_ingredient.name)) continue; // not expected to be immutable - if (has_property(current_ingredient, "contained-in")) { - const string_tree* tmp = property(current_ingredient, "contained-in"); - if (tmp->left || tmp->right - || !is_present_in_ingredients(caller, tmp->value) - || !is_present_in_products(caller, tmp->value)) - raise_error << maybe(caller.name) << "contained-in can only point to another ingredient+product, but got " << to_string(property(current_ingredient, "contained-in")) << '\n' << end(); - continue; - } - - // End Immutable Ingredients Special-cases - set<string> immutable_vars; - immutable_vars.insert(current_ingredient.name); - for (long long int i = 0; i < SIZE(caller.steps); ++i) { - const instruction& inst = caller.steps.at(i); - check_immutable_ingredient_in_instruction(inst, immutable_vars, current_ingredient.name, caller); - update_aliases(inst, immutable_vars); - } - } -} - -void update_aliases(const instruction& inst, set<string>& current_ingredient_and_aliases) { - set<long long int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases); - if (!contains_key(Recipe, inst.operation)) { - // primitive recipe - if (inst.operation == COPY) { - for (set<long long int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) { - current_ingredient_and_aliases.insert(inst.products.at(*p).name); - } - } - } - else { - // defined recipe - set<long long int> contained_in_product_indices = scan_contained_in_product_indices(inst, current_ingredient_indices); - for (set<long long int>::iterator p = contained_in_product_indices.begin(); p != contained_in_product_indices.end(); ++p) { - if (*p < SIZE(inst.products)) - current_ingredient_and_aliases.insert(inst.products.at(*p).name); - } - } -} - -set<long long int> scan_contained_in_product_indices(const instruction& inst, set<long long int>& ingredient_indices) { - set<string> selected_ingredient_names; - const recipe& callee = get(Recipe, inst.operation); - for (set<long long int>::iterator p = ingredient_indices.begin(); p != ingredient_indices.end(); ++p) { - if (*p >= SIZE(callee.ingredients)) continue; // optional immutable ingredient - selected_ingredient_names.insert(callee.ingredients.at(*p).name); - } - set<long long int> result; - for (long long int i = 0; i < SIZE(callee.products); ++i) { - const reagent& current_product = callee.products.at(i); - const string_tree* contained_in_name = property(current_product, "contained-in"); - if (contained_in_name && selected_ingredient_names.find(contained_in_name->value) != selected_ingredient_names.end()) - result.insert(i); - } - return result; -} - -void test_immutability_infects_contained_in_variables() { - Trace_file = "immutability_infects_contained_in_variables"; - Hide_warnings = true; - transform("container test-list [\n next:address:shared:test-list\n]\nrecipe main [\n local-scope\n p:address:shared:test-list <- new test-list:type\n foo p\n]\nrecipe foo p:address:shared:test-list [ # p is immutable\n local-scope\n load-ingredients\n p2:address:shared:test-list <- test-next p # p2 is immutable\n p3:address:address:shared:test-list <- get-address *p2, next:offset # signal modification of p2\n]\nrecipe test-next x:address:shared:test-list -> y:address:shared:test-list/contained-in:x [\n local-scope\n load-ingredients\n y <- get *x, next:offset\n]\n"); - CHECK_TRACE_CONTENTS("warn: foo: cannot modify p2 after instruction 'p3:address:address:shared:test-list <- get-address *p2, next:offset' because that would modify ingredient p which is not also a product of foo"); -} -void check_immutable_ingredient_in_instruction(const instruction& inst, const set<string>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) { - set<long long int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases); - if (current_ingredient_indices.empty()) return; // ingredient not found in call - for (set<long long int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) { - const long long int current_ingredient_index = *p; - reagent current_ingredient = inst.ingredients.at(current_ingredient_index); - canonize_type(current_ingredient); - const string& current_ingredient_name = current_ingredient.name; - if (!contains_key(Recipe, inst.operation)) { - // primitive recipe - if (inst.operation == GET_ADDRESS || inst.operation == INDEX_ADDRESS) { - if (current_ingredient_name == original_ingredient_name) - raise << maybe(caller.name) << "cannot modify ingredient " << current_ingredient_name << " after instruction '" << to_string(inst) << "' because it's not also a product of " << caller.name << '\n' << end(); - else - raise << maybe(caller.name) << "cannot modify " << current_ingredient_name << " after instruction '" << to_string(inst) << "' because that would modify ingredient " << original_ingredient_name << " which is not also a product of " << caller.name << '\n' << end(); - } - } - else { - // defined recipe - if (!is_mu_address(current_ingredient)) return; // making a copy is ok - if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) { - if (current_ingredient_name == original_ingredient_name) - raise << maybe(caller.name) << "cannot modify ingredient " << current_ingredient_name << " at instruction '" << to_string(inst) << "' because it's not also a product of " << caller.name << '\n' << end(); - else - raise << maybe(caller.name) << "cannot modify " << current_ingredient_name << " after instruction '" << to_string(inst) << "' because that would modify ingredient " << original_ingredient_name << " which is not also a product of " << caller.name << '\n' << end(); - } - } - } -} - -bool is_modified_in_recipe(recipe_ordinal r, long long int ingredient_index, const recipe& caller) { - const recipe& callee = get(Recipe, r); - if (!callee.has_header) { - raise << maybe(caller.name) << "can't check mutability of ingredients in " << callee.name << " because it uses 'next-ingredient' directly, rather than a recipe header.\n" << end(); - return true; - } - if (ingredient_index >= SIZE(callee.ingredients)) return false; // optional immutable ingredient - return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name); -} - -bool is_present_in_products(const recipe& callee, const string& ingredient_name) { - for (long long int i = 0; i < SIZE(callee.products); ++i) { - if (callee.products.at(i).name == ingredient_name) - return true; - } - return false; -} - -bool is_present_in_ingredients(const recipe& callee, const string& ingredient_name) { - for (long long int i = 0; i < SIZE(callee.ingredients); ++i) { - if (callee.ingredients.at(i).name == ingredient_name) - return true; - } - return false; -} - -set<long long int> ingredient_indices(const instruction& inst, const set<string>& ingredient_names) { - set<long long int> result; - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (is_literal(inst.ingredients.at(i))) continue; - if (ingredient_names.find(inst.ingredients.at(i).name) != ingredient_names.end()) - result.insert(i); - } - return result; -} - - -void test_can_modify_contained_in_addresses() { - Trace_file = "can_modify_contained_in_addresses"; - Hide_warnings = true; - transform("container test-list [\n next:address:shared:test-list\n]\nrecipe main [\n local-scope\n p:address:shared:test-list <- new test-list:type\n foo p\n]\nrecipe foo p:address:shared:test-list -> p:address:shared:test-list [\n local-scope\n load-ingredients\n p2:address:shared:test-list <- test-next p\n p <- test-remove p2, p\n]\nrecipe test-next x:address:shared:test-list -> y:address:shared:test-list [\n local-scope\n load-ingredients\n y <- get *x, next:offset\n]\nrecipe test-remove x:address:shared:test-list/contained-in:from, from:address:shared:test-list -> from:address:shared:test-list [\n local-scope\n load-ingredients\n x2:address:address:shared:test-list <- get-address *x, next:offset # pretend modification\n]\n"); - CHECK_TRACE_COUNT("warn", 0); -} - -void test_call_literal_recipe() { - Trace_file = "call_literal_recipe"; - run("recipe main [\n 1:number <- call f, 34\n]\nrecipe f x:number -> y:number [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1"); -} -void test_call_variable() { - Trace_file = "call_variable"; - run("recipe main [\n {1: (recipe number -> number)} <- copy f\n 2:number <- call {1: (recipe number -> number)}, 34\n]\nrecipe f x:number -> y:number [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 2"); -} -void test_call_check_literal_recipe() { - Trace_file = "call_check_literal_recipe"; - Hide_errors = true; - run("recipe main [\n 1:number <- call f, 34\n]\nrecipe f x:boolean -> y:boolean [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '1:number <- call f, 34'error: main: product 0 has the wrong type at '1:number <- call f, 34'"); -} -void test_call_check_variable_recipe() { - Trace_file = "call_check_variable_recipe"; - Hide_errors = true; - run("recipe main [\n {1: (recipe boolean -> boolean)} <- copy f\n 2:number <- call {1: (recipe boolean -> boolean)}, 34\n]\nrecipe f x:boolean -> y:boolean [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '2:number <- call {1: (recipe boolean -> boolean)}, 34'error: main: product 0 has the wrong type at '2:number <- call {1: (recipe boolean -> boolean)}, 34'"); -} -void check_indirect_calls_against_header(const recipe_ordinal r) { - trace(9991, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end(); - const recipe& caller = get(Recipe, r); - for (long long int i = 0; i < SIZE(caller.steps); ++i) { - const instruction& inst = caller.steps.at(i); - if (inst.operation != CALL) continue; - if (inst.ingredients.empty()) continue; // error raised above - const reagent& callee = inst.ingredients.at(0); - if (!is_mu_recipe(callee)) continue; // error raised above - const recipe callee_header = is_literal(callee) ? get(Recipe, callee.value) : from_reagent(inst.ingredients.at(0)); - if (!callee_header.has_header) continue; - for (long int i = /*skip callee*/1; i < min(SIZE(inst.ingredients), SIZE(callee_header.ingredients)+/*skip callee*/1); ++i) { - if (!types_coercible(callee_header.ingredients.at(i-/*skip callee*/1), inst.ingredients.at(i))) - raise_error << maybe(caller.name) << "ingredient " << i-/*skip callee*/1 << " has the wrong type at '" << to_string(inst) << "'\n" << end(); - } - for (long int i = 0; i < min(SIZE(inst.products), SIZE(callee_header.products)); ++i) { - if (is_dummy(inst.products.at(i))) continue; - if (!types_coercible(callee_header.products.at(i), inst.products.at(i))) - raise_error << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_string(inst) << "'\n" << end(); - } - } -} - -recipe from_reagent(const reagent& r) { - assert(r.type->name == "recipe"); - recipe result_header; // will contain only ingredients and products, nothing else - result_header.has_header = true; - const type_tree* curr = r.type->right; - for (; curr; curr=curr->right) { - if (curr->name == "->") { - curr = curr->right; // skip delimiter - break; - } - result_header.ingredients.push_back("recipe:"+curr->name); - } - for (; curr; curr=curr->right) - result_header.products.push_back("recipe:"+curr->name); - return result_header; -} - -bool is_mu_recipe(reagent r) { - if (!r.type) return false; - if (r.type->name == "recipe") return true; - if (r.type->name == "recipe-literal") return true; - // End is_mu_recipe Cases - return false; -} - -void test_copy_typecheck_recipe_variable() { - Trace_file = "copy_typecheck_recipe_variable"; - Hide_errors = true; - run("recipe main [\n 3:number <- copy 34 # abc def\n {1: (recipe number -> number)} <- copy f # store literal in a matching variable\n {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)} # mismatch between recipe variables\n]\nrecipe f x:number -> y:number [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("error: main: can't copy {1: (recipe number -> number)} to {2: (recipe boolean -> boolean)}; types don't match"); -} -void test_copy_typecheck_recipe_variable_2() { - Trace_file = "copy_typecheck_recipe_variable_2"; - Hide_errors = true; - run("recipe main [\n {1: (recipe number -> number)} <- copy f # mismatch with a recipe literal\n]\nrecipe f x:boolean -> y:boolean [\n local-scope\n load-ingredients\n y <- copy x\n]\n"); - CHECK_TRACE_CONTENTS("error: main: can't copy f to {1: (recipe number -> number)}; types don't match"); -} - -void test_scheduler() { - Trace_file = "scheduler"; - run("recipe f1 [\n start-running f2\n # wait for f2 to run\n {\n jump-unless 1:number, -1\n }\n]\nrecipe f2 [\n 1:number <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("schedule: f1schedule: f2"); -} -void run(routine* rr) { - Routines.push_back(rr); - Current_routine_index = 0, Current_routine = Routines.at(0); - while (!all_routines_done()) { - skip_to_next_routine(); - assert(Current_routine); - assert(Current_routine->state == RUNNING); - trace(9990, "schedule") << current_routine_label() << end(); - run_current_routine(Scheduling_interval); - // Scheduler State Transitions - if (Current_routine->completed()) - Current_routine->state = COMPLETED; - if (Current_routine->limit >= 0) { - if (Current_routine->limit <= Scheduling_interval) { - trace(9999, "schedule") << "discontinuing routine " << Current_routine->id << end(); - Current_routine->state = DISCONTINUED; - Current_routine->limit = 0; - } - else { - Current_routine->limit -= Scheduling_interval; - } - } - - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->state != WAITING) continue; - if (Routines.at(i)->waiting_on_location && - get_or_insert(Memory, Routines.at(i)->waiting_on_location) != Routines.at(i)->old_value_of_waiting_location) { - trace(9999, "schedule") << "waking up routine\n" << end(); - Routines.at(i)->state = RUNNING; - Routines.at(i)->waiting_on_location = Routines.at(i)->old_value_of_waiting_location = 0; - } - } - - - // Wake up any routines waiting for other routines to go to sleep. - // Important: this must come after the scheduler loop above giving routines - // waiting for locations to change a chance to wake up. - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->state != WAITING) continue; - if (!Routines.at(i)->waiting_on_routine) continue; - long long int id = Routines.at(i)->waiting_on_routine; - assert(id != Routines.at(i)->id); // routine can't wait on itself - for (long long int j = 0; j < SIZE(Routines); ++j) { - if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) { - trace(9999, "schedule") << "waking up routine " << Routines.at(i)->id << end(); - Routines.at(i)->state = RUNNING; - Routines.at(i)->waiting_on_routine = 0; - } - } - } - - // End Scheduler State Transitions - - // Scheduler Cleanup - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->state == COMPLETED) continue; - if (Routines.at(i)->parent_index < 0) continue; // root thread - if (has_completed_parent(i)) { - Routines.at(i)->state = COMPLETED; - } - } - - // End Scheduler Cleanup - } -} - -bool all_routines_done() { - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->state == RUNNING) { - return false; - } - } - return true; -} - -// skip Current_routine_index past non-RUNNING routines -void skip_to_next_routine() { - assert(!Routines.empty()); - assert(Current_routine_index < SIZE(Routines)); - for (long long int i = (Current_routine_index+1)%SIZE(Routines); i != Current_routine_index; i = (i+1)%SIZE(Routines)) { - if (Routines.at(i)->state == RUNNING) { - Current_routine_index = i; - Current_routine = Routines.at(i); - return; - } - } -} - -string current_routine_label() { - ostringstream result; - const call_stack& calls = Current_routine->calls; - for (call_stack::const_iterator p = calls.begin(); p != calls.end(); ++p) { - if (p != calls.begin()) result << '/'; - result << get(Recipe, p->running_recipe).name; - } - return result.str(); -} - -void test_scheduler_runs_single_routine() { - Trace_file = "scheduler_runs_single_routine"; - Scheduling_interval = 1; - run("recipe f1 [\n 1:number <- copy 0\n 2:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("schedule: f1run: 1:number <- copy 0schedule: f1run: 2:number <- copy 0"); -} -void test_scheduler_interleaves_routines() { - Trace_file = "scheduler_interleaves_routines"; - Scheduling_interval = 1; - run("recipe f1 [\n start-running f2\n 1:number <- copy 0\n 2:number <- copy 0\n]\nrecipe f2 [\n 3:number <- copy 0\n 4:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("schedule: f1run: start-running f2schedule: f2run: 3:number <- copy 0schedule: f1run: 1:number <- copy 0schedule: f2run: 4:number <- copy 0schedule: f1run: 2:number <- copy 0"); -} -void test_start_running_takes_ingredients() { - Trace_file = "start_running_takes_ingredients"; - run("recipe f1 [\n start-running f2, 3\n # wait for f2 to run\n {\n jump-unless 1:number, -1\n }\n]\nrecipe f2 [\n 1:number <- next-ingredient\n 2:number <- add 1:number, 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 4 in location 2"); -} -void test_start_running_returns_routine_id() { - Trace_file = "start_running_returns_routine_id"; - run("recipe f1 [\n 1:number <- start-running f2\n]\nrecipe f2 [\n 12:number <- copy 44\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 2 in location 1"); -} -void test_scheduler_skips_completed_routines() { - Trace_file = "scheduler_skips_completed_routines"; - recipe_ordinal f1 = load("recipe f1 [\n1:number <- copy 0\n]\n").front(); - recipe_ordinal f2 = load("recipe f2 [\n2:number <- copy 0\n]\n").front(); - Routines.push_back(new routine(f1)); // f1 meant to run - Routines.push_back(new routine(f2)); - Routines.back()->state = COMPLETED; // f2 not meant to run - run("# must have at least one routine without escaping\nrecipe f3 [\n 3:number <- copy 0\n]\n# by interleaving '+' lines with '-' lines, we allow f1 and f3 to run in any order\n"); - CHECK_TRACE_CONTENTS("schedule: f1mem: storing 0 in location 1"); - CHECK_TRACE_DOESNT_CONTAIN("schedule: f2"); - CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 2"); - run(""); - CHECK_TRACE_CONTENTS("schedule: f3mem: storing 0 in location 3"); -} -void test_scheduler_starts_at_middle_of_routines() { - Trace_file = "scheduler_starts_at_middle_of_routines"; - Routines.push_back(new routine(COPY)); - Routines.back()->state = COMPLETED; - run("recipe f1 [\n 1:number <- copy 0\n 2:number <- copy 0\n]\n"); - CHECK_TRACE_CONTENTS("schedule: f1"); - CHECK_TRACE_DOESNT_CONTAIN("run: idle"); -} -void test_scheduler_terminates_routines_after_errors() { - Trace_file = "scheduler_terminates_routines_after_errors"; - Hide_errors = true; - Scheduling_interval = 2; - run("recipe f1 [\n start-running f2\n 1:number <- copy 0\n 2:number <- copy 0\n]\nrecipe f2 [\n # divide by 0 twice\n 3:number <- divide-with-remainder 4, 0\n 4:number <- divide-with-remainder 4, 0\n]\n# f2 should stop after first divide by 0\n"); - CHECK_TRACE_CONTENTS("error: f2: divide by zero in '3:number <- divide-with-remainder 4, 0'"); - CHECK_TRACE_DOESNT_CONTAIN("error: f2: divide by zero in '4:number <- divide-with-remainder 4, 0'"); -} -void test_scheduler_kills_orphans() { - Trace_file = "scheduler_kills_orphans"; - run("recipe main [\n start-running f1\n # f1 never actually runs because its parent completes without waiting for it\n]\nrecipe f1 [\n 1:number <- copy 0\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("schedule: f1"); -} -bool has_completed_parent(long long int routine_index) { - for (long long int j = routine_index; j >= 0; j = Routines.at(j)->parent_index) { - if (Routines.at(j)->state == COMPLETED) - return true; - } - return false; -} - - -void test_routine_state_test() { - Trace_file = "routine_state_test"; - Scheduling_interval = 2; - run("recipe f1 [\n 1:number/child-id <- start-running f2\n 12:number <- copy 0 # race condition since we don't care about location 12\n # thanks to Scheduling_interval, f2's one instruction runs in between here and completes\n 2:number/state <- routine-state 1:number/child-id\n]\nrecipe f2 [\n 12:number <- copy 0\n # trying to run a second instruction marks routine as completed\n]\n# recipe f2 should be in state COMPLETED\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 2"); -} -void test_routine_discontinues_past_limit() { - Trace_file = "routine_discontinues_past_limit"; - Scheduling_interval = 2; - run("recipe f1 [\n 1:number/child-id <- start-running f2\n limit-time 1:number/child-id, 10\n # padding loop just to make sure f2 has time to completed\n 2:number <- copy 20\n 2:number <- subtract 2:number, 1\n jump-if 2:number, -2:offset\n]\nrecipe f2 [\n jump -1:offset # run forever\n $print [should never get here], 10/newline\n]\n# f2 terminates\n"); - CHECK_TRACE_CONTENTS("schedule: discontinuing routine 2"); -} -void test_new_concurrent() { - Trace_file = "new_concurrent"; - run("recipe f1 [\n start-running f2\n 1:address:shared:number/raw <- new number:type\n # wait for f2 to complete\n {\n loop-unless 4:number/raw\n }\n]\nrecipe f2 [\n 2:address:shared:number/raw <- new number:type\n # hack: assumes scheduler implementation\n 3:boolean/raw <- equal 1:address:shared:number/raw, 2:address:shared:number/raw\n # signal f2 complete\n 4:number/raw <- copy 1\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} - -void test_wait_for_location() { - Trace_file = "wait_for_location"; - run("recipe f1 [\n 1:number <- copy 0\n start-running f2\n wait-for-location 1:number\n # now wait for f2 to run and modify location 1 before using its value\n 2:number <- copy 1:number\n]\nrecipe f2 [\n 1:number <- copy 34\n]\n# if we got the synchronization wrong we'd be storing 0 in location 2\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 2"); -} -void test_wait_for_routine() { - Trace_file = "wait_for_routine"; - run("recipe f1 [\n 1:number <- copy 0\n 12:number/routine <- start-running f2\n wait-for-routine 12:number/routine\n # now wait for f2 to run and modify location 1 before using its value\n 3:number <- copy 1:number\n]\nrecipe f2 [\n 1:number <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("schedule: f1run: waiting for routine 2schedule: f2schedule: waking up routine 1schedule: f1mem: storing 34 in location 3"); -} -long long int some_other_running_routine() { - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (i == Current_routine_index) continue; - assert(Routines.at(i) != Current_routine); - assert(Routines.at(i)->id != Current_routine->id); - if (Routines.at(i)->state == RUNNING) - return Routines.at(i)->id; - } - return 0; -} - -void test_round_to_nearest_integer() { - Trace_file = "round_to_nearest_integer"; - run("recipe main [\n 1:number <- round 12.2\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 12 in location 1"); -} -// A universal hash function that can handle objects of any type. -// -// The way it's currently implemented, two objects will have the same hash if -// all their non-address fields (all the way down) expand to the same sequence -// of scalar values. In particular, a container with all zero addresses hashes -// to 0. Hopefully this won't be an issue because we are usually hashing -// objects of a single type in any given hash table. -// -// Based on http://burtleburtle.net/bob/hash/hashfaq.html - -size_t hash(size_t h, reagent& r) { -//? cerr << debug_string(r) << '\n'; - canonize(r); - if (is_mu_string(r)) // optimization - return hash_mu_string(h, r); - else if (is_mu_address(r)) - return hash_mu_address(h, r); - else if (is_mu_scalar(r)) - return hash_mu_scalar(h, r); - else if (is_mu_array(r)) - return hash_mu_array(h, r); - else if (is_mu_container(r)) - return hash_mu_container(h, r); - else if (is_mu_exclusive_container(r)) - return hash_mu_exclusive_container(h, r); - assert(false); -} - -size_t hash_mu_scalar(size_t h, const reagent& r) { - double input = is_literal(r) ? r.value : get_or_insert(Memory, r.value); - return hash_iter(h, static_cast<size_t>(input)); -} - -size_t hash_mu_address(size_t h, reagent& r) { - if (r.value == 0) return 0; - r.value = get_or_insert(Memory, r.value); - drop_from_type(r, "address"); - if (r.type->name == "shared") { - ++r.value; - drop_from_type(r, "shared"); - } - return hash(h, r); -} - -size_t hash_mu_string(size_t h, const reagent& r) { - string input = read_mu_string(get_or_insert(Memory, r.value)); - for (long long int i = 0; i < SIZE(input); ++i) { - h = hash_iter(h, static_cast<size_t>(input.at(i))); -//? cerr << i << ": " << h << '\n'; - } - return h; -} - -size_t hash_mu_array(size_t h, const reagent& r) { - long long int size = get_or_insert(Memory, r.value); - reagent elem = r; - delete elem.type; - elem.type = new type_tree(*array_element(r.type)); - for (long long int i=0, address = r.value+1; i < size; ++i, address += size_of(elem)) { - reagent tmp = elem; - tmp.value = address; - h = hash(h, tmp); -//? cerr << i << " (" << address << "): " << h << '\n'; - } - return h; -} - -bool is_mu_container(const reagent& r) { - if (r.type->value == 0) return false; - type_info& info = get(Type, r.type->value); - return info.kind == CONTAINER; -} - -size_t hash_mu_container(size_t h, const reagent& r) { - assert(r.type->value); - type_info& info = get(Type, r.type->value); - long long int address = r.value; - long long int offset = 0; - for (long long int i = 0; i < SIZE(info.elements); ++i) { - reagent element = element_type(r, i); - if (has_property(element, "ignore-for-hash")) continue; - element.set_value(address+offset); - h = hash(h, element); -//? cerr << i << ": " << h << '\n'; - offset += size_of(info.elements.at(i).type); - } - return h; -} - -bool is_mu_exclusive_container(const reagent& r) { - if (r.type->value == 0) return false; - type_info& info = get(Type, r.type->value); - return info.kind == EXCLUSIVE_CONTAINER; -} - -size_t hash_mu_exclusive_container(size_t h, const reagent& r) { - assert(r.type->value); - long long int tag = get(Memory, r.value); - reagent variant = variant_type(r, tag); - // todo: move this warning to container definition time - if (has_property(variant, "ignore-for-hash")) - raise << get(Type, r.type->value).name << ": /ignore-for-hash won't work in exclusive containers\n"; - variant.set_value(r.value + /*skip tag*/1); - h = hash(h, variant); - return h; -} - -size_t hash_iter(size_t h, size_t input) { - h += input; - h += (h<<10); - h ^= (h>>6); - - h += (h<<3); - h ^= (h>>11); - h += (h<<15); - return h; -} - -void test_hash_container_checks_all_elements() { - Trace_file = "hash_container_checks_all_elements"; - run("container foo [\n x:number\n y:character\n]\nrecipe main [\n 1:foo <- merge 34, 97/a\n 3:number <- hash 1:foo\n reply-unless 3:number\n 4:foo <- merge 34, 98/a\n 6:number <- hash 4:foo\n reply-unless 6:number\n 7:boolean <- equal 3:number, 6:number\n]\n# hash on containers includes all elements\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 7"); -} -void test_hash_exclusive_container_checks_all_elements() { - Trace_file = "hash_exclusive_container_checks_all_elements"; - run("exclusive-container foo [\n x:bar\n y:number\n]\ncontainer bar [\n a:number\n b:number\n]\nrecipe main [\n 1:foo <- merge 0/x, 34, 35\n 4:number <- hash 1:foo\n reply-unless 4:number\n 5:foo <- merge 0/x, 34, 36\n 8:number <- hash 5:foo\n reply-unless 8:number\n 9:boolean <- equal 4:number, 8:number\n]\n# hash on containers includes all elements\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 9"); -} -void test_hash_can_ignore_container_elements() { - Trace_file = "hash_can_ignore_container_elements"; - run("container foo [\n x:number\n y:character/ignore-for-hash\n]\nrecipe main [\n 1:foo <- merge 34, 97/a\n 3:number <- hash 1:foo\n reply-unless 3:number\n 4:foo <- merge 34, 98/a\n 6:number <- hash 4:foo\n reply-unless 6:number\n 7:boolean <- equal 3:number, 6:number\n]\n# hashes match even though y is different\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 7"); -} -void test_hash_of_zero_address() { - Trace_file = "hash_of_zero_address"; - run("recipe main [\n 1:address:number <- copy 0\n 2:number <- hash 1:address:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 2"); -} -void test_hash_of_numbers_ignores_fractional_part() { - Trace_file = "hash_of_numbers_ignores_fractional_part"; - run("recipe main [\n 1:number <- hash 1.5\n 2:number <- hash 1\n 3:boolean <- equal 1:number, 2:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 3"); -} -void test_hash_of_array_same_as_string() { - Trace_file = "hash_of_array_same_as_string"; - run("recipe main [\n 10:number <- copy 3\n 11:number <- copy 97\n 12:number <- copy 98\n 13:number <- copy 99\n 2:number <- hash 10:array:number/unsafe\n reply-unless 2:number\n 3:address:shared:array:character <- new [abc]\n 4:number <- hash 3:address:shared:array:character\n reply-unless 4:number\n 5:boolean <- equal 2:number, 4:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 5"); -} -void test_hash_ignores_address_value() { - Trace_file = "hash_ignores_address_value"; - run("recipe main [\n 1:address:shared:number <- new number:type\n *1:address:shared:number <- copy 34\n 2:number <- hash 1:address:shared:number\n 3:address:shared:number <- new number:type\n *3:address:shared:number <- copy 34\n 4:number <- hash 3:address:shared:number\n 5:boolean <- equal 2:number, 4:number\n]\n# different addresses hash to the same result as long as the values the point to do so\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 5"); -} -void test_hash_ignores_address_refcount() { - Trace_file = "hash_ignores_address_refcount"; - run("recipe main [\n 1:address:shared:number <- new number:type\n *1:address:shared:number <- copy 34\n 2:number <- hash 1:address:shared:number\n reply-unless 2:number\n # increment refcount\n 3:address:shared:number <- copy 1:address:shared:number\n 4:number <- hash 3:address:shared:number\n reply-unless 4:number\n 5:boolean <- equal 2:number, 4:number\n]\n# hash doesn't change when refcount changes\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 5"); -} -void test_hash_container_depends_only_on_elements() { - Trace_file = "hash_container_depends_only_on_elements"; - run("container foo [\n x:number\n y:character\n]\ncontainer bar [\n x:number\n y:character\n]\nrecipe main [\n 1:foo <- merge 34, 97/a\n 3:number <- hash 1:foo\n reply-unless 3:number\n 4:bar <- merge 34, 97/a\n 6:number <- hash 4:bar\n reply-unless 6:number\n 7:boolean <- equal 3:number, 6:number\n]\n# containers with identical elements return identical hashes\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 7"); -} -void test_hash_container_depends_only_on_elements_2() { - Trace_file = "hash_container_depends_only_on_elements_2"; - run("container foo [\n x:number\n y:character\n z:address:shared:number\n]\nrecipe main [\n 1:address:shared:number <- new number:type\n *1:address:shared:number <- copy 34\n 2:foo <- merge 34, 97/a, 1:address:shared:number\n 5:number <- hash 2:foo\n reply-unless 5:number\n 6:address:shared:number <- new number:type\n *6:address:shared:number <- copy 34\n 7:foo <- merge 34, 97/a, 6:address:shared:number\n 10:number <- hash 7:foo\n reply-unless 10:number\n 11:boolean <- equal 5:number, 10:number\n]\n# containers with identical 'leaf' elements return identical hashes\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 11"); -} -void test_hash_container_depends_only_on_elements_3() { - Trace_file = "hash_container_depends_only_on_elements_3"; - run("container foo [\n x:number\n y:character\n z:bar\n]\ncontainer bar [\n x:number\n y:number\n]\nrecipe main [\n 1:foo <- merge 34, 97/a, 47, 48\n 6:number <- hash 1:foo\n reply-unless 6:number\n 7:foo <- merge 34, 97/a, 47, 48\n 12:number <- hash 7:foo\n reply-unless 12:number\n 13:boolean <- equal 6:number, 12:number\n]\n# containers with identical 'leaf' elements return identical hashes\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 13"); -} -void test_hash_exclusive_container_ignores_tag() { - Trace_file = "hash_exclusive_container_ignores_tag"; - run("exclusive-container foo [\n x:bar\n y:number\n]\ncontainer bar [\n a:number\n b:number\n]\nrecipe main [\n 1:foo <- merge 0/x, 34, 35\n 4:number <- hash 1:foo\n reply-unless 4:number\n 5:bar <- merge 34, 35\n 7:number <- hash 5:bar\n reply-unless 7:number\n 8:boolean <- equal 4:number, 7:number\n]\n# hash on containers includes all elements\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 8"); -} -void test_hash_matches_old_version() { - Trace_file = "hash_matches_old_version"; - run("recipe main [\n 1:address:shared:array:character <- new [abc]\n 2:number <- hash 1:address:shared:array:character\n 3:number <- hash_old 1:address:shared:array:character\n 4:number <- equal 2:number, 3:number\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 1 in location 4"); -} - - - -void test_convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations() { - Trace_file = "convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations"; - Scenario_testing_scenario = true; - Hide_errors = true; - run("recipe main [\n screen:number <- copy 1:number\n]\n"); - CHECK_TRACE_DOESNT_CONTAIN("error: mixing variable names and numeric addresses in main"); - CHECK_TRACE_COUNT("error", 0); -} -void check_screen(const string& expected_contents, const int color) { - assert(!current_call().default_space); // not supported - long long int screen_location = get_or_insert(Memory, SCREEN)+/*skip refcount*/1; - int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", ""); - assert(data_offset >= 0); - long long int screen_data_location = screen_location+data_offset; // type: address:shared:array:character - long long int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1; // type: array:character - int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", ""); - long long int screen_width = get_or_insert(Memory, screen_location+width_offset); - int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", ""); - long long int screen_height = get_or_insert(Memory, screen_location+height_offset); - raw_string_stream cursor(expected_contents); - // todo: too-long expected_contents should fail - long long int addr = screen_data_start+/*skip length*/1; - for (long long int row = 0; row < screen_height; ++row) { - cursor.skip_whitespace_and_comments(); - if (cursor.at_end()) break; - assert(cursor.get() == '.'); - for (long long int column = 0; column < screen_width; ++column, addr+= /*size of screen-cell*/2) { - const int cell_color_offset = 1; - uint32_t curr = cursor.get(); - if (get_or_insert(Memory, addr) == 0 && isspace(curr)) continue; - if (curr == ' ' && color != -1 && color != get_or_insert(Memory, addr+cell_color_offset)) { - // filter out other colors - continue; - } - if (get_or_insert(Memory, addr) != 0 && get_or_insert(Memory, addr) == curr) { - if (color == -1 || color == get_or_insert(Memory, addr+cell_color_offset)) continue; - // contents match but color is off - if (Current_scenario && !Scenario_testing_scenario) { - // genuine test in a mu file - raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(get_or_insert(Memory, addr)) << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << "\n" << end(); - } - else { - // just testing check_screen - raise_error << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << '\n' << end(); - } - if (!Scenario_testing_scenario) { - Passed = false; - ++Num_failures; - } - return; - } - - // really a mismatch - // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway. - char expected_pretty[10] = {0}; - if (curr < 256 && !iscntrl(curr)) { - // " ('<curr>')" - expected_pretty[0] = ' ', expected_pretty[1] = '(', expected_pretty[2] = '\'', expected_pretty[3] = static_cast<unsigned char>(curr), expected_pretty[4] = '\'', expected_pretty[5] = ')', expected_pretty[6] = '\0'; - } - char actual_pretty[10] = {0}; - if (get_or_insert(Memory, addr) < 256 && !iscntrl(get_or_insert(Memory, addr))) { - // " ('<curr>')" - actual_pretty[0] = ' ', actual_pretty[1] = '(', actual_pretty[2] = '\'', actual_pretty[3] = static_cast<unsigned char>(get_or_insert(Memory, addr)), actual_pretty[4] = '\'', actual_pretty[5] = ')', actual_pretty[6] = '\0'; - } - - ostringstream color_phrase; - if (color != -1) color_phrase << " in color " << color; - if (Current_scenario && !Scenario_testing_scenario) { - // genuine test in a mu file - raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end(); - dump_screen(); - } - else { - // just testing check_screen - raise_error << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end(); - } - if (!Scenario_testing_scenario) { - Passed = false; - ++Num_failures; - } - return; - } - assert(cursor.get() == '.'); - } - cursor.skip_whitespace_and_comments(); - assert(cursor.at_end()); -} - -raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {} - -bool raw_string_stream::at_end() const { - if (index >= max) return true; - if (tb_utf8_char_length(buf[index]) > max-index) { - raise_error << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end(); - return true; - } - return false; -} - -uint32_t raw_string_stream::get() { - assert(index < max); // caller must check bounds before calling 'get' - uint32_t result = 0; - int length = tb_utf8_char_to_unicode(&result, &buf[index]); - assert(length != TB_EOF); - index += length; - return result; -} - -uint32_t raw_string_stream::peek() { - assert(index < max); // caller must check bounds before calling 'get' - uint32_t result = 0; - int length = tb_utf8_char_to_unicode(&result, &buf[index]); - assert(length != TB_EOF); - return result; -} - -void raw_string_stream::skip_whitespace_and_comments() { - while (!at_end()) { - if (isspace(peek())) get(); - else if (peek() == '#') { - // skip comment - get(); - while (peek() != '\n') get(); // implicitly also handles CRLF - } - else break; - } -} - -void dump_screen() { - assert(!current_call().default_space); // not supported - long long int screen_location = get_or_insert(Memory, SCREEN) + /*skip refcount*/1; - int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", ""); - long long int screen_width = get_or_insert(Memory, screen_location+width_offset); - int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", ""); - long long int screen_height = get_or_insert(Memory, screen_location+height_offset); - int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", ""); - assert(data_offset >= 0); - long long int screen_data_location = screen_location+data_offset; // type: address:shared:array:character - long long int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1; // type: array:character - assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height); - long long int curr = screen_data_start+1; // skip length - for (long long int row = 0; row < screen_height; ++row) { - cerr << '.'; - for (long long int col = 0; col < screen_width; ++col) { - if (get_or_insert(Memory, curr)) - cerr << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr))); - else - cerr << ' '; - curr += /*size of screen-cell*/2; - } - cerr << ".\n"; - } -} - - -void initialize_key_names() { - Key["F1"] = TB_KEY_F1; - Key["F2"] = TB_KEY_F2; - Key["F3"] = TB_KEY_F3; - Key["F4"] = TB_KEY_F4; - Key["F5"] = TB_KEY_F5; - Key["F6"] = TB_KEY_F6; - Key["F7"] = TB_KEY_F7; - Key["F8"] = TB_KEY_F8; - Key["F9"] = TB_KEY_F9; - Key["F10"] = TB_KEY_F10; - Key["F11"] = TB_KEY_F11; - Key["F12"] = TB_KEY_F12; - Key["insert"] = TB_KEY_INSERT; - Key["delete"] = TB_KEY_DELETE; - Key["home"] = TB_KEY_HOME; - Key["end"] = TB_KEY_END; - Key["page-up"] = TB_KEY_PGUP; - Key["page-down"] = TB_KEY_PGDN; - Key["up-arrow"] = TB_KEY_ARROW_UP; - Key["down-arrow"] = TB_KEY_ARROW_DOWN; - Key["left-arrow"] = TB_KEY_ARROW_LEFT; - Key["right-arrow"] = TB_KEY_ARROW_RIGHT; - Key["ctrl-a"] = TB_KEY_CTRL_A; - Key["ctrl-b"] = TB_KEY_CTRL_B; - Key["ctrl-c"] = TB_KEY_CTRL_C; - Key["ctrl-d"] = TB_KEY_CTRL_D; - Key["ctrl-e"] = TB_KEY_CTRL_E; - Key["ctrl-f"] = TB_KEY_CTRL_F; - Key["ctrl-g"] = TB_KEY_CTRL_G; - Key["backspace"] = TB_KEY_BACKSPACE; - Key["ctrl-h"] = TB_KEY_CTRL_H; - Key["tab"] = TB_KEY_TAB; - Key["ctrl-i"] = TB_KEY_CTRL_I; - Key["ctrl-j"] = TB_KEY_CTRL_J; - Key["enter"] = TB_KEY_NEWLINE; // ignore CR/LF distinction; there is only 'enter' - Key["ctrl-k"] = TB_KEY_CTRL_K; - Key["ctrl-l"] = TB_KEY_CTRL_L; - Key["ctrl-m"] = TB_KEY_CTRL_M; - Key["ctrl-n"] = TB_KEY_CTRL_N; - Key["ctrl-o"] = TB_KEY_CTRL_O; - Key["ctrl-p"] = TB_KEY_CTRL_P; - Key["ctrl-q"] = TB_KEY_CTRL_Q; - Key["ctrl-r"] = TB_KEY_CTRL_R; - Key["ctrl-s"] = TB_KEY_CTRL_S; - Key["ctrl-t"] = TB_KEY_CTRL_T; - Key["ctrl-u"] = TB_KEY_CTRL_U; - Key["ctrl-v"] = TB_KEY_CTRL_V; - Key["ctrl-w"] = TB_KEY_CTRL_W; - Key["ctrl-x"] = TB_KEY_CTRL_X; - Key["ctrl-y"] = TB_KEY_CTRL_Y; - Key["ctrl-z"] = TB_KEY_CTRL_Z; - Key["escape"] = TB_KEY_ESC; -} - -long long int count_events(const recipe& r) { - long long int result = 0; - for (long long int i = 0; i < SIZE(r.steps); ++i) { - const instruction& curr = r.steps.at(i); - if (curr.name == "type") - result += unicode_length(curr.ingredients.at(0).name); - else - result++; - } - return result; -} - -long long int size_of_event() { - // memoize result if already computed - static long long int result = 0; - if (result) return result; - type_tree* type = new type_tree("event", get(Type_ordinal, "event")); - result = size_of(type); - delete type; - return result; -} - -long long int size_of_console() { - // memoize result if already computed - static long long int result = 0; - if (result) return result; - assert(get(Type_ordinal, "console")); - type_tree* type = new type_tree("console", get(Type_ordinal, "console")); - result = size_of(type)+/*refcount*/1; - delete type; - return result; -} - - -void start_trace_browser() { - if (!Trace_stream) return; - cerr << "computing min depth to display\n"; - long long int min_depth = 9999; - for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) { - trace_line& curr_line = Trace_stream->past_lines.at(i); - if (curr_line.depth < min_depth) min_depth = curr_line.depth; - } - cerr << "min depth is " << min_depth << '\n'; - cerr << "computing lines to display\n"; - for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) { - if (Trace_stream->past_lines.at(i).depth == min_depth) - Visible.insert(i); - } - tb_init(); - Display_row = Display_column = 0; - tb_event event; - Top_of_screen = 0; - refresh_screen_rows(); - while (true) { - render(); - do { - tb_poll_event(&event); - } while (event.type != TB_EVENT_KEY); - long long int key = event.key ? event.key : event.ch; - if (key == 'q' || key == 'Q') break; - if (key == 'j' || key == TB_KEY_ARROW_DOWN) { - // move cursor one line down - if (Display_row < Last_printed_row) ++Display_row; - } - if (key == 'k' || key == TB_KEY_ARROW_UP) { - // move cursor one line up - if (Display_row > 0) --Display_row; - } - if (key == 'H') { - // move cursor to top of screen - Display_row = 0; - } - if (key == 'M') { - // move cursor to center of screen - Display_row = tb_height()/2; - } - if (key == 'L') { - // move cursor to bottom of screen - Display_row = tb_height()-1; - } - if (key == 'J' || key == TB_KEY_PGDN) { - // page-down - if (Trace_index.find(tb_height()-1) != Trace_index.end()) { - Top_of_screen = get(Trace_index, tb_height()-1) + 1; - refresh_screen_rows(); - } - } - if (key == 'K' || key == TB_KEY_PGUP) { - // page-up is more convoluted - for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) { - --Top_of_screen; - if (Top_of_screen <= 0) break; - while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen)) - --Top_of_screen; - } - if (Top_of_screen >= 0) - refresh_screen_rows(); - } - if (key == 'G') { - // go to bottom of screen; largely like page-up, interestingly - Top_of_screen = SIZE(Trace_stream->past_lines)-1; - for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) { - --Top_of_screen; - if (Top_of_screen <= 0) break; - while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen)) - --Top_of_screen; - } - refresh_screen_rows(); - // move cursor to bottom - Display_row = Last_printed_row; - refresh_screen_rows(); - } - if (key == TB_KEY_CARRIAGE_RETURN) { - // expand lines under current by one level - assert(contains_key(Trace_index, Display_row)); - long long int start_index = get(Trace_index, Display_row); - long long int index = 0; - // simultaneously compute end_index and min_depth - int min_depth = 9999; - for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) { - if (contains_key(Visible, index)) break; - trace_line& curr_line = Trace_stream->past_lines.at(index); - assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth); - if (curr_line.depth < min_depth) min_depth = curr_line.depth; - } - long long int end_index = index; - // mark as visible all intervening indices at min_depth - for (index = start_index; index < end_index; ++index) { - trace_line& curr_line = Trace_stream->past_lines.at(index); - if (curr_line.depth == min_depth) { - Visible.insert(index); - } - } - refresh_screen_rows(); - } - if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) { - // collapse all lines under current - assert(contains_key(Trace_index, Display_row)); - long long int start_index = get(Trace_index, Display_row); - long long int index = 0; - // end_index is the next line at a depth same as or lower than start_index - int initial_depth = Trace_stream->past_lines.at(start_index).depth; - for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) { - if (!contains_key(Visible, index)) continue; - trace_line& curr_line = Trace_stream->past_lines.at(index); - if (curr_line.depth <= initial_depth) break; - } - long long int end_index = index; - // mark as visible all intervening indices at min_depth - for (index = start_index+1; index < end_index; ++index) { - Visible.erase(index); - } - refresh_screen_rows(); - } - } - tb_shutdown(); -} - -// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible -void refresh_screen_rows() { - long long int screen_row = 0, index = 0; - Trace_index.clear(); - for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) { - // skip lines without depth for now - while (!contains_key(Visible, index)) { - ++index; - if (index >= SIZE(Trace_stream->past_lines)) goto done; - } - assert(index < SIZE(Trace_stream->past_lines)); - put(Trace_index, screen_row, index); - } -done:; -} - -void render() { - long long int screen_row = 0; - for (screen_row = 0; screen_row < tb_height(); ++screen_row) { - if (!contains_key(Trace_index, screen_row)) break; - trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row)); - ostringstream out; - out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents; - if (screen_row < tb_height()-1) { - long long int delta = lines_hidden(screen_row); - // home-brew escape sequence for red - if (delta > 999) out << "{"; - out << " (" << delta << ")"; - if (delta > 999) out << "}"; - } - render_line(screen_row, out.str()); - } - // clear rest of screen - Last_printed_row = screen_row-1; - for (; screen_row < tb_height(); ++screen_row) { - render_line(screen_row, "~"); - } - // move cursor back to display row at the end - tb_set_cursor(0, Display_row); - tb_present(); -} - -long long int lines_hidden(long long int screen_row) { - assert(contains_key(Trace_index, screen_row)); - if (!contains_key(Trace_index, screen_row+1)) - return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row); - else - return get(Trace_index, screen_row+1) - get(Trace_index, screen_row); -} - -void render_line(int screen_row, const string& s) { - long long int col = 0; - int color = TB_WHITE; - for (col = 0; col < tb_width() && col < SIZE(s); ++col) { - char c = s.at(col); // todo: unicode - if (c == '\n') c = ';'; // replace newlines with semi-colons - // escapes. hack: can't start a line with them. - if (c == '{') { color = /*red*/1; c = ' '; } - if (c == '}') { color = TB_WHITE; c = ' '; } - tb_change_cell(col, screen_row, c, color, TB_BLACK); - } - for (; col < tb_width(); ++col) { - tb_change_cell(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)) { - tin >> std::noskipws; - skip_whitespace_but_not_newline(tin); - if (!isdigit(tin.peek())) { - string dummy; - getline(tin, dummy); - continue; - } - tin >> std::skipws; - 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)); - } - cerr << "lines read: " << Trace_stream->past_lines.size() << '\n'; -} - - -void test_run_interactive_code() { - Trace_file = "run_interactive_code"; - run("recipe main [\n 1:number/raw <- copy 0\n 2:address:shared:array:character <- new [1:number/raw <- copy 34]\n run-interactive 2:address:shared:array:character\n 3:number/raw <- copy 1:number/raw\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 3"); -} -void test_run_interactive_empty() { - Trace_file = "run_interactive_empty"; - run("recipe main [\n 1:address:shared:array:character <- copy 0/unsafe\n 2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n]\n# result is null\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 2"); -} -// reads a string, tries to call it as code (treating it as a test), saving -// all warnings. -// returns true if successfully called (no errors found during load and transform) -bool run_interactive(long long int address) { - assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0); - // try to sandbox the run as best you can - // todo: test this - if (!Current_scenario) { - for (long long int i = 1; i < Reserved_for_tests; ++i) - Memory.erase(i); - } - string command = trim(strip_comments(read_mu_string(address))); - if (command.empty()) return false; - Name[get(Recipe_ordinal, "interactive")].clear(); - run_code_begin(/*snapshot_recently_added_recipes*/true); - // don't kill the current routine on parse errors - routine* save_current_routine = Current_routine; - Current_routine = NULL; - // call run(string) but without the scheduling - load(string("recipe! interactive [\n") + - "local-scope\n" + - "screen:address:shared:screen <- next-ingredient\n" + - "$start-tracking-products\n" + - command + "\n" + - "$stop-tracking-products\n" + - "reply screen\n" + - "]\n"); - transform_all(); - Current_routine = save_current_routine; - if (trace_count("error") > 0) return false; - // now call 'sandbox' which will run 'interactive' in a separate routine, - // and wait for it - if (Save_trace_stream) { - ++Save_trace_stream->callstack_depth; - trace(9999, "trace") << "run-interactive: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end(); - assert(Save_trace_stream->callstack_depth < 9000); // 9998-101 plus cushion - } - Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox"))); - return true; -} - -void run_code_begin(bool snapshot_recently_added_recipes) { -//? cerr << "loading new trace\n"; - // stuff to undo later, in run_code_end() - Hide_warnings = true; - Hide_errors = true; - Disable_redefine_warnings = true; - if (snapshot_recently_added_recipes) { - Save_recently_added_recipes = Recently_added_recipes; - Recently_added_recipes.clear(); - Save_recently_added_shape_shifting_recipes = Recently_added_shape_shifting_recipes; - Recently_added_shape_shifting_recipes.clear(); - } - Save_trace_stream = Trace_stream; - Save_trace_file = Trace_file; - Trace_file = ""; - Trace_stream = new trace_stream; - Trace_stream->collect_depth = App_depth; -} - -void run_code_end() { -//? cerr << "back to old trace\n"; - Hide_warnings = false; - Hide_errors = false; - Disable_redefine_warnings = false; - delete Trace_stream; - Trace_stream = Save_trace_stream; - Save_trace_stream = NULL; - Trace_file = Save_trace_file; - Save_trace_file.clear(); - Recipe.erase(get(Recipe_ordinal, "interactive")); // keep past sandboxes from inserting errors - if (!Save_recently_added_recipes.empty()) { - clear_recently_added_recipes(); - Recently_added_recipes = Save_recently_added_recipes; - Save_recently_added_recipes.clear(); - Recently_added_shape_shifting_recipes = Save_recently_added_shape_shifting_recipes; - Save_recently_added_shape_shifting_recipes.clear(); - } -} - -void test_run_interactive_comments() { - Trace_file = "run_interactive_comments"; - run("recipe main [\n 1:address:shared:array:character <- new [# ab\nadd 2, 2]\n 2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n 3:array:character <- copy *2:address:shared:array:character\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 52 in location 4"); -} -void test_run_interactive_converts_result_to_text() { - Trace_file = "run_interactive_converts_result_to_text"; - run("recipe main [\n # try to interactively add 2 and 2\n 1:address:shared:array:character <- new [add 2, 2]\n 2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n 10:array:character <- copy 2:address:shared:array:character/lookup\n]\n# first letter in the output should be '4' in unicode\n"); - CHECK_TRACE_CONTENTS("mem: storing 52 in location 11"); -} -void test_run_interactive_returns_text() { - Trace_file = "run_interactive_returns_text"; - run("recipe main [\n # try to interactively add 2 and 2\n 1:address:shared:array:character <- new [\n x:address:shared:array:character <- new [a]\n y:address:shared:array:character <- new [b]\n z:address:shared:array:character <- append x:address:shared:array:character, y:address:shared:array:character\n ]\n 2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n 10:array:character <- copy 2:address:shared:array:character/lookup\n]\n# output contains \"ab\"\n"); - CHECK_TRACE_CONTENTS("mem: storing 97 in location 11mem: storing 98 in location 12"); -} -void test_run_interactive_returns_errors() { - Trace_file = "run_interactive_returns_errors"; - run("recipe main [\n # run a command that generates an error\n 1:address:shared:array:character <- new [x:number <- copy 34\nget x:number, foo:offset]\n 2:address:shared:array:character, 3:address:shared:array:character <- run-interactive 1:address:shared:array:character\n 10:array:character <- copy 3:address:shared:array:character/lookup\n]\n# error should be \"unknown element foo in container number\"\n"); - CHECK_TRACE_CONTENTS("mem: storing 117 in location 11mem: storing 110 in location 12mem: storing 107 in location 13mem: storing 110 in location 14"); -} -void test_run_interactive_with_comment() { - Trace_file = "run_interactive_with_comment"; - run("recipe main [\n # 2 instructions, with a comment after the first\n 1:address:shared:array:number <- new [a:number <- copy 0 # abc\nb:number <- copy 0\n]\n 2:address:shared:array:character, 3:address:shared:array:character <- run-interactive 1:address:shared:array:character\n]\n# no errors\n"); - CHECK_TRACE_CONTENTS("mem: storing 0 in location 3"); -} -void test_run_interactive_cleans_up_any_created_specializations() { - // define a generic recipe - assert(!contains_key(Recipe_ordinal, "foo")); - load("recipe foo x:_elem -> n:number [\n" - " reply 34\n" - "]\n"); - assert(SIZE(Recently_added_recipes) == 1); // foo - assert(variant_count("foo") == 1); - // run-interactive a call that specializes this recipe - run("recipe main [\n" - " 1:number/raw <- copy 0\n" - " 2:address:shared:array:character <- new [foo 1:number/raw]\n" - " run-interactive 2:address:shared:array:character\n" - "]\n"); - assert(SIZE(Recently_added_recipes) == 2); // foo, main - // check that number of variants doesn't change - CHECK_EQ(variant_count("foo"), 1); -} - -long long int variant_count(string recipe_name) { - if (!contains_key(Recipe_variants, recipe_name)) return 0; - return non_ghost_size(get(Recipe_variants, recipe_name)); -} - -void track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) { - ostringstream out; - for (long long int i = 0; i < SIZE(products); ++i) { - // string - if (i < SIZE(instruction.products)) { - if (is_mu_string(instruction.products.at(i))) { - if (!scalar(products.at(i))) { - tb_shutdown(); - cerr << read_mu_string(trace_error_warning_contents()) << '\n'; - cerr << SIZE(products.at(i)) << ": "; - for (long long int j = 0; j < SIZE(products.at(i)); ++j) - cerr << no_scientific(products.at(i).at(j)) << ' '; - cerr << '\n'; - } - assert(scalar(products.at(i))); - out << read_mu_string(products.at(i).at(0)) << '\n'; - continue; - } - // End Record Product Special-cases - } - for (long long int j = 0; j < SIZE(products.at(i)); ++j) - out << no_scientific(products.at(i).at(j)) << ' '; - out << '\n'; - } - Most_recent_products = out.str(); -} - -string strip_comments(string in) { - ostringstream result; - for (long long int i = 0; i < SIZE(in); ++i) { - if (in.at(i) != '#') { - result << in.at(i); - } - else { - while (i+1 < SIZE(in) && in.at(i+1) != '\n') - ++i; - } - } - return result.str(); -} - -long long int stringified_value_of_location(long long int address) { - // convert to string - ostringstream out; - out << no_scientific(get_or_insert(Memory, address)); - return new_mu_string(out.str()); -} - -long long int trace_error_warning_contents() { - if (!Trace_stream) return 0; - ostringstream out; - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (p->depth > Warning_depth) continue; - out << p->contents; - if (*--p->contents.end() != '\n') out << '\n'; - } - string result = out.str(); - if (result.empty()) return 0; - truncate(result); - return new_mu_string(result); -} - -long long int trace_app_contents() { - if (!Trace_stream) return 0; - ostringstream out; - for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (p->depth != App_depth) continue; - out << p->contents; - if (*--p->contents.end() != '\n') out << '\n'; - } - string result = out.str(); - if (result.empty()) return 0; - truncate(result); - return new_mu_string(result); -} - -void truncate(string& x) { - if (SIZE(x) > 512) { - x.erase(512); - *x.rbegin() = '\n'; - *++x.rbegin() = '.'; - *++++x.rbegin() = '.'; - } -} - - -void test_reload_continues_past_error() { - Trace_file = "reload_continues_past_error"; - run("recipe main [\n local-scope\n x:address:shared:array:character <- new [recipe foo [\n get 1234:number, foo:offset\n]]\n reload x\n 1:number/raw <- copy 34\n]\n"); - CHECK_TRACE_CONTENTS("mem: storing 34 in location 1"); -} -void test_reload_cleans_up_any_created_specializations() { - // define a generic recipe and a call to it - assert(!contains_key(Recipe_ordinal, "foo")); - assert(variant_count("foo") == 0); - // a call that specializes this recipe - run("recipe main [\n" - " local-scope\n" - " x:address:shared:array:character <- new [recipe foo x:_elem -> n:number [\n" - "local-scope\n" - "load-ingredients\n" - "reply 34\n" - "]\n" - "recipe main2 [\n" - "local-scope\n" - "load-ingredients\n" - "x:number <- copy 34\n" - "foo x:number\n" - "]]\n" - " reload x\n" - "]\n"); - // check that number of variants includes specialization - assert(SIZE(Recently_added_recipes) == 4); // foo, main, main2, foo specialization - CHECK_EQ(variant_count("foo"), 2); -} - - -string slurp(const string& filename) { - ostringstream result; - ifstream fin(filename.c_str()); - fin.peek(); - if (!fin) return result.str(); // don't bother checking errno - const int N = 1024; - char buf[N]; - while (has_data(fin)) { - bzero(buf, N); - fin.read(buf, N-1); // leave at least one null - result << buf; - } - fin.close(); - return result.str(); -} - -bool exists(const string& filename) { - struct stat dummy; - return 0 == stat(filename.c_str(), &dummy); -} - -//? :(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; -//? } -//? } -//? } -//? } -//? } - |