/*******************************************************************************
* Copyright (c) 2009-2013 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
*
* Contributors:
* * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI
* * Emilie Balland - (CWI)
* * Paul Klint - Paul.Klint@cwi.nl - CWI
* * Mark Hills - Mark.Hills@cwi.nl (CWI)
* * Arnold Lankamp - Arnold.Lankamp@cwi.nl
* * Anastasia Izmaylova - A.Izmaylova@cwi.nl - CWI
*******************************************************************************/
package org.rascalmpl.interpreter.matching;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.rascalmpl.ast.Expression;
import org.rascalmpl.ast.QualifiedName;
import org.rascalmpl.interpreter.IEvaluatorContext;
import org.rascalmpl.interpreter.env.Environment;
import org.rascalmpl.interpreter.result.ConstructorFunction;
import org.rascalmpl.interpreter.result.Result;
import org.rascalmpl.interpreter.result.ResultFactory;
import org.rascalmpl.interpreter.staticErrors.UninitializedPatternMatch;
import org.rascalmpl.interpreter.utils.Cases;
import org.rascalmpl.interpreter.utils.Names;
import org.rascalmpl.value.IAnnotatable;
import org.rascalmpl.value.IConstructor;
import org.rascalmpl.value.IList;
import org.rascalmpl.value.INode;
import org.rascalmpl.value.IString;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.IWithKeywordParameters;
import org.rascalmpl.value.exceptions.FactTypeUseException;
import org.rascalmpl.value.exceptions.IllegalOperationException;
import org.rascalmpl.value.type.Type;
import org.rascalmpl.value.type.TypeFactory;
import org.rascalmpl.value.visitors.IValueVisitor;
import org.rascalmpl.values.uptr.ITree;
import org.rascalmpl.values.uptr.RascalValueFactory;
import org.rascalmpl.values.uptr.TreeAdapter;
public class NodePattern extends AbstractMatchingResult {
private final TypeFactory tf = TypeFactory.getInstance();
private Type type;
private final Type patternConstructorType;
private final QualifiedName qName;
private final List<IMatchingResult> patternChildren;
private INode subject;
private int nextChild;
private final IMatchingResult namePattern;
private final Map<String, IMatchingResult> keywordParameters;
private final boolean matchUPTR;
public NodePattern(IEvaluatorContext ctx, Expression x, IMatchingResult matchPattern, QualifiedName name, Type constructorType, List<IMatchingResult> list, Map<String,IMatchingResult> keywordParameters){
super(ctx, x);
this.patternConstructorType = constructorType;
this.patternChildren = list;
this.keywordParameters = keywordParameters;
if (matchPattern != null) {
namePattern = matchPattern;
matchUPTR = false;
qName = null;
}
else if (name != null) {
namePattern = null;
qName = name;
matchUPTR = Cases.IUPTR_NAMES.contains(Names.fullName(qName));
}
else {
namePattern = null;
qName = null;
matchUPTR = false;
}
}
@Override
public void initMatch(Result<IValue> subject){
super.initMatch(subject);
hasNext = false;
if (subject.isVoid()) {
throw new UninitializedPatternMatch("Uninitialized pattern match: trying to match a value of the type 'void'", ctx.getCurrentAST());
}
if (!subject.getValue().getType().isNode()) {
return;
}
if (!matchUPTR && subject.getType().isSubtypeOf(RascalValueFactory.Tree) && TreeAdapter.isAppl((ITree) subject.getValue())) {
this.subject = new TreeAsNode((ITree) subject.getValue());
}
else {
this.subject = (INode) subject.getValue();
}
String sname = this.subject.getName();
if(qName != null){
if(!((org.rascalmpl.semantics.dynamic.QualifiedName.Default) qName).lastName().equals(sname)){
return;
}
} else {
IString nameVal = ctx.getValueFactory().string(sname);
namePattern.initMatch(ResultFactory.makeResult(tf.stringType(), nameVal, ctx));
if(!(namePattern.hasNext() && namePattern.next())){
return; // TODO What if the name has alternatives?
}
}
// Determine type compatibility between pattern and subject
Type patternType = getType(ctx.getCurrentEnvt(), null);
Type subjectType = this.subject.getType();
if (subjectType.isAbstractData()) {
subjectType = ((IConstructor) this.subject).getConstructorType();
}
INode node = ((INode) this.subject);
if (node.arity() != patternChildren.size()) {
return; // that can never match
}
if (patternType.comparable(subjectType)) {
hasNext = true;
} else {
return;
}
IValue[] subjectChildren = new IValue[patternChildren.size()];
for (int i = 0; i < patternChildren.size(); i++){
subjectChildren[i] = this.subject.get(i);
IMatchingResult patternChild = patternChildren.get(i);
patternChild.initMatch(ResultFactory.makeResult(subjectChildren[i].getType(), subjectChildren[i], ctx));
hasNext = patternChild.hasNext();
if (!hasNext) {
break; // saves time!
}
}
if (ctx.getCurrentEnvt().getStore().hasKeywordParameters(type)) {
ConstructorFunction func = ctx.getCurrentEnvt().getConstructorFunction(type);
Map<String, IValue> kwArgs = ((INode) subject).asWithKeywordParameters().getParameters();
Map<String, Type> kwFormals = ctx.getCurrentEnvt().getStore().getKeywordParameters(type);
for (String kwLabel : keywordParameters.keySet()) {
IValue subjectParam = kwArgs.get(kwLabel);
if (subjectParam == null) {
subjectParam = func.computeDefaultKeywordParameter(kwLabel, (IConstructor) subject.getValue(), ctx.getCurrentEnvt()).getValue();
}
IMatchingResult matcher = keywordParameters.get(kwLabel);
matcher.initMatch(ResultFactory.makeResult(kwFormals.get(kwLabel), subjectParam, ctx));
if (!matcher.hasNext()) {
// the matcher can never work, so we can skip initializing the rest
hasNext = false;
break;
}
}
}
else if (this.subject.mayHaveKeywordParameters()) {
Map<String, IValue> kwArgs = this.subject.asWithKeywordParameters().getParameters();
ConstructorFunction func = null;
Type paramType = tf.valueType();
if (this.subject.getType().isAbstractData()) {
func = ctx.getCurrentEnvt().getConstructorFunction(((IConstructor) this.subject).getConstructorType());
}
for (Entry<String,IMatchingResult> entry : keywordParameters.entrySet()) {
IValue subjectParam = kwArgs.get(entry.getKey());
if (subjectParam == null && func != null) { // we may have a default available
subjectParam = func.computeDefaultKeywordParameter(entry.getKey(), (IConstructor) subject.getValue(), ctx.getCurrentEnvt()).getValue();
}
if (func != null) {
// this works around the problem of imprecisely resolved types for constructors in patterns
paramType = ctx.getCurrentEnvt().getStore().getKeywordParameterType(func.getConstructorType(), entry.getKey());
}
if (subjectParam != null) {
// we are matching a keyword parameter, and indeed the subject has one
IMatchingResult matcher = entry.getValue();
matcher.initMatch(ResultFactory.makeResult(paramType, subjectParam, ctx));
if (!matcher.hasNext()) {
// the subject parameter can never match so we bail out early
hasNext = false;
break;
}
} else {
// we are matching a keyword parameter, but the subject has none.
hasNext = false;
break;
}
}
}
nextChild = 0;
}
@Override
public Type getType(Environment env, HashMap<String,IVarPattern> patternVars) {
if (type == null) {
type = getConstructorType(env);
if (type != null && type.isConstructor()) {
type = getConstructorType(env).getAbstractDataType();
}
if (type == null) {
type = TypeFactory.getInstance().nodeType();
}
}
return type;
}
public Type getConstructorType(Environment env) {
return patternConstructorType;
}
@Override
public List<IVarPattern> getVariables(){
java.util.LinkedList<IVarPattern> res = new java.util.LinkedList<IVarPattern> ();
for (IMatchingResult c : patternChildren) {
res.addAll(c.getVariables());
}
for (IMatchingResult v : keywordParameters.values()) {
res.addAll(v.getVariables());
}
if (res.isEmpty()) {
// nodes may have ignorable keyword parameters which we simulate here
// by adding a dummy variable to indicate this pattern is not constant. The reason
// is other containing patterns may treat a constructor without parameters as
// constant and simulate matching with isEqual otherwise.
res.add(new IVarPattern() {
@Override
public String name() {
return "_";
}
@Override
public boolean isVarIntroducing() {
return false;
}
@Override
public Type getType() {
return tf.voidType();
}
});
}
return res;
}
@Override
public boolean next(){
checkInitialized();
if (!hasNext) {
return false;
}
if (patternChildren.size() == 0) {
hasNext = false;
return nextKeywordParameters();
}
while (nextChild >= 0) {
IMatchingResult nextPattern = patternChildren.get(nextChild);
if (nextPattern.hasNext() && nextPattern.next()) {
if (nextChild == patternChildren.size() - 1) {
// We need to make sure if there are no
// more possible matches for any of the tuple's fields,
// then the next call to hasNext() will definitely returns false.
hasNext = false;
for (int i = nextChild; i >= 0; i--) {
IMatchingResult child = patternChildren.get(i);
hasNext |= child.hasNext();
if (patternConstructorType != null && !patternConstructorType.isNode() && hasNext) {
// This code should disappear as soon as we have a type checker.
// Since constructors give us specific type contexts, an inferred type
// for a child pattern variable should get this specific type. A type
// checker would have inferred that already, but now we just push
// the information down to all children of the constructor.
child.updateType(patternConstructorType.getFieldType(i++));
}
}
return nextKeywordParameters();
}
nextChild++;
}
else {
nextChild--;
if (nextChild >= 0) {
for (int i = nextChild + 1; i < patternChildren.size(); i++) {
IValue childValue = subject.get(i);
IMatchingResult tailChild = patternChildren.get(i);
tailChild.initMatch(ResultFactory.makeResult(childValue.getType(), childValue, ctx));
}
}
}
}
hasNext = false;
return false;
}
private boolean nextKeywordParameters() {
for (Entry<String,IMatchingResult> entry : keywordParameters.entrySet()) {
if (!entry.getValue().next()) {
return false;
}
}
return true;
}
@Override
public String toString(){
int n = patternChildren.size();
StringBuilder res = new StringBuilder(qName != null ? Names.fullName(qName) : namePattern.toString());
res.append("(");
if (n == 1) {
res.append(")");
return res.toString() ;
}
String sep = "";
for (IMatchingResult c : patternChildren){
res.append(sep);
sep = ", ";
res.append(c.toString());
}
for (Entry<String,IMatchingResult> e : keywordParameters.entrySet()) {
res.append(sep);
sep = ", ";
res.append(e.getKey());
res.append("=");
res.append(e.getValue().toString());
}
res.append(")");
return res.toString();
}
private class TreeAsNode implements INode {
private final String name;
private final IList args;
public TreeAsNode(ITree tree) {
this.name = TreeAdapter.getConstructorName(tree);
this.args = TreeAdapter.isContextFree(tree) ? TreeAdapter.getASTArgs(tree) : TreeAdapter.getArgs(tree);
}
@Override
public Type getType() {
return TypeFactory.getInstance().nodeType();
}
@Override
public <T, E extends Throwable> T accept(IValueVisitor<T,E> v) throws E {
throw new UnsupportedOperationException();
}
@Override
public boolean isEqual(IValue other) {
throw new UnsupportedOperationException();
}
@Override
public IValue get(int i) throws IndexOutOfBoundsException {
// TODO: this should deal with regular expressions in the "right" way, such as skipping
// over optionals and alternatives.
return args.get(i);
}
@Override
public INode set(int i, IValue newChild) throws IndexOutOfBoundsException {
throw new UnsupportedOperationException();
}
@Override
public int arity() {
return args.length();
}
@Override
public String getName() {
return name;
}
@Override
public Iterable<IValue> getChildren() {
return args;
}
@Override
public Iterator<IValue> iterator() {
return args.iterator();
}
@Override
public INode replace(int first, int second, int end, IList repl) throws FactTypeUseException,
IndexOutOfBoundsException {
throw new UnsupportedOperationException();
}
@Override
public boolean isAnnotatable() {
return false;
}
@Override
public IAnnotatable<? extends INode> asAnnotatable() {
throw new IllegalOperationException(
"Facade cannot be viewed as annotatable.", getType());
}
@Override
public boolean mayHaveKeywordParameters() {
return false;
}
@Override
public IWithKeywordParameters<? extends INode> asWithKeywordParameters() {
throw new IllegalOperationException(
"Facade cannot be viewed as with keyword parameters.", getType());
}
}
}