diff --git a/pom.xml b/pom.xml
index 44ea78e8140..feba2b5d1c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -219,6 +219,7 @@
**/org/rascalmpl/test/AllSuiteParallel.java
**/org/rascalmpl/test/value/AllTests.java
+ **/org/rascalmpl/*Test.java
@@ -361,10 +362,10 @@
junit
4.13.1
-
+
io.usethesource
vallang
- 0.15.1
+ 1.0.0-RC3
org.ow2.asm
diff --git a/src/org/rascalmpl/interpreter/result/AbstractFunction.java b/src/org/rascalmpl/interpreter/result/AbstractFunction.java
index a0a29e38fc2..326a1a19725 100644
--- a/src/org/rascalmpl/interpreter/result/AbstractFunction.java
+++ b/src/org/rascalmpl/interpreter/result/AbstractFunction.java
@@ -39,8 +39,8 @@
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.RascalValueFactory;
import org.rascalmpl.values.functions.IFunction;
-
-import io.usethesource.vallang.IConstructor;
+
+import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IExternalValue;
import io.usethesource.vallang.IListWriter;
import io.usethesource.vallang.ISourceLocation;
diff --git a/src/org/rascalmpl/library/lang/rascal/matching/Fingerprint.rsc b/src/org/rascalmpl/library/lang/rascal/matching/Fingerprint.rsc
new file mode 100644
index 00000000000..fa06484a54a
--- /dev/null
+++ b/src/org/rascalmpl/library/lang/rascal/matching/Fingerprint.rsc
@@ -0,0 +1,136 @@
+@license{
+ Copyright (c) 2023 CWI
+ All rights reserved. This program and the accompanying materials
+ are made available under the terms of the Eclipse Public License v1.0
+ which accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html
+}
+@contributor{Jurgen Vinju - Jurgen.Vinju@cwi.nl}
+@synopsis{Core functions for implementing fast pattern matching in the Rascal compiler.}
+@description{
+These functions tie together the run-time features of IValue and ITree for computing fast
+fingerprints, with compile-time information for generating switch cases that uses these fingerprints.
+
+There are several explicit contracts implemented here:
+ * a fingerprint is (almost) never `0`.
+ * the fingerprint functions in this module implement exactly the fingerprinting of the run-time that the generated code will be linked against.
+ This contract is tested with internal tests in this module: fingerprintAlignment and concreteFingerprintAlignment.
+ If these tests fail, it is possible that during a bootstrap cycle of 3 steps,
+ the contract is temporarily not satisfied in the first and second steps. To break the impasse, the code below allows us to generate fingerprints for
+ the _next_ run-time version, while the current run-time still runs the _previous_ version of the compiler. We have to disable the `concreteFingerprintAlignment`
+ and `fingerprintAlignment` tests temporarily during the first and second run.
+ * `value matches pattern ==> fingerprint(pattern) == fingerprint(value)` such that a fingerprint is always an over-approximation of matching. It may
+ never be the case that a value should match a pattern and the fingerprint contradicts this.
+ This contract is tested by the pattern matching tests for the interpreter and the compiler.
+ * fingerprints distinguish the identity of the outermost value construct as much as possible. I.e. production rules and constructors are
+ mapped to different codes as much as possible, without breaking the fingerprinting contract.
+ This contract is not automatically tested. Performance regressions may be caused by accidental fingerprinting collisions.
+ * there is also an equals contract: `value1 equals value2 ==> fingerprint(value1) == fingerprint(value2)`, which is a collorary from the pattern
+ matching contract if you consider that patterns may also be equality tests.
+
+As you can read the computation of fingerprints reuses a lot of internal hashcodes. Mainly these boil down to the hash codes of:
+* Java internal strings
+* Java integers
+* Vallang implementations of nested constructors for Symbol and Production.
+
+And so when one of these hashCode implementations changes, the code below may _not_ break and _not_ fail any test
+and still break the backward compatibility of all previously generated code. The tests in the vallang project try to
+detect such an event by replicating the hashcode computations literally in some of the regression tests.
+}
+module lang::rascal::matching::Fingerprint
+
+extend ParseTree;
+import Node;
+import List;
+
+@synopsis{Computes a unique fingerprint for each kind of tree based on the identity of the top-level tree node.}
+@description{
+Concrete fingerprint implements the pattern matching contract:
+`value matches pattern ==> fingerprint(pattern) == fingerprint(value)`
+
+For normal parse trees the fingerprint function makes sure that there are different integers if the
+top-level production is different. This makes it possible to quickly switch on the outermost production rule
+while pattern matching.
+
+To complete the function for the other kinds of trees, even though less important for efficiency, we also
+implement a sensible encoding that follows the contract and tries to differentiate as much as possible between different values.
+}
+int concreteFingerprint(appl(Production p, list[Tree] _)) = concreteFingerprint(p);
+int concreteFingerprint(amb({appl(prod(Symbol s, _, _), list[Tree] _), _})) = internalHashCode("amb") + 43 * internalHashCode(t)
+ when label(_, Symbol t) := s || Symbol t := s;
+int concreteFingerprint(amb({})) = internalHashCode("amb");
+int concreteFingerprint(char(int ch)) = internalHashCode("char") + internalHashCode(ch);
+int concreteFingerprint(cycle(Symbol s, int _)) = internalHashCode("cycle") + 13 * internalHashCode(s);
+
+@synopsis{Compute a fingerprint for a match pattern with this outermost production rule}
+int concreteFingerprint(Production p) = internalHashCode("appl") + 41 * internalHashCode(p);
+
+@synopsis{Computes a unique fingerprint for each kind of value based on the identity of the top-level kind.}
+@description{
+Fingerprint implements the pattern matching contract:
+`value matches pattern ==> fingerprint(pattern) == fingerprint(value)`
+
+Work is done to avoid generating the 0 fingerprint for simple values like empty strings and 0 integers, etc.
+}
+int fingerprint(str r) = hash == 0 ? internalHashCode("str") : hash when int hash := internalHashCode(r);
+int fingerprint(int r) = hash == 0 ? internalHashCode("int") : hash when int hash := internalHashCode(r);
+int fingerprint(real r) = hash == 0 ? internalHashCode("real") : hash when int hash := internalHashCode(r);
+int fingerprint(rat r) = hash == 0 ? internalHashCode("rat") : hash when int hash := internalHashCode(r);
+int fingerprint(value t) = tupleFingerprint(size(fields)) when \tuple(list[Symbol] fields) := typeOf(t);
+default int fingerprint(value n) = internalHashCode(n);
+
+int fingerprint(node n) = nodeFingerprint(getName(n), arity(n));
+
+int fingerprint(list[value] l) = listFingerprint();
+int fingerprint(set[value] l) = setFingerprint();
+int fingerprint(map[value,value] l) = mapFingerprint();
+
+int fingerprint(true) = internalHashCode("true");
+int fingerprint(false) = internalHashCode("true");
+
+int nodeFingerprint("" , int arity) = internalHashCode("node") + 131 * arity;
+default int nodeFingerprint(str name, int arity) = internalHashCode(name) + 131 * arity;
+
+int tupleFingerprint(int arity) = internalHashCode("tuple") + arity;
+int listFingerprint() = internalHashCode("list");
+int setFingerprint() = internalHashCode("set");
+int mapFingerprint() = internalHashCode("map");
+int constructorFingerprint(str name, int arity) = nodeFingerprint(name, arity);
+
+
+@javaClass{org.rascalmpl.library.lang.rascal.matching.internal.Fingerprint}
+@synopsis{Compute the match fingerprint for any constant value. Only used for testing purposes.}
+@description{
+To decouple the Rascal compilers code generator from the bootstrapped run-time it is running in itself,
+the fingerprinting computation is replicated in this module. However, the computation should be the
+same as this internalFingerprint function as long as nothing changes between compiler and run-time versions
+in the computations for fingerprinting.
+}
+private java int internalFingerprint(value x);
+
+@javaClass{org.rascalmpl.library.lang.rascal.matching.internal.Fingerprint}
+@synopsis{Compute the concrete match fingerprint for any parse `Tree`. Only used for testing purposes.}
+@description{
+To decouple the Rascal compilers code generator from the bootstrapped run-time it is running in itself,
+the fingerprinting computation is replicated in this module. However, the computation should be the
+same as this internalFingerprint function as long as nothing changes between compiler and run-time versions
+in the computations for fingerprinting.
+}
+@javaClass{org.rascalmpl.library.lang.rascal.matching.internal.Fingerprint}
+private java int internalConcreteFingerprint(Tree x);
+
+@javaClass{org.rascalmpl.library.lang.rascal.matching.internal.Fingerprint}
+@synopsis{Get the Object.hashCode() of the Java implementation of a Rascal value.}
+@description{
+This hash code is sometimes a part of computing a fingerprint. Do not make this function
+public. Rascal values are hashed already and exactly these hashes are used internally by the
+set, relation and map data-structures. There is no need to write Rascal programs that "hash
+on the hash", and it would leak implementation details that are very hard to encapsulate again.
+}
+private java int internalHashCode(value x);
+
+@synopsis{These two implementations are intentional clones.}
+test bool fingerprintAlignment(value x) = fingerprint(x) == internalFingerprint(x);
+
+@synopsis{These two implementations are intentional clones.}
+test bool concreteFingerprintAlignment(Tree x) = concreteFingerprint(x) == internalConcreteFingerprint(x);
diff --git a/src/org/rascalmpl/library/lang/rascal/matching/internal/Fingerprint.java b/src/org/rascalmpl/library/lang/rascal/matching/internal/Fingerprint.java
new file mode 100644
index 00000000000..1ad933f4e3f
--- /dev/null
+++ b/src/org/rascalmpl/library/lang/rascal/matching/internal/Fingerprint.java
@@ -0,0 +1,28 @@
+package org.rascalmpl.library.lang.rascal.matching.internal;
+
+import org.rascalmpl.values.parsetrees.ITree;
+
+import io.usethesource.vallang.IConstructor;
+import io.usethesource.vallang.IInteger;
+import io.usethesource.vallang.IValue;
+import io.usethesource.vallang.IValueFactory;
+
+public class Fingerprint {
+ private final IValueFactory vf;
+
+ public Fingerprint(IValueFactory vf) {
+ this.vf = vf;
+ }
+
+ public IInteger internalFingerprint(IValue v) {
+ return vf.integer(v.getMatchFingerprint());
+ }
+
+ public IInteger internalConcreteFingerprint(IConstructor v) {
+ return vf.integer(((ITree) v).getConcreteMatchFingerprint());
+ }
+
+ public IInteger internalHashCode(IValue v) {
+ return vf.integer(v.hashCode());
+ }
+}
diff --git a/src/org/rascalmpl/library/vis/Charts.rsc b/src/org/rascalmpl/library/vis/Charts.rsc
index 0a49bc12493..9c2e5cfc2e0 100644
--- a/src/org/rascalmpl/library/vis/Charts.rsc
+++ b/src/org/rascalmpl/library/vis/Charts.rsc
@@ -344,6 +344,7 @@ data ChartType
data ChartOptions
= chartOptions(
bool responsive=true,
+ bool animations=true,
ChartPlugins plugins = chartPlugins()
);
@@ -395,13 +396,14 @@ A chart has a typical default layout that we can reuse for all kinds of chart ty
provides the template and immediately instantiates the client and the server to start displaying the chart
in a browser.
}
-Response(Request) chartServer(ChartData theData, ChartType \type=\bar(), str title="Chart", ChartAutoColorMode colorMode=\data(), bool legend=false)
+Response(Request) chartServer(ChartData theData, ChartType \type=\bar(), str title="Chart", ChartAutoColorMode colorMode=\data(), bool legend=false, bool animations=false)
= chartServer(
chart(
\type=\type,
\data=theData,
options=chartOptions(
responsive=true,
+ animations=animations,
plugins=chartPlugins(
legend=chartLegend(
position=top(),
diff --git a/src/org/rascalmpl/tasks/Transaction.java b/src/org/rascalmpl/tasks/Transaction.java
index f5fd61968e6..a5973405a33 100644
--- a/src/org/rascalmpl/tasks/Transaction.java
+++ b/src/org/rascalmpl/tasks/Transaction.java
@@ -417,6 +417,11 @@ public synchronized void expire(Object key) {
map.remove(k);
removed.add(k);
}
+
+ @Override
+ public int getMatchFingerprint() {
+ return hashCode();
+ }
}
class Key {
diff --git a/src/org/rascalmpl/values/RascalValueFactory.java b/src/org/rascalmpl/values/RascalValueFactory.java
index abab1594b4e..8ec9fc76a0a 100644
--- a/src/org/rascalmpl/values/RascalValueFactory.java
+++ b/src/org/rascalmpl/values/RascalValueFactory.java
@@ -20,6 +20,7 @@
import java.util.function.Supplier;
import org.rascalmpl.parser.gtd.util.ArrayList;
+import org.rascalmpl.types.NonTerminalType;
import org.rascalmpl.types.RascalTypeFactory;
import org.rascalmpl.types.TypeReifier;
import org.rascalmpl.values.parsetrees.ITree;
@@ -30,7 +31,6 @@
import io.usethesource.capsule.util.collection.AbstractSpecialisedImmutableMap;
import io.usethesource.vallang.IConstructor;
-import io.usethesource.vallang.IExternalValue;
import io.usethesource.vallang.IInteger;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.IListWriter;
@@ -385,8 +385,16 @@ else if (constructor == Type_Reified || constructor.getAbstractDataType() == ADT
@Override
public IConstructor reifiedType(IConstructor symbol, IMap definitions) {
+ // This is where the "reified type contract" is implemented.
+ // A few inocuous lines of code that have a lot riding on them.
+
+ // Contract: The dynamic type of a `type(symbol, definitionsMap)` constructor instance
+ // is `type[what the symbol value represents]`.
+ // So here the symbol value is "unlifted" to the {@see Type} representation (by `symbolToType`).
+ // Therefore you can count on that, for example, `type(int(), ())` has type `type[int]`.
java.util.Map bindings =
Collections.singletonMap(RascalValueFactory.TypeParam, tr.symbolToType(symbol, definitions));
+
return super.constructor(RascalValueFactory.Type_Reified.instantiate(bindings), symbol, definitions);
}
@@ -505,13 +513,18 @@ public ITree amb(ISet alternatives) {
* and {@link AbstractArgumentList} abstract classes.
*/
- static class CharInt implements ITree, IExternalValue {
+ static class CharInt implements ITree {
final int ch;
-
+
@Override
public boolean isChar() {
return true;
}
+
+ @Override
+ public int getConcreteMatchFingerprint() {
+ return 3052374 /* "char".hashCode() */ + ch;
+ }
@Override
public INode setChildren(IValue[] childArray) {
@@ -526,11 +539,6 @@ public ITree accept(TreeVisitor v) throws E {
public CharInt(int ch) {
this.ch = ch;
}
-
- @Override
- public IConstructor encodeAsConstructor() {
- return this;
- }
@Override
public IValue get(int i) throws IndexOutOfBoundsException {
@@ -675,12 +683,17 @@ protected ITree wrap(ITree content, io.usethesource.capsule.Map.Immutable ITree accept(TreeVisitor v) throws E {
return (ITree) v.visitTreeChar(this);
}
-
- @Override
- public IConstructor encodeAsConstructor() {
- return this;
- }
@Override
public IValue get(int i) throws IndexOutOfBoundsException {
@@ -850,7 +858,7 @@ protected ITree wrap(ITree content, io.usethesource.capsule.Map.Immutable ITree accept(TreeVisitor v) throws E {
return (ITree) v.visitTreeCycle(this);
}
- @Override
- public IConstructor encodeAsConstructor() {
- return this;
- }
-
@Override
public String getName() {
return Tree_Cycle.getName();
@@ -1034,28 +1042,33 @@ public IValue get(int i) throws IndexOutOfBoundsException {
}
}
- private static class Amb implements ITree, IExternalValue {
+ private static class Amb implements ITree {
protected final ISet alternatives;
public Amb(ISet alts) {
+ assert alts.size() > 0;
this.alternatives = alts;
}
+ @Override
+ public int getConcreteMatchFingerprint() {
+ if (alternatives.isEmpty()) {
+ return 96694;
+ }
+
+ return 96694 /* "amb".hashCode() */ + 43 * ((NonTerminalType) alternatives.getElementType()).getSymbol().hashCode();
+ }
+
@Override
public boolean isAmb() {
return true;
}
-
+
@Override
public ITree accept(TreeVisitor v) throws E {
return (ITree) v.visitTreeAmb(this);
}
- @Override
- public IConstructor encodeAsConstructor() {
- return this;
- }
-
@Override
public String getName() {
return Tree_Amb.getName();
@@ -1114,8 +1127,8 @@ public boolean hasNext() {
public IValue next() {
count++;
switch(count) {
- case 1: return getAlternatives();
- default: return null;
+ case 1: return getAlternatives();
+ default: return null;
}
}
};
@@ -1230,6 +1243,11 @@ public ApplWithKeywordParametersFacade(IConstructor content, io.usethesource.cap
super(content, parameters);
}
+ @Override
+ public int getConcreteMatchFingerprint() {
+ return ((ITree) content).getConcreteMatchFingerprint();
+ }
+
@Override
public ITree accept(TreeVisitor v) throws E {
return v.visitTreeAppl(this);
@@ -1281,6 +1299,11 @@ public AmbWithKeywordParametersFacade(IConstructor content, io.usethesource.caps
super(content, parameters);
}
+ @Override
+ public int getConcreteMatchFingerprint() {
+ return ((ITree) content).getConcreteMatchFingerprint();
+ }
+
@Override
public ITree accept(TreeVisitor v) throws E {
return v.visitTreeAmb(this);
@@ -1327,6 +1350,11 @@ public CycleWithKeywordParametersFacade(IConstructor content, io.usethesource.ca
super(content, parameters);
}
+ @Override
+ public int getConcreteMatchFingerprint() {
+ return ((ITree) content).getConcreteMatchFingerprint();
+ }
+
@Override
public ITree accept(TreeVisitor v) throws E {
return v.visitTreeCycle(this);
@@ -1369,6 +1397,11 @@ public CharWithKeywordParametersFacade(IConstructor content, io.usethesource.cap
super(content, parameters);
}
+ @Override
+ public int getConcreteMatchFingerprint() {
+ return ((ITree) content).getConcreteMatchFingerprint();
+ }
+
@Override
public ITree accept(TreeVisitor v) throws E {
return v.visitTreeChar(this);
@@ -1410,12 +1443,17 @@ public IInteger getCharacter() {
}
}
- private static abstract class AbstractAppl implements ITree, IExternalValue {
+ private static abstract class AbstractAppl implements ITree {
protected final IConstructor production;
protected final boolean isMatchIgnorable;
protected Type type = null;
+ @Override
+ public int getConcreteMatchFingerprint() {
+ return 3000939 /* "appl".hashCode() */ + 41 * production.hashCode();
+ }
+
@Override
public ITree accept(TreeVisitor v) throws E {
return v.visitTreeAppl(this);
@@ -1439,11 +1477,6 @@ public boolean isAppl() {
return true;
}
- @Override
- public IConstructor encodeAsConstructor() {
- return this;
- }
-
@Override
public String getName() {
return Tree_Appl.getName();
diff --git a/src/org/rascalmpl/values/functions/IFunction.java b/src/org/rascalmpl/values/functions/IFunction.java
index 9ff1ad04d67..b3b449f5587 100644
--- a/src/org/rascalmpl/values/functions/IFunction.java
+++ b/src/org/rascalmpl/values/functions/IFunction.java
@@ -22,6 +22,11 @@
public interface IFunction extends IExternalValue {
+ @Override
+ default int getMatchFingerprint() {
+ return 3154628 /* "func".hashCode() */ + 89 * getType().hashCode();
+ }
+
/**
* Invokes the receiver function.
*
diff --git a/src/org/rascalmpl/values/parsetrees/ITree.java b/src/org/rascalmpl/values/parsetrees/ITree.java
index 8917e3d8b3b..e534be637af 100644
--- a/src/org/rascalmpl/values/parsetrees/ITree.java
+++ b/src/org/rascalmpl/values/parsetrees/ITree.java
@@ -3,14 +3,46 @@
import org.rascalmpl.values.parsetrees.visitors.TreeVisitor;
import io.usethesource.vallang.IConstructor;
+import io.usethesource.vallang.IExternalValue;
import io.usethesource.vallang.IInteger;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.INode;
import io.usethesource.vallang.ISet;
import io.usethesource.vallang.IValue;
+import io.usethesource.vallang.visitors.IValueVisitor;
-public interface ITree extends IConstructor {
+public interface ITree extends IConstructor, IExternalValue {
+ @Override
+ default IConstructor encodeAsConstructor() {
+ return this;
+ }
+
+ @Override
+ default T accept(IValueVisitor v) throws E {
+ return v.visitExternal(this);
+ }
+
+ @Override
+ default int getMatchFingerprint() {
+ // ITrees must simulate their constructor prints in case
+ // we pattern match on the abstract Tree data-type
+ return IConstructor.super.getMatchFingerprint();
+ }
+
+ /**
+ * Concrete patterns need another layer of fingerprinting on top
+ * of `getMatchFingerprint`. The reason is that _the same IValue_
+ * can be matched against an abstract pattern of the Tree data-type,
+ * and against concrete patterns.
+ *
+ * Like before, the match-fingerprint contract is:
+ * if pattern.match(tree) ==> pattern.fingerprint() == match.fingerprint();
+ *
+ * @return a unique code for each outermost ITree node
+ */
+ int getConcreteMatchFingerprint();
+
default boolean isAppl() {
return false;
}
@@ -55,4 +87,4 @@ default INode setChildren(IValue[] childArray) {
return result;
}
-}
\ No newline at end of file
+}
diff --git a/src/org/rascalmpl/values/parsetrees/TreeAdapter.java b/src/org/rascalmpl/values/parsetrees/TreeAdapter.java
index 68cb24672da..4ab329a2a92 100644
--- a/src/org/rascalmpl/values/parsetrees/TreeAdapter.java
+++ b/src/org/rascalmpl/values/parsetrees/TreeAdapter.java
@@ -288,6 +288,7 @@ else if (isChar(tree)) {
return SymbolAdapter.charClass(TreeAdapter.getCharacter(tree));
}
else if (isAmb(tree)) {
+ // ambiguities are never empty
return getType((ITree) getAlternatives(tree).iterator().next());
}
throw new ImplementationError("ITree does not have a type");
diff --git a/test/org/rascalmpl/MatchFingerprintTest.java b/test/org/rascalmpl/MatchFingerprintTest.java
new file mode 100644
index 00000000000..41614b94b05
--- /dev/null
+++ b/test/org/rascalmpl/MatchFingerprintTest.java
@@ -0,0 +1,171 @@
+/**
+ * Copyright (c) 2016, Jurgen J. Vinju, Centrum Wiskunde & Informatica (CWI)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.rascalmpl;
+
+import static org.junit.Assert.assertNotEquals;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.rascalmpl.interpreter.Evaluator;
+import org.rascalmpl.interpreter.env.GlobalEnvironment;
+import org.rascalmpl.interpreter.env.ModuleEnvironment;
+import io.usethesource.vallang.IConstructor;
+import io.usethesource.vallang.IInteger;
+import io.usethesource.vallang.ISourceLocation;
+import io.usethesource.vallang.exceptions.FactTypeUseException;
+import io.usethesource.vallang.io.StandardTextReader;
+import io.usethesource.vallang.type.Type;
+import io.usethesource.vallang.type.TypeFactory;
+import org.rascalmpl.values.IRascalValueFactory;
+import org.rascalmpl.values.RascalFunctionValueFactory;
+import org.rascalmpl.values.functions.IFunction;
+import org.rascalmpl.values.parsetrees.ITree;
+import org.rascalmpl.values.parsetrees.TreeAdapter;
+
+import junit.framework.TestCase;
+
+/**
+ * These tests check the hard contract on the integer values for `int IValue.getMatchFingerprint()`.
+ * Do not change these tests unless you are absolutely sure you know what you are doing. For one
+ * thing, changing fingerprints implies a pretty hairy bootstrap dependency hoop you have to jump through.
+ * Typically you would have to add a mode to the run-time to make it switch between the next version
+ * of the fingerprints for new code, and the old version of the fingerprints for the current run-time
+ * that runs the compiler.
+ */
+public class MatchFingerprintTest extends TestCase {
+ private final GlobalEnvironment heap = new GlobalEnvironment();
+ private final ModuleEnvironment root = new ModuleEnvironment("root", heap);
+ private final Evaluator eval = new Evaluator(IRascalValueFactory.getInstance(), System.in, System.err, System.out, root, heap);
+ private final RascalFunctionValueFactory VF = eval.getFunctionValueFactory();
+ private final TypeFactory TF = TypeFactory.getInstance();
+
+ public void testFunctionFingerPrintStability() {
+ Type intint = TF.functionType(TF.integerType(), TF.tupleType(TF.integerType()), TF.tupleEmpty());
+
+ IFunction func = VF.function(intint, (args, kwargs) -> {
+ return VF.integer(0);
+ });
+
+ // these magic numbers are sacred
+ assertEquals(func.getMatchFingerprint(), "func".hashCode() + 89 * intint.hashCode());
+ }
+
+ public void testTreeApplFingerPrintStability() {
+ String prodString = "prod(sort(\"E\"),[],{})";
+ ISourceLocation loc = VF.sourceLocation("BLABLA");
+
+ try {
+ IConstructor prod = (IConstructor) new StandardTextReader().read(VF, RascalFunctionValueFactory.getStore(), RascalFunctionValueFactory.Production, new StringReader(prodString));
+ ITree tree = VF.appl(prod, VF.list());
+
+ assertEquals(tree.getMatchFingerprint(), "appl".hashCode() + 131 * 2);
+ assertEquals(tree.getConcreteMatchFingerprint(), "appl".hashCode() + 41 * prod.hashCode());
+
+ // and now WITH a keyword parameter
+ tree = (ITree) tree.asWithKeywordParameters().setParameter("src", loc);
+
+ assertEquals(tree.getMatchFingerprint(), "appl".hashCode() + 131 * 2);
+ assertEquals(tree.getConcreteMatchFingerprint(), "appl".hashCode() + 41 * prod.hashCode());
+ }
+ catch (FactTypeUseException | IOException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testTreeFingerprintDifferentiation() {
+ String prodString1 = "prod(sort(\"E\"),[],{})";
+ String prodString2 = "prod(sort(\"E\"),[empty()],{})";
+
+ try {
+ IConstructor prod1= (IConstructor) new StandardTextReader().read(VF, RascalFunctionValueFactory.getStore(), RascalFunctionValueFactory.Production, new StringReader(prodString1));
+ ITree tree1 = VF.appl(prod1, VF.list());
+ IConstructor prod2= (IConstructor) new StandardTextReader().read(VF, RascalFunctionValueFactory.getStore(), RascalFunctionValueFactory.Production, new StringReader(prodString2));
+ ITree tree2 = VF.appl(prod2, VF.list());
+
+ // these two assert explain the different between `getMatchFingerprint` and `getConcreteMatchFingerprint`
+ assertEquals(tree1.getMatchFingerprint(), tree2.getMatchFingerprint());
+ assertNotEquals(tree1.getConcreteMatchFingerprint(), tree2.getMatchFingerprint());
+ }
+ catch (FactTypeUseException | IOException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testTreeAmbFingerPrintStability() {
+ String prodString1 = "prod(sort(\"E\"),[],{})";
+ String prodString2 = "prod(sort(\"E\"),[empty()],{})";
+ ISourceLocation loc = VF.sourceLocation("BLABLA");
+
+ try {
+ IConstructor prod1= (IConstructor) new StandardTextReader().read(VF, RascalFunctionValueFactory.getStore(), RascalFunctionValueFactory.Production, new StringReader(prodString1));
+ ITree tree1 = VF.appl(prod1, VF.list());
+ IConstructor prod2= (IConstructor) new StandardTextReader().read(VF, RascalFunctionValueFactory.getStore(), RascalFunctionValueFactory.Production, new StringReader(prodString2));
+ ITree tree2 = VF.appl(prod2, VF.list());
+
+ ITree amb = VF.amb(VF.set(tree1, tree2));
+
+ assertEquals(amb.getMatchFingerprint(), "amb".hashCode() + 131);
+ assertEquals(amb.getConcreteMatchFingerprint(), "amb".hashCode() + 43 * TreeAdapter.getType(amb).hashCode());
+
+ // and now WITH a keyword parameter
+ amb = (ITree) amb.asWithKeywordParameters().setParameter("src", loc);
+
+ assertEquals(amb.getMatchFingerprint(), "amb".hashCode() + 131);
+ assertEquals(amb.getConcreteMatchFingerprint(), "amb".hashCode() + 43 * TreeAdapter.getType(amb).hashCode());
+ }
+ catch (FactTypeUseException | IOException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testTreeCharFingerPrintStability() {
+ ISourceLocation loc = VF.sourceLocation("BLABLA");
+
+ try {
+ ITree theChar = (ITree) new StandardTextReader().read(VF, RascalFunctionValueFactory.getStore(), RascalFunctionValueFactory.Tree, new StringReader("char(32)"));
+
+ assertEquals(theChar.getMatchFingerprint(), "char".hashCode() + 131);
+ assertEquals(theChar.getConcreteMatchFingerprint(), "char".hashCode() + ((IInteger) theChar.get(0)).intValue());
+
+ // and now WITH a keyword parameter
+ theChar = (ITree) theChar.asWithKeywordParameters().setParameter("src", loc);
+
+ assertEquals(theChar.getMatchFingerprint(), "char".hashCode() + 131);
+ assertEquals(theChar.getConcreteMatchFingerprint(), "char".hashCode() + ((IInteger) theChar.get(0)).intValue());
+ }
+ catch (FactTypeUseException | IOException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testTreeCycleFingerPrintStability() {
+ ISourceLocation loc = VF.sourceLocation("BLABLA");
+
+ try {
+ ITree theCycle = (ITree) new StandardTextReader().read(VF, RascalFunctionValueFactory.getStore(), RascalFunctionValueFactory.Tree, new StringReader("cycle(sort(\"A\"), 3)"));
+
+ assertEquals(theCycle.getMatchFingerprint(), "cycle".hashCode() + 2 * 131);
+ assertEquals(theCycle.getConcreteMatchFingerprint(), "cycle".hashCode() + 13 * theCycle.get(0).hashCode());
+
+ // and now WITH a keyword parameter
+ theCycle = (ITree) theCycle.asWithKeywordParameters().setParameter("src", loc);
+
+ assertEquals(theCycle.getMatchFingerprint(), "cycle".hashCode() + 2 * 131);
+ assertEquals(theCycle.getConcreteMatchFingerprint(), "cycle".hashCode() + 13 * theCycle.get(0).hashCode());
+ }
+ catch (FactTypeUseException | IOException e) {
+ fail(e.getMessage());
+ }
+ }
+}
diff --git a/test/org/rascalmpl/TypeReificationTest.java b/test/org/rascalmpl/TypeReificationTest.java
index 4ac07edbfd6..78c0e83dafc 100644
--- a/test/org/rascalmpl/TypeReificationTest.java
+++ b/test/org/rascalmpl/TypeReificationTest.java
@@ -36,7 +36,7 @@ public void testJustRandomTypesWithoutExceptions() {
Random rnd = new Random();
List collector = new LinkedList<>();
- int tries = 50000;
+ int tries = 500;
for (int i = 0; i < tries; i++) {
collector.add(tf.randomType(store, rnd, 5));
@@ -53,11 +53,7 @@ public void testEmptyTupleBidirectionality() {
public void testEmptyTupleReturnFunBidirectionality() {
TypeFactory tf = TypeFactory.getInstance();
- testOne(tf.functionType(tf.tupleEmpty(), tf.voidType(), tf.voidType()), new TypeStore());
- testOne(tf.functionType(tf.tupleEmpty(), tf.voidType(), null), new TypeStore());
- testOne(tf.functionType(tf.tupleEmpty(), tf.voidType(), tf.tupleEmpty()), new TypeStore());
- testOne(tf.functionType(tf.tupleEmpty(), tf.tupleEmpty(), tf.voidType()), new TypeStore());
- testOne(tf.functionType(tf.tupleEmpty(), tf.tupleEmpty(), null), new TypeStore());
+
testOne(tf.functionType(tf.tupleEmpty(), tf.tupleEmpty(), tf.tupleEmpty()), new TypeStore());
}
@@ -91,8 +87,8 @@ public void testFuncTypeKeywordParameter() {
public void testFuncTypeParametersOrder() {
TypeFactory tf = TypeFactory.getInstance();
- testOne(tf.functionType(tf.voidType(), tf.tupleType(new Type[] {tf.integerType(), tf.realType()}, new String[] {"a", "b"}), null), new TypeStore());
- testOne(tf.functionType(tf.voidType(), tf.tupleType(tf.integerType(), tf.realType()), null), new TypeStore());
+ testOne(tf.functionType(tf.voidType(), tf.tupleType(new Type[] {tf.integerType(), tf.realType()}, new String[] {"a", "b"}), tf.tupleEmpty()), new TypeStore());
+ testOne(tf.functionType(tf.voidType(), tf.tupleType(tf.integerType(), tf.realType()), tf.tupleEmpty()), new TypeStore());
}
public void testFuncTypeReificationBidirectionality() {
@@ -100,10 +96,14 @@ public void testFuncTypeReificationBidirectionality() {
TypeStore store = new TypeStore();
for (int i = 0; i < 50; i++) {
- Type type = tf.randomType(store);
- while (!type.isFunction()) {
- type = tf.randomType(store);
+ Type returnType = tf.randomType(store);
+ Type arg = tf.randomType(store);
+
+ if (arg.isBottom()) {
+ continue;
}
+
+ Type type = tf.functionType(returnType, tf.tupleType(arg), tf.tupleEmpty());
testOne(type, store);
}
@@ -113,7 +113,7 @@ public void testTypeReificationBidirectionality() {
TypeFactory tf = TypeFactory.getInstance();
TypeStore store = new TypeStore();
- for (int i = 0; i < 10_000; i++) {
+ for (int i = 0; i < 100; i++) {
testOne(tf.randomType(store), store);
}
}