blob: b669bc7df69fb29162c8dbd15a78de4c63724353 [file] [log] [blame]
/*
* 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;}
}