Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ternary expression and new syntax for expressions #899

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public VisitResult postVisitBlock(Block block, Context ctx) {

@Override
public VisitResult visitCondition(Condition condition, Context ctx) {
if (condition.expression().eval(ctx::getValue)) {
if (condition.expression().eval(ctx::getValue).asBoolean()) {
return VisitResult.CONTINUE;
}
return VisitResult.SKIP_SUBTREE;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022 Oracle and/or its affiliates.
* Copyright (c) 2021, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -83,7 +83,7 @@ public <A> VisitResult accept(Visitor<A> visitor, A arg) {
*/
public static boolean filter(Node node, Function<String, Value> resolver) {
if (node instanceof Condition) {
return ((Condition) node).expression().eval(resolver);
return ((Condition) node).expression().eval(resolver).asBoolean();
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022 Oracle and/or its affiliates.
* Copyright (c) 2021, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,6 +35,7 @@
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;

import io.helidon.build.archetype.engine.v2.ast.Value.TypedValue;
import io.helidon.build.common.GenericType;

import static java.util.Spliterator.ORDERED;
Expand Down Expand Up @@ -91,7 +92,7 @@ public List<Token> tokens() {
*
* @return result
*/
public boolean eval() {
public Value eval() {
return eval(s -> null);
}

Expand All @@ -101,46 +102,55 @@ public boolean eval() {
* @param resolver variable resolver
* @return result
*/
public boolean eval(Function<String, Value> resolver) {
public Value eval(Function<String, Value> resolver) {
Deque<Value> stack = new ArrayDeque<>();
for (Token token : tokens) {
Value value;
if (token.operator != null) {
boolean result;
Value operand1 = stack.pop();
Value result = null;
Value lastOperand = stack.pop();
if (token.operator == Operator.NOT) {
result = !operand1.asBoolean();
result = new Value.TypedValue(!lastOperand.asBoolean(), ValueTypes.BOOLEAN);
} else if (token.operator == Operator.TERNARY_IF) {
result = lastOperand;
} else if (token.operator == Operator.TERNARY_ELSE) {
Value ifOperand = stack.pop();
Value condition = stack.pop();
result = condition.asBoolean() ? ifOperand : lastOperand;
} else {
Value operand2 = stack.pop();
switch (token.operator) {
case OR:
result = operand2.asBoolean() || operand1.asBoolean();
result = new TypedValue(operand2.asBoolean() || lastOperand.asBoolean(), ValueTypes.BOOLEAN);
break;
case AND:
result = operand2.asBoolean() && operand1.asBoolean();
result = new TypedValue(operand2.asBoolean() && lastOperand.asBoolean(), ValueTypes.BOOLEAN);
break;
case EQUAL:
result = Value.equals(operand2, operand1);
result = new TypedValue(Value.equals(operand2, lastOperand), ValueTypes.BOOLEAN);
break;
case NOT_EQUAL:
result = !Value.equals(operand2, operand1);
result = new TypedValue(!Value.equals(operand2, lastOperand), ValueTypes.BOOLEAN);
break;
case CONTAINS:
if (operand1.type() == ValueTypes.STRING_LIST) {
result = new HashSet<>(operand2.asList()).containsAll(operand1.asList());
if (lastOperand.type() == ValueTypes.STRING_LIST) {
result = new TypedValue(new HashSet<>(operand2.asList()).containsAll(lastOperand.asList()),
ValueTypes.BOOLEAN);
} else {
if (operand2.type() == ValueTypes.STRING) {
result = operand2.asString().contains(operand1.asString());
result = new TypedValue(operand2.asString().contains(lastOperand.asString()),
ValueTypes.BOOLEAN);
} else {
result = operand2.asList().contains(operand1.asString());
result = new TypedValue(operand2.asList().contains(lastOperand.asString()),
ValueTypes.BOOLEAN);
}
}
break;
default:
throw new IllegalStateException("Unsupported operator: " + token.operator);
}
}
value = Value.create(result);
value = result;
} else if (token.operand != null) {
value = token.operand;
} else if (token.variable != null) {
Expand All @@ -153,7 +163,7 @@ public boolean eval(Function<String, Value> resolver) {
}
stack.push(value);
}
return stack.pop().asBoolean();
return stack.pop();
}

/**
Expand Down Expand Up @@ -186,7 +196,12 @@ public String variable() {
*/
public static final class FormatException extends RuntimeException {

private FormatException(String message) {
/**
* Create new instance.
*
* @param message message
*/
public FormatException(String message) {
super(message);
}
}
Expand Down Expand Up @@ -226,7 +241,38 @@ public static Expression parse(String expression) {
while (!stack.isEmpty() && OPS.containsKey(stack.peek().value)) {
Operator currentOp = OPS.get(symbol.value);
Operator leftOp = OPS.get(stack.peek().value);
if ((leftOp.precedence >= currentOp.precedence)) {
if (leftOp.precedence >= currentOp.precedence) {
stackSize += 1 - addToken(stack.pop(), tokens);
continue;
}
break;
}
stack.push(symbol);
break;
case TERNARY_IF_OPERATOR:
while (!stack.isEmpty() && OPS.containsKey(stack.peek().value)) {
Operator currentOp = OPS.get(symbol.value);
Operator leftOp = OPS.get(stack.peek().value);
if (leftOp == currentOp) {
break;
}
if (leftOp.precedence > currentOp.precedence) {
stackSize += 1 - addToken(stack.pop(), tokens);
continue;
}
break;
}
stack.push(symbol);
break;
case TERNARY_ELSE_OPERATOR:
while (!stack.isEmpty() && OPS.containsKey(stack.peek().value)) {
Operator currentOp = OPS.get(symbol.value);
Operator leftOp = OPS.get(stack.peek().value);
if (leftOp.precedence == currentOp.precedence) {
stackSize += 1 - addToken(stack.pop(), tokens);
break;
}
if (leftOp.precedence > currentOp.precedence) {
stackSize += 1 - addToken(stack.pop(), tokens);
continue;
}
Expand All @@ -252,6 +298,7 @@ public static Expression parse(String expression) {
case BOOLEAN:
case STRING:
case ARRAY:
case INTEGER:
case VARIABLE:
stackSize += 1 - addToken(symbol, tokens);
break;
Expand Down Expand Up @@ -332,7 +379,17 @@ public enum Operator {
/**
* Not operator.
*/
NOT(13, "!");
NOT(13, "!"),

/**
* Ternary operator (first part).
*/
TERNARY_IF(2, "?"),

/**
* Ternary operator (second part).
*/
TERNARY_ELSE(2, ":");

private final int precedence;
private final String symbol;
Expand Down Expand Up @@ -485,11 +542,15 @@ private static Token create(Symbol symbol) {
case UNARY_LOGICAL_OPERATOR:
case EQUALITY_OPERATOR:
case CONTAINS_OPERATOR:
case TERNARY_IF_OPERATOR:
case TERNARY_ELSE_OPERATOR:
return new Token(OPS.get(symbol.value), null, null);
case BOOLEAN:
return new Token(null, null, Value.create(Boolean.parseBoolean(symbol.value)));
case STRING:
return new Token(null, null, Value.create(symbol.value.substring(1, symbol.value.length() - 1)));
case INTEGER:
return new Token(null, null, Value.create(Integer.parseInt(symbol.value), ValueTypes.INT));
case ARRAY:
return new Token(null, null, Value.create(parseArray(symbol.value)));
case VARIABLE:
Expand Down Expand Up @@ -523,7 +584,10 @@ enum Type {
UNARY_LOGICAL_OPERATOR("^[!]"),
CONTAINS_OPERATOR("^contains"),
PARENTHESIS("^[()]"),
COMMENT("#.*\\R");
COMMENT("#.*\\R"),
TERNARY_IF_OPERATOR("^\\?"),
TERNARY_ELSE_OPERATOR("^:"),
INTEGER("^[0-9]+");

private final Pattern pattern;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public VisitResult visitCondition(Condition condition, Context ctx) {
default:
throw new IllegalStateException("Bad input type");
}
});
}).asBoolean();
} catch (Expression.UnresolvedVariableException ex) {
errors.add(String.format("%s %s: '%s'",
condition.location(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ private static boolean filter(List<Expression> filters, Map<String, String> perm
return DynamicValue.create(v);
}
return null;
});
}).asBoolean();
if (!result) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed 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 io.helidon.build.archetype.engine.v2.util;

import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import io.helidon.build.archetype.engine.v2.ast.Expression;
import io.helidon.build.archetype.engine.v2.ast.Value;

/**
* Process string and if it represents an expression evaluate it and return {@link Value}.
*
* @see Expression
*/
public class ValueHandler {

private static final Pattern VAR_NO_BRACE = Pattern.compile("^\\w+");

private ValueHandler() {
}

/**
* Process expression.
*
* @param expression expression
* @param resolver variable resolver
* @return {@link Value} object
*/
public static Value process(String expression, Function<String, Value> resolver) {
if (expression == null) {
return Value.NULL;
}
for (ExpressionType type : ExpressionType.values()) {
Matcher matcher = type.pattern.matcher(expression);
if (matcher.find()) {
String value = matcher.group("expression");
if (type == ExpressionType.NO_BRACE_VARS) {
value = preprocessExpression(value);
}
return Expression.parse(value).eval(resolver);
}

}
return Value.create(expression);
}

/**
* Process expression.
*
* @param expression expression
* @return {@link Value} object
*/
public static Value process(String expression) {
return process(expression, s -> null);
}

private static String preprocessExpression(String expression) {
StringBuilder output = new StringBuilder();
int cursor = 0;
String current = expression.trim();
while (current.length() > 0) {
current = current.substring(cursor);
cursor = 0;
boolean tokenFound = false;
for (Token token : Token.values()) {
Matcher matcher = token.pattern.matcher(current);
if (matcher.find()) {
String value = matcher.group();
cursor += value.length();
output.append(value);
tokenFound = true;
break;
}
}
if (tokenFound) {
continue;
}
Matcher matcherVar = VAR_NO_BRACE.matcher(current);
while (matcherVar.find()) {
var value = matcherVar.group();
cursor += value.length();
output.append("${").append(value).append("}");
tokenFound = true;
}
if (!tokenFound && current.trim().length() > 0) {
throw new Expression.FormatException("Unexpected token - " + current);
}
}
return output.toString();
}

private enum ExpressionType {

BACKTICK("^`(?<expression>.*)`$"),
BRACE_VARS("^#\\{(?<expression>.*(\\$\\{)+.*}+.*)}$"),
NO_BRACE_VARS("^#\\{(?<expression>[^\\$\\{}]*)}$");

private final Pattern pattern;

ExpressionType(String regex) {
this.pattern = Pattern.compile(regex);
}
}

private enum Token {
SKIP("^\\s+"),
ARRAY("^\\[[^]\\[]*]"),
BOOLEAN("^(true|false)\\b"),
STRING("^['\"][^'\"]*['\"]"),
VARIABLE("^\\$\\{(?<varName>~?[\\w.-]+)}"),
EQUALITY_OPERATOR("^(!=|==)"),
BINARY_LOGICAL_OPERATOR("^(\\|\\||&&)"),
UNARY_LOGICAL_OPERATOR("^[!]"),
CONTAINS_OPERATOR("^contains\\b"),
PARENTHESIS("^[()]"),
COMMENT("#.*\\R"),
TERNARY_IF_OPERATOR("^\\?"),
TERNARY_ELSE_OPERATOR("^:"),
INTEGER("^[0-9]+");

private final Pattern pattern;

Token(String regex) {
this.pattern = Pattern.compile(regex);
}
}
}
Loading