| /******************************************************************************* |
| * Copyright (c) 2015, 2021 Obeo. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Obeo - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.acceleo.query.parser; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EmptyStackException; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import org.antlr.v4.runtime.ANTLRErrorListener; |
| import org.antlr.v4.runtime.BaseErrorListener; |
| import org.antlr.v4.runtime.ParserRuleContext; |
| import org.antlr.v4.runtime.RecognitionException; |
| import org.antlr.v4.runtime.Recognizer; |
| import org.antlr.v4.runtime.Token; |
| import org.antlr.v4.runtime.tree.ErrorNode; |
| import org.antlr.v4.runtime.tree.TerminalNode; |
| import org.eclipse.acceleo.query.ast.And; |
| import org.eclipse.acceleo.query.ast.Binding; |
| import org.eclipse.acceleo.query.ast.BooleanLiteral; |
| import org.eclipse.acceleo.query.ast.Call; |
| import org.eclipse.acceleo.query.ast.CallType; |
| import org.eclipse.acceleo.query.ast.CollectionTypeLiteral; |
| import org.eclipse.acceleo.query.ast.Conditional; |
| import org.eclipse.acceleo.query.ast.EnumLiteral; |
| import org.eclipse.acceleo.query.ast.Error; |
| import org.eclipse.acceleo.query.ast.ErrorBinding; |
| import org.eclipse.acceleo.query.ast.ErrorCall; |
| import org.eclipse.acceleo.query.ast.ErrorConditional; |
| import org.eclipse.acceleo.query.ast.ErrorEClassifierTypeLiteral; |
| import org.eclipse.acceleo.query.ast.ErrorEnumLiteral; |
| import org.eclipse.acceleo.query.ast.ErrorExpression; |
| import org.eclipse.acceleo.query.ast.ErrorStringLiteral; |
| import org.eclipse.acceleo.query.ast.ErrorTypeLiteral; |
| import org.eclipse.acceleo.query.ast.ErrorVariableDeclaration; |
| import org.eclipse.acceleo.query.ast.Expression; |
| import org.eclipse.acceleo.query.ast.Implies; |
| import org.eclipse.acceleo.query.ast.IntegerLiteral; |
| import org.eclipse.acceleo.query.ast.Lambda; |
| import org.eclipse.acceleo.query.ast.Let; |
| import org.eclipse.acceleo.query.ast.Literal; |
| import org.eclipse.acceleo.query.ast.NullLiteral; |
| import org.eclipse.acceleo.query.ast.Or; |
| import org.eclipse.acceleo.query.ast.RealLiteral; |
| import org.eclipse.acceleo.query.ast.SequenceInExtensionLiteral; |
| import org.eclipse.acceleo.query.ast.SetInExtensionLiteral; |
| import org.eclipse.acceleo.query.ast.StringLiteral; |
| import org.eclipse.acceleo.query.ast.TypeLiteral; |
| import org.eclipse.acceleo.query.ast.TypeSetLiteral; |
| import org.eclipse.acceleo.query.ast.VarRef; |
| import org.eclipse.acceleo.query.ast.VariableDeclaration; |
| import org.eclipse.acceleo.query.parser.QueryParser.AddContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.AndContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.BindingContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.BooleanTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.CallExpContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.CallOrApplyContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ClassifierSetTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ClassifierTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ClassifierTypeRuleContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.CollectionCallContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.CompContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ConditionalContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.EnumLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ErrorClassifierTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ErrorEnumLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ErrorStringLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ExplicitSeqLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ExplicitSetLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ExpressionContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.FalseLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.FeatureContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ImpliesContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.IntTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.IntegerLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.IterationCallContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.LetExprContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.LiteralContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.MinContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.MultContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.NavigationSegmentContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.NotContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.NullLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.OrContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ParenContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.RealLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.RealTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.SeqTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.ServiceCallContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.SetTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.StrTypeContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.StringLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.TrueLitContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.TypeLiteralContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.VarRefContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.VariableDefinitionContext; |
| import org.eclipse.acceleo.query.parser.QueryParser.XorContext; |
| import org.eclipse.acceleo.query.runtime.AcceleoQueryEvaluationException; |
| import org.eclipse.acceleo.query.runtime.IQueryBuilderEngine.AstResult; |
| import org.eclipse.acceleo.query.runtime.IReadOnlyQueryEnvironment; |
| import org.eclipse.emf.common.util.BasicDiagnostic; |
| import org.eclipse.emf.common.util.Diagnostic; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EEnumLiteral; |
| import org.eclipse.emf.ecore.EObject; |
| |
| /** |
| * The {@link AstBuilderListener} builds an AST when plugged into the parser. |
| * |
| * @author <a href="mailto:romain.guider@obeo.fr">Romain Guider</a> |
| */ |
| public class AstBuilderListener extends QueryBaseListener { |
| |
| /** |
| * The plugin ID. |
| */ |
| public static final String PLUGIN_ID = "org.eclipse.acceleo.query"; |
| |
| /** |
| * Feature access service name. |
| */ |
| public static final String FEATURE_ACCESS_SERVICE_NAME = "aqlFeatureAccess"; |
| |
| /** |
| * OCL is kind of service name. |
| */ |
| public static final String OCL_IS_KIND_OF_SERVICE_NAME = "oclIsKindOf"; |
| |
| /** |
| * OCL is type of service name. |
| */ |
| public static final String OCL_IS_TYPE_OF_SERVICE_NAME = "oclIsTypeOf"; |
| |
| /** |
| * <code>if<code> operator. |
| */ |
| public static final String CONDITIONAL_OPERATOR = "if"; |
| |
| /** |
| * <code>not<code> service name. |
| */ |
| public static final String NOT_SERVICE_NAME = "not"; |
| |
| /** |
| * <code>not<code> operator. |
| */ |
| public static final String NOT_OPERATOR = "not"; |
| |
| /** |
| * <code>let<code> operator. |
| */ |
| public static final String LET_OPERATOR = "let"; |
| |
| /** |
| * <code><><code> service name. |
| */ |
| public static final String DIFFERS_SERVICE_NAME = "differs"; |
| |
| /** |
| * <code><><code> operator. |
| */ |
| public static final String DIFFERS_OPERATOR = "<>"; |
| |
| /** |
| * <code>!=<code> operator. |
| */ |
| public static final String DIFFERS_JAVA_OPERATOR = "!="; |
| |
| /** |
| * <code>=<code> service name. |
| */ |
| public static final String EQUALS_SERVICE_NAME = "equals"; |
| |
| /** |
| * <code>=<code> operator. |
| */ |
| public static final String EQUALS_OPERATOR = "="; |
| |
| /** |
| * <code>==<code> operator. |
| */ |
| public static final String EQUALS_JAVA_OPERATOR = "=="; |
| |
| /** |
| * <code>>=<code> service name. |
| */ |
| public static final String GREATER_THAN_EQUAL_SERVICE_NAME = "greaterThanEqual"; |
| |
| /** |
| * <code>>=<code> operator. |
| */ |
| public static final String GREATER_THAN_EQUAL_OPERATOR = ">="; |
| |
| /** |
| * <code>><code> service name. |
| */ |
| public static final String GREATER_THAN_SERVICE_NAME = "greaterThan"; |
| |
| /** |
| * <code>><code> operator. |
| */ |
| public static final String GREATER_THAN_OPERATOR = ">"; |
| |
| /** |
| * <code><=<code> service name. |
| */ |
| public static final String LESS_THAN_EQUAL_SERVICE_NAME = "lessThanEqual"; |
| |
| /** |
| * <code><=<code> operator. |
| */ |
| public static final String LESS_THAN_EQUAL_OPERATOR = "<="; |
| |
| /** |
| * <code><<code> service name. |
| */ |
| public static final String LESS_THAN_SERVICE_NAME = "lessThan"; |
| |
| /** |
| * <code><<code> operator. |
| */ |
| public static final String LESS_THAN_OPERATOR = "<"; |
| |
| /** |
| * <code>/<code> service name. |
| */ |
| public static final String DIV_SERVICE_NAME = "divOp"; |
| |
| /** |
| * <code>/<code> operator. |
| */ |
| public static final String DIV_OPERATOR = "/"; |
| |
| /** |
| * <code>*<code> service name. |
| */ |
| public static final String MULT_SERVICE_NAME = "mult"; |
| |
| /** |
| * <code>*<code> operator. |
| */ |
| public static final String MULT_OPERATOR = "*"; |
| |
| /** |
| * <code>-<code> service name. |
| */ |
| public static final String SUB_SERVICE_NAME = "sub"; |
| |
| /** |
| * <code>-<code> operator. |
| */ |
| public static final String SUB_OPERATOR = "-"; |
| |
| /** |
| * <code>+<code> service name. |
| */ |
| public static final String ADD_SERVICE_NAME = "add"; |
| |
| /** |
| * <code>+<code> operator. |
| */ |
| public static final String ADD_OPERATOR = "+"; |
| |
| /** |
| * <code>-<code> service name. |
| */ |
| public static final String UNARY_MIN_SERVICE_NAME = "unaryMin"; |
| |
| /** |
| * <code>-<code> operator. |
| */ |
| public static final String UNARY_MIN_OPERATOR = "-"; |
| |
| /** |
| * <code>and<code> service name. |
| */ |
| public static final String AND_SERVICE_NAME = "and"; |
| |
| /** |
| * <code>and<code> operator. |
| */ |
| public static final String AND_OPERATOR = "and"; |
| |
| /** |
| * <code>or<code> service name. |
| */ |
| public static final String OR_SERVICE_NAME = "or"; |
| |
| /** |
| * <code>or<code> operator. |
| */ |
| public static final String OR_OPERATOR = "or"; |
| |
| /** |
| * <code>xor<code> service name. |
| */ |
| public static final String XOR_SERVICE_NAME = "xor"; |
| |
| /** |
| * <code>xor<code> operator. |
| */ |
| public static final String XOR_OPERATOR = "xor"; |
| |
| /** |
| * <code>implies<code> service name. |
| */ |
| public static final String IMPLIES_SERVICE_NAME = "implies"; |
| |
| /** |
| * <code>implies<code> operator. |
| */ |
| public static final String IMPLIES_OPERATOR = "implies"; |
| |
| /** |
| * {@link Set} of operator service names. |
| */ |
| public static final Set<String> OPERATOR_SERVICE_NAMES = initOperatorServiceNames(); |
| |
| /** |
| * This should not happen. |
| */ |
| private static final String THIS_SHOULDN_T_HAPPEN = "This shouldn't happen."; |
| |
| /** |
| * Invalid enum literal message. |
| */ |
| private static final String INVALID_ENUM_LITERAL = "invalid enum literal: %s"; |
| |
| /** |
| * Invalid type literal. |
| */ |
| private static final String INVALID_TYPE_LITERAL = "invalid type literal %s"; |
| |
| /** |
| * Ambiguous {@link EEnumLiteral} message. |
| */ |
| private static final String AMBIGUOUS_ENUM_LITERAL = "several enumliterals are matching the literal name: %s, eenum : %s and package name : %s"; |
| |
| /** |
| * Ambiguous {@link EClassifier} message. |
| */ |
| private static final String AMBIGUOUS_TYPE_LITERAL = "several types are matching the EClassifier name: %s , package name : %s"; |
| |
| /** |
| * Number of children in {@link ConditionalContext}. |
| */ |
| private static final int CONDITIONAL_CONTEXT_CHILD_COUNT = 7; |
| |
| /** |
| * Error listener. |
| * |
| * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a> |
| */ |
| private final class QueryErrorListener extends BaseErrorListener { |
| |
| /** |
| * Missing expression message. |
| */ |
| private static final String MISSING_EXPRESSION = "missing expression"; |
| |
| @Override |
| public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, |
| int charPositionInLine, String msg, RecognitionException e) { |
| if (e != null) { |
| if (e.getCtx() instanceof IterationCallContext) { |
| iterationCallContextError(e); |
| } else if (e.getCtx() instanceof TypeLiteralContext) { |
| typeLiteralContextError(offendingSymbol, msg, e); |
| } else if (e.getCtx() instanceof LiteralContext) { |
| literalContextError(offendingSymbol, msg, e); |
| } else if (e.getCtx() instanceof ClassifierTypeRuleContext) { |
| classifierTypeRuleContextError(offendingSymbol, msg, e); |
| } else if (e.getCtx() instanceof VariableDefinitionContext) { |
| variableDefinitionContextError(offendingSymbol, e); |
| } else if (e.getCtx() instanceof CallExpContext) { |
| callExpContextError(offendingSymbol, e); |
| } else if (e.getCtx() instanceof NavigationSegmentContext) { |
| navigationSegmentContextError(offendingSymbol); |
| } else if (e.getCtx() instanceof BindingContext) { |
| bindingContextError(offendingSymbol, e); |
| } else if (e.getCtx() instanceof ConditionalContext) { |
| errorRule = QueryParser.RULE_expression; |
| } else if (e.getCtx() instanceof ParenContext) { |
| // nothing to do here |
| } else { |
| defaultError(offendingSymbol, msg, e); |
| } |
| } else if (recognizer instanceof QueryParser) { |
| noRecognitionException(recognizer, offendingSymbol, msg); |
| } else { |
| final QueryParser parser = (QueryParser)recognizer; |
| final Integer startPosition = Integer.valueOf(((EnumLitContext)parser.getContext()).start |
| .getStartIndex()); |
| final Integer endPosition = Integer.valueOf(((Token)offendingSymbol).getStopIndex() + 1); |
| diagnosticStack.push(new BasicDiagnostic(Diagnostic.WARNING, PLUGIN_ID, 0, msg, new Object[] { |
| startPosition, endPosition, })); |
| } |
| } |
| |
| /** |
| * {@link ClassifierTypeRuleContext} error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| * @param msg |
| * the error message |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void classifierTypeRuleContextError(Object offendingSymbol, String msg, |
| RecognitionException e) { |
| final ClassifierTypeRuleContext ctx = (ClassifierTypeRuleContext)e.getCtx(); |
| if (e.getCtx().getParent().getParent() instanceof VariableDefinitionContext) { |
| errorRule = QueryParser.RULE_expression; |
| final String variableName = e.getCtx().getParent().getChild(0).getText(); |
| final ErrorTypeLiteral errorTypeLiteral; |
| if (ctx.getChildCount() > 0) { |
| errorTypeLiteral = builder.errorEClassifierTypeLiteral(false, new String[] {ctx.getChild( |
| 0).getText(), }); |
| } else { |
| errorTypeLiteral = builder.errorEClassifierTypeLiteral(false, new String[] {}); |
| } |
| setPositions(errorTypeLiteral, ctx.start, (Token)offendingSymbol); |
| diagnostics.add(new BasicDiagnostic(Diagnostic.ERROR, PLUGIN_ID, 0, String.format( |
| INVALID_TYPE_LITERAL, ctx.getText()), new Object[] {errorTypeLiteral })); |
| errors.add(errorTypeLiteral); |
| final Expression variableExpression = popExpression(); |
| final VariableDeclaration variableDeclaration = builder.variableDeclaration(variableName, |
| errorTypeLiteral, variableExpression); |
| setPositions(variableDeclaration, ctx.start, (Token)offendingSymbol); |
| push(variableDeclaration); |
| final ErrorExpression errorExpression = builder.errorExpression(); |
| pushError(errorExpression, MISSING_EXPRESSION); |
| setPositions(errorExpression, (Token)offendingSymbol, (Token)offendingSymbol); |
| } else { |
| errorRule = QueryParser.RULE_classifierTypeRule; |
| final ErrorTypeLiteral errorTypeLiteral; |
| if (ctx.getChildCount() > 0) { |
| errorTypeLiteral = builder.errorEClassifierTypeLiteral(false, new String[] {ctx.getChild( |
| 0).getText(), }); |
| } else { |
| errorTypeLiteral = builder.errorEClassifierTypeLiteral(false, new String[] {}); |
| } |
| setPositions(errorTypeLiteral, ctx.start, (Token)offendingSymbol); |
| pushError(errorTypeLiteral, "missing classifier literal"); |
| } |
| } |
| |
| /** |
| * {@link IterationCallContext} error case. |
| * |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void iterationCallContextError(RecognitionException e) { |
| if (e.getCtx().getChildCount() == 0) { |
| errorRule = QueryParser.RULE_expression; |
| final ErrorExpression errorExpression = builder.errorExpression(); |
| pushError(errorExpression, MISSING_EXPRESSION); |
| final int position = ((IterationCallContext)e.getCtx()).start.getStartIndex(); |
| final int line = ((IterationCallContext)e.getCtx()).start.getLine() - 1; |
| final int column = ((IterationCallContext)e.getCtx()).start.getCharPositionInLine(); |
| setPositions(errorExpression, position, line, column); |
| } |
| } |
| |
| /** |
| * {@link TypeLiteralContext} error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| * @param msg |
| * the error message |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void typeLiteralContextError(Object offendingSymbol, String msg, RecognitionException e) { |
| if (e.getCtx().getParent() instanceof VariableDefinitionContext) { |
| errorRule = QueryParser.RULE_expression; |
| final String variableName = e.getCtx().getParent().getChild(0).getText(); |
| final ErrorTypeLiteral type = builder.errorTypeLiteral(false, new String[] {}); |
| setPositions(type, ((TypeLiteralContext)e.getCtx()).start, (Token)offendingSymbol); |
| diagnostics.add(new BasicDiagnostic(Diagnostic.ERROR, PLUGIN_ID, 0, String.format( |
| INVALID_TYPE_LITERAL, msg), new Object[] {type })); |
| errors.add(type); |
| final Expression variableExpression = popExpression(); |
| final VariableDeclaration variableDeclaration = builder.variableDeclaration(variableName, |
| type, variableExpression); |
| setPositions(variableDeclaration, ((TypeLiteralContext)e.getCtx()).start, |
| (Token)offendingSymbol); |
| push(variableDeclaration); |
| final ErrorExpression errorExpression = builder.errorExpression(); |
| pushError(errorExpression, MISSING_EXPRESSION); |
| setPositions(errorExpression, ((TypeLiteralContext)e.getCtx()).start, (Token)offendingSymbol); |
| } else if (stack.isEmpty() || !(stack.peek() instanceof TypeLiteral)) { |
| errorRule = QueryParser.RULE_typeLiteral; |
| final ErrorTypeLiteral errorTypeLiteral = builder.errorTypeLiteral(false, new String[] {}); |
| setPositions(errorTypeLiteral, ((TypeLiteralContext)e.getCtx()).start, |
| (Token)offendingSymbol); |
| pushError(errorTypeLiteral, String.format(INVALID_TYPE_LITERAL, msg)); |
| } else { |
| diagnosticStack.push(new BasicDiagnostic(Diagnostic.WARNING, PLUGIN_ID, 0, msg, new Object[] { |
| ((TypeLiteralContext)e.getCtx()).start.getStartIndex(), ((Token)offendingSymbol) |
| .getStopIndex() + 1, })); |
| } |
| } |
| |
| /** |
| * {@link LiteralContext} error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| * @param msg |
| * the error message |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void literalContextError(Object offendingSymbol, String msg, RecognitionException e) { |
| final LiteralContext ctx = (LiteralContext)e.getCtx(); |
| final Token start = ctx.start; |
| final Token end = (Token)offendingSymbol; |
| final String ePackage = ctx.getParent().getStart().getText(); |
| errorRule = QueryParser.RULE_typeLiteral; |
| if (ctx.getChildCount() == 4) { |
| final String eEnumName = ctx.getChild(2).getText(); |
| final ErrorEnumLiteral errorEnumLiteral = builder.errorEnumLiteral(false, ePackage, |
| eEnumName); |
| setPositions(errorEnumLiteral, start, end); |
| pushError(errorEnumLiteral, String.format(INVALID_ENUM_LITERAL, msg)); |
| } else { |
| final ErrorTypeLiteral errorTypeLiteral = builder.errorTypeLiteral(false, new String[] { |
| ePackage, }); |
| setPositions(errorTypeLiteral, start, end); |
| pushError(errorTypeLiteral, String.format(INVALID_TYPE_LITERAL, msg)); |
| } |
| } |
| |
| /** |
| * {@link VariableDefinitionContext} error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void variableDefinitionContextError(Object offendingSymbol, RecognitionException e) { |
| if (e.getCtx().getChildCount() > 0) { |
| errorRule = QueryParser.RULE_expression; |
| final String variableName = e.getCtx().getChild(0).getText(); |
| final TypeLiteral type; |
| if (e.getCtx().getChildCount() > 2) { |
| type = popTypeLiteral(); |
| // overwrite the end position to the current error position (the start position is |
| // fine) |
| final int position = ((Token)offendingSymbol).getStopIndex() + 1; |
| final int line = ((Token)offendingSymbol).getLine() - 1; |
| final int column = ((Token)offendingSymbol).getCharPositionInLine() |
| + ((Token)offendingSymbol).getText().length(); |
| positions.setEndPositions(type, position); |
| positions.setEndLines(type, line); |
| positions.setEndColumns(type, column); |
| } else { |
| type = null; |
| } |
| final Expression variableExpression = popExpression(); |
| final ErrorVariableDeclaration errorVariableDeclaration = builder.errorVariableDeclaration( |
| variableName, type, variableExpression); |
| setPositions(errorVariableDeclaration, ((VariableDefinitionContext)e.getCtx()).start, |
| (Token)offendingSymbol); |
| pushError(errorVariableDeclaration, "incomplete variable definition"); |
| } else { |
| final Expression variableExpression = popExpression(); |
| errorRule = QueryParser.RULE_variableDefinition; |
| final ErrorVariableDeclaration errorVariableDeclaration = builder.errorVariableDeclaration( |
| null, null, variableExpression); |
| setPositions(errorVariableDeclaration, ((VariableDefinitionContext)e.getCtx()).start, |
| (Token)offendingSymbol); |
| pushError(errorVariableDeclaration, "missing variable declaration"); |
| } |
| if (((Token)offendingSymbol).getText().isEmpty() || ")".equals(((Token)offendingSymbol) |
| .getText())) { |
| final ErrorExpression errorExpression = builder.errorExpression(); |
| // not missing ')' only missing expression |
| if (((Token)offendingSymbol).getStartIndex() == ((Token)offendingSymbol).getStopIndex()) { |
| // position is indeed before the closing parenthesis |
| final int position = ((Token)offendingSymbol).getStopIndex() /* + 1 - 1 */; |
| final int line = ((Token)offendingSymbol).getLine() - 1; |
| final int column = ((Token)offendingSymbol).getCharPositionInLine() |
| + ((Token)offendingSymbol).getText().length() - 1; |
| setPositions(errorExpression, position, line, column); |
| } else { |
| final int position = ((Token)offendingSymbol).getStopIndex() + 1; |
| final int line = ((Token)offendingSymbol).getLine() - 1; |
| final int column = ((Token)offendingSymbol).getCharPositionInLine() |
| + ((Token)offendingSymbol).getText().length(); |
| setPositions(errorExpression, position, line, column); |
| } |
| pushError(errorExpression, MISSING_EXPRESSION); |
| } |
| } |
| |
| /** |
| * {@link CallExpContext} error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void callExpContextError(Object offendingSymbol, RecognitionException e) { |
| errorRule = QueryParser.RULE_navigationSegment; |
| final String name; |
| if (e.getCtx().getChildCount() > 0) { |
| name = e.getCtx().getChild(0).getText(); |
| } else { |
| name = null; |
| } |
| final Expression receiver; |
| final ErrorCall errorCollectionCall; |
| if (e.getCtx().getChildCount() == 3) { |
| final int argc = getNumberOfArgs(e.getCtx().getChild(2).getChildCount()); |
| final Expression[] args = new Expression[argc]; |
| for (int i = argc - 1; i >= 0; i--) { |
| args[i] = popExpression(); |
| } |
| receiver = args[0]; |
| errorCollectionCall = builder.errorCall(name, false, args); |
| } else { |
| receiver = popExpression(); |
| errorCollectionCall = builder.errorCall(null, false, receiver); |
| } |
| setPositions(errorCollectionCall, receiver, (Token)offendingSymbol); |
| pushError(errorCollectionCall, "missing collection service call"); |
| } |
| |
| /** |
| * {@link NavigationSegmentContext} error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| */ |
| private void navigationSegmentContextError(Object offendingSymbol) { |
| final Expression receiver = popExpression(); |
| final ErrorCall errorCall = builder.errorCall(FEATURE_ACCESS_SERVICE_NAME, false, receiver); |
| errorCall.setType(CallType.CALLORAPPLY); |
| setPositions(errorCall, receiver, (Token)offendingSymbol); |
| pushError(errorCall, "missing feature access or service call"); |
| } |
| |
| /** |
| * {@link BindingContext} error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void bindingContextError(Object offendingSymbol, RecognitionException e) { |
| errorRule = QueryParser.RULE_binding; |
| final String name; |
| final TypeLiteral type; |
| if (e.getCtx().getChildCount() > 0 && !"in".equals(e.getCtx().getChild(0).getText())) { |
| name = e.getCtx().getChild(0).getText(); |
| if (e.getCtx().getChildCount() == 3) { |
| type = popTypeLiteral(); |
| } else { |
| type = null; |
| } |
| } else { |
| name = null; |
| type = null; |
| } |
| final ErrorBinding errorBinding = builder.errorBinding(name, type); |
| final int position = ((Token)offendingSymbol).getStopIndex() + 1; |
| final int line = ((Token)offendingSymbol).getLine() - 1; |
| final int column = ((Token)offendingSymbol).getCharPositionInLine() + ((Token)offendingSymbol) |
| .getText().length(); |
| setPositions(errorBinding, position, line, column); |
| pushError(errorBinding, "invalid variable declaration in let"); |
| } |
| |
| /** |
| * Default error case. |
| * |
| * @param offendingSymbol |
| * the offending symbol |
| * @param msg |
| * the error message |
| * @param e |
| * the {@link RecognitionException} |
| */ |
| private void defaultError(Object offendingSymbol, String msg, RecognitionException e) { |
| if (offendingSymbol == null && e.getCtx() == null) { |
| diagnostics.add(new BasicDiagnostic(Diagnostic.ERROR, PLUGIN_ID, 0, msg, new Object[] {})); |
| } else { |
| switch (e.getCtx().getRuleIndex()) { |
| case QueryParser.RULE_expression: |
| errorRule = QueryParser.RULE_expression; |
| final ErrorExpression errorExpression = builder.errorExpression(); |
| final int position = ((ParserRuleContext)e.getCtx()).start.getStartIndex(); |
| final int line = ((ParserRuleContext)e.getCtx()).start.getLine() - 1; |
| final int column = ((ParserRuleContext)e.getCtx()).start.getCharPositionInLine(); |
| setPositions(errorExpression, position, line, column); |
| pushError(errorExpression, MISSING_EXPRESSION); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Handles parser error when the {@link RecognitionException} is <code>null</code>. |
| * |
| * @param recognizer |
| * the {@link Recognizer} |
| * @param offendingSymbol |
| * the offending symbol |
| * @param msg |
| * the error message |
| */ |
| private void noRecognitionException(Recognizer<?, ?> recognizer, Object offendingSymbol, String msg) { |
| final QueryParser parser = (QueryParser)recognizer; |
| final Integer startPosition = Integer.valueOf(((ParserRuleContext)parser.getContext()).start |
| .getStartIndex()); |
| final Integer endPosition = Integer.valueOf(((Token)offendingSymbol).getStopIndex() + 1); |
| diagnosticStack.push(new BasicDiagnostic(Diagnostic.WARNING, PLUGIN_ID, 0, msg, new Object[] { |
| startPosition, endPosition, })); |
| } |
| } |
| |
| /** |
| * No error rule marker. |
| */ |
| private static final int NO_ERROR = -1; |
| |
| /** |
| * Message logged when an internal error occurs. |
| */ |
| private static final String INTERNAL_ERROR_MSG = "Internal exception occured while evaluating an expression"; |
| |
| /** |
| * The evaluation stack used to hold temporary results. |
| */ |
| private Stack<Object> stack = new Stack<Object>(); |
| |
| /** |
| * The last rule index if any error. see {@link QueryParser}. |
| */ |
| private int errorRule = -1; |
| |
| /** |
| * The parsed {@link Positions}. |
| */ |
| private Positions positions = new Positions(); |
| |
| /** |
| * The {@link List} of {@link Error}. |
| */ |
| private List<Error> errors = new ArrayList<Error>(); |
| |
| /** |
| * Temporary lexing warnings waiting for their {@link Expression}. |
| */ |
| private Stack<Diagnostic> diagnosticStack = new Stack<Diagnostic>(); |
| |
| /** Aggregated status of the parsing. */ |
| private final List<Diagnostic> diagnostics = new ArrayList<Diagnostic>(); |
| |
| /** |
| * The {@link ANTLRErrorListener} pushing {@link org.eclipse.acceleo.query.ast.Error Error}. |
| */ |
| private final ANTLRErrorListener errorListener = new QueryErrorListener(); |
| |
| /** |
| * Ast Builder. |
| */ |
| private final AstBuilder builder = new AstBuilder(); |
| |
| /** |
| * The {@link IReadOnlyQueryEnvironment}. |
| */ |
| private final IReadOnlyQueryEnvironment environment; |
| |
| /** |
| * Creates a new {@link AstBuilderListener}. |
| * |
| * @param environment |
| * the package provider |
| */ |
| public AstBuilderListener(IReadOnlyQueryEnvironment environment) { |
| this.environment = environment; |
| } |
| |
| /** |
| * Gets the {@link Set} of operator service names. |
| * |
| * @return the {@link Set} of operator service names |
| */ |
| private static Set<String> initOperatorServiceNames() { |
| final Set<String> result = new LinkedHashSet<String>(); |
| |
| result.add(ADD_SERVICE_NAME); |
| result.add(AND_SERVICE_NAME); |
| result.add(DIFFERS_SERVICE_NAME); |
| result.add(DIV_SERVICE_NAME); |
| result.add(EQUALS_SERVICE_NAME); |
| result.add(GREATER_THAN_EQUAL_SERVICE_NAME); |
| result.add(GREATER_THAN_SERVICE_NAME); |
| result.add(IMPLIES_SERVICE_NAME); |
| result.add(LESS_THAN_EQUAL_SERVICE_NAME); |
| result.add(LESS_THAN_SERVICE_NAME); |
| result.add(MULT_SERVICE_NAME); |
| result.add(NOT_SERVICE_NAME); |
| result.add(OR_SERVICE_NAME); |
| result.add(SUB_SERVICE_NAME); |
| result.add(UNARY_MIN_SERVICE_NAME); |
| result.add(XOR_SERVICE_NAME); |
| |
| return result; |
| } |
| |
| /** |
| * Sets the positions of the given node. |
| * |
| * @param node |
| * the node |
| * @param position |
| * the position |
| * @param line |
| * the line |
| * @param column |
| * the column |
| */ |
| private void setPositions(EObject node, int position, int line, int column) { |
| positions.setStartPositions(node, position); |
| positions.setStartLines(node, line); |
| positions.setStartColumns(node, column); |
| positions.setEndPositions(node, position); |
| positions.setEndLines(node, line); |
| positions.setEndColumns(node, column); |
| } |
| |
| /** |
| * Sets the positions of the given node. |
| * |
| * @param node |
| * the node |
| * @param start |
| * the start {@link Token} |
| * @param end |
| * the end {@link Token} |
| */ |
| private void setPositions(EObject node, Token start, Token end) { |
| positions.setStartPositions(node, Integer.valueOf(start.getStartIndex())); |
| positions.setStartLines(node, Integer.valueOf(start.getLine() - 1)); |
| positions.setStartColumns(node, Integer.valueOf(start.getCharPositionInLine())); |
| positions.setEndPositions(node, Integer.valueOf(end.getStopIndex() + 1)); |
| positions.setEndLines(node, Integer.valueOf(end.getLine() - 1)); |
| positions.setEndColumns(node, Integer.valueOf(end.getCharPositionInLine() + end.getText().length())); |
| } |
| |
| /** |
| * Sets the positions of the given node. |
| * |
| * @param node |
| * the node |
| * @param expressionStart |
| * the start {@link Expression} |
| * @param expressionEnd |
| * the end {@link Expression} |
| */ |
| private void setPositions(EObject node, Expression expressionStart, Expression expressionEnd) { |
| positions.setStartPositions(node, positions.getStartPositions(expressionStart)); |
| positions.setStartLines(node, positions.getStartLines(expressionStart)); |
| positions.setStartColumns(node, positions.getStartColumns(expressionStart)); |
| positions.setEndPositions(node, positions.getEndPositions(expressionEnd)); |
| positions.setEndLines(node, positions.getEndLines(expressionEnd)); |
| positions.setEndColumns(node, positions.getEndColumns(expressionEnd)); |
| } |
| |
| /** |
| * Sets the node positions. |
| * |
| * @param node |
| * the node |
| * @param expressionStart |
| * the start {@link Expression} |
| * @param end |
| * the end {@link Token} |
| */ |
| private void setPositions(EObject node, Expression expressionStart, Token end) { |
| positions.setStartPositions(node, positions.getStartPositions(expressionStart)); |
| positions.setStartLines(node, positions.getStartLines(expressionStart)); |
| positions.setStartColumns(node, positions.getStartColumns(expressionStart)); |
| positions.setEndPositions(node, Integer.valueOf(end.getStopIndex() + 1)); |
| positions.setEndLines(node, Integer.valueOf(end.getLine() - 1)); |
| positions.setEndColumns(node, Integer.valueOf(end.getCharPositionInLine() + end.getText().length())); |
| } |
| |
| /** |
| * Returns the {@link AstResult}. |
| * |
| * @return the {@link AstResult}. |
| */ |
| public AstResult getAstResult() { |
| final Expression ast = popExpression(); |
| final BasicDiagnostic diagnostic = new BasicDiagnostic(); |
| for (Diagnostic diag : diagnostics) { |
| diagnostic.add(diag); |
| } |
| final Positions resultPositions = positions; |
| positions = new Positions(); |
| final List<Error> resultErrors = errors; |
| errors = new ArrayList<Error>(); |
| return new AstResult(ast, resultPositions, resultErrors, diagnostic); |
| } |
| |
| /** |
| * Pop the top of the stack. |
| * |
| * @return the value on top of the stack |
| */ |
| private Expression popExpression() { |
| try { |
| final Expression expression = (Expression)pop(); |
| |
| if (!diagnosticStack.isEmpty()) { |
| final List<?> data = diagnosticStack.peek().getData(); |
| if (data.get(0).equals(positions.getStartPositions(expression)) && data.get(1).equals( |
| positions.getEndPositions(expression))) { |
| final Diagnostic tmpDiagnostic = diagnosticStack.pop(); |
| diagnostics.add(new BasicDiagnostic(tmpDiagnostic.getSeverity(), tmpDiagnostic |
| .getSource(), tmpDiagnostic.getCode(), tmpDiagnostic.getMessage(), new Object[] { |
| expression })); |
| } |
| } |
| |
| return expression; |
| } catch (EmptyStackException e) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, e); |
| } catch (ClassCastException cce) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, cce); |
| } |
| } |
| |
| /** |
| * Pop the top of the stack and returns it as a type literal. |
| * |
| * @return the value on top of the stack. |
| */ |
| private Binding popBinding() { |
| try { |
| return (Binding)pop(); |
| } catch (EmptyStackException e) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, e); |
| } catch (ClassCastException e2) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, e2); |
| } |
| } |
| |
| /** |
| * Pop the top of the stack and returns it as a {@link VariableDeclaration}. |
| * |
| * @return the value on top of the stack. |
| */ |
| private VariableDeclaration popVariableDeclaration() { |
| try { |
| return (VariableDeclaration)pop(); |
| } catch (EmptyStackException e) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, e); |
| } catch (ClassCastException e2) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, e2); |
| } |
| } |
| |
| /** |
| * Pop the top of the stack and returns it as a binding. |
| * |
| * @return the value on top of the stack. |
| */ |
| private TypeLiteral popTypeLiteral() { |
| try { |
| return (TypeLiteral)pop(); |
| } catch (EmptyStackException e) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, e); |
| } catch (ClassCastException e2) { |
| throw new AcceleoQueryEvaluationException(INTERNAL_ERROR_MSG, e2); |
| } |
| } |
| |
| /** |
| * Peeks the current {@link Call} at the top of the stack. |
| * |
| * @return the current {@link Call} at the top of the stack |
| */ |
| private Call peekCall() { |
| return (Call)stack.peek(); |
| } |
| |
| /** |
| * push an object on the stack. |
| * |
| * @param obj |
| * the pushed object. |
| */ |
| private void push(Object obj) { |
| this.stack.push(obj); |
| } |
| |
| /** |
| * Pops the stack. |
| * |
| * @return the element at the top of the stack if any |
| */ |
| protected Object pop() { |
| final Object element = stack.pop(); |
| return element; |
| } |
| |
| /** |
| * Pushes an {@link Error} on the stack. |
| * |
| * @param error |
| * the {@link Error} to push |
| * @param msg |
| * the error message |
| */ |
| private void pushError(Error error, String msg) { |
| errors.add(error); |
| diagnostics.add(new BasicDiagnostic(Diagnostic.ERROR, PLUGIN_ID, 0, msg, new Object[] {error })); |
| push(error); |
| } |
| |
| /** |
| * Pops the last {@link ErrorExpression}. |
| */ |
| private void popErrorExpression() { |
| if (!stack.isEmpty() && stack.peek() instanceof ErrorExpression) { |
| final ErrorExpression error = (ErrorExpression)pop(); |
| errors.remove(error); |
| positions.remove(error); |
| diagnostics.remove(diagnostics.size() - 1); |
| } |
| } |
| |
| @Override |
| public void exitIntType(IntTypeContext ctx) { |
| final Literal typeLiteral = builder.typeLiteral(java.lang.Integer.class); |
| |
| setPositions(typeLiteral, ctx.start, ctx.stop); |
| |
| push(typeLiteral); |
| } |
| |
| @Override |
| public void exitFalseLit(FalseLitContext ctx) { |
| final BooleanLiteral booleanLiteral = builder.booleanLiteral(false); |
| |
| setPositions(booleanLiteral, ctx.start, ctx.stop); |
| |
| push(booleanLiteral); |
| } |
| |
| @Override |
| public void exitRealType(RealTypeContext ctx) { |
| final Literal realLiteral = builder.typeLiteral(java.lang.Double.class); |
| |
| setPositions(realLiteral, ctx.start, ctx.stop); |
| |
| push(realLiteral); |
| } |
| |
| @Override |
| public void exitTrueLit(TrueLitContext ctx) { |
| final BooleanLiteral booleanLiteral = builder.booleanLiteral(true); |
| |
| setPositions(booleanLiteral, ctx.start, ctx.stop); |
| |
| push(booleanLiteral); |
| } |
| |
| @Override |
| public void exitSeqType(SeqTypeContext ctx) { |
| final TypeLiteral elementType = popTypeLiteral(); |
| final CollectionTypeLiteral collectionTypeLiteral = builder.collectionTypeLiteral(List.class, |
| elementType); |
| |
| setPositions(collectionTypeLiteral, ctx.start, ctx.stop); |
| |
| push(collectionTypeLiteral); |
| } |
| |
| @Override |
| public void exitSetType(SetTypeContext ctx) { |
| final TypeLiteral elementType = popTypeLiteral(); |
| final CollectionTypeLiteral collectionTypeLiteral = builder.collectionTypeLiteral(Set.class, |
| elementType); |
| |
| setPositions(collectionTypeLiteral, ctx.start, ctx.stop); |
| |
| push(collectionTypeLiteral); |
| } |
| |
| @Override |
| public void exitNot(NotContext ctx) { |
| final Call callService = builder.callService(NOT_SERVICE_NAME, popExpression()); |
| |
| setPositions(callService, ctx.start, ctx.stop); |
| |
| push(callService); |
| } |
| |
| @Override |
| public void exitStringLit(StringLitContext ctx) { |
| final String text = ctx.getText(); |
| final StringLiteral stringLiteral = builder.stringLiteral(text.substring(1, text.length() - 1)); |
| |
| setPositions(stringLiteral, ctx.start, ctx.stop); |
| |
| push(stringLiteral); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitErrorStringLit(org.eclipse.acceleo.query.parser.QueryParser.ErrorStringLitContext) |
| */ |
| @Override |
| public void exitErrorStringLit(ErrorStringLitContext ctx) { |
| final String text = ctx.getText(); |
| final ErrorStringLiteral errorStringLiteral = builder.errorStringLiteral(text.substring(1, text |
| .length())); |
| |
| setPositions(errorStringLiteral, ctx.start, ctx.stop); |
| |
| pushError(errorStringLiteral, "String literal is not properly closed by a simple-quote."); |
| } |
| |
| @Override |
| public void exitRealLit(RealLitContext ctx) { |
| final RealLiteral realLiteral = builder.realLiteral(Double.valueOf(ctx.getText())); |
| |
| setPositions(realLiteral, ctx.start, ctx.stop); |
| |
| push(realLiteral); |
| } |
| |
| @Override |
| public void exitStrType(StrTypeContext ctx) { |
| final Literal typeLiteral = builder.typeLiteral(java.lang.String.class); |
| |
| setPositions(typeLiteral, ctx.start, ctx.stop); |
| |
| push(typeLiteral); |
| } |
| |
| @Override |
| public void exitOr(OrContext ctx) { |
| Expression op2 = popExpression(); |
| Expression op1 = popExpression(); |
| final Or callService = builder.callOrService(op1, op2); |
| |
| setPositions(callService, op1, op2); |
| |
| push(callService); |
| } |
| |
| @Override |
| public void exitXor(XorContext ctx) { |
| pushBinary(XOR_SERVICE_NAME, ctx); |
| } |
| |
| @Override |
| public void exitImplies(ImpliesContext ctx) { |
| Expression op2 = popExpression(); |
| Expression op1 = popExpression(); |
| final Implies callService = builder.callImpliesService(op1, op2); |
| |
| setPositions(callService, op1, op2); |
| |
| push(callService); |
| } |
| |
| @Override |
| public void exitBooleanType(BooleanTypeContext ctx) { |
| final Literal typeLiteral = builder.typeLiteral(java.lang.Boolean.class); |
| |
| setPositions(typeLiteral, ctx.start, ctx.stop); |
| |
| push(typeLiteral); |
| } |
| |
| @Override |
| public void exitIntegerLit(IntegerLitContext ctx) { |
| final IntegerLiteral integerLiteral = builder.integerLiteral(Integer.valueOf(ctx.getText())); |
| |
| setPositions(integerLiteral, ctx.start, ctx.stop); |
| |
| push(integerLiteral); |
| } |
| |
| @Override |
| public void exitAnd(AndContext ctx) { |
| Expression op2 = popExpression(); |
| Expression op1 = popExpression(); |
| final And callService = builder.callAndService(op1, op2); |
| |
| setPositions(callService, op1, op2); |
| |
| push(callService); |
| } |
| |
| @Override |
| public void exitVarRef(VarRefContext ctx) { |
| final VarRef varRef = builder.varRef(ctx.getText()); |
| |
| setPositions(varRef, ctx.start, ctx.stop); |
| |
| push(varRef); |
| } |
| |
| @Override |
| public void exitFeature(FeatureContext ctx) { |
| final Expression receiver = popExpression(); |
| final StringLiteral featureName = builder.stringLiteral(ctx.getChild(1).getText()); |
| final Call call = builder.callService(FEATURE_ACCESS_SERVICE_NAME, receiver, featureName); |
| call.setType(CallType.CALLORAPPLY); |
| |
| setPositions(featureName, ctx.stop, ctx.stop); |
| setPositions(call, receiver, featureName); |
| |
| push(call); |
| } |
| |
| /** |
| * Factorization of binary operation evaluation. |
| * |
| * @param service |
| * the called service. |
| * @param ctx |
| * the {@link ParserRuleContext} |
| */ |
| private void pushBinary(String service, ParserRuleContext ctx) { |
| Expression op2 = popExpression(); |
| Expression op1 = popExpression(); |
| final Call callService = builder.callService(service, op1, op2); |
| |
| setPositions(callService, op1, op2); |
| |
| push(callService); |
| } |
| |
| /** |
| * Evaluation stack is as follows : [/.../,receiver, service name (string), START_EXPR_SEQ, expr*]. |
| * |
| * @param ctx |
| * the parsing context |
| */ |
| @Override |
| public void exitServiceCall(ServiceCallContext ctx) { |
| if (errorRule != QueryParser.RULE_navigationSegment) { |
| final int argc = getNumberOfArgs(ctx.getChild(2).getChildCount()); |
| final Expression[] args = new Expression[argc]; |
| for (int i = argc - 1; i >= 0; i--) { |
| args[i] = popExpression(); |
| } |
| final String serviceName = ctx.getChild(0).getText().replace("::", "."); |
| final Call call; |
| if (ctx.getChild(ctx.getChildCount() - 1) instanceof ErrorNode) { |
| call = builder.errorCall(serviceName, true, args); |
| pushError((Error)call, "missing ')'"); |
| } else { |
| call = builder.callService(serviceName, args); |
| push(call); |
| } |
| |
| setPositions(call, args[0], ctx.stop); |
| } |
| } |
| |
| /** |
| * Gets the number of arguments separated by a token given a context child count. |
| * |
| * @param childCount |
| * the context child count |
| * @return the number of arguments separated by a token given a context child count |
| */ |
| private int getNumberOfArgs(int childCount) { |
| final int result; |
| |
| if (childCount == 0) { |
| result = 1; |
| } else { |
| result = 1 + 1 + childCount / 2; |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public void exitMin(MinContext ctx) { |
| final Call callService = builder.callService(UNARY_MIN_SERVICE_NAME, popExpression()); |
| |
| setPositions(callService, ctx.start, ctx.stop); |
| |
| push(callService); |
| } |
| |
| @Override |
| public void exitAdd(AddContext ctx) { |
| final String op = ctx.getChild(1).getText(); |
| |
| if (ADD_OPERATOR.equals(op)) { |
| pushBinary(ADD_SERVICE_NAME, ctx); |
| } else if (SUB_OPERATOR.equals(op)) { |
| pushBinary(SUB_SERVICE_NAME, ctx); |
| } else { |
| throw new AcceleoQueryEvaluationException(THIS_SHOULDN_T_HAPPEN); |
| } |
| } |
| |
| @Override |
| public void exitMult(MultContext ctx) { |
| final String op = ctx.getChild(1).getText(); |
| |
| if (MULT_OPERATOR.equals(op)) { |
| pushBinary(MULT_SERVICE_NAME, ctx); |
| } else if (DIV_OPERATOR.equals(op)) { |
| pushBinary(DIV_SERVICE_NAME, ctx); |
| } else { |
| throw new AcceleoQueryEvaluationException(THIS_SHOULDN_T_HAPPEN); |
| } |
| } |
| |
| @Override |
| public void exitComp(CompContext ctx) { |
| final String op = ctx.getChild(1).getText(); |
| |
| if (LESS_THAN_OPERATOR.equals(op)) { |
| pushBinary(LESS_THAN_SERVICE_NAME, ctx); |
| } else if (LESS_THAN_EQUAL_OPERATOR.equals(op)) { |
| pushBinary(LESS_THAN_EQUAL_SERVICE_NAME, ctx); |
| } else if (GREATER_THAN_OPERATOR.equals(op)) { |
| pushBinary(GREATER_THAN_SERVICE_NAME, ctx); |
| } else if (GREATER_THAN_EQUAL_OPERATOR.equals(op)) { |
| pushBinary(GREATER_THAN_EQUAL_SERVICE_NAME, ctx); |
| } else if (EQUALS_OPERATOR.equals(op) || EQUALS_JAVA_OPERATOR.equals(op)) { |
| pushBinary(EQUALS_SERVICE_NAME, ctx); |
| } else if (DIFFERS_OPERATOR.equals(op) || DIFFERS_JAVA_OPERATOR.equals(op)) { |
| pushBinary(DIFFERS_SERVICE_NAME, ctx); |
| } else { |
| throw new AcceleoQueryEvaluationException(THIS_SHOULDN_T_HAPPEN); |
| } |
| } |
| |
| @Override |
| public void exitCollectionCall(CollectionCallContext ctx) { |
| peekCall().setType(CallType.COLLECTIONCALL); |
| } |
| |
| @Override |
| public void exitCallOrApply(CallOrApplyContext ctx) { |
| peekCall().setType(CallType.CALLORAPPLY); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitVariableDefinition(org.eclipse.acceleo.query.parser.QueryParser.VariableDefinitionContext) |
| */ |
| @Override |
| public void exitVariableDefinition(VariableDefinitionContext ctx) { |
| // If the error flag is raised an error occurred and the variable is already |
| // there. |
| if (errorRule == NO_ERROR) { |
| final VariableDeclaration variableDeclaration; |
| final Token stop; |
| if (ctx.getChildCount() == 4) { |
| final TypeLiteral typeLiteral = popTypeLiteral(); |
| final Expression variableExpression = popExpression(); |
| variableDeclaration = builder.variableDeclaration(ctx.getChild(0).getText(), typeLiteral, |
| variableExpression); |
| stop = ((ParserRuleContext)ctx.getChild(2)).stop; |
| } else { |
| final Expression variableExpression = popExpression(); |
| variableDeclaration = builder.variableDeclaration(ctx.getChild(0).getText(), |
| variableExpression); |
| stop = ((TerminalNode)ctx.getChild(0)).getSymbol(); |
| } |
| setPositions(variableDeclaration, ctx.start, stop); |
| |
| push(variableDeclaration); |
| } else { |
| errorRule = NO_ERROR; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitSelect(org.eclipse.acceleo.query.parser.QueryParser.SelectContext) |
| */ |
| @Override |
| public void exitIterationCall(IterationCallContext ctx) { |
| // the stack contains [variableDef, expression] |
| final String serviceName = ctx.getChild(0).getText(); |
| final Expression ast = popExpression(); |
| popErrorExpression(); |
| final VariableDeclaration iterator = popVariableDeclaration(); |
| final Lambda lambda = builder.lambda(ast, iterator); |
| setPositions(lambda, ast, ast); |
| final Call call; |
| if (ctx.getChild(ctx.getChildCount() - 1) instanceof ErrorNode) { |
| // at this point ANTLR can report a missing ')' even is the closing parenthesis is present |
| // so we check by hand |
| final ParserRuleContext parenthesisNode = (ParserRuleContext)ctx.getChild(ctx.getChildCount() - 2) |
| .getChild(0); |
| final boolean missingParenthesis = parenthesisNode != null && !")".equals(parenthesisNode.stop |
| .getText()); |
| call = builder.errorCall(serviceName, missingParenthesis, iterator.getExpression(), lambda); |
| if (missingParenthesis) { |
| pushError((Error)call, "missing ')'"); |
| } else { |
| pushError((Error)call, "invalid iteration call"); |
| } |
| } else { |
| call = builder.callService(serviceName, iterator.getExpression(), lambda); |
| push(call); |
| } |
| |
| setPositions(call, iterator.getExpression(), ctx.stop); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitEnumLit(org.eclipse.acceleo.query.parser.QueryParser.EnumLitContext) |
| */ |
| @Override |
| public void exitEnumLit(EnumLitContext ctx) { |
| if (ctx.getChildCount() >= 5) { |
| final EnumLiteral toPush; |
| final String ePackageName = ctx.getChild(0).getText(); |
| final String eEnumName = ctx.getChild(2).getText(); |
| final String eEnumLiteralName = ctx.getChild(4).getText(); |
| final Collection<EEnumLiteral> eEnumLiterals = environment.getEPackageProvider().getEnumLiterals( |
| ePackageName, eEnumName, eEnumLiteralName); |
| if (eEnumLiterals.size() == 0) { |
| List<String> segments = new ArrayList<String>(3); |
| segments.add(ePackageName); |
| segments.add(eEnumName); |
| if (!(ctx.getChild(4) instanceof ErrorNode)) { |
| segments.add(eEnumLiteralName); |
| } |
| toPush = builder.errorEnumLiteral(false, segments.toArray(new String[segments.size()])); |
| if (segments.size() == 3) { |
| pushError((Error)toPush, String.format(INVALID_ENUM_LITERAL, |
| "no literal registered with this name")); |
| } else { |
| pushError((Error)toPush, String.format(INVALID_ENUM_LITERAL, "missing literal name")); |
| } |
| } else { |
| toPush = builder.enumLiteral(eEnumLiterals.iterator().next()); |
| push(toPush); |
| if (eEnumLiterals.size() > 1) { |
| final Integer startPosition = Integer.valueOf(ctx.start.getStartIndex()); |
| final Integer stopPosition = Integer.valueOf(ctx.stop.getStopIndex() + 1); |
| diagnosticStack.push(new BasicDiagnostic(Diagnostic.WARNING, PLUGIN_ID, 0, String.format( |
| AMBIGUOUS_ENUM_LITERAL, eEnumLiteralName, eEnumName, ePackageName), new Object[] { |
| startPosition, stopPosition, })); |
| } |
| } |
| setPositions(toPush, ctx.start, ctx.stop); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitErrorEnumLit(org.eclipse.acceleo.query.parser.QueryParser.ErrorEnumLitContext) |
| */ |
| @Override |
| public void exitErrorEnumLit(ErrorEnumLitContext ctx) { |
| if (errorRule == NO_ERROR) { |
| final String ePackageName = ctx.getChild(0).getText(); |
| final String eEnumName = ctx.getChild(2).getText(); |
| |
| final ErrorEnumLiteral errorEnumLiteral = builder.errorEnumLiteral(true, ePackageName, eEnumName); |
| |
| pushError(errorEnumLiteral, String.format(INVALID_ENUM_LITERAL, "':' instead of '::'")); |
| setPositions(errorEnumLiteral, ctx.start, ctx.stop); |
| } else { |
| errorRule = NO_ERROR; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitClassifierType(org.eclipse.acceleo.query.parser.QueryParser.ClassifierTypeContext) |
| */ |
| @Override |
| public void exitClassifierType(ClassifierTypeContext ctx) { |
| if (errorRule == NO_ERROR) { |
| final Literal toPush; |
| final String ePackageName = ctx.getChild(0).getText(); |
| final String eClassName; |
| Collection<EClassifier> type = Collections.emptySet(); |
| if (ctx.getChild(2) == null || ctx.getChild(2) instanceof ErrorNode) { |
| eClassName = null; |
| type = Collections.emptySet(); |
| } else { |
| eClassName = ctx.getChild(2).getText(); |
| type = environment.getEPackageProvider().getTypes(ePackageName, eClassName); |
| } |
| if (type.size() == 0) { |
| List<String> segments = new ArrayList<String>(2); |
| segments.add(ePackageName); |
| if (eClassName != null) { |
| segments.add(eClassName); |
| } |
| toPush = builder.errorEClassifierTypeLiteral(false, segments.toArray(new String[segments |
| .size()])); |
| pushError((Error)toPush, String.format(INVALID_TYPE_LITERAL, ctx.getText())); |
| } else { |
| toPush = builder.typeLiteral(type); |
| push(toPush); |
| if (type.size() > 1) { |
| final Integer startPosition = Integer.valueOf(ctx.start.getStartIndex()); |
| final Integer stopPosition = Integer.valueOf(ctx.stop.getStopIndex() + 1); |
| diagnosticStack.push(new BasicDiagnostic(Diagnostic.WARNING, PLUGIN_ID, 0, String.format( |
| AMBIGUOUS_TYPE_LITERAL, eClassName, ePackageName), new Object[] {startPosition, |
| stopPosition, })); |
| } |
| } |
| setPositions(toPush, ctx.start, ctx.stop); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitErrorClassifierType(org.eclipse.acceleo.query.parser.QueryParser.ErrorClassifierTypeContext) |
| */ |
| @Override |
| public void exitErrorClassifierType(ErrorClassifierTypeContext ctx) { |
| final String ePackageName = ctx.getChild(0).getText(); |
| |
| final ErrorEClassifierTypeLiteral errorTypeLiteral = builder.errorEClassifierTypeLiteral(true, |
| ePackageName); |
| |
| pushError((Error)errorTypeLiteral, String.format(INVALID_TYPE_LITERAL, ctx.getText())); |
| setPositions(errorTypeLiteral, ctx.start, ctx.stop); |
| } |
| |
| /** |
| * Gets the {@link ANTLRErrorListener}. |
| * |
| * @return the {@link ANTLRErrorListener} |
| */ |
| public ANTLRErrorListener getErrorListener() { |
| return errorListener; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitNullLit(org.eclipse.acceleo.query.parser.QueryParser.NullLitContext) |
| */ |
| @Override |
| public void exitNullLit(NullLitContext ctx) { |
| final NullLiteral nullLiteral = builder.nullLiteral(); |
| |
| setPositions(nullLiteral, ctx.start, ctx.stop); |
| |
| push(nullLiteral); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitExplicitSetLit(org.eclipse.acceleo.query.parser.QueryParser.ExplicitSetLitContext) |
| */ |
| @Override |
| public void exitExplicitSetLit(ExplicitSetLitContext ctx) { |
| final SetInExtensionLiteral setInExtension = builder.setInExtension(getExpressions(ctx)); |
| |
| setPositions(setInExtension, ctx.start, ctx.stop); |
| |
| push(setInExtension); |
| } |
| |
| /** |
| * Gets the {@link List} of {@link Expression} from the given {@link LiteralContext} ( |
| * {@link SetLitContext} or {@link ExplicitSetLitContext} or {@link SeqLitContext} or |
| * {@link ExplicitSeqLitContext}). |
| * |
| * @param ctx |
| * the {@link LiteralContext} ( {@link SetLitContext} or {@link ExplicitSetLitContext} or |
| * {@link SeqLitContext} or {@link ExplicitSeqLitContext}) |
| * @return the {@link List} of {@link Expression} from the given {@link LiteralContext} ( |
| * {@link SetLitContext} or {@link ExplicitSetLitContext} or {@link SeqLitContext} or |
| * {@link ExplicitSeqLitContext}) |
| */ |
| private List<Expression> getExpressions(LiteralContext ctx) { |
| final int nbExpressions = (ctx.getChild(1).getChildCount() + 1) / 2; |
| final Expression[] expressions = new Expression[nbExpressions]; |
| |
| for (int i = nbExpressions - 1; i >= 0; i--) { |
| expressions[i] = popExpression(); |
| } |
| |
| return Arrays.asList(expressions); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitExplicitSeqLit(org.eclipse.acceleo.query.parser.QueryParser.ExplicitSeqLitContext) |
| */ |
| @Override |
| public void exitExplicitSeqLit(ExplicitSeqLitContext ctx) { |
| final SequenceInExtensionLiteral sequenceInExtension = builder.sequenceInExtension(getExpressions( |
| ctx)); |
| |
| setPositions(sequenceInExtension, ctx.start, ctx.stop); |
| |
| push(sequenceInExtension); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitConditional(org.eclipse.acceleo.query.parser.QueryParser.ConditionalContext) |
| */ |
| @Override |
| public void exitConditional(ConditionalContext ctx) { |
| final int count = ctx.getChildCount(); |
| final Expression predicate; |
| final Expression trueBranch; |
| final Expression falseBranch; |
| if (count <= 3) { |
| predicate = popExpression(); |
| trueBranch = null; |
| falseBranch = null; |
| } else if (count <= 5) { |
| trueBranch = popExpression(); |
| predicate = popExpression(); |
| falseBranch = null; |
| } else { |
| falseBranch = popExpression(); |
| trueBranch = popExpression(); |
| predicate = popExpression(); |
| } |
| |
| final Conditional conditional; |
| if (errorRule == QueryParser.RULE_expression || count == CONDITIONAL_CONTEXT_CHILD_COUNT && ctx |
| .getChild(6) instanceof ErrorNode) { |
| conditional = builder.errorConditional(predicate, trueBranch, falseBranch); |
| errorRule = NO_ERROR; |
| pushError((ErrorConditional)conditional, "incomplet conditional"); |
| } else { |
| conditional = builder.conditional(predicate, trueBranch, falseBranch); |
| push(conditional); |
| } |
| |
| setPositions(conditional, ctx.start, ctx.stop); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitBinding(org.eclipse.acceleo.query.parser.QueryParser.BindingContext) |
| */ |
| @Override |
| public void exitBinding(BindingContext ctx) { |
| // If the error flag is raised an error occurred and the binding is already |
| // there. |
| if (errorRule != QueryParser.RULE_binding) { |
| final String variable = ctx.getChild(0).getText(); |
| final Expression expression = popExpression(); |
| final TypeLiteral type; |
| if (ctx.getChildCount() == 5) { |
| type = popTypeLiteral(); |
| } else { |
| type = null; |
| } |
| final Binding binding = builder.binding(variable, type, expression); |
| |
| setPositions(binding, ctx.start, ctx.stop); |
| |
| push(binding); |
| } else { |
| errorRule = NO_ERROR; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitLetExpr(org.eclipse.acceleo.query.parser.QueryParser.LetExprContext) |
| */ |
| @Override |
| public void exitLetExpr(LetExprContext ctx) { |
| final Expression body; |
| final Binding[] bindings; |
| |
| if (!(ctx.getChild(ctx.getChildCount() - 1) instanceof ExpressionContext)) { |
| popErrorExpression(); |
| body = builder.errorExpression(); |
| final int position = ctx.stop.getStopIndex() + 1; |
| final int line = ctx.stop.getLine() - 1; |
| final int column = ctx.stop.getCharPositionInLine() + ctx.stop.getText().length(); |
| setPositions(body, position, line, column); |
| final List<Binding> bindingList = new ArrayList<Binding>(); |
| while (!stack.isEmpty() && stack.peek() instanceof Binding) { |
| bindingList.add(popBinding()); |
| } |
| bindings = bindingList.toArray(new Binding[bindingList.size()]); |
| } else { |
| body = popExpression(); |
| int bindingNumber = 1 + (ctx.getChildCount() - 3) / 2; |
| bindings = new Binding[bindingNumber]; |
| for (int i = bindingNumber - 1; i >= 0; i--) { |
| bindings[i] = popBinding(); |
| } |
| } |
| final Let let = builder.let(body, bindings); |
| |
| setPositions(let, ctx.start, ctx.stop); |
| |
| push(let); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.acceleo.query.parser.QueryBaseListener#exitClassifierSetType(org.eclipse.acceleo.query.parser.QueryParser.ClassifierSetTypeContext) |
| */ |
| @Override |
| public void exitClassifierSetType(ClassifierSetTypeContext ctx) { |
| final int nbTypes = (ctx.getChildCount() + 1) / 2 - 1; |
| final TypeLiteral[] types = new TypeLiteral[nbTypes]; |
| |
| for (int i = nbTypes - 1; i >= 0; i--) { |
| types[i] = popTypeLiteral(); |
| } |
| |
| final TypeSetLiteral classifierSetType = builder.typeSetLiteral(Arrays.asList(types)); |
| |
| setPositions(classifierSetType, ctx.start, ctx.stop); |
| |
| push(classifierSetType); |
| } |
| |
| } |