/**
* 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.compiler;
import org.textmapper.lapg.LapgCore;
import org.textmapper.lapg.api.*;
import org.textmapper.lapg.api.TemplateParameter.Type;
import org.textmapper.lapg.api.builder.GrammarBuilder;
import org.textmapper.lapg.api.rule.*;
import org.textmapper.lapg.api.rule.RhsSet.Operation;
import org.textmapper.tool.compiler.TMTypeHint.Kind;
import org.textmapper.tool.parser.TMTree;
import org.textmapper.tool.parser.ast.*;
import org.textmapper.tool.parser.ast.TmaRhsQuantifier.TmaQuantifierKind;
import org.textmapper.tool.parser.ast.TmaSetBinary.TmaKindKind;
import java.util.*;
/**
* evgeny, 1/29/13
*/
public class TMParserCompiler {
private final TMTree<TmaInput> tree;
private final TMResolver resolver;
private final GrammarBuilder builder;
private TMExpressionResolver expressionResolver;
private Map<Nonterminal, InputRef> inputs = new HashMap<>();
private Set<String> interfaces = new HashSet<>();
private boolean hasInputs = false;
public TMParserCompiler(TMResolver resolver, TMExpressionResolver expressionResolver) {
this.resolver = resolver;
this.expressionResolver = expressionResolver;
this.tree = resolver.getTree();
this.builder = resolver.getBuilder();
}
public void compile() {
collectAnnotations();
collectAstTypes();
collectRules();
collectDirectives();
if (!hasInputs) {
Symbol input = resolver.getSymbol("input");
if (input == null) {
error(tree.getRoot(), "no input nonterminal");
} else if (!(input instanceof Nonterminal)) {
error(tree.getRoot(), "input must be a nonterminal");
} else {
InputRef ref = builder.addInput((Nonterminal) input, true, input);
TMDataUtil.setUserRequested(ref);
inputs.put((Nonterminal) input, ref);
}
}
}
private Nonterminal asNonterminalWithoutType(TmaSymref ref, Set<String> withType) {
String name = ref.getName();
Symbol type = resolver.getSymbol(name);
if (type == null) {
error(ref, name + " cannot be resolved");
} else if (!(type instanceof Nonterminal)) {
error(ref, "ast type must be a nonterminal");
} else if (withType != null && withType.contains(name)) {
error(ref, "nonterminal without a type is expected (instead of `" + name + "')");
} else {
return (Nonterminal) type;
}
return null;
}
private void collectAstTypes() {
Set<String> withType = new HashSet<>();
for (ITmaGrammarPart clause : tree.getRoot().getParser()) {
if (clause instanceof TmaNonterm) {
TmaNonterm nonterm = (TmaNonterm) clause;
if (nonterm.getType() instanceof TmaRawType) {
withType.add(nonterm.getName().getID());
}
} else if (clause instanceof TmaDirectiveInterface) {
for (TmaIdentifier id : ((TmaDirectiveInterface) clause).getIds()) {
String name = id.getID();
if (name.equals(TMEventMapper.TOKEN_CATEGORY)) {
error(id, TMEventMapper.TOKEN_CATEGORY +
" is reserved for a set of token node types");
continue;
}
if (!interfaces.add(id.getID())) {
error(id, "a duplicate interface identifier");
}
}
}
}
for (ITmaGrammarPart clause : tree.getRoot().getParser()) {
if (clause instanceof TmaNonterm) {
TmaNonterm nonterm = (TmaNonterm) clause;
Symbol left = resolver.getSymbol(nonterm.getName().getID());
if (!(left instanceof Nonterminal)) continue; /* error is already reported */
TmaReportClause defaultAction = nonterm.getDefaultAction();
if (defaultAction != null) {
String name = defaultAction.getAction().getID();
if (name.equals(TMEventMapper.TOKEN_CATEGORY)) {
error(defaultAction.getAction(), TMEventMapper.TOKEN_CATEGORY +
" is reserved for a set of token node types");
} else {
TMDataUtil.putRangeType(left, new RangeType(name,
defaultAction.getKind() != null ? defaultAction.getKind().getID() :
null,
interfaces.contains(name)));
}
}
if (nonterm.getType() instanceof TmaNontermTypeAST) {
final TmaNontermTypeAST astType = (TmaNontermTypeAST) nonterm.getType();
Nonterminal type = asNonterminalWithoutType(astType.getReference(), withType);
if (type != null) {
TMDataUtil.putCustomType((Nonterminal) left, type);
}
} else if (nonterm.getType() instanceof TmaNontermTypeHint) {
TmaNontermTypeHint hint = (TmaNontermTypeHint) nonterm.getType();
if (hint.isInline()) {
error(hint, "inline classes are not supported yet");
continue;
}
TMTypeHint.Kind kind;
switch (hint.getKind()) {
case LCLASS:
kind = Kind.CLASS;
break;
case LINTERFACE:
kind = Kind.INTERFACE;
break;
case LVOID:
kind = Kind.VOID;
break;
default:
throw new IllegalStateException();
}
TMDataUtil.putTypeHint((Nonterminal) left, new TMTypeHint(kind,
hint.getName() == null ? null : hint.getName().getID()));
if (hint.getImplementsClause() != null
&& !hint.getImplementsClause().isEmpty()) {
List<Nonterminal> interfaces = new ArrayList<>();
for (TmaSymref ref : hint.getImplementsClause()) {
Nonterminal type = asNonterminalWithoutType(ref, withType);
if (type != null) {
interfaces.add(type);
}
}
if (!interfaces.isEmpty()) {
TMDataUtil.putImplements((Nonterminal) left, interfaces);
}
}
}
}
}
}
private void collectRules() {
for (ITmaGrammarPart clause : tree.getRoot().getParser()) {
if (clause instanceof TmaNonterm) {
TmaNonterm nonterm = (TmaNonterm) clause;
Symbol left = resolver.getSymbol(nonterm.getName().getID());
if (!(left instanceof Nonterminal)) continue; /* error is already reported */
for (TmaRule0 right : nonterm.getRules()) {
if (right.getError() == null) {
createRule((Nonterminal) left, right);
}
}
}
}
}
private void collectDirectives() {
Set<String> seenSets = new HashSet<>();
for (ITmaGrammarPart clause : tree.getRoot().getParser()) {
if (clause instanceof TmaDirectivePrio) {
TmaDirectivePrio directive = (TmaDirectivePrio) clause;
TmaAssoc key = directive.getAssoc();
List<Terminal> val = resolveTerminals(directive.getSymbols());
int prio;
if (key == TmaAssoc.LLEFT) {
prio = Prio.LEFT;
} else if (key == TmaAssoc.LRIGHT) {
prio = Prio.RIGHT;
} else if (key == TmaAssoc.LNONASSOC) {
prio = Prio.NONASSOC;
} else {
error(directive, "unknown directive identifier used: `" + key + "`");
continue;
}
builder.addPrio(prio, val, directive);
} else if (clause instanceof TmaDirectiveInput) {
List<TmaInputref> refs = ((TmaDirectiveInput) clause).getInputRefs();
for (TmaInputref inputRef : refs) {
Symbol sym = resolver.resolve(inputRef.getReference());
boolean hasEoi = !inputRef.isNoeoi();
if (sym instanceof Nonterminal) {
InputRef existing = inputs.get(sym);
hasInputs = true;
if (existing != null) {
if (existing.hasEoi() != hasEoi) {
error(inputRef, "conflicting inputs (eoi - no-eoi)");
}
continue;
}
InputRef ref = builder.addInput((Nonterminal) sym, hasEoi, inputRef);
TMDataUtil.setUserRequested(ref);
inputs.put((Nonterminal) sym, ref);
} else if (sym != null) {
error(inputRef, "input must be a nonterminal");
}
}
} else if (clause instanceof TmaDirectiveAssert) {
// TODO implement
} else if (clause instanceof TmaDirectiveSet) {
TmaDirectiveSet namedSet = (TmaDirectiveSet) clause;
if (!seenSets.add(namedSet.getName())) {
error(clause, "named set `" + namedSet.getName() + "' already exists");
continue;
}
RhsSet set = convertSet(namedSet.getRhsSet().getExpr(), null);
if (set != null) {
builder.addSet(LapgCore.name(namedSet.getName()), set, clause);
}
}
}
}
private void addSymbolAnnotations(TmaIdentifier id, Map<String, Object> annotations) {
if (annotations != null) {
Symbol sym = resolver.getSymbol(id.getID());
Map<String, Object> symAnnotations = TMDataUtil.getAnnotations(sym);
if (symAnnotations == null) {
symAnnotations = new HashMap<>();
TMDataUtil.putAnnotations(sym, symAnnotations);
}
for (Map.Entry<String, Object> ann : annotations.entrySet()) {
if (symAnnotations.containsKey(ann.getKey())) {
error(id, "redeclaration of annotation `" + ann.getKey() +
"' for non-terminal: " + id.getID()
+ ", skipped");
} else {
symAnnotations.put(ann.getKey(), ann.getValue());
}
}
}
}
private void collectAnnotations() {
for (ITmaGrammarPart clause : tree.getRoot().getParser()) {
if (clause instanceof TmaNonterm) {
TmaNonterm nonterm = (TmaNonterm) clause;
addSymbolAnnotations(nonterm.getName(),
expressionResolver.convert(nonterm.getAnnotations(), "AnnotateSymbol"));
}
}
}
private void createRule(Nonterminal left, TmaRule0 right) {
List<RhsPart> rhs = new ArrayList<>();
List<ITmaRhsPart> list = right.getList();
TmaCommand lastAction = null;
if (list != null) {
ITmaRhsPart last = list.size() > 0 ? list.get(list.size() - 1) : null;
if (last instanceof TmaCommand) {
lastAction = (TmaCommand) last;
list = list.subList(0, list.size() - 1);
}
for (ITmaRhsPart part : list) {
RhsPart rulePart = convertPart(left, part);
if (rulePart != null) {
rhs.add(rulePart);
}
}
}
TmaRhsSuffix ruleAttribute = right.getSuffix();
TmaSymref rulePrio = ruleAttribute != null &&
ruleAttribute.getKind() == TmaRhsSuffix.TmaKindKind.LPREC
? ruleAttribute.getSymref() : null;
Terminal prio = null;
if (rulePrio != null) {
Symbol prioSym = resolver.resolve(rulePrio);
if (prioSym instanceof Terminal) {
prio = (Terminal) prioSym;
} else if (prioSym != null) {
error(rulePrio, "symbol `" + prioSym.getNameText() + "' is not a terminal");
}
}
// TODO store %shift attribute
TmaReportClause action = right.getAction();
String alias = action != null && action.getAction() != null
? action.getAction().getID() : null;
RhsSequence rule = builder.sequence(alias, rhs, right);
if (prio != null) {
rule = builder.addPrecedence(rule, prio);
}
annotateSequence(rule, action);
RhsPredicate predicate = convertPredicate(right.getPredicate(), left);
builder.addRule(left, predicate == null
? rule : builder.conditional(predicate, rule, right));
Map<String, Object> annotations = expressionResolver.convert(right.getPrefix() != null ?
right.getPrefix().getAnnotations() : null, "AnnotateRule");
TMDataUtil.putAnnotations(rule, annotations);
TMDataUtil.putCode(rule, lastAction);
}
private RhsPredicate convertPredicate(ITmaPredicateExpression e, Nonterminal context) {
if (e == null) return null;
if (e instanceof TmaBoolPredicate) {
TemplateParameter param = resolver.resolveParam(
((TmaBoolPredicate) e).getParamRef(), context);
if (param == null) return null;
if (param.getType() != Type.Flag) {
error(e, "type of " + param.getNameText() + " must be boolean");
return null;
}
return builder.predicate(
RhsPredicate.Operation.Equals, null,
param, ((TmaBoolPredicate) e).isNegated() ? Boolean.FALSE : Boolean.TRUE, e);
} else if (e instanceof TmaComparePredicate) {
TemplateParameter param = resolver.resolveParam(
((TmaComparePredicate) e).getParamRef(), context);
if (param == null) return null;
Object val = resolver.getParamValue(
param.getType(), ((TmaComparePredicate) e).getLiteral());
RhsPredicate result = builder.predicate(
RhsPredicate.Operation.Equals, null,
param, val, e);
if (((TmaComparePredicate) e).getKind()
== TmaComparePredicate.TmaKindKind.EXCL_ASSIGN) {
result = builder.predicate(RhsPredicate.Operation.Not,
Collections.singleton(result), null, null, e);
}
return result;
} else if (e instanceof TmaPredicateBinary) {
RhsPredicate left = convertPredicate(((TmaPredicateBinary) e).getLeft(), context);
RhsPredicate right = convertPredicate(((TmaPredicateBinary) e).getRight(), context);
if (left == null || right == null) return null;
RhsPredicate.Operation op;
switch (((TmaPredicateBinary) e).getKind()) {
case AND_AND:
op = RhsPredicate.Operation.And;
break;
case OR_OR:
op = RhsPredicate.Operation.Or;
break;
default:
throw new UnsupportedOperationException();
}
return builder.predicate(op, Arrays.asList(left, right), null, null, e);
}
throw new IllegalArgumentException();
}
private RhsPart convertPart(Nonterminal outer, ITmaRhsPart part) {
if (part instanceof TmaCommand) {
TmaCommand astCode = (TmaCommand) part;
Nonterminal codeSym = resolver.createNestedNonTerm(outer, astCode);
RhsSequence actionRule = builder.empty(astCode);
builder.addRule(codeSym, actionRule);
TMDataUtil.putCode(actionRule, astCode);
return builder.symbol(codeSym, null, astCode);
} else if (part instanceof TmaRhsLookahead) {
List<LookaheadPredicate> rules = new ArrayList<>();
for (TmaLookaheadPredicate p : ((TmaRhsLookahead) part).getPredicates()) {
Symbol s = resolver.resolve(p.getSymrefNoargs());
if (s == null) continue;
if (!(s instanceof Nonterminal)
|| resolver.templateParams((Nonterminal) s) != null) {
error(p, "lookahead assertion must reference a non-templated nonterminal");
continue;
}
InputRef i = inputs.get(s);
if (i == null) {
i = builder.addInput((Nonterminal) s, false /* hasEoi */, p);
inputs.put((Nonterminal) s, i);
}
rules.add(builder.lookaheadPredicate(i, p.isNegate()));
}
if (rules.isEmpty()) return null;
return builder.symbol(builder.lookahead(rules, outer, part), null, part);
} else if (part instanceof TmaRhsUnordered) {
List<ITmaRhsPart> refParts = new ArrayList<>();
extractUnorderedParts(part, refParts);
if (refParts.size() < 2 || refParts.size() > 5) {
error(part, "max 5 elements are allowed for permutation");
return null;
}
List<RhsPart> resolved = new ArrayList<>(refParts.size());
for (ITmaRhsPart refPart : refParts) {
RhsPart rulePart = convertPart(outer, refPart);
if (rulePart == null) {
return null;
}
resolved.add(rulePart);
}
return builder.unordered(resolved, part);
} else if (part instanceof TmaRhsStateMarker) {
return builder.stateMarker(((TmaRhsStateMarker) part).getName(), part);
}
Map<String, Object> annotations = null;
if (part instanceof TmaRhsAnnotated) {
final TmaAnnotations rhsAnnotations = ((TmaRhsAnnotated) part).getAnnotations();
annotations = expressionResolver.convert(rhsAnnotations, "AnnotateReference");
part = ((TmaRhsAnnotated) part).getInner();
}
TmaRhsAssignment assignment = null;
if (part instanceof TmaRhsAssignment) {
assignment = (TmaRhsAssignment) part;
part = assignment.getInner();
}
TmaRhsQuantifier optional = null;
if (part instanceof TmaRhsQuantifier &&
((TmaRhsQuantifier) part).getQuantifier() == TmaQuantifierKind.QUEST) {
optional = (TmaRhsQuantifier) part;
part = optional.getInner();
}
TmaRhsCast cast = null;
TmaRhsAsLiteral literalCast = null;
if (part instanceof TmaRhsCast) {
cast = (TmaRhsCast) part;
part = cast.getInner();
} else if (part instanceof TmaRhsAsLiteral) {
literalCast = (TmaRhsAsLiteral) part;
part = literalCast.getInner();
}
TmaRhsClass cl = null;
if (part instanceof TmaRhsClass) {
cl = (TmaRhsClass) part;
part = cl.getInner();
}
boolean canInline = (annotations == null);
RhsPart result;
// inline (...)
if (canInline && isGroupPart(part)) {
TmaRule0 rule = getGroupRule(part);
result = convertGroup(outer, rule.getList(), part);
annotateSequence((RhsSequence) result, rule.getAction());
// inline (...|...|...)
} else if (canInline && isChoicePart(part)) {
List<TmaRule0> rules = ((TmaRhsNested) part).getRules();
result = convertChoice(outer, rules, part);
} else {
result = convertPrimary(outer, part);
if (result == null) {
return null;
}
TMDataUtil.putAnnotations(result, annotations);
}
if (cast != null) {
final Symbol asSymbol = resolver.resolve(cast.getTarget());
if (asSymbol != null) {
result = builder.cast(asSymbol,
resolver.resolveArgs(cast.getTarget(), outer), result, cast);
}
} else if (literalCast != null) {
if (result instanceof RhsSymbol) {
TMDataUtil.putLiteral((RhsSymbol) result, literalCast.getLiteral().getValue());
} else {
error(literalCast, "cannot apply `as literal' to a group");
}
}
if (cl != null) {
// TODO apply class name to `result'
error(cl, "internal error: classes are not supported yet");
}
if (optional != null) {
result = builder.optional(result, optional);
}
if (assignment != null) {
result = builder.assignment(assignment.getId().getID(), result,
assignment.isAddition(), assignment);
}
return result;
}
private Collection<RhsSet> asCollection(RhsSet... sets) {
if (sets.length == 0) return null;
for (RhsSet s : sets) {
if (s == null) return null;
}
if (sets.length == 1) return Collections.singleton(sets[0]);
return Arrays.asList(sets);
}
private void annotateSequence(RhsSequence seq, TmaReportClause clause) {
if (clause == null) return;
String name = clause.getAction().getID();
if (interfaces.contains(name)) {
error(clause.getAction(), "interface types are not expected here");
return;
}
if (name.equals(TMEventMapper.TOKEN_CATEGORY)) {
error(clause.getAction(), TMEventMapper.TOKEN_CATEGORY +
" is reserved for a set of token node types");
return;
}
TMDataUtil.putRangeType(seq, new RangeType(name,
clause.getKind() != null ? clause.getKind().getID() : null,
false /*interface*/));
}
private RhsSet convertSet(ITmaSetExpression expr, Nonterminal context) {
if (expr instanceof TmaSetBinary) {
TmaSetBinary binary = (TmaSetBinary) expr;
boolean is_and = binary.getKind() == TmaKindKind.AND;
Collection<RhsSet> parts = asCollection(convertSet(binary.getLeft(), context),
convertSet(binary.getRight(), context));
if (parts == null) return null;
return builder.set(is_and ? Operation.Intersection : Operation.Union, null, null,
parts, expr);
} else if (expr instanceof TmaSetComplement) {
Collection<RhsSet> parts = asCollection(
convertSet(((TmaSetComplement) expr).getInner(), context));
if (parts == null) return null;
return builder.set(Operation.Complement, null, null, parts, expr);
} else if (expr instanceof TmaSetCompound) {
return convertSet(((TmaSetCompound) expr).getInner(), context);
} else if (expr instanceof TmaSetSymbol) {
TmaSetSymbol ss = (TmaSetSymbol) expr;
Symbol s = resolver.resolve(ss.getSymbol());
if (s == null) return null;
Operation kind = Operation.Any;
if (ss.getOperator() != null) {
String op = ss.getOperator();
switch (op) {
case "first":
kind = Operation.First;
break;
case "last":
kind = Operation.Last;
break;
case "precede":
kind = Operation.Precede;
break;
case "follow":
kind = Operation.Follow;
break;
default:
error(ss, "operator can be either 'first', 'last', 'precede' or 'follow'");
break;
}
}
return builder.set(kind, s, resolver.resolveArgs(ss.getSymbol(), context), null, expr);
}
error(expr, "internal error: unknown set expression found");
return null;
}
private RhsSymbol convertPrimary(Nonterminal outer, ITmaRhsPart part) {
if (part instanceof TmaRhsSymbol) {
TmaSymref symref = ((TmaRhsSymbol) part).getReference();
TemplateParameter param = resolver.tryResolveParam(symref, outer);
if (param != null) {
return builder.templateSymbol(param, resolver.resolveArgs(symref, outer), part);
}
Symbol resolved = resolver.resolve(symref);
if (resolved != null) {
return builder.symbol(resolved, resolver.resolveArgs(symref, outer), part);
}
return null;
} else if (part instanceof TmaRhsNested) {
Nonterminal nested = resolver.createNestedNonTerm(outer, part);
copyParameters(outer, nested);
List<TmaRule0> rules = ((TmaRhsNested) part).getRules();
for (TmaRule0 right : rules) {
if (right.getError() == null) {
createRule(nested, right);
}
}
return builder.symbolFwdAll(nested, part);
} else if (part instanceof TmaRhsIgnored) {
error(part, "$( ) is not supported, yet");
// TODO
return null;
} else if (part instanceof TmaRhsSet) {
RhsSet set = convertSet(((TmaRhsSet) part).getExpr(), outer);
if (set == null) return null;
String setName = set.getProvisionalName();
Nonterminal result = builder.addShared(set, outer, setName);
// Mark as set.
HashMap<String, Object> annotations = new HashMap<>();
annotations.put("_set", true);
TMDataUtil.putAnnotations(result, annotations);
return builder.symbolFwdAll(result, part);
} else if (part instanceof TmaRhsList) {
TmaRhsList listWithSeparator = (TmaRhsList) part;
RhsSequence inner = convertGroup(outer, listWithSeparator.getRuleParts(),
listWithSeparator);
List<RhsPart> sep = new ArrayList<>();
for (TmaSymref ref : listWithSeparator.getSeparator()) {
Symbol s = resolver.resolve(ref);
if (s == null) {
continue;
}
if (s instanceof Terminal) {
sep.add(builder.symbol(s, null, ref));
} else {
error(ref, "separator must be a terminal symbol");
}
}
RhsPart separator = builder.sequence(null, sep, listWithSeparator);
return createList(outer, inner, listWithSeparator.isAtLeastOne(), separator, part);
} else if (part instanceof TmaRhsQuantifier) {
TmaRhsQuantifier nestedQuantifier = (TmaRhsQuantifier) part;
RhsSequence inner;
ITmaRhsPart innerSymRef = nestedQuantifier.getInner();
if (isGroupPart(innerSymRef)) {
TmaRule0 rule = getGroupRule(innerSymRef);
inner = convertGroup(outer, rule.getList(), innerSymRef);
} else {
RhsSymbol symref = convertPrimary(outer, innerSymRef);
if (symref == null) {
return null;
}
inner = builder.sequence(null, Collections.<RhsPart>singleton(symref),
innerSymRef);
}
TmaQuantifierKind quantifier = nestedQuantifier.getQuantifier();
if (quantifier == TmaQuantifierKind.QUEST) {
error(part, "? cannot be a child of another quantifier");
return null;
}
return createList(outer, inner, quantifier == TmaQuantifierKind.PLUS, null, part);
}
error(part, "internal error: unknown right-hand side part found");
return null;
}
private RhsPart convertChoice(Nonterminal outer, List<TmaRule0> rules, SourceElement origin) {
Collection<RhsPart> result = new ArrayList<>(rules.size());
for (TmaRule0 rule : rules) {
RhsSequence abstractRulePart = convertGroup(outer, rule.getList(), rule);
if (abstractRulePart == null) {
return null;
}
annotateSequence(abstractRulePart, rule.getAction());
RhsPredicate predicate = convertPredicate(rule.getPredicate(), outer);
if (predicate != null) {
result.add(builder.conditional(predicate, abstractRulePart, rule));
} else {
result.add(abstractRulePart);
}
}
return builder.choice(result, origin);
}
private RhsSequence convertGroup(Nonterminal outer, List<ITmaRhsPart> groupPart,
SourceElement origin) {
List<RhsPart> groupResult = new ArrayList<>();
if (groupPart == null) {
return null;
}
for (ITmaRhsPart innerPart : groupPart) {
RhsPart rulePart = convertPart(outer, innerPart);
if (rulePart != null) {
groupResult.add(rulePart);
}
}
return groupResult.isEmpty() ? null : builder.sequence(null, groupResult, origin);
}
private RhsSymbol createList(Nonterminal outer, RhsSequence inner, boolean nonEmpty,
RhsPart separator, ITmaRhsPart origin) {
RhsList list = builder.list(inner, separator,
(separator != null && !nonEmpty) || nonEmpty, origin);
String listName = list.getProvisionalName();
Nonterminal result = builder.addShared(list, outer, listName);
copyParameters(outer, result);
if (separator != null && !nonEmpty) {
// (a separator ',')* => alistopt : alist | ; alist : a | alist ',' a ;
result = builder.addShared(builder.optional(builder.symbolFwdAll(result, origin),
origin), outer, listName + "_opt");
copyParameters(outer, result);
}
return builder.symbolFwdAll(result, origin);
}
private boolean isGroupPart(ITmaRhsPart symbolRef) {
if (!(symbolRef instanceof TmaRhsNested)) {
return false;
}
List<TmaRule0> innerRules = ((TmaRhsNested) symbolRef).getRules();
if (innerRules.size() == 1) {
TmaRule0 first = innerRules.get(0);
return isSimpleNonEmpty(first, false /* without predicates */);
}
return false;
}
private boolean isChoicePart(ITmaRhsPart symbolRef) {
if (!(symbolRef instanceof TmaRhsNested)) {
return false;
}
List<TmaRule0> innerRules = ((TmaRhsNested) symbolRef).getRules();
if (innerRules.isEmpty()) {
return false;
}
for (TmaRule0 rule : innerRules) {
if (!isSimpleNonEmpty(rule, true /* allow predicates */)) {
return false;
}
}
return true;
}
private boolean isSimpleNonEmpty(TmaRule0 rule, boolean allowPredicates) {
return rule != null
&& (allowPredicates || rule.getPredicate() == null)
&& rule.getPrefix() == null
&& rule.getSuffix() == null
&& rule.getList() != null && !rule.getList().isEmpty()
&& rule.getError() == null;
}
private TmaRule0 getGroupRule(ITmaRhsPart symbolRef) {
return ((TmaRhsNested) symbolRef).getRules().get(0);
}
private void extractUnorderedParts(ITmaRhsPart unorderedRulePart, List<ITmaRhsPart> result) {
if (unorderedRulePart instanceof TmaRhsUnordered) {
extractUnorderedParts(((TmaRhsUnordered) unorderedRulePart).getLeft(), result);
extractUnorderedParts(((TmaRhsUnordered) unorderedRulePart).getRight(), result);
} else if (unorderedRulePart instanceof TmaCommand) {
error(unorderedRulePart, "semantic action cannot be used as a part of unordered group");
} else if (!(unorderedRulePart instanceof TmaSyntaxProblem)) {
result.add(unorderedRulePart);
}
}
private List<Terminal> resolveTerminals(List<TmaSymref> input) {
List<Terminal> result = new ArrayList<>(input.size());
for (TmaSymref id : input) {
Symbol sym = resolver.resolve(id);
if (sym instanceof Terminal) {
result.add((Terminal) sym);
} else if (sym != null) {
error(id, "terminal is expected");
}
}
return result;
}
private void copyParameters(Nonterminal source, Nonterminal target) {
List<TemplateParameter> params = resolver.templateParams(source);
if (params != null) target.putUserData(Nonterminal.UD_TEMPLATE_PARAMS, params);
}
private void error(ITmaNode n, String message) {
resolver.error(n, message);
}
}