aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Newton <kddnewton@gmail.com>2023-09-28 12:14:38 -0400
committerKevin Newton <kddnewton@gmail.com>2023-09-28 15:13:09 -0400
commit6e88a56f63958a7939927545528df2aad78a32ff (patch)
tree1837753d0a7a7cda1ac2cb1c57a1b4d7392dd6b5
parent223e9fcb275379c7b397b56f5b32efa616b0f7e7 (diff)
downloadruby-6e88a56f63958a7939927545528df2aad78a32ff.tar.gz
Compile basic pattern matching expressions
-rw-r--r--prism_compile.c96
-rw-r--r--test/ruby/test_compile_prism.rb41
2 files changed, 137 insertions, 0 deletions
diff --git a/prism_compile.c b/prism_compile.c
index 3e88459d6a..18c070d9de 100644
--- a/prism_compile.c
+++ b/prism_compile.c
@@ -611,6 +611,62 @@ pm_reg_flags(const pm_node_t *node) {
return flags;
}
+/**
+ * Compile a pattern matching expression.
+ */
+static void
+pm_compile_pattern(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const uint8_t *src, pm_compile_context_t *compile_context, LABEL *matched_label, LABEL *unmatched_label)
+{
+ int lineno = (int) pm_newline_list_line_column(&compile_context->parser->newline_list, node->location.start).line;
+ NODE dummy_line_node = generate_dummy_line_node(lineno, lineno);
+
+ switch (PM_NODE_TYPE(node)) {
+ case PM_ARRAY_NODE:
+ case PM_CLASS_VARIABLE_READ_NODE:
+ case PM_CONSTANT_PATH_NODE:
+ case PM_CONSTANT_READ_NODE:
+ case PM_FALSE_NODE:
+ case PM_FLOAT_NODE:
+ case PM_GLOBAL_VARIABLE_READ_NODE:
+ case PM_IMAGINARY_NODE:
+ case PM_INSTANCE_VARIABLE_READ_NODE:
+ case PM_INTEGER_NODE:
+ case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE:
+ case PM_INTERPOLATED_STRING_NODE:
+ case PM_INTERPOLATED_SYMBOL_NODE:
+ case PM_INTERPOLATED_X_STRING_NODE:
+ case PM_LAMBDA_NODE:
+ case PM_LOCAL_VARIABLE_READ_NODE:
+ case PM_NIL_NODE:
+ case PM_RANGE_NODE:
+ case PM_RATIONAL_NODE:
+ case PM_REGULAR_EXPRESSION_NODE:
+ case PM_SELF_NODE:
+ case PM_STRING_NODE:
+ case PM_SYMBOL_NODE:
+ case PM_TRUE_NODE:
+ case PM_X_STRING_NODE:
+ PM_COMPILE_NOT_POPPED(node);
+ ADD_INSN1(ret, &dummy_line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
+ ADD_INSNL(ret, &dummy_line_node, branchif, matched_label);
+ ADD_INSNL(ret, &dummy_line_node, jump, unmatched_label);
+ break;
+ case PM_PINNED_VARIABLE_NODE: {
+ pm_pinned_variable_node_t *cast = (pm_pinned_variable_node_t *) node;
+ pm_compile_pattern(iseq, cast->variable, ret, src, compile_context, matched_label, unmatched_label);
+ break;
+ }
+ case PM_PINNED_EXPRESSION_NODE: {
+ pm_pinned_expression_node_t *cast = (pm_pinned_expression_node_t *) node;
+ pm_compile_pattern(iseq, cast->expression, ret, src, compile_context, matched_label, unmatched_label);
+ break;
+ }
+ default:
+ rb_bug("Unexpected node type in pattern matching expression: %s", pm_node_type_to_str(PM_NODE_TYPE(node)));
+ break;
+ }
+}
+
/*
* Compiles a prism node into instruction sequences
*
@@ -938,6 +994,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
pm_constant_path_node_t *constant_path_node = (pm_constant_path_node_t*) node;
if (constant_path_node->parent) {
PM_COMPILE_NOT_POPPED(constant_path_node->parent);
+ } else {
+ ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject);
}
ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse);
@@ -1617,6 +1675,44 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
return;
}
+ case PM_MATCH_PREDICATE_NODE: {
+ pm_match_predicate_node_t *cast = (pm_match_predicate_node_t *) node;
+
+ // First, allocate some stack space for the cached return value of any
+ // calls to #deconstruct.
+ ADD_INSN(ret, &dummy_line_node, putnil);
+
+ // Next, compile the expression that we're going to match against.
+ PM_COMPILE_NOT_POPPED(cast->value);
+ ADD_INSN(ret, &dummy_line_node, dup);
+
+ // Now compile the pattern that is going to be used to match against the
+ // expression.
+ LABEL *matched_label = NEW_LABEL(lineno);
+ LABEL *unmatched_label = NEW_LABEL(lineno);
+ LABEL *done_label = NEW_LABEL(lineno);
+ pm_compile_pattern(iseq, cast->pattern, ret, src, compile_context, matched_label, unmatched_label);
+
+ // If the pattern did not match, then compile the necessary instructions
+ // to handle pushing false onto the stack, then jump to the end.
+ ADD_LABEL(ret, unmatched_label);
+ ADD_INSN(ret, &dummy_line_node, pop);
+ ADD_INSN(ret, &dummy_line_node, pop);
+
+ if (!popped) ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse);
+ ADD_INSNL(ret, &dummy_line_node, jump, done_label);
+ ADD_INSN(ret, &dummy_line_node, putnil);
+
+ // If the pattern did match, then compile the necessary instructions to
+ // handle pushing true onto the stack, then jump to the end.
+ ADD_LABEL(ret, matched_label);
+ ADD_INSN1(ret, &dummy_line_node, adjuststack, INT2FIX(2));
+ if (!popped) ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue);
+ ADD_INSNL(ret, &dummy_line_node, jump, done_label);
+
+ ADD_LABEL(ret, done_label);
+ return;
+ }
case PM_MATCH_WRITE_NODE: {
pm_match_write_node_t *cast = (pm_match_write_node_t *)node;
LABEL *fail_label = NEW_LABEL(lineno);
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
index d5a00a2a8e..c74ecfef8c 100644
--- a/test/ruby/test_compile_prism.rb
+++ b/test/ruby/test_compile_prism.rb
@@ -334,6 +334,47 @@ module Prism
test_prism_eval("(1)")
end
+ ############################################################################
+ # Pattern matching #
+ ############################################################################
+
+ def test_MatchPredicateNode
+ test_prism_eval("1 in 1")
+ test_prism_eval("1.0 in 1.0")
+ test_prism_eval("1i in 1i")
+ test_prism_eval("1r in 1r")
+
+ test_prism_eval("\"foo\" in \"foo\"")
+ test_prism_eval("\"foo \#{1}\" in \"foo \#{1}\"")
+
+ test_prism_eval("false in false")
+ test_prism_eval("nil in nil")
+ test_prism_eval("self in self")
+ test_prism_eval("true in true")
+
+ test_prism_eval("5 in 0..10")
+ test_prism_eval("5 in 0...10")
+
+ test_prism_eval("module Prism; @@prism = 1; 1 in ^@@prism; end")
+ test_prism_eval("module Prism; @prism = 1; 1 in ^@prism; end")
+ test_prism_eval("$prism = 1; 1 in ^$prism")
+ test_prism_eval("prism = 1; 1 in ^prism")
+
+ test_prism_eval("[\"5\"] in %w[5]")
+
+ test_prism_eval("Prism in Prism")
+ test_prism_eval("Prism in ::Prism")
+
+ test_prism_eval(":prism in :prism")
+ test_prism_eval("%s[prism\#{1}] in %s[prism\#{1}]")
+ test_prism_eval("\"foo\" in /.../")
+ test_prism_eval("\"foo1\" in /...\#{1}/")
+ test_prism_eval("4 in ->(v) { v.even? }")
+ test_prism_eval("4 in ^(4)")
+
+ test_prism_eval("1 in 2")
+ end
+
private
def compare_eval(source)