/** * Copyright 2002-2017 Evgeny Gryaznov * * 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 org.textmapper.lapg.lalr; import org.textmapper.lapg.api.*; import org.textmapper.lapg.api.LookaheadRule.LookaheadCase; import org.textmapper.lapg.api.rule.LookaheadPredicate; import org.textmapper.lapg.builder.LiUtil; import java.util.*; import java.util.stream.Collectors; class ExplicitLookaheadBuilder { private static final LookaheadCase[] EMPTY_CASES = new LookaheadCase[0]; private static class Node { InputRef input; Set<Node> prev = new HashSet<>(); List<Lookahead> positive = new ArrayList<>(); List<Lookahead> negative = new ArrayList<>(); boolean processing = false; boolean done = false; public Node(InputRef input) { this.input = input; } String getName() { return input.getTarget().getNameText(); } void addPredicate(boolean negated, Lookahead la) { (negated ? negative : positive).add(la); } boolean serializeTo(List<Node> res) { if (processing) return false; if (done) return true; processing = true; Node[] nodes = prev.toArray(new Node[prev.size()]); if (nodes.length > 1) { Arrays.sort(nodes, (o1, o2) -> o1.getName().compareTo(o2.getName())); } for (Node n : nodes) { if (!n.serializeTo(res)) return false; } processing = false; done = true; if (input != null) { res.add(this); } return true; } List<Node> serialize() { List<Node> result = new ArrayList<>(); if (!serializeTo(result)) return null; return result; } Lookahead pickLookahead(LinkedHashSet<Lookahead> lookaheads, boolean negated) { List<Lookahead> list = (negated ? negative : positive).stream() .filter(lookaheads::contains) .collect(Collectors.toList()); if (list.size() != 1) return null; return list.get(0); } } private static class LiLookaheadCase implements LookaheadCase { InputRef input; boolean negated; Nonterminal target; public LiLookaheadCase(InputRef input, boolean negated, Nonterminal target) { this.input = input; this.negated = negated; this.target = target; } @Override public InputRef getInput() { return input; } @Override public boolean isNegated() { return negated; } @Override public Nonterminal getTarget() { return target; } } private static class LiLookaheadRule implements LookaheadRule { private int index; private int refCount; private int refRule; private Set<Lookahead> lookaheads; private LookaheadCase[] cases = EMPTY_CASES; private Nonterminal defaultTarget = null; private LiLookaheadRule(int index, Set<Lookahead> lookaheads, int refRule) { this.index = index; this.lookaheads = lookaheads; this.refCount = 1; this.refRule = refRule; } void incRef() { refCount++; } void decRef() { refCount--; } @Override public int getIndex() { return index; } @Override public LookaheadCase[] getCases() { return cases; } @Override public Nonterminal getDefaultTarget() { return defaultTarget; } Node addNode(Map<InputRef, Node> nodes, Node prev, InputRef input) { Node node = nodes.get(input); if (node == null) { node = new Node(input); nodes.put(input, node); } if (prev != null) { node.prev.add(prev); } return node; } void reportError(ProcessingStatus status, String message) { status.report(ProcessingStatus.KIND_ERROR, message + lookaheads.stream().map(Lookahead::asString) .collect(Collectors.joining(", ")), lookaheads.toArray(new SourceElement[lookaheads.size()])); } void computeCases(ProcessingStatus status) { Node root = new Node(null); Map<InputRef, Node> nodes = new HashMap<>(); for (Lookahead la : lookaheads) { Node prev = null; for (LookaheadPredicate p : la.getLookaheadPredicates()) { prev = addNode(nodes, prev, p.getInput()); prev.addPredicate(p.isNegated(), la); } if (prev == null) { throw new IllegalStateException(); } root.prev.add(prev); } List<Node> evalOrder = root.serialize(); if (evalOrder == null) { reportError(status, "Conflicting lookaheads (inconsistent nonterminal order): "); return; } LinkedHashSet<Lookahead> lookaheads = new LinkedHashSet<>(this.lookaheads); List<LookaheadCase> cases = new ArrayList<>(); for (Node node : evalOrder) { if (lookaheads.size() == 1) break; boolean negated = false; Lookahead target = node.pickLookahead(lookaheads, false /*negated*/); if (target == null) { target = node.pickLookahead(lookaheads, true /*negated*/); negated = true; } if (target == null) continue; lookaheads.remove(target); cases.add(new LiLookaheadCase(node.input, negated, target)); } if (lookaheads.size() != 1) { reportError(status, "Conflicting lookaheads: "); return; } this.cases = cases.toArray(new LookaheadCase[cases.size()]); defaultTarget = lookaheads.iterator().next(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (LookaheadCase c : cases) { if (c.isNegated()) { sb.append("!"); } sb.append(c.getInput().getTarget().getNameText()); sb.append(" -> "); sb.append(LiUtil.getSymbolName(c.getTarget())); sb.append("; "); } sb.append("default -> "); if (defaultTarget == null) { sb.append("ERROR"); } else { sb.append(LiUtil.getSymbolName(defaultTarget)); } return sb.toString(); } } private final int rulesCount; private final ProcessingStatus status; private final List<LiLookaheadRule> rules = new ArrayList<>(); private final Map<Set<Lookahead>, LiLookaheadRule> resolutionMap = new HashMap<>(); ExplicitLookaheadBuilder(int rulesCount, ProcessingStatus status) { this.rulesCount = rulesCount; this.status = status; } /** * Creates or returns an existing resolution rule for the given set of lookahead rules. */ int addResolutionRule(Set<Lookahead> set, int refRule) { LiLookaheadRule rule = resolutionMap.get(set); if (rule != null) { rule.incRef(); return rule.getIndex(); } rule = new LiLookaheadRule(rulesCount + rules.size(), set, refRule); rules.add(rule); resolutionMap.put(set, rule); return rule.getIndex(); } int addResolutionRule(int resolutionRule, Lookahead la) { LiLookaheadRule laRule = rules.get(resolutionRule - rulesCount); laRule.decRef(); Set<Lookahead> set = new LinkedHashSet<>(laRule.lookaheads); set.add(la); return addResolutionRule(set, laRule.refRule); } int getRefRule(int resolutionRule) { return rules.get(resolutionRule - rulesCount).refRule; } void assignIndices() { int i = rulesCount; for (LiLookaheadRule r : rules) { if (r.refCount == 0) { r.index = -1; } else { r.index = i++; } } } void compact() { rules.removeIf(rule -> rule.getIndex() == -1); } int getRuleIndex(int resolutionRule) { int index = rules.get(resolutionRule - rulesCount).getIndex(); if (index == -1) { throw new IllegalStateException(); } return index; } boolean isResolutionRule(int rule) { return rule >= this.rulesCount; } LookaheadRule[] extractRules() { for (LiLookaheadRule rule : rules) { rule.computeCases(status); } return rules.toArray(new LookaheadRule[rules.size()]); } }