| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.metron.stellar.common; |
| |
| import org.apache.commons.lang3.StringEscapeUtils; |
| import org.apache.metron.stellar.dsl.Context; |
| import org.apache.metron.stellar.dsl.Context.ActivityType; |
| import org.apache.metron.stellar.dsl.Token; |
| import org.apache.metron.stellar.dsl.VariableResolver; |
| import org.apache.metron.stellar.dsl.functions.resolver.FunctionResolver; |
| import org.apache.metron.stellar.common.evaluators.ArithmeticEvaluator; |
| import org.apache.metron.stellar.common.evaluators.ComparisonExpressionWithOperatorEvaluator; |
| import org.apache.metron.stellar.common.evaluators.NumberLiteralEvaluator; |
| import org.apache.metron.stellar.common.generated.StellarBaseListener; |
| import org.apache.metron.stellar.common.generated.StellarParser; |
| import com.google.common.base.Joiner; |
| import org.apache.commons.lang3.tuple.Pair; |
| import org.apache.metron.stellar.dsl.FunctionMarker; |
| import org.apache.metron.stellar.dsl.ParseException; |
| import org.apache.metron.stellar.dsl.StellarFunction; |
| import org.apache.metron.stellar.common.utils.ConversionUtils; |
| |
| import java.io.Serializable; |
| import java.util.*; |
| |
| import static java.lang.String.format; |
| |
| public class StellarCompiler extends StellarBaseListener { |
| private static Token<?> EXPRESSION_REFERENCE = new Token<>(null, Object.class); |
| private static Token<?> LAMBDA_VARIABLES = new Token<>(null, Object.class); |
| |
| private Expression expression; |
| private final ArithmeticEvaluator arithmeticEvaluator; |
| private final NumberLiteralEvaluator numberLiteralEvaluator; |
| private final ComparisonExpressionWithOperatorEvaluator comparisonExpressionWithOperatorEvaluator; |
| |
| public interface ShortCircuitOp {} |
| |
| public static class ShortCircuitFrame {} |
| public static class BooleanArg implements ShortCircuitOp {} |
| public static class IfExpr implements ShortCircuitOp {} |
| public static class ThenExpr implements ShortCircuitOp {} |
| public static class ElseExpr implements ShortCircuitOp {} |
| public static class EndConditional implements ShortCircuitOp {} |
| |
| public static class ExpressionState { |
| Context context; |
| FunctionResolver functionResolver; |
| VariableResolver variableResolver; |
| public ExpressionState(Context context |
| , FunctionResolver functionResolver |
| , VariableResolver variableResolver |
| ) { |
| this.context = context; |
| this.variableResolver = variableResolver; |
| this.functionResolver = functionResolver; |
| } |
| } |
| |
| public static class Expression implements Serializable { |
| final Deque<Token<?>> tokenDeque; |
| final Deque<FrameContext.Context> multiArgumentState; |
| final Set<String> variablesUsed; |
| public Expression(Deque<Token<?>> tokenDeque) { |
| this.tokenDeque = tokenDeque; |
| this.variablesUsed = new HashSet<>(); |
| this.multiArgumentState = new ArrayDeque<>(); |
| } |
| |
| public void clear() { |
| tokenDeque.clear(); |
| variablesUsed.clear(); |
| multiArgumentState.clear(); |
| } |
| |
| public Deque<Token<?>> getTokenDeque() { |
| return tokenDeque; |
| } |
| |
| public Object apply(ExpressionState state) { |
| Deque<Token<?>> instanceDeque = new ArrayDeque<>(); |
| { |
| boolean skipElse = false; |
| Token<?> token = null; |
| for (Iterator<Token<?>> it = getTokenDeque().descendingIterator(); it.hasNext(); ) { |
| token = it.next(); |
| //if we've skipped an else previously, then we need to skip the deferred tokens associated with the else. |
| if(skipElse && token.getUnderlyingType() == ElseExpr.class) { |
| while(it.hasNext()) { |
| token = it.next(); |
| if(token.getUnderlyingType() == EndConditional.class) { |
| break; |
| } |
| } |
| skipElse = false; |
| } |
| /* |
| curr is the current value on the stack. This is the non-deferred actual evaluation for this expression |
| and with the current context. |
| */ |
| Token<?> curr = instanceDeque.peek(); |
| if( curr != null |
| && curr.getValue() != null && curr.getValue() instanceof Boolean |
| && ShortCircuitOp.class.isAssignableFrom(token.getUnderlyingType()) |
| ) { |
| //if we have a boolean as the current value and the next non-contextual token is a short circuit op |
| //then we need to short circuit possibly |
| if(token.getUnderlyingType() == BooleanArg.class) { |
| if (token.getMultiArgContext() != null |
| && token.getMultiArgContext().getVariety() == FrameContext.BOOLEAN_OR |
| && (Boolean) (curr.getValue()) |
| ) { |
| //short circuit the or |
| FrameContext.Context context = curr.getMultiArgContext(); |
| shortCircuit(it, context); |
| } else if (token.getMultiArgContext() != null |
| && token.getMultiArgContext().getVariety() == FrameContext.BOOLEAN_AND |
| && !(Boolean) (curr.getValue()) |
| ) { |
| //short circuit the and |
| FrameContext.Context context = curr.getMultiArgContext(); |
| shortCircuit(it, context); |
| } |
| } |
| else if(token.getUnderlyingType() == IfExpr.class) { |
| //short circuit the if/then/else |
| instanceDeque.pop(); |
| if((Boolean)curr.getValue()) { |
| //choose then |
| skipElse = true; |
| } |
| else { |
| //choose else |
| while(it.hasNext()) { |
| Token<?> t = it.next(); |
| if(t.getUnderlyingType() == ElseExpr.class) { |
| break; |
| } |
| } |
| } |
| } |
| } |
| if (token.getUnderlyingType() == DeferredFunction.class) { |
| DeferredFunction func = (DeferredFunction) token.getValue(); |
| func.apply(instanceDeque, state); |
| } |
| else if(token.getUnderlyingType() != ShortCircuitFrame.class |
| && !ShortCircuitOp.class.isAssignableFrom(token.getUnderlyingType()) |
| ) { |
| instanceDeque.push(token); |
| } |
| |
| } |
| } |
| |
| if (instanceDeque.isEmpty()) { |
| throw new ParseException("Invalid predicate: Empty stack."); |
| } |
| Token<?> token = instanceDeque.pop(); |
| if (instanceDeque.isEmpty()) { |
| return token.getValue(); |
| } |
| if (instanceDeque.isEmpty()) { |
| throw new ParseException("Invalid parse, stack not empty: " + Joiner.on(',').join(instanceDeque)); |
| } else { |
| throw new ParseException("Invalid parse, found " + token); |
| } |
| } |
| |
| public void shortCircuit(Iterator<Token<?>> it, FrameContext.Context context) { |
| while (it.hasNext()) { |
| Token<?> token = it.next(); |
| if (token.getUnderlyingType() == ShortCircuitFrame.class && token.getMultiArgContext() == context) { |
| break; |
| } |
| } |
| } |
| } |
| |
| interface DeferredFunction { |
| void apply( Deque<Token<?>> tokenDeque |
| , ExpressionState state |
| ); |
| } |
| |
| public StellarCompiler( |
| final ArithmeticEvaluator arithmeticEvaluator, |
| final NumberLiteralEvaluator numberLiteralEvaluator, |
| final ComparisonExpressionWithOperatorEvaluator comparisonExpressionWithOperatorEvaluator |
| ){ |
| this(new Expression(new ArrayDeque<>()), arithmeticEvaluator, numberLiteralEvaluator, comparisonExpressionWithOperatorEvaluator); |
| } |
| |
| public StellarCompiler( |
| final Expression expression, |
| final ArithmeticEvaluator arithmeticEvaluator, |
| final NumberLiteralEvaluator numberLiteralEvaluator, |
| final ComparisonExpressionWithOperatorEvaluator comparisonExpressionWithOperatorEvaluator |
| ){ |
| this.expression = expression; |
| this.arithmeticEvaluator = arithmeticEvaluator; |
| this.numberLiteralEvaluator = numberLiteralEvaluator; |
| this.comparisonExpressionWithOperatorEvaluator = comparisonExpressionWithOperatorEvaluator; |
| } |
| |
| @Override |
| public void enterTransformation(StellarParser.TransformationContext ctx) { |
| expression.clear(); |
| } |
| |
| private boolean handleIn(final Token<?> left, final Token<?> right) { |
| Object key = right.getValue(); |
| |
| |
| if (left.getValue() != null) { |
| if (left.getValue() instanceof String && key instanceof String) { |
| return ((String) left.getValue()).contains(key.toString()); |
| } |
| else if (left.getValue() instanceof Collection) { |
| return ((Collection) left.getValue()).contains(key); |
| } |
| else if (left.getValue() instanceof Map) { |
| return ((Map) left.getValue()).containsKey(key); |
| } |
| else { |
| if (key == null) { |
| return key == left.getValue(); |
| } |
| else { |
| return key.equals(left.getValue()); |
| } |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| public void exitNullConst(StellarParser.NullConstContext ctx) { |
| expression.tokenDeque.push(new Token<>(null, Object.class, getArgContext())); |
| } |
| |
| @Override |
| public void exitArithExpr_plus(StellarParser.ArithExpr_plusContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>((tokenDeque, state) -> { |
| Pair<Token<? extends Number>, Token<? extends Number>> p = getArithExpressionPair(tokenDeque); |
| tokenDeque.push(arithmeticEvaluator.evaluate(ArithmeticEvaluator.ArithmeticEvaluatorFunctions.addition(context), p)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @Override |
| public void exitArithExpr_minus(StellarParser.ArithExpr_minusContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Pair<Token<? extends Number>, Token<? extends Number>> p = getArithExpressionPair(tokenDeque); |
| tokenDeque.push(arithmeticEvaluator.evaluate(ArithmeticEvaluator.ArithmeticEvaluatorFunctions.subtraction(context), p)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @Override |
| public void exitArithExpr_div(StellarParser.ArithExpr_divContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Pair<Token<? extends Number>, Token<? extends Number>> p = getArithExpressionPair(tokenDeque); |
| tokenDeque.push(arithmeticEvaluator.evaluate(ArithmeticEvaluator.ArithmeticEvaluatorFunctions.division(context), p)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @Override |
| public void exitArithExpr_mul(StellarParser.ArithExpr_mulContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Pair<Token<? extends Number>, Token<? extends Number>> p = getArithExpressionPair(tokenDeque); |
| tokenDeque.push(arithmeticEvaluator.evaluate(ArithmeticEvaluator.ArithmeticEvaluatorFunctions.multiplication(context), p)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Pair<Token<? extends Number>, Token<? extends Number>> getArithExpressionPair(Deque<Token<?>> tokenDeque) { |
| Token<? extends Number> right = (Token<? extends Number>) popDeque(tokenDeque); |
| Token<? extends Number> left = (Token<? extends Number>) popDeque(tokenDeque); |
| return Pair.of(left, right); |
| } |
| |
| @Override |
| public void exitIf_expr(StellarParser.If_exprContext ctx) { |
| expression.tokenDeque.push(new Token<>(new IfExpr(), IfExpr.class, getArgContext())); |
| } |
| |
| @Override |
| public void enterThen_expr(StellarParser.Then_exprContext ctx) { |
| expression.tokenDeque.push(new Token<>(new ThenExpr(), ThenExpr.class, getArgContext())); |
| } |
| |
| @Override |
| public void enterElse_expr(StellarParser.Else_exprContext ctx) { |
| expression.tokenDeque.push(new Token<>(new ElseExpr(), ElseExpr.class, getArgContext())); |
| } |
| |
| @Override |
| public void exitElse_expr(StellarParser.Else_exprContext ctx) { |
| expression.tokenDeque.push(new Token<>(new EndConditional(), EndConditional.class, getArgContext())); |
| } |
| |
| @Override |
| public void exitInExpressionStatement(StellarParser.InExpressionStatementContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Token<?> left = popDeque(tokenDeque); |
| Token<?> right = popDeque(tokenDeque); |
| tokenDeque.push(new Token<>(handleIn(left, right), Boolean.class, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| |
| @Override |
| public void exitNInExpressionStatement(StellarParser.NInExpressionStatementContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Token<?> left = popDeque(tokenDeque); |
| Token<?> right = popDeque(tokenDeque); |
| tokenDeque.push(new Token<>(!handleIn(left, right), Boolean.class, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @Override |
| public void exitNotFunc(StellarParser.NotFuncContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Token<Boolean> arg = (Token<Boolean>) popDeque(tokenDeque); |
| tokenDeque.push(new Token<>(!arg.getValue(), Boolean.class, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| |
| @Override |
| public void exitVariable(StellarParser.VariableContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| String varName = ctx.getText(); |
| if(state.context.getActivityType().equals(ActivityType.PARSE_ACTIVITY) && !state.variableResolver.exists(varName)) { |
| // when parsing, missing variables are an error! |
| throw new ParseException(String.format("variable: %s is not defined",varName)); |
| } |
| tokenDeque.push(new Token<>(state.variableResolver.resolve(varName), Object.class, context)); |
| }, DeferredFunction.class, context)); |
| expression.variablesUsed.add(ctx.getText()); |
| } |
| |
| @Override |
| public void exitStringLiteral(StellarParser.StringLiteralContext ctx) { |
| String rawToken = ctx.getText(); |
| String literal = StringEscapeUtils.UNESCAPE_JSON.translate(rawToken); |
| expression.tokenDeque.push(new Token<>(literal.substring(1, literal.length()-1), String.class, getArgContext())); |
| } |
| |
| @Override |
| public void exitIntLiteral(StellarParser.IntLiteralContext ctx) { |
| expression.tokenDeque.push(numberLiteralEvaluator.evaluate(ctx, getArgContext())); |
| } |
| |
| @Override |
| public void exitDoubleLiteral(StellarParser.DoubleLiteralContext ctx) { |
| expression.tokenDeque.push(numberLiteralEvaluator.evaluate(ctx, getArgContext())); |
| } |
| |
| @Override |
| public void exitFloatLiteral(StellarParser.FloatLiteralContext ctx) { |
| expression.tokenDeque.push(numberLiteralEvaluator.evaluate(ctx, getArgContext())); |
| } |
| |
| @Override |
| public void exitLongLiteral(StellarParser.LongLiteralContext ctx) { |
| expression.tokenDeque.push(numberLiteralEvaluator.evaluate(ctx, getArgContext())); |
| } |
| |
| @Override |
| public void enterB_expr(StellarParser.B_exprContext ctx) { |
| //Enter is not guaranteed to be called by Antlr for logical labels, so we need to |
| //emulate it like this. See https://github.com/antlr/antlr4/issues/802 |
| if(ctx.getParent() instanceof StellarParser.LogicalExpressionOrContext) { |
| expression.multiArgumentState.push(FrameContext.BOOLEAN_OR.create()); |
| } |
| else if(ctx.getParent() instanceof StellarParser.LogicalExpressionAndContext) { |
| expression.multiArgumentState.push(FrameContext.BOOLEAN_AND.create()); |
| } |
| } |
| |
| @Override |
| public void exitB_expr(StellarParser.B_exprContext ctx) { |
| if(ctx.getParent() instanceof StellarParser.LogicalExpressionOrContext |
| || ctx.getParent() instanceof StellarParser.LogicalExpressionAndContext |
| ) |
| { |
| //we want to know when the argument to the boolean expression is complete |
| expression.tokenDeque.push(new Token<>(new BooleanArg(), BooleanArg.class, getArgContext())); |
| } |
| } |
| |
| @Override |
| public void exitLogicalExpressionAnd(StellarParser.LogicalExpressionAndContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| popArgContext(); |
| final FrameContext.Context parentContext = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Token<?> left = popDeque(tokenDeque); |
| Token<?> right = popDeque(tokenDeque); |
| tokenDeque.push(new Token<>(booleanOp(left, right, (l, r) -> l && r, "&&"), Boolean.class, parentContext)); |
| }, DeferredFunction.class, context)); |
| expression.tokenDeque.push(new Token<>(new ShortCircuitFrame(), ShortCircuitFrame.class, context)); |
| } |
| |
| @Override |
| public void exitLogicalExpressionOr(StellarParser.LogicalExpressionOrContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| popArgContext(); |
| final FrameContext.Context parentContext = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| Token<?> left = popDeque(tokenDeque); |
| Token<?> right = popDeque(tokenDeque); |
| |
| tokenDeque.push(new Token<>(booleanOp(left, right, (l, r) -> l || r, "||"), Boolean.class, parentContext)); |
| }, DeferredFunction.class, context)); |
| expression.tokenDeque.push(new Token<>(new ShortCircuitFrame(), ShortCircuitFrame.class, context)); |
| } |
| |
| @Override |
| public void exitLogicalConst(StellarParser.LogicalConstContext ctx) { |
| Boolean b; |
| switch (ctx.getText().toUpperCase()) { |
| case "TRUE": |
| b = true; |
| break; |
| case "FALSE": |
| b = false; |
| break; |
| default: |
| throw new ParseException("Unable to process " + ctx.getText() + " as a boolean constant"); |
| } |
| expression.tokenDeque.push(new Token<>(b, Boolean.class, getArgContext())); |
| } |
| |
| private boolean booleanOp(final Token<?> left, final Token<?> right, final BooleanOp op, final String opName) { |
| Boolean l = ConversionUtils.convert(left.getValue(), Boolean.class); |
| Boolean r = ConversionUtils.convert(right.getValue(), Boolean.class); |
| if (l == null || r == null) { |
| throw new ParseException("Unable to operate on " + left.getValue() + " " + opName + " " + right.getValue() + ", null value"); |
| } |
| return op.op(l, r); |
| } |
| |
| |
| @Override |
| public void enterSingle_lambda_variable(StellarParser.Single_lambda_variableContext ctx) { |
| enterLambdaVariables(); |
| } |
| |
| @Override |
| public void exitSingle_lambda_variable(StellarParser.Single_lambda_variableContext ctx) { |
| exitLambdaVariables(); |
| } |
| |
| @Override |
| public void enterLambda_variables(StellarParser.Lambda_variablesContext ctx) { |
| enterLambdaVariables(); |
| } |
| |
| @Override |
| public void exitLambda_variables(StellarParser.Lambda_variablesContext ctx) { |
| exitLambdaVariables(); |
| } |
| |
| @Override |
| public void exitLambda_variable(StellarParser.Lambda_variableContext ctx) { |
| expression.tokenDeque.push(new Token<>(ctx.getText(), String.class, getArgContext())); |
| } |
| |
| private void enterLambdaVariables() { |
| expression.tokenDeque.push(LAMBDA_VARIABLES); |
| } |
| |
| private void exitLambdaVariables() { |
| Token<?> t = expression.tokenDeque.pop(); |
| LinkedList<String> variables = new LinkedList<>(); |
| for(; !expression.tokenDeque.isEmpty() && t != LAMBDA_VARIABLES; t = expression.tokenDeque.pop()) { |
| variables.addFirst(t.getValue().toString()); |
| } |
| expression.tokenDeque.push(new Token<>(variables, List.class, getArgContext())); |
| } |
| |
| private void enterLambda() { |
| expression.tokenDeque.push(EXPRESSION_REFERENCE); |
| } |
| |
| private void exitLambda(boolean hasArgs) { |
| final FrameContext.Context context = getArgContext(); |
| Token<?> t = expression.tokenDeque.pop(); |
| final Deque<Token<?>> instanceDeque = new ArrayDeque<>(); |
| for(; !expression.tokenDeque.isEmpty() && t != EXPRESSION_REFERENCE; t = expression.tokenDeque.pop()) { |
| instanceDeque.addLast(t); |
| } |
| final List<String> variables = hasArgs? (List<String>) instanceDeque.removeLast().getValue() :new ArrayList<>(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| LambdaExpression expr = new LambdaExpression(variables, instanceDeque, state); |
| tokenDeque.push(new Token<>(expr, Object.class, context)); |
| }, DeferredFunction.class, context) ); |
| } |
| |
| @Override |
| public void enterLambda_with_args(StellarParser.Lambda_with_argsContext ctx) { |
| enterLambda(); |
| } |
| |
| @Override |
| public void exitLambda_with_args(StellarParser.Lambda_with_argsContext ctx) { |
| exitLambda(true); |
| } |
| |
| @Override |
| public void enterLambda_without_args(StellarParser.Lambda_without_argsContext ctx) { |
| enterLambda(); |
| } |
| |
| @Override |
| public void exitLambda_without_args(StellarParser.Lambda_without_argsContext ctx) { |
| exitLambda(false); |
| } |
| |
| @Override |
| public void exitTransformationFunc(StellarParser.TransformationFuncContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| // resolve and initialize the function |
| String functionName = ctx.getChild(0).getText(); |
| StellarFunction function = resolveFunction(state.functionResolver, functionName); |
| initializeFunction(state.context, function, functionName); |
| |
| // fetch the args, execute, and push result onto the stack |
| List<Object> args = getFunctionArguments(popDeque(tokenDeque)); |
| Object result = function.apply(args, state.context); |
| tokenDeque.push(new Token<>(result, Object.class, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| /** |
| * Get function arguments. |
| * @param token The token containing the function arguments. |
| * @return |
| */ |
| @SuppressWarnings("unchecked") |
| private List<Object> getFunctionArguments(final Token<?> token) { |
| if (token.getUnderlyingType().equals(List.class)) { |
| return (List<Object>) token.getValue(); |
| |
| } else { |
| throw new ParseException("Unable to process in clause because " + token.getValue() + " is not a set"); |
| } |
| } |
| |
| /** |
| * Resolves a function by name. |
| * @param funcName |
| * @return |
| */ |
| private StellarFunction resolveFunction(FunctionResolver functionResolver, String funcName) { |
| try { |
| return functionResolver.apply(funcName); |
| |
| } catch (Exception e) { |
| String valid = Joiner.on(',').join(functionResolver.getFunctions()); |
| String error = format("Unable to resolve function named '%s'. Valid functions are %s", funcName, valid); |
| throw new ParseException(error, e); |
| } |
| } |
| |
| /** |
| * Initialize a Stellar function. |
| * @param function The function to initialize. |
| * @param functionName The name of the functions. |
| */ |
| private void initializeFunction(Context context, StellarFunction function, String functionName) { |
| try { |
| if (!function.isInitialized()) { |
| function.initialize(context); |
| } |
| } catch (Throwable t) { |
| String error = format("Unable to initialize function '%s'", functionName); |
| throw new ParseException(error, t); |
| } |
| } |
| |
| @Override |
| public void exitExistsFunc(StellarParser.ExistsFuncContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| String variable = ctx.getChild(2).getText(); |
| boolean exists = state.variableResolver.resolve(variable) != null; |
| tokenDeque.push(new Token<>(exists, Boolean.class, context)); |
| }, DeferredFunction.class, context)); |
| String variable = ctx.getChild(2).getText(); |
| expression.variablesUsed.add(variable); |
| } |
| |
| @Override |
| public void enterFunc_args(StellarParser.Func_argsContext ctx) { |
| expression.tokenDeque.push(new Token<>(new FunctionMarker(), FunctionMarker.class, getArgContext())); |
| } |
| |
| @Override |
| public void exitFunc_args(StellarParser.Func_argsContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>((tokenDeque, state) -> { |
| LinkedList<Object> args = new LinkedList<>(); |
| while (true) { |
| Token<?> token = popDeque(tokenDeque); |
| if (token.getUnderlyingType().equals(FunctionMarker.class)) { |
| break; |
| } else { |
| args.addFirst(token.getValue()); |
| } |
| } |
| tokenDeque.push(new Token<>(args, List.class, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @Override |
| public void enterMap_entity(StellarParser.Map_entityContext ctx) { |
| expression.tokenDeque.push(new Token<>(new FunctionMarker(), FunctionMarker.class, getArgContext())); |
| } |
| |
| @Override |
| public void exitMap_entity(StellarParser.Map_entityContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| HashMap<String, Object> args = new HashMap<>(); |
| Object value = null; |
| for (int i = 0; true; i++) { |
| Token<?> token = popDeque(tokenDeque); |
| if (token.getUnderlyingType().equals(FunctionMarker.class)) { |
| break; |
| } else { |
| if (i % 2 == 0) { |
| value = token.getValue(); |
| } else { |
| args.put(token.getValue() + "", value); |
| } |
| } |
| } |
| tokenDeque.push(new Token<>(args, Map.class, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @Override |
| public void exitList_entity(StellarParser.List_entityContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| LinkedList<Object> args = new LinkedList<>(); |
| while (true) { |
| Token<?> token = popDeque(tokenDeque); |
| if (token.getUnderlyingType().equals(FunctionMarker.class)) { |
| break; |
| } else { |
| args.addFirst(token.getValue()); |
| } |
| } |
| tokenDeque.push(new Token<>(args, List.class, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| |
| |
| @Override |
| public void exitComparisonExpressionWithOperator(StellarParser.ComparisonExpressionWithOperatorContext ctx) { |
| final FrameContext.Context context = getArgContext(); |
| expression.tokenDeque.push(new Token<>( (tokenDeque, state) -> { |
| StellarParser.Comp_operatorContext op = ctx.comp_operator(); |
| Token<?> right = popDeque(tokenDeque); |
| Token<?> left = popDeque(tokenDeque); |
| |
| tokenDeque.push(comparisonExpressionWithOperatorEvaluator.evaluate(left, right, (StellarParser.ComparisonOpContext) op, context)); |
| }, DeferredFunction.class, context)); |
| } |
| |
| @Override |
| public void enterList_entity(StellarParser.List_entityContext ctx) { |
| expression.tokenDeque.push(new Token<>(new FunctionMarker(), FunctionMarker.class, getArgContext())); |
| } |
| |
| private void popArgContext() { |
| if(!expression.multiArgumentState.isEmpty()) { |
| expression.multiArgumentState.pop(); |
| } |
| } |
| |
| private FrameContext.Context getArgContext() { |
| return expression.multiArgumentState.isEmpty()?null:expression.multiArgumentState.peek(); |
| } |
| |
| private Token<?> popDeque(Deque<Token<?>> tokenDeque) { |
| if (tokenDeque.isEmpty()) { |
| throw new ParseException("Unable to pop an empty stack"); |
| } |
| return tokenDeque.pop(); |
| } |
| |
| public Expression getExpression() {return expression;} |
| |
| } |