
org.textmapper.tool.gen.GrammarIxFactory Maven / Gradle / Ivy
/**
* 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.tool.gen;
import org.textmapper.lapg.api.*;
import org.textmapper.lapg.api.rule.RhsCFPart;
import org.textmapper.lapg.api.rule.RhsPart;
import org.textmapper.lapg.api.rule.RhsSequence;
import org.textmapper.lapg.api.rule.RhsSymbol;
import org.textmapper.lapg.common.RuleUtil;
import org.textmapper.lapg.util.RhsUtil;
import org.textmapper.templates.api.EvaluationContext;
import org.textmapper.templates.api.EvaluationException;
import org.textmapper.templates.api.IEvaluationStrategy;
import org.textmapper.templates.api.SourceElement;
import org.textmapper.templates.objects.DefaultJavaIxObject;
import org.textmapper.templates.objects.IxObject;
import org.textmapper.templates.objects.IxWrapper;
import org.textmapper.templates.objects.JavaIxFactory;
import org.textmapper.tool.compiler.CustomRange;
import org.textmapper.tool.compiler.RangeType;
import org.textmapper.tool.compiler.TMDataUtil;
import org.textmapper.tool.compiler.TMGrammar;
import java.util.*;
public class GrammarIxFactory extends JavaIxFactory {
private final String templatePackage;
private final EvaluationContext rootContext;
private IEvaluationStrategy evaluationStrategy;
private final TMGrammar grammar;
public GrammarIxFactory(TMGrammar g, String templatePackage, EvaluationContext context) {
grammar = g;
this.templatePackage = templatePackage;
rootContext = context;
}
@Override
public void setStrategy(IEvaluationStrategy strategy) {
evaluationStrategy = strategy;
}
@Override
public IxObject asObject(Object o) {
if (o instanceof IxObject) {
return (IxObject) o;
}
if (o instanceof IxWrapper) {
o = ((IxWrapper) o).getObject();
}
if (o instanceof LexerRule) {
return new LexerRuleIxObject((LexerRule) o);
}
if (o instanceof Rule) {
return new RuleIxObject((Rule) o);
}
if (o instanceof CustomRange) {
return new CustomRangeIxObject((CustomRange) o);
}
if (o instanceof Name) {
return new NameIxObject((Name) o);
}
if (o instanceof Symbol) {
return new SymbolIxObject((Symbol) o);
}
if (o instanceof RhsSymbol) {
return new RhsSymbolIxObject((RhsSymbol) o);
}
if (o instanceof Grammar) {
return new GrammarIxObject((Grammar) o);
}
if (o instanceof TMGrammar) {
return new TMGrammarIxObject((TMGrammar) o);
}
if (o instanceof InputRef) {
return new InputRefIxObject((InputRef) o);
}
return super.asObject(o);
}
private final class LexerRuleIxObject extends DefaultJavaIxObject {
private final LexerRule lexerRule;
private LexerRuleIxObject(LexerRule lexerRule) {
super(lexerRule);
this.lexerRule = lexerRule;
}
@Override
public Object getProperty(SourceElement caller, String propertyName)
throws EvaluationException {
if ("codeTemplate".equals(propertyName)) {
return TMDataUtil.getCodeTemplate(lexerRule);
}
if ("code".equals(propertyName)) {
return TMDataUtil.getCode(lexerRule);
}
return super.getProperty(caller, propertyName);
}
}
private final class CustomRangeIxObject extends DefaultJavaIxObject {
private final CustomRange range;
public CustomRangeIxObject(CustomRange range) {
super(range);
this.range = range;
}
@Override
public Object callMethod(SourceElement caller, String methodName, Object... args)
throws EvaluationException {
if (args == null || args.length == 0) {
if ("rangeType".equals(methodName)) {
return range.getType().getName();
}
if (methodName.equals("last") || methodName.equals("first")) {
int index = methodName.charAt(0) == 'l' ? range.getEnd() : range.getStart();
int rhsSize = 0;
RhsSymbol sym = null;
for (RhsCFPart p : range.getRule().getRight()) {
if (!(p instanceof RhsSymbol)) continue;
if (rhsSize == index) {
sym = (RhsSymbol) p;
}
rhsSize++;
}
if (sym == null || rhsSize == 0) {
throw new IllegalStateException();
}
return new ActionSymbol(grammar, sym.getTarget(), sym, false,
rhsSize - 1 - index, index,
evaluationStrategy, rootContext, templatePackage, caller);
}
}
return super.callMethod(caller, methodName, args);
}
}
private final class RuleIxObject extends DefaultJavaIxObject {
private final Rule rule;
private RhsSymbol[] sourceSymbols;
private RuleIxObject(Rule rule) {
super(rule);
this.rule = rule;
}
@Override
public Object callMethod(SourceElement caller, String methodName, Object... args)
throws EvaluationException {
if (args == null || args.length == 0) {
if ("codeTemplate".equals(methodName)) {
return TMDataUtil.getCodeTemplate(rule);
}
if ("code".equals(methodName)) {
return TMDataUtil.getCode(rule);
}
if ("precedence".equals(methodName)) {
return rule.getPrecedenceSymbol();
}
if ("left".equals(methodName)) {
return new ActionSymbol(grammar, rule.getLeft(), null, true, 0, 0,
evaluationStrategy, rootContext,
templatePackage, caller);
}
if ("sourceSymbols".equals(methodName)) {
loadSourceSymbols();
return sourceSymbols;
}
if ("rangeType".equals(methodName)) {
RangeType rangeType = TMDataUtil.getRangeType(rule);
return rangeType != null ? rangeType.getName() : "";
}
if ("customRanges".equals(methodName)) {
Collection customRanges = TMDataUtil.getCustomRanges(rule);
if (customRanges != null) {
CustomRange prev = null;
for (CustomRange cr : customRanges) {
if (prev != null && prev.compareTo(cr) == 1) {
throw new IllegalStateException("unsorted");
}
prev = cr;
}
}
return customRanges;
}
if (methodName.equals("last") || methodName.equals("first")) {
int rhsSize = 0;
int index = -1;
RhsSymbol sym = null;
for (RhsCFPart p : rule.getRight()) {
if (!(p instanceof RhsSymbol)) continue;
if (rhsSize == 0 || methodName.charAt(0) == 'l') {
sym = (RhsSymbol) p;
index = rhsSize;
}
rhsSize++;
}
if (rhsSize == 0) {
throw new EvaluationException(methodName
+ "() cannot be used on an empty rule");
}
return new ActionSymbol(grammar, sym.getTarget(), sym, false,
rhsSize - 1 - index, index,
evaluationStrategy, rootContext, templatePackage, caller);
}
}
if (args != null && args.length == 1) {
if ("mappedSymbols".equals(methodName)) {
return getMappedSymbols((RhsSequence) args[0],
new HashSet<>(Arrays.asList(rule.getRight())));
}
if ("isMatched".equals(methodName)) {
return isMatched(((RhsSequence) args[0]),
new HashSet<>(Arrays.asList(rule.getRight())));
}
}
return super.callMethod(caller, methodName, args);
}
private boolean isMatched(RhsPart part, Set active) {
for (RhsSymbol o : RhsUtil.getRhsSymbols(part)) {
if (active.contains(o)) {
return true;
}
}
return false;
}
private void loadSourceSymbols() {
if (sourceSymbols == null) {
sourceSymbols = RhsUtil.getRhsSymbols(rule.getSource());
}
}
private RhsPart[] getMappedSymbols(RhsSequence seq, Set active) {
List result = new ArrayList<>();
for (RhsPart p : seq.getParts()) {
collectMappedSymbols(p, result, active);
}
return result.toArray(new RhsPart[result.size()]);
}
private void collectMappedSymbols(RhsPart part, List r, Set active) {
if (part instanceof RhsSymbol) {
RhsSymbol sym = (RhsSymbol) part;
RhsSymbol rewrittenTo = TMDataUtil.getRewrittenTo(sym);
if ((rewrittenTo != null ? rewrittenTo : sym).getMapping() != null) {
if (active.contains(part)) {
r.add(part);
}
return;
}
}
if (part instanceof RhsSequence && RhsUtil.hasMapping((RhsSequence) part)) {
if (isMatched(part, active)) {
r.add(part);
}
return;
}
Iterable children = RhsUtil.getChildren(part);
if (children == null) return;
for (RhsPart c : children) {
collectMappedSymbols(c, r, active);
}
}
@Override
public Object getByIndex(SourceElement caller, Object index) throws EvaluationException {
if (index instanceof Integer) {
int i = (Integer) index;
loadSourceSymbols();
if (i < 0 || i >= sourceSymbols.length) {
throw new EvaluationException("index is out of range");
}
RhsSymbol ref = sourceSymbols[i];
int leftOffset = -1;
int symSize = 0;
for (RhsCFPart p : rule.getRight()) {
if (!(p instanceof RhsSymbol)) continue;
if (p == ref) {
leftOffset = symSize;
}
symSize++;
}
int rightOffset = leftOffset == -1 ? -1 : symSize - 1 - leftOffset;
return new ActionSymbol(grammar, ref.getTarget(), ref, false, rightOffset,
leftOffset, evaluationStrategy, rootContext, templatePackage, caller);
} else if (index instanceof String) {
return grammar.getAnnotation(rule, (String) index);
} else {
throw new EvaluationException("index object should be integer (right part index) "
+ "or string (annotation name)");
}
}
@Override
public Object getProperty(SourceElement caller, String id) throws EvaluationException {
Set matching = RuleUtil.getSymbolsByName(id, rule.getSource());
if (matching == null || matching.isEmpty()) {
throw new EvaluationException("symbol `" + id + "' is " +
(matching == null ? "undefined" : "ambiguous"));
}
int leftOffset = -1;
int symSize = 0;
RhsSymbol sym = null;
for (RhsCFPart p : rule.getRight()) {
if (!(p instanceof RhsSymbol)) continue;
if (matching.contains(p)) {
assert sym == null : "internal error in RuleUtil.getSymbols()";
leftOffset = symSize;
sym = (RhsSymbol) p;
}
symSize++;
}
int rightOffset = leftOffset == -1 ? -1 : symSize - 1 - leftOffset;
if (sym == null) {
sym = matching.iterator().next();
}
return new ActionSymbol(grammar, sym.getTarget(), sym,
false, rightOffset, leftOffset, evaluationStrategy,
rootContext, templatePackage, caller);
}
}
private final class SymbolIxObject extends DefaultJavaIxObject {
private final Symbol sym;
private SymbolIxObject(Symbol sym) {
super(sym);
this.sym = sym;
}
@Override
public Object getByIndex(SourceElement caller, Object index) throws EvaluationException {
if (index instanceof String) {
return grammar.getAnnotation(sym, (String) index);
} else {
throw new EvaluationException("index object should be string (annotation name)");
}
}
@Override
public Object getProperty(SourceElement caller, String propertyName)
throws EvaluationException {
if ("id".equals(propertyName)) {
return TMDataUtil.getId(sym);
}
return super.getProperty(caller, propertyName);
}
}
private final class InputRefIxObject extends DefaultJavaIxObject {
private final InputRef ref;
private InputRefIxObject(InputRef ref) {
super(ref);
this.ref = ref;
}
@Override
public Object getProperty(SourceElement caller, String propertyName)
throws EvaluationException {
if ("requested".equals(propertyName)) {
return TMDataUtil.isUserRequested(ref);
}
return super.getProperty(caller, propertyName);
}
}
private final class RhsSymbolIxObject extends DefaultJavaIxObject {
private final RhsSymbol sym;
private RhsSymbolIxObject(RhsSymbol sym) {
super(sym);
this.sym = sym;
}
@Override
public Object getByIndex(SourceElement caller, Object index) throws EvaluationException {
if (index instanceof String) {
return grammar.getAnnotation(sym, (String) index);
} else {
throw new EvaluationException("index object should be string (annotation name)");
}
}
@Override
public Object getProperty(SourceElement caller, String propertyName)
throws EvaluationException {
if ("mapping".equals(propertyName)) {
RhsSymbol rewrittenTo = TMDataUtil.getRewrittenTo(sym);
return (rewrittenTo != null ? rewrittenTo : sym).getMapping();
}
return super.getProperty(caller, propertyName);
}
}
private final class TMGrammarIxObject extends DefaultJavaIxObject {
private final GrammarIxObject grammarIxObject;
private TMGrammarIxObject(TMGrammar grammar) {
super(grammar);
grammarIxObject = new GrammarIxObject(grammar.getGrammar());
}
@Override
public Object getProperty(SourceElement caller, String id) throws EvaluationException {
if ("templates".equals(id) || "hasErrors".equals(id) || "options".equals(id)
|| "copyrightHeader".equals(id)) {
return super.getProperty(caller, id);
} else {
return grammarIxObject.getProperty(caller, id);
}
}
@Override
public Object callMethod(SourceElement caller, String methodName, Object... args)
throws EvaluationException {
return grammarIxObject.callMethod(caller, methodName, args);
}
}
private final Map rules = new HashMap<>();
private final class GrammarIxObject extends DefaultJavaIxObject {
private final Grammar grammar;
private GrammarIxObject(Grammar grammar) {
super(grammar);
this.grammar = grammar;
}
@Override
public Object getProperty(SourceElement caller, String propertyName)
throws EvaluationException {
if ("rules".equals(propertyName)) {
return rules.computeIfAbsent(grammar, k -> new GrammarRules(grammar));
}
if ("lexerRuleTokens".equals(propertyName)) {
LexerRule[] lexerRules = grammar.getLexerRules();
int[] result = new int[lexerRules.length + 2];
result[0] = grammar.getInvalidToken() != null
? grammar.getInvalidToken().getIndex()
: -1;
result[1] = grammar.getEoi().getIndex();
for (int i = 0; i < lexerRules.length; i++) {
result[i + 2] = lexerRules[i].getSymbol().getIndex();
}
return result;
}
if ("canInlineLexerRules".equals(propertyName)) {
return canInlineLexerRules();
}
if ("categories".equals(propertyName)) {
return TMDataUtil.getCategoryList(grammar);
}
if ("rangeTypes".equals(propertyName)) {
return TMDataUtil.getTypes(grammar);
}
return super.getProperty(caller, propertyName);
}
private boolean canInlineLexerRules() {
Map seenSpaceRules = new HashMap<>();
Map seenClassRules = new HashMap<>();
if (grammar.getInvalidToken() == null) {
return false;
}
for (LexerRule rule : grammar.getLexerRules()) {
if (TMDataUtil.getCodeTemplate(rule) != null) {
return false;
}
// (space) annotations must be consistent.
Boolean existing = seenSpaceRules.get(rule.getSymbol());
boolean isSpace = (rule.getKind() == LexerRule.KIND_SPACE);
if (existing != null && existing != isSpace) {
return false;
}
seenSpaceRules.put(rule.getSymbol(), isSpace);
// A (class) rule must be the only rule for its terminal.
existing = seenClassRules.get(rule.getSymbol());
boolean isClass = (rule.getKind() == LexerRule.KIND_CLASS);
if (existing != null && (isClass || existing)) {
return false;
}
seenClassRules.put(rule.getSymbol(), isClass);
}
return true;
}
@Override
public Object callMethod(SourceElement caller, String methodName, Object... args)
throws EvaluationException {
if (args.length == 2 && "inlineLexerRules".equals(methodName)) {
int tmFirstRule = (Integer) args[0];
int[] action = (int[]) args[1];
action = Arrays.copyOf(action, action.length);
for (int i = 0; i < action.length; i++) {
if (action[i] <= tmFirstRule) {
int rule = tmFirstRule - action[i];
int symbol;
switch (rule) {
case 0: // invalid token
symbol = grammar.getInvalidToken().getIndex();
break;
case 1: // eoi
symbol = grammar.getEoi().getIndex();
break;
default:
symbol = grammar.getLexerRules()[rule - 2].getSymbol().getIndex();
break;
}
action[i] = tmFirstRule - symbol;
}
}
return action;
}
if (args.length == 1 && "inlineLexerRulesBT".equals(methodName)) {
int[] backtracking = (int[]) args[0];
backtracking = Arrays.copyOf(backtracking, backtracking.length);
for (int i = 0; i < backtracking.length; i += 2) {
backtracking[i] = grammar.getLexerRules()[backtracking[i]-2].getSymbol().getIndex();
}
return backtracking;
}
if (args.length == 1 && "rangeFields".equals(methodName)) {
return TMDataUtil.getRangeFields(grammar, (String) args[0]);
}
if (args.length == 1 && "categoryTypes".equals(methodName)) {
return TMDataUtil.getCategoryTypes(grammar, (String) args[0]);
}
return super.callMethod(caller, methodName, args);
}
}
private class GrammarRules extends DefaultJavaIxObject implements Iterable {
private final Rule[] myRules;
private Map> rulesBySymbol;
private Map> rulesWithSymbol;
public GrammarRules(Grammar grammar) {
super(grammar);
myRules = grammar.getRules();
}
public Map> getRulesBySymbol() {
if (rulesBySymbol != null) {
return rulesBySymbol;
}
rulesBySymbol = new HashMap<>();
for (Rule r : myRules) {
List target = rulesBySymbol.get(r.getLeft());
if (target == null) {
target = new ArrayList<>();
rulesBySymbol.put(r.getLeft(), target);
}
target.add(r);
}
return rulesBySymbol;
}
public Map> getRulesContainingSymbol() {
if (rulesWithSymbol != null) {
return rulesWithSymbol;
}
rulesWithSymbol = new HashMap<>();
Set seen = new HashSet<>();
for (Rule r : myRules) {
seen.clear();
for (RhsCFPart sref : r.getRight()) {
Symbol s = sref.getTarget();
if (s == null || seen.contains(s)) {
continue;
}
seen.add(s);
List list = rulesWithSymbol.get(s);
if (list == null) {
list = new ArrayList<>();
rulesWithSymbol.put(s, list);
}
list.add(r);
}
}
return rulesWithSymbol;
}
@Override
public Iterator iterator() {
return new Iterator() {
int index = 0;
@Override
public boolean hasNext() {
return index < myRules.length;
}
@Override
public Rule next() {
return index < myRules.length ? myRules[index++] : null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public Object callMethod(SourceElement caller, String methodName, Object... args)
throws EvaluationException {
if (args.length == 1 && "with".equals(methodName) && args[0] instanceof Symbol) {
List list = getRulesContainingSymbol().get(args[0]);
return list != null ? list : Collections.emptyList();
}
return asObject(myRules).callMethod(caller, methodName, args);
}
@Override
public Object getByIndex(SourceElement caller, Object index) throws EvaluationException {
if (index instanceof Symbol) {
List list = getRulesBySymbol().get(index);
return list != null ? list : Collections.emptyList();
}
return asObject(myRules).getByIndex(caller, index);
}
@Override
public Object getProperty(SourceElement caller, String id) throws EvaluationException {
return asObject(myRules).getProperty(caller, id);
}
@Override
public Iterator asSequence() {
return iterator();
}
}
private class NameIxObject extends DefaultJavaIxObject {
private final Name name;
NameIxObject(Name name) {
super(name);
this.name = name;
}
@Override
public Object getProperty(SourceElement caller, String id) throws EvaluationException {
switch (id) {
case "CamelCase":
return name.camelCase(true);
case "camelCase":
return name.camelCase(false);
}
return super.getProperty(caller, id);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy