Добавлен оператор объединения с 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.STARSTAR);
OPERATORS.put("^^", TokenType.CARETCARET); OPERATORS.put("^^", TokenType.CARETCARET);
OPERATORS.put("?:", TokenType.QUESTIONCOLON); OPERATORS.put("?:", TokenType.QUESTIONCOLON);
OPERATORS.put("??", TokenType.QUESTIONQUESTION);
} }
private static final Map<String, TokenType> KEYWORDS; private static final Map<String, TokenType> KEYWORDS;

View File

@ -473,7 +473,7 @@ public final class Parser {
} }
private Expression ternary() { private Expression ternary() {
Expression result = logicalOr(); Expression result = nullCoalesce();
if (match(TokenType.QUESTION)) { if (match(TokenType.QUESTION)) {
final Expression trueExpr = expression(); final Expression trueExpr = expression();
@ -487,6 +487,20 @@ public final class Parser {
return result; 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() { private Expression logicalOr() {
Expression result = logicalAnd(); Expression result = logicalAnd();

View File

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

View File

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