Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Pattern Matching in TruffleRuby (GSoC) #2683

Merged
merged 83 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
07b19c7
start of pattern matching changes
razetime Jun 16, 2022
8635675
array pattern node (incomplete)
razetime Jun 18, 2022
96894c7
array pattern node (complete)
razetime Jun 20, 2022
1eb17b8
resolve problems in array pattern node file
razetime Jun 20, 2022
7ff074a
add find pattern node
razetime Jun 24, 2022
f09dc8a
add find pattern node type and visitor
razetime Jun 24, 2022
f9b5eb4
add hash pattern node
razetime Jun 27, 2022
e33cb71
add basic expr rule to the parser
razetime Jun 28, 2022
fc8c327
add empty nd_set_first_loc function
razetime Jun 28, 2022
13a067b
add productions nodes and support till p_value
razetime Jun 29, 2022
48621d5
productions till p_const added (parser support functions pending)
razetime Jul 7, 2022
720fbee
all types added, no errors
razetime Jul 12, 2022
a860973
remove all major type errors and fix missed untranslated segments
razetime Jul 13, 2022
45ec8d5
add placeholder bodies for new pattern nodes
razetime Jul 14, 2022
e8a4f4f
Fix accept() error on NilRestArgParseNode
razetime Jul 14, 2022
4b74d22
add getPosition overload, gettable and related functions to support,
razetime Jul 14, 2022
cb3f5a3
add type for rbrace to parser
razetime Jul 14, 2022
227a916
add getRubySourceLine for __LINE__
razetime Jul 21, 2022
5ee4b04
conditionally assign expressionNodes for new pattern matching
razetime Jul 21, 2022
98bd9cc
add abstract node visitors
razetime Jul 26, 2022
2cfdc26
start of new translator changes
razetime Jul 31, 2022
4d517b4
add array pattern function to translator
razetime Aug 3, 2022
5520813
move caseInPatternMatch over to translatePatternNode
razetime Aug 5, 2022
5d504f7
changes till 9 August meeting
razetime Aug 9, 2022
ef846da
regen
eregon Aug 9, 2022
d22b342
Simple working pattern
eregon Aug 9, 2022
4ca0713
Use visitor pattern for ArrayPatternParseNode
eregon Aug 9, 2022
af0244b
new changes for post arg
razetime Aug 15, 2022
3acd99f
try an AndNode to check pre and post args
razetime Aug 16, 2022
d96940d
rearrange code to add on to AndParseNode, add restarg var
razetime Aug 19, 2022
e0d958a
Array pattern pre args and post args matching
razetime Aug 22, 2022
54f4bb3
Delegate true, false, nil, const nodes to bodytranslator
razetime Aug 23, 2022
63d19e9
fix problem with tLBRACK, tRBRACK, tLBRACE, tRBRACE in parser
razetime Aug 23, 2022
a426f1f
debugging changes
razetime Aug 29, 2022
0afdee3
TruffleString adjustments to previous work
razetime Aug 31, 2022
efea21e
deconstruct changes pt1
razetime Sep 1, 2022
3afa7e0
fix shift reduce conflicts from lambda production
razetime Sep 2, 2022
fe8cd2b
fixed shift/reduce conflicts(w/ Kevin)+pattern matching error removal…
razetime Sep 5, 2022
cb0c4ef
Add array pattern length check
razetime Sep 8, 2022
e73e0c1
correct array pattern length check
razetime Sep 13, 2022
f0bda8d
update tags for pattern matching
razetime Sep 13, 2022
6e06738
check for rest arg in array pattern length check
razetime Sep 14, 2022
a79292b
add NoPatternMatching error (no usage yet)
razetime Sep 19, 2022
300391f
fix string and range errors in pattern match
razetime Sep 19, 2022
814d05e
finish adding all value nodes from body translator
razetime Sep 20, 2022
935d26f
NoMatchingPatternError: further accuracy corrections, add unless to p…
razetime Oct 4, 2022
2689681
Check Constant === object
razetime Oct 10, 2022
afa8434
check for deconstruct method in pattern
razetime Oct 10, 2022
63e1a4c
update tags and spec for new changes
razetime Oct 10, 2022
eb358b6
The rbrace rule should match tRCURLY since that is what RubyLexer emits
eregon Oct 17, 2022
032a68f
Guard pattern evaluation fix
razetime Oct 12, 2022
06a7092
Empty else condition fix, avoid NoMatchingPatternError
razetime Oct 12, 2022
96eefa2
add safe deconstruct helper
razetime Oct 17, 2022
8e63023
update parser
razetime Oct 17, 2022
bbb8c67
add condition profile to length check
razetime Oct 17, 2022
b05a914
update tags
razetime Oct 17, 2022
d7933df
add deconstruct helper to internal
razetime Oct 17, 2022
721e768
add inspected value to NoMatchingPatternError
razetime Oct 25, 2022
3882ded
remove verbose output from generate_parser
razetime Oct 31, 2022
ccc97de
fix new issues after rebase
razetime Nov 9, 2022
d07cb5d
Remove expressionNode from PatternMatchingTranslator
razetime Nov 14, 2022
2a7891f
remove unnecessary visitors from translators
razetime Nov 17, 2022
a00f4a3
remove redundant comments, document parameters
razetime Nov 20, 2022
6853fed
set fields of PatternMatchingTranslator as final
razetime Nov 20, 2022
47dd4f9
PatternMatchingTranslator: add javadoc comments
razetime Nov 20, 2022
8d58e21
tag failing specs for hash pattern
razetime Nov 21, 2022
79d3394
add SyntaxError for Hash pattern
razetime Nov 21, 2022
0e9c363
use ToJavaStringNode for NoMatchingPatternError
razetime Nov 22, 2022
15143fd
CheckIfPatternsMatchedNode: fix PE code and remove cast to String
razetime Nov 22, 2022
64abcbe
Cleanups
eregon Apr 18, 2023
cb62fc2
NoMatchingPatternError should not re-evaluate the case expression
eregon Apr 18, 2023
42e9625
Rename CheckIfPatternsMatchedNode to NoMatchingPatternError and simplify
eregon Apr 18, 2023
63bcd37
Remove dead code
eregon Apr 18, 2023
8382c70
Fix raw type warning
eregon Apr 18, 2023
da03fee
Cleanups
eregon Apr 18, 2023
fcbbe3e
Keep pattern matching opt-in until it is complete
eregon Apr 18, 2023
16a58c1
Cleanups
eregon Apr 18, 2023
0bb6dde
Check InParseNode has a size=1 ArrayParseNode
eregon Apr 18, 2023
51e30b3
Remove confusing comment
eregon Apr 18, 2023
09225a1
Cleanups
eregon Apr 18, 2023
f18b45f
isSplatted is always false for the simpler RubyCallNodeParameters con…
eregon Apr 18, 2023
a6f87f3
Cleanups
eregon Apr 18, 2023
d902957
Add ChangeLog entry
eregon Apr 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Compatibility:
* Fix `Hash#shift` when Hash is empty but has initial default value or initial default proc (@itarato).
* Make `Array#shuffle` produce the same results as CRuby (@rwstauner).
* Add `Process.argv0` method (@andrykonchin).
* Add support for array pattern matching. This is opt-in via `--pattern-matching` since pattern matching is not fully supported yet. (#2683, @razetime).

Performance:

Expand Down
16 changes: 14 additions & 2 deletions spec/ruby/language/pattern_matching_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
in []
end
RUBY
}.should raise_error(SyntaxError, /syntax error, unexpected `in'/)
}.should raise_error(SyntaxError, /syntax error, unexpected `in'|\(eval\):3: syntax error, unexpected keyword_in/)

-> {
eval <<~RUBY
Expand All @@ -214,7 +214,7 @@
when 1 == 1
end
RUBY
}.should raise_error(SyntaxError, /syntax error, unexpected `when'/)
}.should raise_error(SyntaxError, /syntax error, unexpected `when'|\(eval\):3: syntax error, unexpected keyword_when/)
end

it "checks patterns until the first matching" do
Expand Down Expand Up @@ -251,6 +251,18 @@
}.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
end

it "raises NoMatchingPatternError if no pattern matches and evaluates the expression only once" do
evals = 0
-> {
eval <<~RUBY
case (evals += 1; [0, 1])
in [0]
end
RUBY
}.should raise_error(NoMatchingPatternError, /\[0, 1\]/)
evals.should == 1
end

it "does not allow calculation or method calls in a pattern" do
-> {
eval <<~RUBY
Expand Down
43 changes: 9 additions & 34 deletions spec/tags/language/pattern_matching_tags.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
fails:Pattern matching binds variables
fails:Pattern matching cannot mix in and when operators
fails:Pattern matching raises NoMatchingPatternError if no pattern matches and no else clause
fails:Pattern matching guards supports if guard
fails:Pattern matching guards supports unless guard
fails:Pattern matching guards makes bound variables visible in guard
fails:Pattern matching guards does not evaluate guard if pattern does not match
fails:Pattern matching guards takes guards into account when there are several matching patterns
fails:Pattern matching guards executes else clause if no guarded pattern matches
fails:Pattern matching guards raises NoMatchingPatternError if no guarded pattern matches and no else clause
fails:Pattern matching variable pattern matches a value and binds variable name to this value
fails:Pattern matching variable pattern makes bounded variable visible outside a case statement scope
fails:Pattern matching variable pattern create local variables even if a pattern doesn't match
fails:Pattern matching variable pattern allow using _ name to drop values
fails:Pattern matching variable pattern supports using _ in a pattern several times
fails:Pattern matching variable pattern supports using any name with _ at the beginning in a pattern several times
fails:Pattern matching variable pattern does not support using variable name (except _) several times
fails:Pattern matching variable pattern supports existing variables in a pattern specified with ^ operator
fails:Pattern matching variable pattern allows applying ^ operator to bound variables
fails:Pattern matching variable pattern requires bound variable to be specified in a pattern before ^ operator when it relies on a bound variable
Expand All @@ -24,20 +8,9 @@ fails:Pattern matching alternative pattern support underscore prefixed variables
fails:Pattern matching AS pattern binds a variable to a value if pattern matches
fails:Pattern matching AS pattern can be used as a nested pattern
fails:Pattern matching Array pattern supports form Constant(pat, pat, ...)
fails:Pattern matching Array pattern supports form pat, pat, ...
fails:Pattern matching Array pattern does not match object if Constant === object returns false
fails:Pattern matching Array pattern does not match object without #deconstruct method
fails:Pattern matching Array pattern raises TypeError if #deconstruct method does not return array
fails:Pattern matching Array pattern does not match object if elements of array returned by #deconstruct method does not match elements in pattern
fails:Pattern matching Array pattern binds variables
fails:Pattern matching Array pattern supports splat operator *rest
fails:Pattern matching Array pattern does match partially from the array beginning if list + , syntax used
fails:Pattern matching Array pattern matches anything with *
fails:Pattern matching Hash pattern supports form id: pat, id: pat, ...
fails:Pattern matching Hash pattern supports a: which means a: a
fails:Pattern matching Hash pattern can mix key (a:) and key-value (a: b) declarations
fails:Pattern matching Hash pattern does not support string interpolation in keys
fails:Pattern matching Hash pattern raise SyntaxError when keys duplicate in pattern
fails:Pattern matching Hash pattern does not match object if Constant === object returns false
fails:Pattern matching Hash pattern does not match object without #deconstruct_keys method
fails:Pattern matching Hash pattern does not match object if #deconstruct_keys method does not return Hash
Expand All @@ -51,17 +24,10 @@ fails:Pattern matching Hash pattern supports double splat operator **rest
fails:Pattern matching Hash pattern treats **nil like there should not be any other keys in a matched Hash
fails:Pattern matching Hash pattern matches anything with **
fails:Pattern matching refinements are used for #deconstruct_keys
fails:Pattern matching does not allow calculation or method calls in a pattern
fails:Pattern matching Hash pattern does not support non-symbol keys
fails:Pattern matching Array pattern accepts a subclass of Array from #deconstruct
fails:Pattern matching can be standalone assoc operator that deconstructs value
fails:Pattern matching Array pattern calls #deconstruct once for multiple patterns, caching the result
fails:Pattern matching find pattern captures preceding elements to the pattern
fails:Pattern matching find pattern captures following elements to the pattern
fails:Pattern matching find pattern captures both preceding and following elements to the pattern
fails:Pattern matching find pattern can capture the entirety of the pattern
fails:Pattern matching find pattern will match an empty Array-like structure
fails:Pattern matching warning when regular form does not warn about pattern matching is experimental feature
fails:Pattern matching warning when one-line form warns about pattern matching is experimental feature
fails:Pattern matching alternative pattern can be used as a nested pattern
fails:Pattern matching Array pattern can be used as a nested pattern
Expand All @@ -76,3 +42,12 @@ fails:Pattern matching supports pinning class variables
fails:Pattern matching supports pinning global variables
fails:Pattern matching supports pinning expressions
fails:Pattern matching warning when one-line form does not warn about pattern matching is experimental feature
fails:Pattern matching Hash pattern supports form Constant(id: pat, id: pat, ...)
fails:Pattern matching Hash pattern supports form Constant[id: pat, id: pat, ...]
fails:Pattern matching Hash pattern supports form {id: pat, id: pat, ...}
fails:Pattern matching Hash pattern supports 'string': key literal
fails:Pattern matching Hash pattern matches an object with #deconstruct_keys method which returns a Hash with equal keys and each value in Hash matches value in pattern
fails:Pattern matching Hash pattern calls #deconstruct_keys per pattern
fails:Pattern matching Hash pattern can match partially
fails:Pattern matching Hash pattern matches {} with {}
fails:Pattern matching refinements are used for #deconstruct
2 changes: 2 additions & 0 deletions src/main/java/org/truffleruby/core/CoreLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ public class CoreLibrary {

public final GlobalVariables globalVariables;
public final BindingLocalVariablesObject interactiveBindingLocalVariablesObject;
public final RubyClass noMatchingPatternErrorClass;

@CompilationFinal private RubyClass eagainWaitReadable;
@CompilationFinal private RubyClass eagainWaitWritable;
Expand Down Expand Up @@ -331,6 +332,7 @@ public CoreLibrary(RubyContext context, RubyLanguage language) {
unsupportedMessageErrorClass = defineClass(polyglotModule, standardErrorClass, "UnsupportedMessageError");
polyglotForeignObjectClass = defineClass(polyglotModule, objectClass, "ForeignObject");
polyglotForeignClasses = new RubyClass[ForeignClassNode.Trait.COMBINATIONS];
noMatchingPatternErrorClass = defineClass(standardErrorClass, "NoMatchingPatternError");

// StandardError > RuntimeError
runtimeErrorClass = defineClass(standardErrorClass, "RuntimeError");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 2.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby.core.array;

import com.oracle.truffle.api.profiles.ConditionProfile;
import org.truffleruby.language.RubyContextSourceNode;
import org.truffleruby.language.RubyNode;

import com.oracle.truffle.api.frame.VirtualFrame;

public class ArrayPatternLengthCheckNode extends RubyContextSourceNode {

@Child RubyNode currentValueToMatch;
final int patternLength;
final boolean hasRest;

final ConditionProfile isArrayProfile = ConditionProfile.create();

public ArrayPatternLengthCheckNode(int patternLength, RubyNode currentValueToMatch, boolean hasRest) {
this.currentValueToMatch = currentValueToMatch;
this.patternLength = patternLength;
this.hasRest = hasRest;
}

@Override
public Object execute(VirtualFrame frame) {
Object matchArray = currentValueToMatch.execute(frame);
if (isArrayProfile.profile(matchArray instanceof RubyArray)) {
long size = ((RubyArray) matchArray).getArraySize();
if (hasRest) {
return patternLength <= size;
} else {
return patternLength == size;
}
} else {
return false;
}
}

@Override
public RubyNode cloneUninitialized() {
return new ArrayPatternLengthCheckNode(patternLength, currentValueToMatch.cloneUninitialized(), hasRest)
.copyFlags(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,15 @@ public RubyException noMemoryError(Node currentNode, OutOfMemoryError javaThrowa
javaThrowable);
}

// NoMatchingPatternError

@TruffleBoundary
public RubyException noMatchingPatternError(Object errorMessage, Node currentNode) {
assert RubyStringLibrary.getUncached().isRubyString(errorMessage);
RubyClass exceptionClass = context.getCoreLibrary().noMatchingPatternErrorClass;
return ExceptionOperations.createRubyException(context, exceptionClass, errorMessage, currentNode, null);
}

// Errno

@TruffleBoundary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public class TStringConstants {
}
}


public static final TruffleString __FILE__ = ascii("__FILE__");
public static final TruffleString __LINE__ = ascii("__LINE__");
public static final TruffleString __ENCODING__ = ascii("__ENCODING__");
public static final TruffleString AMPERSAND = ascii("&");
public static final TruffleString AMPERSAND_AMPERSAND = ascii("&&");
public static final TruffleString AMPERSAND_DOT = ascii("&.");
Expand Down Expand Up @@ -93,6 +97,7 @@ public class TStringConstants {
public static final TruffleString RBRACKET = ascii("]");
public static final TruffleString RCURLY = ascii("}");
public static final TruffleString RPAREN = ascii(")");
public static final TruffleString SELF = ascii("self");
public static final TruffleString SEMICOLON = ascii(";");
public static final TruffleString SLASH = ascii("/");
public static final TruffleString STAR = ascii("*");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 2.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby.language.control;

import org.truffleruby.language.RubyContextSourceNode;
import org.truffleruby.language.RubyNode;

import com.oracle.truffle.api.frame.VirtualFrame;

public final class ExecuteAndReturnTrueNode extends RubyContextSourceNode {

@Child RubyNode child;

public ExecuteAndReturnTrueNode(RubyNode child) {
this.child = child;
}

@Override
public Object execute(VirtualFrame frame) {
child.doExecuteVoid(frame);
return true;
}

@Override
public RubyNode cloneUninitialized() {
return new ExecuteAndReturnTrueNode(child.cloneUninitialized()).copyFlags(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 2.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby.language.control;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import org.truffleruby.language.RubyContextSourceNode;

import org.truffleruby.language.RubyNode;
import org.truffleruby.language.dispatch.DispatchNode;

@NodeChild(value = "expressionNode", type = RubyNode.class)
public abstract class NoMatchingPatternNode extends RubyContextSourceNode {

protected abstract RubyNode getExpressionNode();

@Specialization
protected Object noMatchingPattern(Object expression,
@Cached DispatchNode inspectNode) {
Object inspected = inspectNode.call(coreLibrary().truffleTypeModule, "rb_inspect", expression);
throw new RaiseException(getContext(), coreExceptions().noMatchingPatternError(inspected, this));
}

@Override
public RubyNode cloneUninitialized() {
return NoMatchingPatternNodeGen.create(getExpressionNode().cloneUninitialized()).copyFlags(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ public RubyCallNodeParameters(
RubyNode block,
ArgumentsDescriptor descriptor,
RubyNode[] arguments,
boolean isSplatted,
boolean ignoreVisibility) {
this(receiver, methodName, block, descriptor, arguments, isSplatted, ignoreVisibility, false, false, false);
this(receiver, methodName, block, descriptor, arguments, false, ignoreVisibility, false, false, false);
}

public RubyCallNodeParameters(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public Object isDefined(VirtualFrame frame, RubyLanguage language, RubyContext c
return FrozenStrings.ASSIGNMENT;
}

public void setValueNode(RubyNode valueNode) {
this.valueNode = valueNode;
}

@Override
public boolean hasTag(Class<? extends Tag> tag) {
return tag == WriteVariableTag.class || super.hasTag(tag);
Expand All @@ -49,5 +53,4 @@ public Object getNodeObject() {
String name = getVariableName();
return new SingleMemberDescriptor(WriteVariableTag.NAME, name);
}

}
55 changes: 55 additions & 0 deletions src/main/java/org/truffleruby/parser/BaseTranslator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 2.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby.parser;

import com.oracle.truffle.api.TruffleSafepoint;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.Source;
import org.truffleruby.RubyLanguage;
import org.truffleruby.core.DummyNode;
import org.truffleruby.language.RubyNode;
import org.truffleruby.language.SourceIndexLength;
import org.truffleruby.parser.ast.ParseNode;

public abstract class BaseTranslator extends Translator {

protected final TranslatorEnvironment environment;

public BaseTranslator(
RubyLanguage language,
Source source,
ParserContext parserContext,
Node currentNode,
TranslatorEnvironment environment) {
super(language, source, parserContext, currentNode);
this.environment = environment;
}

protected RubyNode addNewlineIfNeeded(ParseNode jrubyNode, RubyNode node) {
if (jrubyNode.isNewline()) {
TruffleSafepoint.poll(DummyNode.INSTANCE);

final SourceIndexLength current = node.getEncapsulatingSourceIndexLength();

if (current == null) {
return node;
}

if (environment.getParseEnvironment().isCoverageEnabled()) {
node.unsafeSetIsCoverageLine();
language.coverageManager.setLineHasCode(source, current.toSourceSection(source).getStartLine());
}
node.unsafeSetIsNewLine();
}

return node;
}

}
Loading