static void add_field_to_object(pass_opt_t* opt, ast_t* field, ast_t* class_members, ast_t* create_params, ast_t* create_body, ast_t* call_args) { AST_GET_CHILDREN(field, id, type, init); ast_t* p_id = ast_from_string(id, package_hygienic_id(&opt->check)); // The param is: $0: type BUILD(param, field, NODE(TK_PARAM, TREE(p_id) TREE(type) NONE)); // The arg is: $seq init BUILD(arg, init, NODE(TK_SEQ, TREE(init))); // The body of create contains: id = consume $0 BUILD(assign, init, NODE(TK_ASSIGN, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(p_id))) NODE(TK_REFERENCE, TREE(id)))); // Remove the initialiser from the field ast_replace(&init, ast_from(init, TK_NONE)); ast_add(class_members, field); ast_append(create_params, param); ast_append(create_body, assign); ast_append(call_args, arg); }
static void add_as_type(typecheck_t* t, ast_t* type, ast_t* pattern, ast_t* body) { assert(type != NULL); switch(ast_id(type)) { case TK_TUPLETYPE: { BUILD(tuple_pattern, pattern, NODE(TK_SEQ, NODE(TK_TUPLE))); ast_append(pattern, tuple_pattern); ast_t* pattern_child = ast_child(tuple_pattern); BUILD(tuple_body, body, NODE(TK_SEQ, NODE(TK_TUPLE))); ast_t* body_child = ast_child(tuple_body); for(ast_t* p = ast_child(type); p != NULL; p = ast_sibling(p)) add_as_type(t, p, pattern_child, body_child); if(ast_childcount(body_child) == 1) { // Only one child, not actually a tuple ast_t* t = ast_pop(body_child); ast_free(tuple_body); tuple_body = t; } ast_append(body, tuple_body); break; } case TK_DONTCARE: ast_append(pattern, type); break; default: { const char* name = package_hygienic_id(t); ast_t* a_type = alias(type); BUILD(pattern_elem, pattern, NODE(TK_SEQ, NODE(TK_LET, ID(name) TREE(a_type)))); BUILD(body_elem, body, NODE(TK_SEQ, NODE(TK_CONSUME, NODE(TK_BORROWED) NODE(TK_REFERENCE, ID(name))))); ast_append(pattern, pattern_elem); ast_append(body, body_elem); break; } } }
static ast_result_t sugar_with(typecheck_t* t, ast_t** astp) { AST_EXTRACT_CHILDREN(*astp, withexpr, body, else_clause); token_id try_token; if(ast_id(else_clause) == TK_NONE) try_token = TK_TRY_NO_CHECK; else try_token = TK_TRY; expand_none(else_clause, false); // First build a skeleton try block without the "with" variables BUILD(replace, *astp, NODE(TK_SEQ, NODE(try_token, NODE(TK_SEQ, AST_SCOPE TREE(body)) NODE(TK_SEQ, AST_SCOPE TREE(else_clause)) NODE(TK_SEQ, AST_SCOPE)))); ast_t* tryexpr = ast_child(replace); AST_GET_CHILDREN(tryexpr, try_body, try_else, try_then); // Add the "with" variables from each with element for(ast_t* p = ast_child(withexpr); p != NULL; p = ast_sibling(p)) { assert(ast_id(p) == TK_SEQ); AST_GET_CHILDREN(p, idseq, init); const char* init_name = package_hygienic_id(t); BUILD(assign, idseq, NODE(TK_ASSIGN, AST_NODEBUG TREE(init) NODE(TK_LET, ID(init_name) NONE))); BUILD(local, idseq, NODE(TK_ASSIGN, AST_NODEBUG NODE(TK_REFERENCE, ID(init_name)) TREE(idseq))); ast_add(replace, assign); ast_add(try_body, local); ast_add(try_else, local); build_with_dispose(try_then, idseq); ast_add(try_then, local); } ast_replace(astp, replace); return AST_OK; }
static ast_result_t sugar_let(typecheck_t* t, ast_t* ast) { ast_t* id = ast_child(ast); if(ast_id(id) == TK_DONTCARE) { // Replace "_" with "$1" in with and for variable lists ast_setid(id, TK_ID); ast_set_name(id, package_hygienic_id(t)); } return AST_OK; }
static ast_result_t sugar_for(typecheck_t* t, ast_t** astp) { AST_EXTRACT_CHILDREN(*astp, for_idseq, for_iter, for_body, for_else); expand_none(for_else, true); const char* iter_name = package_hygienic_id(t); BUILD(try_next, for_iter, NODE(TK_TRY_NO_CHECK, NODE(TK_SEQ, AST_SCOPE NODE(TK_CALL, NONE NONE NODE(TK_DOT, NODE(TK_REFERENCE, ID(iter_name)) ID("next")))) NODE(TK_SEQ, AST_SCOPE NODE(TK_CONTINUE, NONE)) NONE)); sugar_try(try_next); REPLACE(astp, NODE(TK_SEQ, NODE(TK_ASSIGN, AST_NODEBUG TREE(for_iter) NODE(TK_LET, NICE_ID(iter_name, "for loop iterator") NONE)) NODE(TK_WHILE, AST_SCOPE NODE(TK_SEQ, NODE_ERROR_AT(TK_CALL, for_iter, NONE NONE NODE(TK_DOT, NODE(TK_REFERENCE, ID(iter_name)) ID("has_next")))) NODE(TK_SEQ, AST_SCOPE NODE_ERROR_AT(TK_ASSIGN, for_idseq, AST_NODEBUG TREE(try_next) TREE(for_idseq)) TREE(for_body)) TREE(for_else)))); return AST_OK; }
// Sugar for partial application, which we convert to a lambda. static bool partial_application(pass_opt_t* opt, ast_t** astp) { /* Example that we refer to throughout this function. * ```pony * class C * fun f[T](a: A, b: B = b_default): R * * let recv: T = ... * recv~f[T2](foo) * ``` * * Partial call is converted to: * ```pony * lambda(b: B = b_default)($0 = recv, a = foo): R => $0.f[T2](a, consume b) * ``` */ ast_t* ast = *astp; typecheck_t* t = &opt->check; if(!method_application(opt, ast, true)) return false; AST_GET_CHILDREN(ast, positional, namedargs, lhs); assert(ast_id(lhs) == TK_FUNAPP || ast_id(lhs) == TK_BEAPP || ast_id(lhs) == TK_NEWAPP); // LHS must be a TK_TILDE, possibly contained in a TK_QUALIFY. AST_GET_CHILDREN(lhs, receiver, method); ast_t* type_args = NULL; switch(ast_id(receiver)) { case TK_NEWAPP: case TK_BEAPP: case TK_FUNAPP: type_args = method; AST_GET_CHILDREN_NO_DECL(receiver, receiver, method); break; default: {} } // The TK_FUNTYPE of the LHS. ast_t* type = ast_type(lhs); if(is_typecheck_error(type)) return false; token_id apply_cap = partial_application_cap(opt, type, receiver, positional); AST_GET_CHILDREN(type, cap, type_params, target_params, result); token_id can_error = ast_canerror(lhs) ? TK_QUESTION : TK_NONE; const char* recv_name = package_hygienic_id(t); // Build captures. We always have at least one capture, for receiver. // Capture: `$0 = recv` BUILD(captures, receiver, NODE(TK_LAMBDACAPTURES, NODE(TK_LAMBDACAPTURE, ID(recv_name) NONE // Infer type. TREE(receiver)))); // Process arguments. ast_t* given_arg = ast_child(positional); ast_t* target_param = ast_child(target_params); ast_t* lambda_params = ast_from(target_params, TK_NONE); ast_t* lambda_call_args = ast_from(positional, TK_NONE); while(given_arg != NULL) { assert(target_param != NULL); const char* target_p_name = ast_name(ast_child(target_param)); if(ast_id(given_arg) == TK_NONE) { // This argument is not supplied already, must be a lambda parameter. // Like `b` in example above. // Build a new a new TK_PARAM node rather than copying the target one, // since the target has already been processed to expr pass, and we need // a clean one. AST_GET_CHILDREN(target_param, p_id, p_type, p_default); // Parameter: `b: B = b_default` BUILD(lambda_param, target_param, NODE(TK_PARAM, TREE(p_id) TREE(sanitise_type(p_type)) TREE(p_default))); ast_append(lambda_params, lambda_param); ast_setid(lambda_params, TK_PARAMS); // Argument: `consume b` BUILD(target_arg, lambda_param, NODE(TK_SEQ, NODE(TK_CONSUME, NONE NODE(TK_REFERENCE, ID(target_p_name))))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } else { // This argument is supplied to the partial, capture it. // Like `a` in example above. // Capture: `a = foo` BUILD(capture, given_arg, NODE(TK_LAMBDACAPTURE, ID(target_p_name) NONE TREE(given_arg))); ast_append(captures, capture); // Argument: `a` BUILD(target_arg, given_arg, NODE(TK_SEQ, NODE(TK_REFERENCE, ID(target_p_name)))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } given_arg = ast_sibling(given_arg); target_param = ast_sibling(target_param); } assert(target_param == NULL); // Build lambda expression. // `$0.f` BUILD(call_receiver, ast, NODE(TK_DOT, NODE(TK_REFERENCE, ID(recv_name)) TREE(method))); if(type_args != NULL) { // The partial call has type args, add them to the actual call in apply(). // `$0.f[T2]` BUILD(qualified, type_args, NODE(TK_QUALIFY, TREE(call_receiver) TREE(type_args))); call_receiver = qualified; } REPLACE(astp, NODE(TK_LAMBDA, NODE(apply_cap) NONE // Lambda function name. NONE // Lambda type params. TREE(lambda_params) TREE(captures) TREE(sanitise_type(result)) NODE(can_error) NODE(TK_SEQ, NODE(TK_CALL, TREE(lambda_call_args) NONE // Named args. TREE(call_receiver))))); // Need to preserve various lambda children. ast_setflag(ast_childidx(*astp, 2), AST_FLAG_PRESERVE); // Type params. ast_setflag(ast_childidx(*astp, 3), AST_FLAG_PRESERVE); // Parameters. ast_setflag(ast_childidx(*astp, 5), AST_FLAG_PRESERVE); // Return type. ast_setflag(ast_childidx(*astp, 7), AST_FLAG_PRESERVE); // Body. // Catch up to this pass. return ast_passes_subtree(astp, opt, PASS_EXPR); }
// Sugar for partial application, which we convert to a lambda. static bool partial_application(pass_opt_t* opt, ast_t** astp) { /* Example that we refer to throughout this function. * ```pony * class C * fun f[T](a: A, b: B = b_default): R * * let recv: T = ... * recv~f[T2](foo) * ``` * * Partial call is converted to: * ```pony * {(b: B = b_default)($0 = recv, a = foo): R => $0.f[T2](a, consume b) } * ``` */ ast_t* ast = *astp; typecheck_t* t = &opt->check; if(!method_application(opt, ast, true)) return false; AST_GET_CHILDREN(ast, positional, namedargs, question, lhs); // LHS must be an application, possibly wrapped in another application // if the method had type parameters for qualification. pony_assert(ast_id(lhs) == TK_FUNAPP || ast_id(lhs) == TK_BEAPP || ast_id(lhs) == TK_NEWAPP); AST_GET_CHILDREN(lhs, receiver, method); ast_t* type_args = NULL; if(ast_id(receiver) == ast_id(lhs)) { type_args = method; AST_GET_CHILDREN_NO_DECL(receiver, receiver, method); } // Look up the original method definition for this method call. ast_t* method_def = lookup(opt, lhs, ast_type(receiver), ast_name(method)); pony_assert(ast_id(method_def) == TK_FUN || ast_id(method_def) == TK_BE || ast_id(method_def) == TK_NEW); // The TK_FUNTYPE of the LHS. ast_t* type = ast_type(lhs); pony_assert(ast_id(type) == TK_FUNTYPE); if(is_typecheck_error(type)) return false; AST_GET_CHILDREN(type, cap, type_params, target_params, result); bool bare = ast_id(cap) == TK_AT; token_id apply_cap = TK_AT; if(!bare) apply_cap = partial_application_cap(opt, type, receiver, positional); token_id can_error = ast_id(ast_childidx(method_def, 5)); const char* recv_name = package_hygienic_id(t); // Build lambda expression. ast_t* call_receiver = NULL; if(bare) { ast_t* arg = ast_child(positional); while(arg != NULL) { if(ast_id(arg) != TK_NONE) { ast_error(opt->check.errors, arg, "the partial application of a bare " "method cannot take arguments"); return false; } arg = ast_sibling(arg); } ast_t* receiver_type = ast_type(receiver); if(is_bare(receiver_type)) { // Partial application on a bare object, simply return the object itself. ast_replace(astp, receiver); return true; } AST_GET_CHILDREN(receiver_type, recv_type_package, recv_type_name); const char* recv_package_str = ast_name(recv_type_package); const char* recv_name_str = ast_name(recv_type_name); ast_t* module = ast_nearest(ast, TK_MODULE); ast_t* package = ast_parent(module); ast_t* pkg_id = package_id(package); const char* pkg_str = ast_name(pkg_id); const char* pkg_alias = NULL; if(recv_package_str != pkg_str) pkg_alias = package_alias_from_id(module, recv_package_str); ast_free_unattached(pkg_id); if(pkg_alias != NULL) { // `package.Type.f` BUILD_NO_DECL(call_receiver, ast, NODE(TK_DOT, NODE(TK_DOT, NODE(TK_REFERENCE, ID(pkg_alias)) ID(recv_name_str)) TREE(method))); } else { // `Type.f` BUILD_NO_DECL(call_receiver, ast, NODE(TK_DOT, NODE(TK_REFERENCE, ID(recv_name_str)) TREE(method))); } } else { // `$0.f` BUILD_NO_DECL(call_receiver, ast, NODE(TK_DOT, NODE(TK_REFERENCE, ID(recv_name)) TREE(method))); } ast_t* captures = NULL; if(bare) { captures = ast_from(receiver, TK_NONE); } else { // Build captures. We always have at least one capture, for receiver. // Capture: `$0 = recv` BUILD_NO_DECL(captures, receiver, NODE(TK_LAMBDACAPTURES, NODE(TK_LAMBDACAPTURE, ID(recv_name) NONE // Infer type. TREE(receiver)))); } // Process arguments. ast_t* target_param = ast_child(target_params); ast_t* lambda_params = ast_from(target_params, TK_NONE); ast_t* lambda_call_args = ast_from(positional, TK_NONE); ast_t* given_arg = ast_child(positional); while(given_arg != NULL) { pony_assert(target_param != NULL); const char* target_p_name = ast_name(ast_child(target_param)); if(ast_id(given_arg) == TK_NONE) { // This argument is not supplied already, must be a lambda parameter. // Like `b` in example above. // Build a new a new TK_PARAM node rather than copying the target one, // since the target has already been processed to expr pass, and we need // a clean one. AST_GET_CHILDREN(target_param, p_id, p_type, p_default); // Parameter: `b: B = b_default` BUILD(lambda_param, target_param, NODE(TK_PARAM, TREE(p_id) TREE(sanitise_type(p_type)) TREE(p_default))); ast_append(lambda_params, lambda_param); ast_setid(lambda_params, TK_PARAMS); // Argument: `consume b` BUILD(target_arg, lambda_param, NODE(TK_SEQ, NODE(TK_CONSUME, NONE NODE(TK_REFERENCE, ID(target_p_name))))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } else { // This argument is supplied to the partial, capture it. // Like `a` in example above. // Capture: `a = foo` BUILD(capture, given_arg, NODE(TK_LAMBDACAPTURE, ID(target_p_name) NONE TREE(given_arg))); ast_append(captures, capture); // Argument: `a` BUILD(target_arg, given_arg, NODE(TK_SEQ, NODE(TK_REFERENCE, ID(target_p_name)))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } given_arg = ast_sibling(given_arg); target_param = ast_sibling(target_param); } pony_assert(target_param == NULL); if(type_args != NULL) { // The partial call has type args, add them to the actual call in apply(). // `$0.f[T2]` BUILD(qualified, type_args, NODE(TK_QUALIFY, TREE(call_receiver) TREE(type_args))); call_receiver = qualified; } REPLACE(astp, NODE((bare ? TK_BARELAMBDA : TK_LAMBDA), NODE(apply_cap) NONE // Lambda function name. NONE // Lambda type params. TREE(lambda_params) TREE(captures) TREE(sanitise_type(result)) NODE(can_error) NODE(TK_SEQ, NODE(TK_CALL, TREE(lambda_call_args) NONE // Named args. NODE(can_error) TREE(call_receiver))) NONE)); // Lambda reference capability. // Need to preserve various lambda children. ast_setflag(ast_childidx(*astp, 2), AST_FLAG_PRESERVE); // Type params. ast_setflag(ast_childidx(*astp, 3), AST_FLAG_PRESERVE); // Parameters. ast_setflag(ast_childidx(*astp, 5), AST_FLAG_PRESERVE); // Return type. ast_setflag(ast_childidx(*astp, 7), AST_FLAG_PRESERVE); // Body. // Catch up to this pass. return ast_passes_subtree(astp, opt, PASS_EXPR); }
bool expr_object(pass_opt_t* opt, ast_t** astp) { ast_t* ast = *astp; bool ok = true; AST_GET_CHILDREN(ast, cap, provides, members); ast_clearflag(cap, AST_FLAG_PRESERVE); ast_clearflag(provides, AST_FLAG_PRESERVE); ast_clearflag(members, AST_FLAG_PRESERVE); ast_t* annotation = ast_consumeannotation(ast); const char* c_id = package_hygienic_id(&opt->check); ast_t* t_params; ast_t* t_args; collect_type_params(ast, &t_params, &t_args); const char* nice_id = (const char*)ast_data(ast); if(nice_id == NULL) nice_id = "object literal"; // Create a new anonymous type. BUILD(def, ast, NODE(TK_CLASS, AST_SCOPE ANNOTATE(annotation) NICE_ID(c_id, nice_id) TREE(t_params) NONE TREE(provides) NODE(TK_MEMBERS) NONE NONE)); // We will have a create method in the type. BUILD(create, members, NODE(TK_NEW, AST_SCOPE NONE ID("create") NONE NODE(TK_PARAMS) NONE NONE NODE(TK_SEQ, NODE(TK_TRUE)) NONE NONE)); BUILD(type_ref, ast, NODE(TK_REFERENCE, ID(c_id))); if(ast_id(t_args) != TK_NONE) { // Need to add type args to our type reference BUILD(t, ast, NODE(TK_QUALIFY, TREE(type_ref) TREE(t_args))); type_ref = t; } ast_free_unattached(t_args); // We will replace object..end with $0.create(...) BUILD(call, ast, NODE(TK_CALL, NODE(TK_POSITIONALARGS) NONE NONE NODE(TK_DOT, TREE(type_ref) ID("create")))); ast_t* create_params = ast_childidx(create, 3); ast_t* create_body = ast_childidx(create, 6); ast_t* call_args = ast_child(call); ast_t* class_members = ast_childidx(def, 4); ast_t* member = ast_child(members); bool has_fields = false; bool has_behaviours = false; while(member != NULL) { switch(ast_id(member)) { case TK_FVAR: case TK_FLET: case TK_EMBED: { add_field_to_object(opt, member, class_members, create_params, create_body, call_args); has_fields = true; break; } case TK_BE: // If we have behaviours, we must be an actor. ast_append(class_members, member); has_behaviours = true; break; default: // Keep all the methods as they are. ast_append(class_members, member); break; } member = ast_sibling(member); } // Add the create function at the end. ast_append(class_members, create); // Add new type to current module and bring it up to date with passes. ast_t* module = ast_nearest(ast, TK_MODULE); ast_append(module, def); // Turn any free variables into fields. ast_t* captures = ast_from(ast, TK_MEMBERS); ast_t* last_capture = NULL; if(!capture_from_type(opt, *astp, &def, captures, &last_capture)) ok = false; for(ast_t* p = ast_child(captures); p != NULL; p = ast_sibling(p)) { add_field_to_object(opt, p, class_members, create_params, create_body, call_args); has_fields = true; } ast_free_unattached(captures); ast_resetpass(def, PASS_SUGAR); // Handle capability and whether the anonymous type is a class, primitive or // actor. token_id cap_id = ast_id(cap); if(has_behaviours) { // Change the type to an actor. ast_setid(def, TK_ACTOR); if(cap_id != TK_NONE && cap_id != TK_TAG) { ast_error(opt->check.errors, cap, "object literals with behaviours are " "actors and so must have tag capability"); ok = false; } cap_id = TK_TAG; } else if(!has_fields && (cap_id == TK_NONE || cap_id == TK_TAG || cap_id == TK_BOX || cap_id == TK_VAL)) { // Change the type from a class to a primitive. ast_setid(def, TK_PRIMITIVE); cap_id = TK_VAL; } if(ast_id(def) != TK_PRIMITIVE) pony_assert(!ast_has_annotation(def, "ponyint_bare")); // Reset constructor to pick up the correct defaults. ast_setid(ast_child(create), cap_id); ast_t* result = ast_childidx(create, 4); ast_replace(&result, type_for_class(opt, def, result, cap_id, TK_EPHEMERAL, false)); // Catch up provides before catching up the entire type. if(!catch_up_provides(opt, provides)) return false; // Type check the anonymous type. if(!ast_passes_type(&def, opt, PASS_EXPR)) return false; // Replace object..end with $0.create(...) ast_replace(astp, call); if(ast_visit(astp, pass_syntax, NULL, opt, PASS_SYNTAX) != AST_OK) return false; if(!ast_passes_subtree(astp, opt, PASS_EXPR)) return false; return ok; }
// Handle a set of case methods, starting at the given method. // Generate wrapper and worker methods and append them to the given list. // Returns: true on success, false on failure. static bool sugar_case_method(ast_t* first_case_method, ast_t* members, const char* name, typecheck_t* t) { assert(first_case_method != NULL); assert(members != NULL); assert(name != NULL); assert(t != NULL); ast_t* wrapper = make_match_wrapper(first_case_method); if(wrapper == NULL) return false; ast_t* match_cases = ast_from(first_case_method, TK_CASES); ast_scope(match_cases); // Process all methods with the same name. // We need to remove processed methods. However, removing nodes from a list // we're iterating over gets rather complex. Instead we mark nodes and remove // them all in one sweep later. for(ast_t* p = first_case_method; p != NULL; p = ast_sibling(p)) { const char* p_name = ast_name(ast_childidx(p, 1)); if(p_name == name) { // This method is in the case set. ast_t* case_ast = add_case_method(wrapper, p); if(case_ast == NULL) { ast_free(wrapper); ast_free(match_cases); return false; } ast_append(match_cases, case_ast); ast_setid(p, TK_NONE); // Mark for removal. } } // Check params and build match operand, worker parameters and worker call // arguments. ast_t* match_params = ast_childidx(wrapper, 3); ast_t* worker_params = ast_from(match_params, TK_PARAMS); ast_t* call_args = ast_from(match_params, TK_POSITIONALARGS); ast_t* match_operand = build_params(match_params, worker_params, call_args, name, t); if(match_operand == NULL) { ast_free(wrapper); ast_free(match_cases); ast_free(worker_params); ast_free(call_args); return false; } // Complete wrapper function and add to containing entity. const char* worker_name = package_hygienic_id(t); ast_t* wrapper_body = ast_childidx(wrapper, 6); ast_t* wrapper_call; ast_t* call_t_args = ast_from(wrapper, TK_NONE); assert(ast_id(wrapper_body) == TK_SEQ); assert(ast_childcount(wrapper_body) == 0); build_t_params(ast_childidx(wrapper, 2), call_t_args); if(ast_id(call_t_args) == TK_NONE) { // No type args needed. ast_free(call_t_args); BUILD(tmp, wrapper_body, NODE(TK_CALL, TREE(call_args) NONE NODE(TK_REFERENCE, ID(worker_name)))); wrapper_call = tmp; } else { // Type args needed on call. BUILD(tmp, wrapper_body, NODE(TK_CALL, TREE(call_args) NONE NODE(TK_QUALIFY, NODE(TK_REFERENCE, ID(worker_name)) TREE(call_t_args)))); wrapper_call = tmp; } ast_append(wrapper_body, wrapper_call); ast_append(members, wrapper); // Make worker function and add to containing entity. AST_GET_CHILDREN(wrapper, cap, wrapper_id, t_params, wrapper_params, ret_type, error); if(ast_id(wrapper) == TK_BE) cap = ast_from(cap, TK_REF); BUILD(worker, wrapper, NODE(TK_FUN, AST_SCOPE TREE(cap) ID(worker_name) TREE(t_params) TREE(worker_params) TREE(ret_type) TREE(error) NODE(TK_SEQ, NODE(TK_MATCH, AST_SCOPE NODE(TK_SEQ, TREE(match_operand)) TREE(match_cases) NONE)) // Else clause. NONE // Doc string. NONE)); // Guard. ast_append(members, worker); return true; }
// Check parameters and build the all parameter structures for a complete set // of case methods with the given name. // Generate match operand, worker parameters and worker call arguments. // Returns: match operand, NULL on failure. static ast_t* build_params(ast_t* match_params, ast_t* worker_params, ast_t* call_args, const char* name, typecheck_t* t) { assert(match_params != NULL); assert(worker_params != NULL); assert(call_args != NULL); assert(name != NULL); assert(t != NULL); ast_t* match_operand = ast_from(match_params, TK_TUPLE); size_t count = 0; bool ok = true; for(ast_t* p = ast_child(match_params); p != NULL; p = ast_sibling(p)) { AST_GET_CHILDREN(p, id, type, def_arg); count++; if(ast_id(id) == TK_NONE) { assert(ast_id(type) == TK_NONE); ast_error(p, "name and type not specified for parameter " __zu " of case function %s", count, name); ok = false; } else { const char* worker_param_name = package_hygienic_id(t); // Add parameter to match operand. BUILD(element, p, NODE(TK_SEQ, NODE(TK_CONSUME, NONE NODE(TK_REFERENCE, ID(worker_param_name))))); ast_append(match_operand, element); // Add parameter to worker function. BUILD(param, p, NODE(TK_PARAM, ID(worker_param_name) TREE(type) NONE)); // Default argument. ast_append(worker_params, param); // Add argument to worker call. BUILD(arg, p, NODE(TK_SEQ, NODE(TK_CONSUME, NONE NODE(TK_REFERENCE, TREE(id))))); ast_append(call_args, arg); } } if(!ok) { ast_free(match_operand); return NULL; } assert(count > 0); if(count == 1) { // Only one parameter, don't need tuple in operand. ast_t* tmp = ast_pop(ast_child(match_operand)); ast_free(match_operand); match_operand = tmp; } return match_operand; }
static ast_result_t sugar_object(pass_opt_t* opt, ast_t** astp) { typecheck_t* t = &opt->check; ast_t* ast = *astp; ast_result_t r = AST_OK; AST_GET_CHILDREN(ast, cap, provides, members); ast_t* c_id = ast_from_string(ast, package_hygienic_id(t)); ast_t* t_params; ast_t* t_args; collect_type_params(ast, &t_params, &t_args); // Create a new anonymous type. BUILD(def, ast, NODE(TK_CLASS, AST_SCOPE TREE(c_id) TREE(t_params) NONE TREE(provides) NODE(TK_MEMBERS) NONE NONE)); // We will have a create method in the type. BUILD_NO_DEBUG(create, members, NODE(TK_NEW, AST_SCOPE NONE ID("create") NONE NONE NONE NONE NODE(TK_SEQ) NONE NONE)); BUILD(type_ref, ast, NODE(TK_REFERENCE, TREE(c_id))); if(ast_id(t_args) != TK_NONE) { // Need to add type args to our type reference BUILD(t, ast, NODE(TK_QUALIFY, TREE(type_ref) TREE(t_args))); type_ref = t; } ast_free_unattached(t_args); // We will replace object..end with $0.create(...) BUILD(call, ast, NODE(TK_CALL, NONE NONE NODE(TK_DOT, TREE(type_ref) ID("create")))); ast_t* create_params = ast_childidx(create, 3); ast_t* create_body = ast_childidx(create, 6); ast_t* call_args = ast_child(call); ast_t* class_members = ast_childidx(def, 4); ast_t* member = ast_child(members); bool has_fields = false; bool has_behaviours = false; while(member != NULL) { switch(ast_id(member)) { case TK_FVAR: case TK_FLET: case TK_EMBED: { AST_GET_CHILDREN(member, id, type, init); ast_t* p_id = ast_from_string(id, package_hygienic_id(t)); // The field is: var/let/embed id: type BUILD(field, member, NODE(ast_id(member), TREE(id) TREE(type) NONE NONE)); // The param is: $0: type BUILD(param, member, NODE(TK_PARAM, TREE(p_id) TREE(type) NONE)); // The arg is: $seq init BUILD(arg, init, NODE(TK_SEQ, TREE(init))); // The body of create contains: id = consume $0 BUILD_NO_DEBUG(assign, init, NODE(TK_ASSIGN, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(p_id))) NODE(TK_REFERENCE, TREE(id)))); ast_setid(create_params, TK_PARAMS); ast_setid(call_args, TK_POSITIONALARGS); ast_append(class_members, field); ast_append(create_params, param); ast_append(create_body, assign); ast_append(call_args, arg); has_fields = true; break; } case TK_BE: // If we have behaviours, we must be an actor. ast_append(class_members, member); has_behaviours = true; break; default: // Keep all the methods as they are. ast_append(class_members, member); break; } member = ast_sibling(member); } if(!has_fields) { // End the constructor with 'true', since it has no parameters. BUILD_NO_DEBUG(true_node, ast, NODE(TK_TRUE)); ast_append(create_body, true_node); } // Handle capability and whether the anonymous type is a class, primitive or // actor. token_id cap_id = ast_id(cap); if(has_behaviours) { // Change the type to an actor. ast_setid(def, TK_ACTOR); if(cap_id != TK_NONE && cap_id != TK_TAG) { ast_error(cap, "object literals with behaviours are actors and so must " "have tag capability"); r = AST_ERROR; } } else if(!has_fields && (cap_id == TK_NONE || cap_id == TK_TAG || cap_id == TK_BOX || cap_id == TK_VAL)) { // Change the type from a class to a primitive. ast_setid(def, TK_PRIMITIVE); } else { // Type is a class, set the create capability as specified ast_setid(ast_child(create), cap_id); } // Add the create function at the end. ast_append(class_members, create); // Replace object..end with $0.create(...) ast_replace(astp, call); // Add new type to current module and bring it up to date with passes. ast_t* module = ast_nearest(ast, TK_MODULE); ast_append(module, def); if(!ast_passes_type(&def, opt)) return AST_FATAL; // Sugar the call. if(!ast_passes_subtree(astp, opt, PASS_SUGAR)) return AST_FATAL; return r; }
static bool partial_application(pass_opt_t* opt, ast_t** astp) { ast_t* ast = *astp; typecheck_t* t = &opt->check; if(!method_application(opt, ast, true)) return false; AST_GET_CHILDREN(ast, positional, namedargs, lhs); // LHS must be a TK_TILDE, possibly contained in a TK_QUALIFY. AST_GET_CHILDREN(lhs, receiver, method); switch(ast_id(receiver)) { case TK_NEWAPP: case TK_BEAPP: case TK_FUNAPP: AST_GET_CHILDREN_NO_DECL(receiver, receiver, method); break; default: {} } // The TK_FUNTYPE of the LHS. ast_t* type = ast_type(lhs); if(is_typecheck_error(type)) return false; token_id apply_cap = partial_application_cap(type, receiver, positional); AST_GET_CHILDREN(type, cap, typeparams, params, result); // Create a new anonymous type. ast_t* c_id = ast_from_string(ast, package_hygienic_id(t)); BUILD(def, ast, NODE(TK_CLASS, AST_SCOPE TREE(c_id) NONE NONE NONE NODE(TK_MEMBERS) NONE NONE)); // We will have a create method in the type. BUILD(create, ast, NODE(TK_NEW, AST_SCOPE NONE ID("create") NONE NODE(TK_PARAMS) NONE NONE NODE(TK_SEQ) NONE)); // We will have an apply method in the type. token_id can_error = ast_canerror(lhs) ? TK_QUESTION : TK_NONE; BUILD(apply, ast, NODE(TK_FUN, AST_SCOPE NODE(apply_cap) ID("apply") NONE NODE(TK_PARAMS) TREE(result) NODE(can_error) NODE(TK_SEQ) NONE)); // We will replace partial application with $0.create(...) BUILD(call_receiver, ast, NODE(TK_REFERENCE, TREE(c_id))); BUILD(call_dot, ast, NODE(TK_DOT, TREE(call_receiver) ID("create"))); BUILD(call, ast, NODE(TK_CALL, NONE NODE(TK_NAMEDARGS) TREE(call_dot))); ast_t* class_members = ast_childidx(def, 4); ast_t* create_params = ast_childidx(create, 3); ast_t* create_body = ast_childidx(create, 6); ast_t* apply_params = ast_childidx(apply, 3); ast_t* apply_body = ast_childidx(apply, 6); ast_t* call_namedargs = ast_childidx(call, 1); // Add the receiver to the anonymous type. ast_t* r_id = ast_from_string(receiver, package_hygienic_id(t)); ast_t* r_field_id = ast_from_string(receiver, package_hygienic_id(t)); ast_t* r_type = ast_type(receiver); if(is_typecheck_error(r_type)) return false; // A field in the type. BUILD(r_field, receiver, NODE(TK_FLET, TREE(r_field_id) TREE(r_type) NONE)); // A parameter of the constructor. BUILD(r_ctor_param, receiver, NODE(TK_PARAM, TREE(r_id) TREE(r_type) NONE)); // An assignment in the constructor body. BUILD(r_assign, receiver, NODE(TK_ASSIGN, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(r_id))) NODE(TK_REFERENCE, TREE(r_field_id)))); // A named argument at the call site. BUILD(r_call_seq, receiver, NODE(TK_SEQ, TREE(receiver))); BUILD(r_call_arg, receiver, NODE(TK_NAMEDARG, TREE(r_id) TREE(r_call_seq))); ast_settype(r_call_seq, r_type); ast_append(class_members, r_field); ast_append(create_params, r_ctor_param); ast_append(create_body, r_assign); ast_append(call_namedargs, r_call_arg); // Add a call to the original method to the apply body. BUILD(apply_call, ast, NODE(TK_CALL, NODE(TK_POSITIONALARGS) NONE NODE(TK_DOT, NODE(TK_REFERENCE, TREE(r_field_id)) TREE(method)))); ast_append(apply_body, apply_call); ast_t* apply_args = ast_child(apply_call); // Add the arguments to the anonymous type. ast_t* arg = ast_child(positional); ast_t* param = ast_child(params); while(arg != NULL) { AST_GET_CHILDREN(param, id, p_type); if(ast_id(arg) == TK_NONE) { // A parameter of the apply method, using the same name, type and default // argument. ast_append(apply_params, param); // An arg in the call to the original method. BUILD(apply_arg, param, NODE(TK_SEQ, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(id))))); ast_append(apply_args, apply_arg); } else { ast_t* p_id = ast_from_string(id, package_hygienic_id(t)); // A field in the type. BUILD(field, arg, NODE(TK_FLET, TREE(id) TREE(p_type) NONE)); // A parameter of the constructor. BUILD(ctor_param, arg, NODE(TK_PARAM, TREE(p_id) TREE(p_type) NONE)); // An assignment in the constructor body. BUILD(assign, arg, NODE(TK_ASSIGN, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(p_id))) NODE(TK_REFERENCE, TREE(id)))); // A named argument at the call site. BUILD(call_arg, arg, NODE(TK_NAMEDARG, TREE(p_id) TREE(arg))); // An arg in the call to the original method. BUILD(apply_arg, arg, NODE(TK_SEQ, NODE(TK_REFERENCE, TREE(id)))); ast_append(class_members, field); ast_append(create_params, ctor_param); ast_append(create_body, assign); ast_append(call_namedargs, call_arg); ast_append(apply_args, apply_arg); } arg = ast_sibling(arg); param = ast_sibling(param); } // Add create and apply to the anonymous type. ast_append(class_members, create); ast_append(class_members, apply); // Typecheck the anonymous type. ast_add(t->frame->module, def); if(!type_passes(def, opt)) return false; // Typecheck the create call. if(!expr_reference(opt, &call_receiver)) return false; if(!expr_dot(opt, &call_dot)) return false; if(!expr_call(opt, &call)) return false; // Replace the partial application with the create call. ast_replace(astp, call); return true; }