Добавлен оператор объединения с null

This commit is contained in:
Victor 2019-07-06 20:32:31 +03:00
parent 2e439d73b5
commit 0bd2aa10d5
5 changed files with 180 additions and 109 deletions

View File

@ -83,6 +83,7 @@ public final class Lexer {
OPERATORS.put("**", TokenType.STARSTAR);
OPERATORS.put("^^", TokenType.CARETCARET);
OPERATORS.put("?:", TokenType.QUESTIONCOLON);
OPERATORS.put("??", TokenType.QUESTIONQUESTION);
}
private static final Map<String, TokenType> KEYWORDS;

View File

@ -473,7 +473,7 @@ public final class Parser {
}
private Expression ternary() {
Expression result = logicalOr();
Expression result = nullCoalesce();
if (match(TokenType.QUESTION)) {
final Expression trueExpr = expression();
@ -487,6 +487,20 @@ public final class Parser {
return result;
}
private Expression nullCoalesce() {
Expression result = logicalOr();
while (true) {
if (match(TokenType.QUESTIONQUESTION)) {
result = new ConditionalExpression(ConditionalExpression.Operator.NULL_COALESCE, result, expression());
continue;
}
break;
}
return result;
}
private Expression logicalOr() {
Expression result = logicalAnd();

View File

@ -69,6 +69,7 @@ public enum TokenType {
DOTDOT, // ..
STARSTAR, // **
QUESTIONCOLON, // ?:
QUESTIONQUESTION, // ??
TILDE, // ~
CARET, // ^

View File

@ -21,7 +21,9 @@ public final class ConditionalExpression implements Expression {
GTEQ(">="),
AND("&&"),
OR("||");
OR("||"),
NULL_COALESCE("??");
private final String name;
@ -45,15 +47,22 @@ public final class ConditionalExpression implements Expression {
@Override
public Value eval() {
final Value value1 = expr1.eval();
switch (operation) {
case AND: return NumberValue.fromBoolean(
(value1.asInt() != 0) && (expr2.eval().asInt() != 0) );
case OR: return NumberValue.fromBoolean(
(value1.asInt() != 0) || (expr2.eval().asInt() != 0) );
case AND:
return NumberValue.fromBoolean((expr1AsInt() != 0) && (expr2AsInt() != 0));
case OR:
return NumberValue.fromBoolean((expr1AsInt() != 0) || (expr2AsInt() != 0));
case NULL_COALESCE:
return nullCoalesce();
default:
return NumberValue.fromBoolean(evalAndCompare());
}
}
private boolean evalAndCompare() {
final Value value1 = expr1.eval();
final Value value2 = expr2.eval();
double number1, number2;
@ -65,20 +74,39 @@ public final class ConditionalExpression implements Expression {
number2 = 0;
}
boolean result;
switch (operation) {
case EQUALS: result = number1 == number2; break;
case NOT_EQUALS: result = number1 != number2; break;
case EQUALS: return number1 == number2;
case NOT_EQUALS: return number1 != number2;
case LT: result = number1 < number2; break;
case LTEQ: result = number1 <= number2; break;
case GT: result = number1 > number2; break;
case GTEQ: result = number1 >= number2; break;
case LT: return number1 < number2;
case LTEQ: return number1 <= number2;
case GT: return number1 > number2;
case GTEQ: return number1 >= number2;
default:
throw new OperationIsNotSupportedException(operation);
}
return NumberValue.fromBoolean(result);
}
private Value nullCoalesce() {
Value value1;
try {
value1 = expr1.eval();
} catch (NullPointerException npe) {
value1 = null;
}
if (value1 == null) {
return expr2.eval();
}
return value1;
}
private int expr1AsInt() {
return expr1.eval().asInt();
}
private int expr2AsInt() {
return expr2.eval().asInt();
}
@Override

View File

@ -0,0 +1,27 @@
def testZero() {
assertEquals(0, 0 ?? 1)
x = 0
assertEquals(0, x ?? 2)
}
def testObject() {
obj = {"a": 12}
assertEquals(12, obj.a ?? 10)
}
def testObjectMissingKey() {
obj = {"a": 12}
assertEquals(10, obj.test ?? 10)
}
def testNestedObjects() {
obj = {"a": {"b": 12}}
assertEquals(12, obj.a.b ?? 10)
}
def testNestedObjectsMissingKey() {
obj = {"a": {"b": 12}}
assertEquals(1, obj.test ?? 1)
assertEquals(2, obj.a.test ?? 2)
assertEquals(3, obj.test1.test2 ?? 3)
}