//: Construct types out of their constituent fields. void test_merge() { run( "container foo [\n" " x:num\n" " y:num\n" "]\n" "def main [\n" " 1:foo <- merge 3, 4\n" "]\n" ); CHECK_TRACE_CONTENTS( "mem: storing 3 in location 1\n" "mem: storing 4 in location 2\n" ); } :(before "End Primitive Recipe Declarations") MERGE, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "merge", MERGE); :(before "End Primitive Recipe Checks") case MERGE: { // type-checking in a separate transform below break; } :(before "End Primitive Recipe Implementations") case MERGE: { products.resize(1); for (int i = 0; i < SIZE(ingredients); ++i) for (int j = 0; j < SIZE(ingredients.at(i)); ++j) products.at(0).push_back(ingredients.at(i).at(j)); break; } //: type-check 'merge' to avoid interpreting numbers as addresses :(code) void test_merge_check() { run( "def main [\n" " 1:point <- merge 3, 4\n" "]\n" ); CHECK_TRACE_COUNT("error", 0); } void test_merge_check_missing_element() { Hide_errors = true; run( "def main [\n" " 1:point <- merge 3\n" "]\n" ); CHECK_TRACE_CONTENTS( "error: main: too few ingredients in '1:point <- merge 3'\n" ); } void test_merge_check_extra_element() { Hide_errors = true; run( "def 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'\n" ); } //: We want to avoid causing memory corruption, but other than that we want to //: be flexible in how we construct containers of containers. It should be //: equally easy to define a container out of primitives or intermediate //: container fields. void test_merge_check_recursive_containers() { run( "def 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() { Hide_errors = true; run( "def 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'\n" ); } void test_merge_check_recursive_containers_3() { run( "def main [\n" " 1:point-number <- merge 3, 4, 5\n" "]\n" ); CHECK_TRACE_COUNT("error", 0); } void test_merge_check_recursive_containers_4() { Hide_errors = true; run( "def 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'\n" ); } void test_merge_check_reflexive() { Hide_errors = true; run( "def main [\n" " 1:point <- merge 3, 4\n" " 2:point <- merge 1:point\n" "]\n" ); CHECK_TRACE_COUNT("error", 0); } //: Since a container can be merged in several ways, we need to be able to //: backtrack through different possibilities. Later we'll allow creating //: exclusive containers which contain just one of rather than all of their //: elements. That will also require backtracking capabilities. Here's the //: state we need to maintain for backtracking: :(before "End Types") struct merge_check_point { reagent container; int container_element_index; merge_check_point(const reagent& c, int i) :container(c), container_element_index(i) {} }; struct merge_check_state { stack data; }; :(before "End Checks") Transform.push_back(check_merge_calls); // idempotent :(code) void check_merge_calls(const recipe_ordinal r) { const recipe& caller = get(Recipe, r); trace(101, "transform") << "--- type-check merge instructions in recipe " << caller.name << end(); for (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) {