/* * Reference ETL Parser for Java * Copyright (c) 2000-2009 Constantine A Plotnikov * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package net.sf.etl.parsers.internal.term_parser.compiler; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.SortedMap; import java.util.TreeMap; import net.sf.etl.parsers.LiteralUtils; import net.sf.etl.parsers.PropertyName; import net.sf.etl.parsers.SyntaxRole; import net.sf.etl.parsers.TermContext; import net.sf.etl.parsers.Terms; import net.sf.etl.parsers.TokenKey; import net.sf.etl.parsers.Tokens; import net.sf.etl.parsers.internal.term_parser.compiler.nodes.FallbackObjectNode; import net.sf.etl.parsers.internal.term_parser.compiler.nodes.FirstChoiceNode; import net.sf.etl.parsers.internal.term_parser.compiler.nodes.Node; import net.sf.etl.parsers.internal.term_parser.flattened.ContextImportView; import net.sf.etl.parsers.internal.term_parser.flattened.ContextView; import net.sf.etl.parsers.internal.term_parser.flattened.DefView; import net.sf.etl.parsers.internal.term_parser.flattened.DefinitionView; import net.sf.etl.parsers.internal.term_parser.flattened.OpLevel; import net.sf.etl.parsers.internal.term_parser.flattened.StatementView; import net.sf.etl.parsers.internal.term_parser.flattened.WrapperLink; import net.sf.etl.parsers.internal.term_parser.grammar.BlankSyntaxStatement; import net.sf.etl.parsers.internal.term_parser.grammar.BlockRef; import net.sf.etl.parsers.internal.term_parser.grammar.ChoiceOp; import net.sf.etl.parsers.internal.term_parser.grammar.DoclinesOp; import net.sf.etl.parsers.internal.term_parser.grammar.Element; import net.sf.etl.parsers.internal.term_parser.grammar.ExpressionRef; import net.sf.etl.parsers.internal.term_parser.grammar.ExpressionStatement; import net.sf.etl.parsers.internal.term_parser.grammar.FirstChoiceOp; import net.sf.etl.parsers.internal.term_parser.grammar.FloatOp; import net.sf.etl.parsers.internal.term_parser.grammar.GraphicsOp; import net.sf.etl.parsers.internal.term_parser.grammar.IdentifierOp; import net.sf.etl.parsers.internal.term_parser.grammar.IntegerOp; import net.sf.etl.parsers.internal.term_parser.grammar.KeywordStatement; import net.sf.etl.parsers.internal.term_parser.grammar.Let; import net.sf.etl.parsers.internal.term_parser.grammar.ListOp; import net.sf.etl.parsers.internal.term_parser.grammar.ModifierOp; import net.sf.etl.parsers.internal.term_parser.grammar.ModifiersOp; import net.sf.etl.parsers.internal.term_parser.grammar.NumberOp; import net.sf.etl.parsers.internal.term_parser.grammar.ObjectOp; import net.sf.etl.parsers.internal.term_parser.grammar.OneOrMoreOp; import net.sf.etl.parsers.internal.term_parser.grammar.OperandOp; import net.sf.etl.parsers.internal.term_parser.grammar.OperatorDefinition; import net.sf.etl.parsers.internal.term_parser.grammar.OptionalOp; import net.sf.etl.parsers.internal.term_parser.grammar.RefOp; import net.sf.etl.parsers.internal.term_parser.grammar.Sequence; import net.sf.etl.parsers.internal.term_parser.grammar.StringOp; import net.sf.etl.parsers.internal.term_parser.grammar.Syntax; import net.sf.etl.parsers.internal.term_parser.grammar.SyntaxStatement; import net.sf.etl.parsers.internal.term_parser.grammar.TokenOp; import net.sf.etl.parsers.internal.term_parser.grammar.Wrapper; import net.sf.etl.parsers.internal.term_parser.grammar.ZeroOrMoreOp; import net.sf.etl.parsers.internal.term_parser.states.ActivationFactory; import net.sf.etl.parsers.internal.term_parser.states.ActivationFactoryImpl; import net.sf.etl.parsers.internal.term_parser.states.StateMachinePeerFactory; /** * <p> * A builder for context. This class is used to build all * {@link ActivationFactory} objects from flattened {@link ContextView}. The * class works in the context of {@link GrammarBuilder}. * </p> * * @author const */ // NOTE POST 0.2: section/block/bracket based error recovery. It is simple on SM // level, just skip until anything matching other section start is found (except // insides of blocks). But what to do on LL1 level? Recovery scope, recovery // points, ...? Any block is recovery scope and any section or block is recovery // point inside block. In brackets "close" is recovery point. // NOTE POST 0.2: add error reporting in some places that actually says what is // alternative beyond enumerating tokens: expression of specified level from // some // grammar or statement in some context. ErrorChoice node and state? This would // allow see and understand errors better. Tools will be able to report as is or // translate it to tool specific way. public class ContextBuilder { /** a grammar builder for this context builder */ private final GrammarBuilder grammarBuilder; /** a view that is being built */ private final ContextView contextView; /** builders for operator levels */ private final TreeMap<Integer, OperatorLevelBuilder> operatorLevels = new TreeMap<Integer, OperatorLevelBuilder>(); /** statement sequence activation */ private ActivationFactoryImpl statementSequence; /** a context name */ private final TermContext contextName; /** a builder for statement sequence in this block */ private StateMachineBuilder statementSequenceBuilder; /** * A constructor * * @param view * a view to compile * @param builder * a parent builder to use * */ public ContextBuilder(GrammarBuilder builder, ContextView view) { super(); this.grammarBuilder = builder; this.contextView = view; this.contextName = new TermContext(grammarBuilder.grammarView() .grammarName(), contextView.name(), grammarBuilder .grammarView().getSystemId()); if (view.isDefault()) { grammarBuilder.peerFactory().setDefaultContext(contextName); } } /** * Prepare context. This method creates instances of activation factories. */ public void prepare() { if (contextView.statements().size() > 0) { statementSequence = new ActivationFactoryImpl( peerFactory(), contextName, name() + StateMachinePeerFactory.STATEMENT_SEQUENCE_ACTIVATION_SUFFIX); statementSequenceBuilder = new StateMachineBuilder(this, statementSequence); grammarBuilder.peerFactory().registerSegmentSequenceActivation( contextName, statementSequence); } for (OpLevel l = contextView.allExpressionsLevel(); l != null; l = l.previousLevel) { final OperatorLevelBuilder builder = new OperatorLevelBuilder(this, l); operatorLevels.put(new Integer(l.precedence), builder); builder.prepare(); } } /** * @return context view associated with this builder */ public ContextView contextView() { return contextView; } /** * @return a peer factory that is being built */ public StateMachinePeerFactory peerFactory() { return grammarBuilder.peerFactory(); } /** * @return a name of context */ public String name() { return contextView.name(); } /** * Compile activations locally. On this phase no information is obtained * from other contexts except information about availability of the * activations which is created on the previous preparea stage. This is what * word locally means. */ public void buildNodes() { for (final Iterator<OperatorLevelBuilder> i = operatorLevels.values() .iterator(); i.hasNext();) { final OperatorLevelBuilder builder = i.next(); builder.buildNodes(); } if (statementSequence != null) { buildStatementSequenceNodes(); } } /** * Compile activations locally. On this phase no information is obtained * from other contexts except information about availability of the * activations which is created on the previous prepare stage. This is what * word locally means. */ public void buildLookAhead() { final HashSet<StateMachineBuilder> visistedBuilders = new HashSet<StateMachineBuilder>(); for (final Iterator<OperatorLevelBuilder> i = operatorLevels.values() .iterator(); i.hasNext();) { final OperatorLevelBuilder builder = i.next(); builder.builder().buildLookAhead(visistedBuilders); } if (statementSequence != null) { statementSequenceBuilder.buildLookAhead(visistedBuilders); } } /** * Compile activations locally. On this phase no information is obtained * from other contexts except information about availability of the * activations which is created on the previous prepared stage. This is what * word locally means. */ public void buildStateMachines() { for (final Iterator<OperatorLevelBuilder> i = operatorLevels.values() .iterator(); i.hasNext();) { final OperatorLevelBuilder builder = i.next(); builder.builder().buildStateMachine(); } if (statementSequence != null) { statementSequenceBuilder.buildStateMachine(); } } /** * Compile a sequence of context statements. This sequence might happen in * block or at top level of the source. */ private void buildStatementSequenceNodes() { final StateMachineBuilder b = statementSequenceBuilder; b.startRepeat(); b.startSegment(termContext()); b.startMarked(); b.startAlternateEntry(); // no actions. Note that this entry point is here because doc comment // indicates that text of source code has started and the default // grammar associated with source being parsed should be activated b.endAlternateEntry(); // Start fallback scope. This scope causes creation of object at mark. // This is used to prevent the case of dangling properties. final FallbackObjectNode fallbackNode = b.startFallbackScope(); // parse documentation comments in the context. if (contextView.documetation() != null) { // if documentation statement present in the context, comments // are parsed according to it. b.startDefinition(contextView.documetation()); b.startChoice(); b.startDocComent(termContext()); compileSyntax(new HashSet<DefView>(), b, contextView.documetation() .statements()); b.endDocComent(); b.startSequence(); b.endSequence(); b.endChoice(); b.endDefinition(); } else { // otherwise comments are treated as ignorable b.startRepeat(); b.tokenText(Terms.IGNORABLE, SyntaxRole.DOCUMENTATION, TokenKey .simple(Tokens.DOC_COMMENT)); b.endRepeat(); } // Compile attributes if they are available if (contextView.attributes() != null) { b.startDefinition(contextView.attributes()); b.startRepeat(); b.startAttributes(termContext()); compileSyntax(new HashSet<DefView>(), b, contextView.attributes() .statements()); b.endAttributes(); b.endRepeat(); b.endDefinition(); } b.endFallbackScope(); // Finally compile statement choice. // NOTE POST 0.2: add alternative that reports error better. b.startChoice(); boolean wasEmpty = false; StatementView fallback = null; for (final StatementView s : contextView.statements()) { final DefinitionView def = s.topObjectDefinition(contextView); if (def == null) { contextView.error(s.definition(), "grammar.ObjectDefinition.missingTopObject", name(), grammarBuilder.grammarView().getSystemId()); } else { final WrapperLink wrappers = s.wrappers(contextView); b.startDefinition(s); b.startSequence(); // handle the case when def/ref has been used final ObjectOp o = s.topObject(contextView); if (def != s) { b.startDefinition(def); } b.startObjectAtMark(def.convertName(o.name), wrappers); b.commitMark(); // this statement tries to commit mark // now body of root object is compiled compileSyntax(new HashSet<DefView>(), b, o.syntax); // object ends here final Node object = b.endObject(); if (object.matchesEmpty()) { wasEmpty = true; if (fallback == null) { fallback = s; } } if (def != s) { b.endDefinition(); // def } b.endSequence(); b.endDefinition(); // s } } if (fallback == null) { // select any statement as fallback fallback = contextView.statements().iterator().next(); } fallbackNode.setFallbackObject(fallback.topObjectName(contextView), fallback.wrappers(contextView)); if (!wasEmpty) { // If there were no empty node, an empty variant of fallback // statement is compiled. // NOTE POST 0.2: that this code messes up with syntax error // reporting. If statement starts with unknown token, fallback // will be executed and error "end of segment expected" reported. // better reaction would have been list of statements. b.startObjectAtMark(fallback.topObjectName(contextView), fallback .wrappers(contextView)); b.commitMark(); b.error("syntax.UnexpectedToken.expectingStatementFromContext", new Object[] { contextName }); b.endObject(); } b.endChoice(); b.endMarked(); b.endSegment(); b.endRepeat(); } /** * Compile syntax expression * * @param visited * a set of visited definition nodes. It is used to detect * definition cycles. * @param b * a builder used for compilation. * @param body * a syntax to compile */ void compileSyntax(HashSet<DefView> visited, StateMachineBuilder b, Syntax body) { if (body instanceof BlockRef) { final BlockRef s = (BlockRef) body; final DefinitionView v = b.topDefinition().originalDefinition(); final StateMachineBuilder f = getStatementSequenceActivation(v, s, s.context); if (f != null) { b.startBlock(f.context()); b.call(f); b.endBlock(); } else { error(b, s, "grammar.Block.referringContextWithoutStatements", s.context); } } else if (body instanceof ExpressionRef) { final ExpressionRef s = (ExpressionRef) body; final DefinitionView v = b.topDefinition().originalDefinition(); final StateMachineBuilder f = getExpressionActivation(v, s, s.context, s.precedence); if (f != null) { final TermContext c = s.precedence != null ? new TermContext(f .context(), s.precedence.intValue()) : f.context(); b.startExpression(c); b.startMarked(); b.call(f); b.endMarked(); b.endExpression(); } } else if (body instanceof ModifiersOp) { final ModifiersOp s = (ModifiersOp) body; final Wrapper defaultWrapper = s.wrapper; b.startModifiers(); b.startRepeat(); b.startChoice(); // modifiers block contains only let instructions for (final Object sso : s.modifiers) { final SyntaxStatement ss = (SyntaxStatement) sso; if (ss instanceof Let) { final Let let = (Let) ss; final ModifierOp m = (ModifierOp) let.expression; Wrapper w = m.wrapper; w = w == null ? defaultWrapper : w; final boolean isList = "+=".equals(let.operator); b.startProperty(new PropertyName(let.name), isList); b.startWrapper(w); b.tokenText(Terms.VALUE, SyntaxRole.MODIFIER, m.value); b.endWrapper(w); b.endProperty(); } else if (ss instanceof BlankSyntaxStatement) { // do nothing, blank statements are just ignored } else { error(b, ss, "grammar.Modifiers.invalidStatement"); } } b.endChoice(); b.endRepeat(); b.endModifiers(); } else if (body instanceof ListOp) { final ListOp s = (ListOp) body; b.startSequence(); compileSyntax(visited, b, s.syntax); b.startRepeat(); final String sep = s.separator == null ? "," : s.separator; b.tokenText(Terms.STRUCTURAL, SyntaxRole.SEPARATOR, sep); compileSyntax(visited, b, s.syntax); b.endRepeat(); b.endSequence(); } else if (body instanceof OptionalOp) { final OptionalOp s = (OptionalOp) body; b.startChoice(); compileSyntax(visited, b, s.syntax); b.startSequence(); b.endSequence(); b.endChoice(); } else if (body instanceof ZeroOrMoreOp) { final ZeroOrMoreOp s = (ZeroOrMoreOp) body; b.startRepeat(); compileSyntax(visited, b, s.syntax); b.endRepeat(); } else if (body instanceof OneOrMoreOp) { final OneOrMoreOp s = (OneOrMoreOp) body; b.startSequence(); compileSyntax(visited, b, s.syntax); b.startRepeat(); compileSyntax(visited, b, s.syntax); b.endRepeat(); b.endSequence(); } else if (body instanceof FirstChoiceOp) { final FirstChoiceOp s = (FirstChoiceOp) body; b.startFirstChoice(); compileSyntax(visited, b, s.first); compileSyntax(visited, b, s.second); FirstChoiceNode n = b.endFirstChoice(); ListIterator<Node> i = n.nodes().listIterator(n.nodes().size() - 1); while (i.hasPrevious()) { Node a = i.previous(); if (a.matchesEmpty()) { error(b, s.first, "grammar.Modifiers.firstChoiceEmptyFirst"); } } } else if (body instanceof ChoiceOp) { final ChoiceOp s = (ChoiceOp) body; b.startChoice(); for (final Object option_o : s.options) { final Syntax option = (Syntax) option_o; compileSyntax(visited, b, option); } b.endChoice(); } else if (body instanceof IdentifierOp) { final IdentifierOp s = (IdentifierOp) body; b.startWrapper(s.wrapper); b.tokenText(Terms.VALUE, SyntaxRole.PRIMARY, TokenKey .simple(Tokens.IDENTIFIER)); b.endWrapper(s.wrapper); } else if (body instanceof GraphicsOp) { final GraphicsOp s = (GraphicsOp) body; b.startWrapper(s.wrapper); b.tokenText(Terms.VALUE, SyntaxRole.PRIMARY, TokenKey .simple(Tokens.GRAPHICS)); b.endWrapper(s.wrapper); } else if (body instanceof TokenOp) { final TokenOp s = (TokenOp) body; b.startWrapper(s.wrapper); if (s.value != null) { b.tokenText(Terms.VALUE, SyntaxRole.KEYWORD, s.value); } else { b.anyToken(Terms.VALUE, SyntaxRole.PRIMARY); } b.endWrapper(s.wrapper); } else if (body instanceof StringOp) { final StringOp s = (StringOp) body; String quote = null; try { quote = LiteralUtils.parseString(s.quote); } catch (final Exception ex) { // do nothing, quote will stay as null } if (!"\"".equals(quote) && !"'".equals(quote)) { error(b, s, "grammar.String.invalidQuote", s.quote); } if (quote != null) { switch (s.prefix.size()) { case 0: compileString(b, s, quote, null); break; case 1: compileString(b, s, quote, s.prefix.getFirst()); break; default: b.startChoice(); for (String prefix : s.prefix) { compileString(b, s, quote, prefix); } b.endChoice(); break; } } } else if (body instanceof IntegerOp) { compileNumber(b, (IntegerOp) body, Tokens.INTEGER, Tokens.INTEGER_WITH_SUFFIX); } else if (body instanceof FloatOp) { compileNumber(b, (FloatOp) body, Tokens.FLOAT, Tokens.FLOAT_WITH_SUFFIX); } else if (body instanceof ObjectOp) { final ObjectOp s = (ObjectOp) body; b.startObject(b.topDefinition().convertName(s.name)); compileSyntax(visited, b, s.syntax); b.endObject(); } else if (body instanceof RefOp) { final RefOp s = (RefOp) body; final DefView d = contextView.def(b.topDefinition(), s); if (d != null) { if (visited.contains(d)) { error(b, body, "grammar.Ref.cyclicRef", s.name, contextView .name(), contextView.grammar().getSystemId()); } else { visited.add(d); b.startDefinition(d); b.startSequence(); compileSyntax(visited, b, d.statements()); b.endSequence(); visited.remove(d); b.endDefinition(); } } } else if (body instanceof DoclinesOp) { final DoclinesOp s = (DoclinesOp) body; b.startWrapper(s.wrapper); b.tokenText(Terms.VALUE, SyntaxRole.DOCUMENTATION, TokenKey .simple(Tokens.DOC_COMMENT)); b.endWrapper(s.wrapper); b.startRepeat(); b.startWrapper(s.wrapper); b.tokenText(Terms.VALUE, SyntaxRole.DOCUMENTATION, TokenKey .simple(Tokens.DOC_COMMENT)); b.endWrapper(s.wrapper); b.endRepeat(); } else if (body instanceof OperandOp) { error(b, body, "grammar.Operand.misplacedOperand"); } else if (body instanceof Sequence) { final Sequence s = (Sequence) body; b.startSequence(); compileSyntax(visited, b, s.syntax); b.endSequence(); } else { throw new RuntimeException("[BUG]Unknown syntax element: " + body); } } /** * Compile string for the specified prefix * * @param b * the builder to use * @param s * the operator to compile * @param quote * the quote for string * @param prefix * the prefix instance */ private void compileString(StateMachineBuilder b, final StringOp s, String quote, String prefix) { if ("true".equals(s.multiline)) { b.startChoice(); compileString(b, s, quote, prefix, TokenPartKind.WHOLE, true); b.startSequence(); compileString(b, s, quote, prefix, TokenPartKind.START, true); b.startRepeat(); compileString(b, s, quote, prefix, TokenPartKind.PART, true); b.endRepeat(); compileString(b, s, quote, prefix, TokenPartKind.END, true); b.endSequence(); b.endChoice(); } else { compileString(b, s, quote, prefix, TokenPartKind.WHOLE, false); } } /** * Compile string token * * @param b * the builder * @param s * the operator to compile * @param quote * the quote to use * @param prefix * the prefix to use * @param partKind * the part kind * @param multiline * the flag indicating if the string is mulitiline */ private void compileString(StateMachineBuilder b, final StringOp s, String quote, String prefix, TokenPartKind partKind, boolean multiline) { b.startWrapper(s.wrapper); Tokens kind; Terms termKind; switch (partKind) { case WHOLE: if (multiline) { kind = prefix != null ? Tokens.PREFIXED_MULTILINE_STRING : Tokens.MULTILINE_STRING; } else { kind = prefix != null ? Tokens.PREFIXED_STRING : Tokens.STRING; } termKind = Terms.VALUE; break; case START: kind = prefix != null ? Tokens.PREFIXED_MULTILINE_STRING_START : Tokens.MULTILINE_STRING_START; termKind = Terms.VALUE_START; break; case PART: kind = prefix != null ? Tokens.PREFIXED_MULTILINE_STRING_PART : Tokens.MULTILINE_STRING_PART; termKind = Terms.VALUE_PART; break; case END: kind = prefix != null ? Tokens.PREFIXED_MULTILINE_STRING_END : Tokens.MULTILINE_STRING_END; termKind = Terms.VALUE_END; break; default: throw new IllegalStateException("Invalid part kind: " + partKind); } final TokenKey key = TokenKey.string(kind, prefix, quote, quote); b.tokenText(termKind, SyntaxRole.PRIMARY, key); b.endWrapper(s.wrapper); } /** * Compile number operation * * @param b * a builder for state machine * @param s * an operator to compile * @param simpleKind * a kind for simple token * @param suffixKind * a kind for suffixed token */ private void compileNumber(StateMachineBuilder b, final NumberOp s, Tokens simpleKind, Tokens suffixKind) { if (s.suffix.size() == 0) { b.startWrapper(s.wrapper); b.tokenText(Terms.VALUE, SyntaxRole.PRIMARY, TokenKey .simple(simpleKind)); b.endWrapper(s.wrapper); } else if (s.suffix.size() == 1) { compileNumberWithSuffix(b, s, suffixKind, s.suffix.getFirst()); } else { b.startChoice(); for (String suffix : s.suffix) { compileNumberWithSuffix(b, s, suffixKind, suffix); } b.endChoice(); } } /** * @param b * @param s * @param suffixKind * @param suffix */ private void compileNumberWithSuffix(StateMachineBuilder b, final NumberOp s, Tokens suffixKind, String suffix) { char ch = suffix.charAt(0); if (ch == 'E' || ch == 'e' || ch == '_') { error(b, s, "grammar.NumberOp.invalidSuffix", s.suffix); } b.startWrapper(s.wrapper); b.tokenText(Terms.VALUE, SyntaxRole.PRIMARY, TokenKey.number( suffixKind, suffix)); b.endWrapper(s.wrapper); } /** * Report error * * @param b * a builder * @param e * a element that contains position of the statement * @param errorId * identifier of the error */ public void error(StateMachineBuilder b, Element e, String errorId) { error(b.topDefinition(), e, errorId); } /** * Report error * * @param d * a context definition * @param e * a element that contains position of the statement * @param errorId * identifier of the error */ public void error(DefinitionView d, Element e, String errorId) { final ContextView ctx = d.originalDefinition().definingContext(); ctx.error(e, errorId); } /** * Report error * * @param b * a builder * @param e * a element that contains position of the statement * @param errorId * identifier of the error * @param errorArg * error argument */ public void error(StateMachineBuilder b, Element e, String errorId, Object errorArg) { final DefinitionView d = b.topDefinition(); error(d, e, errorId, errorArg); } /** * Report error * * @param d * a context definition * @param e * a element that contains position of the statement * @param errorId * identifier of the error * @param errorArg * error argument */ public void error(DefinitionView d, Element e, String errorId, Object errorArg) { final ContextView ctx = d.originalDefinition().definingContext(); ctx.error(e, errorId, errorArg); } /** * Report error * * @param b * a builder * @param e * a element that contains position of the statement * @param errorId * identifier of the error * @param errorArg1 * error argument * @param errorArg2 * error argument */ public void error(StateMachineBuilder b, Element e, String errorId, Object errorArg1, Object errorArg2) { final ContextView ctx = b.topDefinition().originalDefinition() .definingContext(); ctx.error(e, errorId, errorArg1, errorArg2); } /** * Report error * * @param b * a builder * @param e * a element that contains position of the statement * @param errorId * identifier of the error * @param errorArg1 * error argument * @param errorArg2 * error argument * @param errorArg3 * error argument */ public void error(StateMachineBuilder b, Element e, String errorId, Object errorArg1, Object errorArg2, Object errorArg3) { final DefinitionView d = b.topDefinition(); error(d, e, errorId, errorArg1, errorArg2, errorArg3); } /** * Report error * * @param d * a context definition * @param e * a element that contains position of the statement * @param errorId * identifier of the error * @param errorArg1 * error argument * @param errorArg2 * error argument * @param errorArg3 * error argument */ public void error(DefinitionView d, Element e, String errorId, Object errorArg1, Object errorArg2, Object errorArg3) { final ContextView ctx = d.originalDefinition().definingContext(); ctx.error(e, errorId, errorArg1, errorArg2, errorArg3); } /** * Report error * * @param d * a context definition * @param e * a element that contains position of the statement * @param errorId * identifier of the error * @param errorArg1 * error argument * @param errorArg2 * error argument */ public void error(DefinitionView d, Element e, String errorId, Object errorArg1, Object errorArg2) { final ContextView ctx = d.originalDefinition().definingContext(); ctx.error(e, errorId, errorArg1, errorArg2); } /** * Get expression activation for the specified context import * * @param contextDefinition * a definition that contains expression elements * @param e * an element that makes a reference. It is used for error * reporting. * @param context * a context import name that have to be resolved * @param precedence * a precedence of expression * @return an activation factory that correspond to expression. Note that * for references to other grammars a proxy activation factory is * returned. */ private StateMachineBuilder getExpressionActivation( DefinitionView contextDefinition, Element e, String context, Integer precedence) { final ContextBuilder referencedContext = getReferencedContext( contextDefinition, e, context); if (referencedContext == null) { // if context builder is not found, than it is dangling reference // that has been reported in method above return null; } // get allowed levels according to precedence final SortedMap<Integer, OperatorLevelBuilder> allowedLevels; if (precedence == null) { allowedLevels = referencedContext.operatorLevels; } else { // check if precedence value is valid. if (precedence.intValue() < 0) { error(contextDefinition, e, "grammar.Expression.invalidPrecedence", precedence); return null; } // note that precedence+1 is used because headMap does not include // the key used to create it. allowedLevels = referencedContext.operatorLevels .headMap(new Integer(precedence.intValue() + 1)); } if (referencedContext.operatorLevels.isEmpty()) { // this means that there are no expressions in the referenced // context or expression definition has serious errors (for example // no primary level). If it were errors, the compiler will not get // here. error(contextDefinition, e, "grammar.Expression.noVaildExpression", referencedContext.name(), contextView.name(), contextView .grammar().getSystemId()); return null; } // now allowed levels are got and we just extract last production return referencedContext.operatorLevels.get(allowedLevels.lastKey()) .builder(); } /** * Get activation for statement sequence. * * @param contextDefinition * a definition that contains block elements * @param e * a block element * @param context * a context import for activation * @return an activation from this grammar or activation proxy. */ private StateMachineBuilder getStatementSequenceActivation( DefinitionView contextDefinition, Element e, String context) { final ContextBuilder referencedContext = getReferencedContext( contextDefinition, e, context); if (referencedContext == null) { // if context builder is not found, than it is dangling reference // that has been reported in method above return null; } return referencedContext.statementSequenceBuilder; } /** * Get referenced context * * @param contextDefinition * a definition that contains element * @param e * a an element that references other context * @param context * a context import name * @return a context builder for specified parameters */ private ContextBuilder getReferencedContext( DefinitionView contextDefinition, Element e, String context) { // if context is not specified, it is this context if (context == null) { return this; } final ContextView referencedContext; // otherwise locate context import that correspond to activation final ContextImportView ci = contextView.contextImport(context); if (ci == null) { referencedContext = grammarBuilder().grammarView().context(context); if (referencedContext == null) { error(contextDefinition, e, "grammar.ContextOp.invalidImportName", context, contextView.name(), contextView.grammar().getSystemId()); return null; } } else { assert ci.referencedContext() != null; referencedContext = ci.referencedContext(); } final ContextBuilder rc = grammarBuilder .contextBuilder(referencedContext); assert rc != null; return rc; } /** * Compile syntax statement. * * @param visited * a set of visited "def"-s * @param b * a state machine builder used by compiler * @param statement * a statement to compile */ private void compileSyntaxStatement(HashSet<DefView> visited, StateMachineBuilder b, SyntaxStatement statement) { if (statement instanceof BlankSyntaxStatement) { // compile empty sequence. This sequence is to be optimized out. b.startSequence(); b.endSequence(); } else if (statement instanceof Let) { final Let s = (Let) statement; if (s.expression instanceof OperandOp) { if (!(s.ownerObject instanceof ObjectOp) || !(s.ownerObject.ownerObject instanceof OperatorDefinition)) { error(b, s.expression, "grammar.Operand.misplacedOperand"); } else { // operand statement is placed correctly, ignore the // statement } } else { final String op = s.operator; final boolean isList = "+=".equals(op); if (isList || "=".equals(op)) { // simple property b.startProperty(new PropertyName(s.name), isList); compileSyntax(visited, b, s.expression); b.endProperty(); } else { throw new RuntimeException("[BUG]Unknown operator: " + op); } } } else if (statement instanceof KeywordStatement) { final KeywordStatement s = (KeywordStatement) statement; b.tokenText(Terms.STRUCTURAL, SyntaxRole.KEYWORD, s.text); } else if (statement instanceof ExpressionStatement) { final ExpressionStatement s = (ExpressionStatement) statement; b.startSequence(); compileSyntax(visited, b, s.syntax); b.endSequence(); } } /** * Compile a list of syntax constructs (statements or syntaxes). * * @param visited * a set of visited definition nodes. It is used to detect * definition cycles. * @param b * a builder to use * @param list * a list of statements to compile */ void compileSyntax(HashSet<DefView> visited, StateMachineBuilder b, List<?> list) { for (final Object o : list) { if (o instanceof Syntax) { final Syntax s = (Syntax) o; compileSyntax(visited, b, s); } else if (o instanceof SyntaxStatement) { final SyntaxStatement s = (SyntaxStatement) o; compileSyntaxStatement(visited, b, s); } } } /** * @return a term context for this node */ public TermContext termContext() { return contextName; } /** * Get level builder by precedence * * @param precedence * a precedence * @return a level builder */ public OperatorLevelBuilder levelBuilder(int precedence) { return operatorLevels.get(new Integer(precedence)); } /** * @return grammar builder for this context builder */ public GrammarBuilder grammarBuilder() { return grammarBuilder; } /** * Token part kind */ enum TokenPartKind { /** the whole token */ WHOLE, /** the start of the token */ START, /** the middle part of the token */ PART, /** the end of the token */ END, } }