diff options
author | Jemma Issroff <[email protected]> | 2023-10-30 16:20:07 -0300 |
---|---|---|
committer | Jemma Issroff <[email protected]> | 2023-10-30 18:07:04 -0300 |
commit | f0b7895637d4c4e4a1c72cecaa8700495f12177b (patch) | |
tree | d890c8d25c48b89eceaeb03594aeafa6e055d356 | |
parent | b17b0336b613b0873542f48f1417ebfeb0079efa (diff) |
[PRISM] Implement all argument types for CallNodes
-rw-r--r-- | prism_compile.c | 162 |
1 files changed, 149 insertions, 13 deletions
diff --git a/prism_compile.c b/prism_compile.c index 55f0bd026d..916e60bea7 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1382,6 +1382,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ID method_id = pm_constant_id_lookup(scope_node, call_node->name); int flags = 0; int orig_argc = 0; + struct rb_callinfo_kwarg *kw_arg = NULL; if (call_node->receiver == NULL) { ADD_INSN(ret, &dummy_line_node, putself); @@ -1393,21 +1394,156 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (flags & VM_CALL_FCALL) { flags |= VM_CALL_VCALL; } - } else { - pm_arguments_node_t *arguments = call_node->arguments; - PM_COMPILE_NOT_POPPED((pm_node_t *) arguments); - orig_argc = (int)arguments->arguments.size; + } + else { + pm_arguments_node_t *arguments_node = call_node->arguments; + pm_node_list_t arguments_node_list = arguments_node->arguments; + + bool has_keyword_splat = (arguments_node->base.flags & PM_ARGUMENTS_NODE_FLAGS_KEYWORD_SPLAT); + bool has_splat = false; + + // We count the number of elements post the splat node that are not keyword elements to + // eventually pass as an argument to newarray + int post_splat_counter = 0; + + for (size_t index = 0; index < arguments_node_list.size; index++) { + pm_node_t *argument = arguments_node_list.nodes[index]; + + switch (PM_NODE_TYPE(argument)) { + // A keyword hash node contains all keyword arguments as AssocNodes and AssocSplatNodes + case PM_KEYWORD_HASH_NODE: { + pm_keyword_hash_node_t *keyword_arg = (pm_keyword_hash_node_t *)argument; + size_t len = keyword_arg->elements.size; + + if (has_keyword_splat) { + int cur_hash_size = 0; + orig_argc++; + + bool new_hash_emitted = false; + for (size_t i = 0; i < len; i++) { + pm_node_t *cur_node = keyword_arg->elements.nodes[i]; + + pm_node_type_t cur_type = PM_NODE_TYPE(cur_node); + + switch (PM_NODE_TYPE(cur_node)) { + case PM_ASSOC_NODE: { + pm_assoc_node_t *assoc = (pm_assoc_node_t *)cur_node; + + PM_COMPILE(assoc->key); + PM_COMPILE(assoc->value); + cur_hash_size++; + + // If we're at the last keyword arg, or the last assoc node of this "set", + // then we want to either construct a newhash or merge onto previous hashes + if (i == (len - 1) || !PM_NODE_TYPE_P(keyword_arg->elements.nodes[i + 1], cur_type)) { + if (new_hash_emitted) { + ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(cur_hash_size * 2 + 1)); + } + else { + ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(cur_hash_size * 2)); + cur_hash_size = 0; + new_hash_emitted = true; + } + } + + break; + } + case PM_ASSOC_SPLAT_NODE: { + if (len > 1) { + ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + if (i == 0) { + ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(0)); + new_hash_emitted = true; + } + else { + PM_SWAP; + } + } + + pm_assoc_splat_node_t *assoc_splat = (pm_assoc_splat_node_t *)cur_node; + PM_COMPILE(assoc_splat->value); + + flags |= VM_CALL_KW_SPLAT | VM_CALL_KW_SPLAT_MUT; + + ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_kwd, INT2FIX(2)); + + if ((i < len - 1) && !PM_NODE_TYPE_P(keyword_arg->elements.nodes[i + 1], cur_type)) { + ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PM_SWAP; + } + + cur_hash_size = 0; + break; + } + default: { + rb_bug("Unknown type"); + } + } + } + break; + } + else { + kw_arg = rb_xmalloc_mul_add(len, sizeof(VALUE), sizeof(struct rb_callinfo_kwarg)); + flags |= VM_CALL_KWARG; + kw_arg->keyword_len += len; + + // TODO: Method callers like `foo(a => b)` + for (size_t i = 0; i < len; i++) { + pm_assoc_node_t *assoc = (pm_assoc_node_t *)keyword_arg->elements.nodes[i]; + kw_arg->keywords[i] = pm_static_literal_value(assoc->key, scope_node, parser); + PM_COMPILE(assoc->value); + } + } + break; + } + case PM_SPLAT_NODE: { + flags |= VM_CALL_ARGS_SPLAT; + pm_splat_node_t *splat_node = (pm_splat_node_t *)argument; + if (splat_node->expression) { + PM_COMPILE(splat_node->expression); + } + + if (!popped) { + ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); + } + + has_splat = true; + post_splat_counter = 0; + + break; + } + default: { + orig_argc++; + post_splat_counter++; + PM_COMPILE(argument); + + if (has_splat) { + // If the next node starts the keyword section of parameters + if ((index < arguments_node_list.size - 1) && PM_NODE_TYPE_P(arguments_node_list.nodes[index + 1], PM_KEYWORD_HASH_NODE)) { + + ADD_INSN1(ret, &dummy_line_node, newarray, INT2FIX(post_splat_counter)); + ADD_INSN1(ret, &dummy_line_node, splatarray, Qfalse); + ADD_INSN(ret, &dummy_line_node, concatarray); + } + // If it's the final node + else if (index == arguments_node_list.size - 1) { + ADD_INSN1(ret, &dummy_line_node, newarray, INT2FIX(post_splat_counter)); + ADD_INSN(ret, &dummy_line_node, concatarray); + } + } + } + } + } } - VALUE block_iseq = Qnil; + const rb_iseq_t *block_iseq = NULL; if (call_node->block != NULL && PM_NODE_TYPE_P(call_node->block, PM_BLOCK_NODE)) { // Scope associated with the block pm_scope_node_t next_scope_node; pm_scope_node_init(call_node->block, &next_scope_node, scope_node, parser); - const rb_iseq_t *block_iseq = NEW_CHILD_ISEQ(next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); + block_iseq = NEW_CHILD_ISEQ(next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); ISEQ_COMPILE_DATA(iseq)->current_block = block_iseq; - ADD_SEND_WITH_BLOCK(ret, &dummy_line_node, method_id, INT2FIX(orig_argc), block_iseq); } else { if (node->flags & PM_CALL_NODE_FLAGS_VARIABLE_CALL) { @@ -1419,16 +1555,16 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, flags |= VM_CALL_ARGS_BLOCKARG; } - if (block_iseq == Qnil && flags == 0) { + if (!flags) { flags |= VM_CALL_ARGS_SIMPLE; } + } - if (call_node->receiver == NULL) { - flags |= VM_CALL_FCALL; - } - - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(orig_argc), INT2FIX(flags)); + if (call_node->receiver == NULL) { + flags |= VM_CALL_FCALL; } + + ADD_SEND_R(ret, &dummy_line_node, method_id, INT2FIX(orig_argc), block_iseq, INT2FIX(flags), kw_arg); PM_POP_IF_POPPED; return; } |