/*
* 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,
}
}