/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.lang.pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Walk/match/extract values of children inside given aggregation link.
*
* @author Artem Tikhomirov
* @since 3.4
*/
public final class ChildMatcher {
private final NodeMatcher myParent;
// FIXME some sort of sparse array would be great
private Map<Integer, NodeMatcher> myIndexToExtractor;
private String myListPatternVarName;
/*package*/ ChildMatcher(@NotNull NodeMatcher parent) {
myParent = parent;
}
public NodeMatcher at(int zeroBasedIndex) {
assert zeroBasedIndex >= 0;
if (myIndexToExtractor == null) {
myIndexToExtractor = new HashMap<Integer, NodeMatcher>(8);
}
NodeMatcher rv = myIndexToExtractor.get(zeroBasedIndex);
if (rv == null) {
myIndexToExtractor.put(zeroBasedIndex, rv = new NodeMatcher(this));
}
return rv;
}
public ChildMatcher capture(String listVarName) {
myListPatternVarName = listVarName;
return this;
}
public NodeMatcher done() {
return myParent;
}
/*package*/ ValueContainer getValues() {
return myParent.getValues();
}
public boolean match(@NotNull List<SNode> pattern, @NotNull List<SNode> actual) {
if (myListPatternVarName != null) {
getValues().put(myListPatternVarName, actual);
// generated code for list pattern variable didn't dig deeper
return true;
}
final boolean noPatternChildrenExtractorsOnly;
if (pattern.size() != actual.size()) {
if (pattern.size() == 0 && myIndexToExtractor != null && actual.size() == myIndexToExtractor.size()) {
// for completeness, shall check that all index keys are continuous values [0..actual.size)
// but don't want to bother unless face an issue that breaks without such check.
noPatternChildrenExtractorsOnly = true;
} else {
return false;
}
} else {
noPatternChildrenExtractorsOnly = false;
}
final Set<Integer> index = new HashSet<Integer>(myIndexToExtractor == null ? Collections.<Integer>emptySet() : myIndexToExtractor.keySet());
final NodeMatcher defaultHandler = new NodeMatcher(this);
for (int i = 0, x = actual.size(); i < x; i++) {
NodeMatcher childExtractor = defaultHandler;
if (index.remove(i)) {
childExtractor = myIndexToExtractor.get(i);
}
if (!childExtractor.internalMatch(noPatternChildrenExtractorsOnly ? null : pattern.get(i), actual.get(i))) {
return false;
}
}
if (!index.isEmpty()) {
// values left indicate index of a child we were going to extract and process
// provided there's no flaw in the way we build #at(int) calls, we can't possibly go out of pattern.size()
assert false : String.format("Children with index %s were expected", index.toArray());
}
return true;
}
}