/*
* Copyright 2011-2011 Gregory Shrago
*
* 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 peg;
import org.antlr.runtime.tree.Tree;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static peg.GeneratorUtil.Rule;
import static peg.GeneratorUtil.getAttribute;
import static peg.GrammarParser.*;
import static peg.GraphHelper.Cardinality.*;
/**
* @author gregory
* Date: 15.07.11 18:19
*/
public class GraphHelper {
public enum Cardinality{NONE, OPTIONAL, REQUIRED, AT_LEAST_ONE, ANY_NUMBER;
boolean optional() { return this == OPTIONAL || this == ANY_NUMBER || this == NONE; }
boolean many() { return this == AT_LEAST_ONE || this == ANY_NUMBER; }
public static Cardinality fromNodeType(int type) {
if (type == OPT) return OPTIONAL;
else if (type == SEQ || type == ID) return REQUIRED;
else if (type == CHOICE) return Cardinality.OPTIONAL;
else if (type == ONEMORE) return AT_LEAST_ONE;
else if (type == ZEROMORE) return ANY_NUMBER;
else throw new AssertionError(GrammarParser.tokenNames[type]);
}
public Cardinality and(Cardinality c) {
if (this == NONE || c == NONE) return NONE;
if (optional() || c.optional()) {
return many() || c.many() ? ANY_NUMBER : OPTIONAL;
} else {
return many() || c.many() ? AT_LEAST_ONE : REQUIRED;
}
}
public Cardinality or(Cardinality c) {
if (c == null) c = NONE;
if (this == NONE && c == NONE) return NONE;
if (this == NONE) return c;
if (c == NONE) return this;
return optional() && c.optional()? ANY_NUMBER : AT_LEAST_ONE;
}
}
public static Distance NO_WAY = new Distance(0, NONE);
public static class Distance {
int num = 0;
Cardinality card = NONE;
public Distance(int num, Cardinality card) {
this.num = num;
this.card = card;
}
public Distance update(Tree iNode, Distance d1, Distance d2, Tree kNode) {
if (d1 == NO_WAY || d2 == NO_WAY) return this;
// calc d1 + d2
Cardinality c = d1.card.and(fromNodeType(kNode.getType())).and(d2.card);
return or(iNode, c);
}
public Distance or(Tree iNode, Cardinality c) {
if (this == NO_WAY) {
return new Distance(1, c);
}
int type = iNode.getType();
if (type == CHOICE && c != NONE) {
num++;
if (num == iNode.getChildCount()) {
assert card == OPTIONAL;
card = REQUIRED;
}
}
else if (type == SEQ && c != NONE) {
num ++;
card = AT_LEAST_ONE;
}
else {
card = card.or(c);
}
return this;
}
public String toString() { return card+":"+num; }
}
private Map<String, Tree> ruleMap;
public GraphHelper(Map<String, Tree> ruleMap) {
this.ruleMap = ruleMap;
}
@Nullable
private static Tree resolveRule(Map<String, Tree> ruleMap, String text) {
Tree rule = ruleMap.get(text);
if (rule == null) return null;
String superRuleName = getAttribute(rule, "extends", null);
if (superRuleName == null) return null;
Tree superRule = ruleMap.get(superRuleName);
return superRule == null? rule : superRule;
}
public Map<Tree, Cardinality> getFor(Tree rule) {
return collectMembers(Rule.body(rule), new HashSet<Tree>());
}
private Map<Tree, Cardinality> collectMembers(Tree tree, HashSet<Tree> visited) {
int type = tree.getType();
if (type == AND || type == NOT) return Collections.emptyMap();
if (type == STRING || type == NUMBER) return Collections.singletonMap(tree, REQUIRED);
if (!visited.add(tree)) return Collections.singletonMap(tree, REQUIRED);
if (type == ID) {
Tree targetRule = resolveRule(ruleMap, tree.getText());
if (targetRule != null) {
if (Rule.isPrivate(targetRule)) {
Tree body = Rule.body(targetRule);
Map<Tree, Cardinality> map = collectMembers(body, visited);
return map.containsKey(body) ? joinMaps(ZEROMORE, Arrays.asList(map, map)) : map;
}
else {
return Collections.singletonMap(targetRule, REQUIRED);
}
}
return Collections.singletonMap(tree, REQUIRED);
}
List<Map<Tree, Cardinality>> list = new ArrayList<Map<Tree, Cardinality>>();
for (int i=0, len = tree.getChildCount(); i<len; i++) {
list.add(collectMembers(tree.getChild(i), visited));
}
return joinMaps(tree.getType(), list);
}
private Map<Tree, Cardinality> joinMaps(int type, List<Map<Tree, Cardinality>> list) {
if (list.isEmpty()) return Collections.emptyMap();
if (type == OPT || type == ONEMORE || type == ZEROMORE) {
HashMap<Tree, Cardinality> map = new HashMap<Tree, Cardinality>();
assert list.size() == 1;
Map<Tree, Cardinality> m = list.get(0);
for (Tree t : m.keySet()) {
map.put(t, fromNodeType(type).and(m.get(t)));
}
return map;
}
else if (type == SEQ) {
if (list.size() == 1) return list.get(0);
HashMap<Tree, Cardinality> map = new HashMap<Tree, Cardinality>();
for (Map<Tree, Cardinality> m : list) {
for (Tree t : m.keySet()) {
map.put(t, m.get(t).or(map.get(t)));
}
}
return map;
}
else if (type == CHOICE) {
HashMap<Tree, Cardinality> map = new HashMap<Tree, Cardinality>();
Map<Tree, Cardinality> m0 = list.get(0);
map.putAll(m0);
for (Map<Tree, Cardinality> m : list) {
map.keySet().retainAll(m.keySet());
}
for (Tree t : map.keySet()) {
map.put(t, REQUIRED.and(m0.get(t)));
for (Map<Tree, Cardinality> m : list) {
if (m == list.get(0)) continue;
map.put(t, map.get(t).and(m.get(t)));
}
}
for (Map<Tree, Cardinality> m : list) {
for (Tree t : m.keySet()) {
if (map.containsKey(t)) continue;
map.put(t, OPTIONAL.and(m.get(t)));
}
}
return map;
}
else throw new AssertionError(GrammarParser.tokenNames[type]);
}
}