mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 08:44:20 +03:00
Добавлен оператор объединения с null
This commit is contained in:
parent
2e439d73b5
commit
0bd2aa10d5
@ -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;
|
||||||
|
@ -17,7 +17,7 @@ import java.util.Map;
|
|||||||
* @author aNNiMON
|
* @author aNNiMON
|
||||||
*/
|
*/
|
||||||
public final class Parser {
|
public final class Parser {
|
||||||
|
|
||||||
public static Statement parse(List<Token> tokens) {
|
public static Statement parse(List<Token> tokens) {
|
||||||
final Parser parser = new Parser(tokens);
|
final Parser parser = new Parser(tokens);
|
||||||
final Statement program = parser.parse();
|
final Statement program = parser.parse();
|
||||||
@ -26,7 +26,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Token EOF = new Token(TokenType.EOF, "", -1, -1);
|
private static final Token EOF = new Token(TokenType.EOF, "", -1, -1);
|
||||||
|
|
||||||
private static final EnumMap<TokenType, BinaryExpression.Operator> ASSIGN_OPERATORS;
|
private static final EnumMap<TokenType, BinaryExpression.Operator> ASSIGN_OPERATORS;
|
||||||
@ -52,7 +52,7 @@ public final class Parser {
|
|||||||
private final int size;
|
private final int size;
|
||||||
private final ParseErrors parseErrors;
|
private final ParseErrors parseErrors;
|
||||||
private Statement parsedStatement;
|
private Statement parsedStatement;
|
||||||
|
|
||||||
private int pos;
|
private int pos;
|
||||||
|
|
||||||
public Parser(List<Token> tokens) {
|
public Parser(List<Token> tokens) {
|
||||||
@ -60,15 +60,15 @@ public final class Parser {
|
|||||||
size = tokens.size();
|
size = tokens.size();
|
||||||
parseErrors = new ParseErrors();
|
parseErrors = new ParseErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Statement getParsedStatement() {
|
public Statement getParsedStatement() {
|
||||||
return parsedStatement;
|
return parsedStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParseErrors getParseErrors() {
|
public ParseErrors getParseErrors() {
|
||||||
return parseErrors;
|
return parseErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Statement parse() {
|
public Statement parse() {
|
||||||
parseErrors.clear();
|
parseErrors.clear();
|
||||||
final BlockStatement result = new BlockStatement();
|
final BlockStatement result = new BlockStatement();
|
||||||
@ -83,13 +83,13 @@ public final class Parser {
|
|||||||
parsedStatement = result;
|
parsedStatement = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getErrorLine() {
|
private int getErrorLine() {
|
||||||
if (size == 0) return 0;
|
if (size == 0) return 0;
|
||||||
if (pos >= size) return tokens.get(size - 1).getRow();
|
if (pos >= size) return tokens.get(size - 1).getRow();
|
||||||
return tokens.get(pos).getRow();
|
return tokens.get(pos).getRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recover() {
|
private void recover() {
|
||||||
int preRecoverPosition = pos;
|
int preRecoverPosition = pos;
|
||||||
for (int i = preRecoverPosition; i <= size; i++) {
|
for (int i = preRecoverPosition; i <= size; i++) {
|
||||||
@ -104,7 +104,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement block() {
|
private Statement block() {
|
||||||
final BlockStatement block = new BlockStatement();
|
final BlockStatement block = new BlockStatement();
|
||||||
consume(TokenType.LBRACE);
|
consume(TokenType.LBRACE);
|
||||||
@ -113,12 +113,12 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement statementOrBlock() {
|
private Statement statementOrBlock() {
|
||||||
if (lookMatch(0, TokenType.LBRACE)) return block();
|
if (lookMatch(0, TokenType.LBRACE)) return block();
|
||||||
return statement();
|
return statement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement statement() {
|
private Statement statement() {
|
||||||
if (match(TokenType.PRINT)) {
|
if (match(TokenType.PRINT)) {
|
||||||
return new PrintStatement(expression());
|
return new PrintStatement(expression());
|
||||||
@ -164,7 +164,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return assignmentStatement();
|
return assignmentStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement assignmentStatement() {
|
private Statement assignmentStatement() {
|
||||||
if (match(TokenType.EXTRACT)) {
|
if (match(TokenType.EXTRACT)) {
|
||||||
return destructuringAssignment();
|
return destructuringAssignment();
|
||||||
@ -175,7 +175,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
throw new ParseException("Unknown statement: " + get(0));
|
throw new ParseException("Unknown statement: " + get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DestructuringAssignmentStatement destructuringAssignment() {
|
private DestructuringAssignmentStatement destructuringAssignment() {
|
||||||
// extract(var1, var2, ...) = ...
|
// extract(var1, var2, ...) = ...
|
||||||
consume(TokenType.LPAREN);
|
consume(TokenType.LPAREN);
|
||||||
@ -191,7 +191,7 @@ public final class Parser {
|
|||||||
consume(TokenType.EQ);
|
consume(TokenType.EQ);
|
||||||
return new DestructuringAssignmentStatement(variables, expression());
|
return new DestructuringAssignmentStatement(variables, expression());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement ifElse() {
|
private Statement ifElse() {
|
||||||
final Expression condition = expression();
|
final Expression condition = expression();
|
||||||
final Statement ifStatement = statementOrBlock();
|
final Statement ifStatement = statementOrBlock();
|
||||||
@ -203,20 +203,20 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return new IfStatement(condition, ifStatement, elseStatement);
|
return new IfStatement(condition, ifStatement, elseStatement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement whileStatement() {
|
private Statement whileStatement() {
|
||||||
final Expression condition = expression();
|
final Expression condition = expression();
|
||||||
final Statement statement = statementOrBlock();
|
final Statement statement = statementOrBlock();
|
||||||
return new WhileStatement(condition, statement);
|
return new WhileStatement(condition, statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement doWhileStatement() {
|
private Statement doWhileStatement() {
|
||||||
final Statement statement = statementOrBlock();
|
final Statement statement = statementOrBlock();
|
||||||
consume(TokenType.WHILE);
|
consume(TokenType.WHILE);
|
||||||
final Expression condition = expression();
|
final Expression condition = expression();
|
||||||
return new DoWhileStatement(condition, statement);
|
return new DoWhileStatement(condition, statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement forStatement() {
|
private Statement forStatement() {
|
||||||
int foreachIndex = lookMatch(0, TokenType.LPAREN) ? 1 : 0;
|
int foreachIndex = lookMatch(0, TokenType.LPAREN) ? 1 : 0;
|
||||||
if (lookMatch(foreachIndex, TokenType.WORD)
|
if (lookMatch(foreachIndex, TokenType.WORD)
|
||||||
@ -243,7 +243,7 @@ public final class Parser {
|
|||||||
final Statement statement = statementOrBlock();
|
final Statement statement = statementOrBlock();
|
||||||
return new ForStatement(initialization, termination, increment, statement);
|
return new ForStatement(initialization, termination, increment, statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ForeachArrayStatement foreachArrayStatement() {
|
private ForeachArrayStatement foreachArrayStatement() {
|
||||||
// for x : arr
|
// for x : arr
|
||||||
boolean optParentheses = match(TokenType.LPAREN);
|
boolean optParentheses = match(TokenType.LPAREN);
|
||||||
@ -256,7 +256,7 @@ public final class Parser {
|
|||||||
final Statement statement = statementOrBlock();
|
final Statement statement = statementOrBlock();
|
||||||
return new ForeachArrayStatement(variable, container, statement);
|
return new ForeachArrayStatement(variable, container, statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ForeachMapStatement foreachMapStatement() {
|
private ForeachMapStatement foreachMapStatement() {
|
||||||
// for k, v : map
|
// for k, v : map
|
||||||
boolean optParentheses = match(TokenType.LPAREN);
|
boolean optParentheses = match(TokenType.LPAREN);
|
||||||
@ -271,7 +271,7 @@ public final class Parser {
|
|||||||
final Statement statement = statementOrBlock();
|
final Statement statement = statementOrBlock();
|
||||||
return new ForeachMapStatement(key, value, container, statement);
|
return new ForeachMapStatement(key, value, container, statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FunctionDefineStatement functionDefine() {
|
private FunctionDefineStatement functionDefine() {
|
||||||
// def name(arg1, arg2 = value) { ... } || def name(args) = expr
|
// def name(arg1, arg2 = value) { ... } || def name(args) = expr
|
||||||
final String name = consume(TokenType.WORD).getText();
|
final String name = consume(TokenType.WORD).getText();
|
||||||
@ -279,7 +279,7 @@ public final class Parser {
|
|||||||
final Statement body = statementBody();
|
final Statement body = statementBody();
|
||||||
return new FunctionDefineStatement(name, arguments, body);
|
return new FunctionDefineStatement(name, arguments, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Arguments arguments() {
|
private Arguments arguments() {
|
||||||
// (arg1, arg2, arg3 = expr1, arg4 = expr2)
|
// (arg1, arg2, arg3 = expr1, arg4 = expr2)
|
||||||
final Arguments arguments = new Arguments();
|
final Arguments arguments = new Arguments();
|
||||||
@ -299,14 +299,14 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Statement statementBody() {
|
private Statement statementBody() {
|
||||||
if (match(TokenType.EQ)) {
|
if (match(TokenType.EQ)) {
|
||||||
return new ReturnStatement(expression());
|
return new ReturnStatement(expression());
|
||||||
}
|
}
|
||||||
return statementOrBlock();
|
return statementOrBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression functionChain(Expression qualifiedNameExpr) {
|
private Expression functionChain(Expression qualifiedNameExpr) {
|
||||||
// f1()()() || f1().f2().f3() || f1().key
|
// f1()()() || f1().f2().f3() || f1().key
|
||||||
final Expression expr = function(qualifiedNameExpr);
|
final Expression expr = function(qualifiedNameExpr);
|
||||||
@ -339,7 +339,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return function;
|
return function;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression array() {
|
private Expression array() {
|
||||||
// [value1, value2, ...]
|
// [value1, value2, ...]
|
||||||
consume(TokenType.LBRACKET);
|
consume(TokenType.LBRACKET);
|
||||||
@ -350,7 +350,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return new ArrayExpression(elements);
|
return new ArrayExpression(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression map() {
|
private Expression map() {
|
||||||
// {key1 : value1, key2 : value2, ...}
|
// {key1 : value1, key2 : value2, ...}
|
||||||
consume(TokenType.LBRACE);
|
consume(TokenType.LBRACE);
|
||||||
@ -364,7 +364,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return new MapExpression(elements);
|
return new MapExpression(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MatchExpression match() {
|
private MatchExpression match() {
|
||||||
// match expression {
|
// match expression {
|
||||||
// case pattern1: result1
|
// case pattern1: result1
|
||||||
@ -378,12 +378,12 @@ public final class Parser {
|
|||||||
MatchExpression.Pattern pattern = null;
|
MatchExpression.Pattern pattern = null;
|
||||||
final Token current = get(0);
|
final Token current = get(0);
|
||||||
if (match(TokenType.NUMBER)) {
|
if (match(TokenType.NUMBER)) {
|
||||||
// case 0.5:
|
// case 0.5:
|
||||||
pattern = new MatchExpression.ConstantPattern(
|
pattern = new MatchExpression.ConstantPattern(
|
||||||
NumberValue.of(createNumber(current.getText(), 10))
|
NumberValue.of(createNumber(current.getText(), 10))
|
||||||
);
|
);
|
||||||
} else if (match(TokenType.HEX_NUMBER)) {
|
} else if (match(TokenType.HEX_NUMBER)) {
|
||||||
// case #FF:
|
// case #FF:
|
||||||
pattern = new MatchExpression.ConstantPattern(
|
pattern = new MatchExpression.ConstantPattern(
|
||||||
NumberValue.of(createNumber(current.getText(), 16))
|
NumberValue.of(createNumber(current.getText(), 16))
|
||||||
);
|
);
|
||||||
@ -393,7 +393,7 @@ public final class Parser {
|
|||||||
new StringValue(current.getText())
|
new StringValue(current.getText())
|
||||||
);
|
);
|
||||||
} else if (match(TokenType.WORD)) {
|
} else if (match(TokenType.WORD)) {
|
||||||
// case value:
|
// case value:
|
||||||
pattern = new MatchExpression.VariablePattern(current.getText());
|
pattern = new MatchExpression.VariablePattern(current.getText());
|
||||||
} else if (match(TokenType.LBRACKET)) {
|
} else if (match(TokenType.LBRACKET)) {
|
||||||
// case [x :: xs]:
|
// case [x :: xs]:
|
||||||
@ -417,7 +417,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
pattern = tuplePattern;
|
pattern = tuplePattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pattern == null) {
|
if (pattern == null) {
|
||||||
throw new ParseException("Wrong pattern in match expression: " + current);
|
throw new ParseException("Wrong pattern in match expression: " + current);
|
||||||
}
|
}
|
||||||
@ -425,7 +425,7 @@ public final class Parser {
|
|||||||
// case e if e > 0:
|
// case e if e > 0:
|
||||||
pattern.optCondition = expression();
|
pattern.optCondition = expression();
|
||||||
}
|
}
|
||||||
|
|
||||||
consume(TokenType.COLON);
|
consume(TokenType.COLON);
|
||||||
if (lookMatch(0, TokenType.LBRACE)) {
|
if (lookMatch(0, TokenType.LBRACE)) {
|
||||||
pattern.result = block();
|
pattern.result = block();
|
||||||
@ -434,14 +434,14 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
patterns.add(pattern);
|
patterns.add(pattern);
|
||||||
} while (!match(TokenType.RBRACE));
|
} while (!match(TokenType.RBRACE));
|
||||||
|
|
||||||
return new MatchExpression(expression, patterns);
|
return new MatchExpression(expression, patterns);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression expression() {
|
private Expression expression() {
|
||||||
return assignment();
|
return assignment();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression assignment() {
|
private Expression assignment() {
|
||||||
final Expression assignment = assignmentStrict();
|
final Expression assignment = assignmentStrict();
|
||||||
if (assignment != null) {
|
if (assignment != null) {
|
||||||
@ -449,7 +449,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return ternary();
|
return ternary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression assignmentStrict() {
|
private Expression assignmentStrict() {
|
||||||
// x[0].prop += ...
|
// x[0].prop += ...
|
||||||
final int position = pos;
|
final int position = pos;
|
||||||
@ -465,16 +465,16 @@ public final class Parser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
match(currentType);
|
match(currentType);
|
||||||
|
|
||||||
final BinaryExpression.Operator op = ASSIGN_OPERATORS.get(currentType);
|
final BinaryExpression.Operator op = ASSIGN_OPERATORS.get(currentType);
|
||||||
final Expression expression = expression();
|
final Expression expression = expression();
|
||||||
|
|
||||||
return new AssignmentExpression(op, (Accessible) targetExpr, expression);
|
return new AssignmentExpression(op, (Accessible) targetExpr, expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
consume(TokenType.COLON);
|
consume(TokenType.COLON);
|
||||||
@ -486,10 +486,24 @@ 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();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.BARBAR)) {
|
if (match(TokenType.BARBAR)) {
|
||||||
result = new ConditionalExpression(ConditionalExpression.Operator.OR, result, logicalAnd());
|
result = new ConditionalExpression(ConditionalExpression.Operator.OR, result, logicalAnd());
|
||||||
@ -497,13 +511,13 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression logicalAnd() {
|
private Expression logicalAnd() {
|
||||||
Expression result = bitwiseOr();
|
Expression result = bitwiseOr();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.AMPAMP)) {
|
if (match(TokenType.AMPAMP)) {
|
||||||
result = new ConditionalExpression(ConditionalExpression.Operator.AND, result, bitwiseOr());
|
result = new ConditionalExpression(ConditionalExpression.Operator.AND, result, bitwiseOr());
|
||||||
@ -511,13 +525,13 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression bitwiseOr() {
|
private Expression bitwiseOr() {
|
||||||
Expression expression = bitwiseXor();
|
Expression expression = bitwiseXor();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.BAR)) {
|
if (match(TokenType.BAR)) {
|
||||||
expression = new BinaryExpression(BinaryExpression.Operator.OR, expression, bitwiseXor());
|
expression = new BinaryExpression(BinaryExpression.Operator.OR, expression, bitwiseXor());
|
||||||
@ -525,13 +539,13 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression bitwiseXor() {
|
private Expression bitwiseXor() {
|
||||||
Expression expression = bitwiseAnd();
|
Expression expression = bitwiseAnd();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.CARET)) {
|
if (match(TokenType.CARET)) {
|
||||||
expression = new BinaryExpression(BinaryExpression.Operator.XOR, expression, bitwiseAnd());
|
expression = new BinaryExpression(BinaryExpression.Operator.XOR, expression, bitwiseAnd());
|
||||||
@ -539,13 +553,13 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression bitwiseAnd() {
|
private Expression bitwiseAnd() {
|
||||||
Expression expression = equality();
|
Expression expression = equality();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.AMP)) {
|
if (match(TokenType.AMP)) {
|
||||||
expression = new BinaryExpression(BinaryExpression.Operator.AND, expression, equality());
|
expression = new BinaryExpression(BinaryExpression.Operator.AND, expression, equality());
|
||||||
@ -553,26 +567,26 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression equality() {
|
private Expression equality() {
|
||||||
Expression result = conditional();
|
Expression result = conditional();
|
||||||
|
|
||||||
if (match(TokenType.EQEQ)) {
|
if (match(TokenType.EQEQ)) {
|
||||||
return new ConditionalExpression(ConditionalExpression.Operator.EQUALS, result, conditional());
|
return new ConditionalExpression(ConditionalExpression.Operator.EQUALS, result, conditional());
|
||||||
}
|
}
|
||||||
if (match(TokenType.EXCLEQ)) {
|
if (match(TokenType.EXCLEQ)) {
|
||||||
return new ConditionalExpression(ConditionalExpression.Operator.NOT_EQUALS, result, conditional());
|
return new ConditionalExpression(ConditionalExpression.Operator.NOT_EQUALS, result, conditional());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression conditional() {
|
private Expression conditional() {
|
||||||
Expression result = shift();
|
Expression result = shift();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.LT)) {
|
if (match(TokenType.LT)) {
|
||||||
result = new ConditionalExpression(ConditionalExpression.Operator.LT, result, shift());
|
result = new ConditionalExpression(ConditionalExpression.Operator.LT, result, shift());
|
||||||
@ -592,13 +606,13 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression shift() {
|
private Expression shift() {
|
||||||
Expression expression = additive();
|
Expression expression = additive();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.LTLT)) {
|
if (match(TokenType.LTLT)) {
|
||||||
expression = new BinaryExpression(BinaryExpression.Operator.LSHIFT, expression, additive());
|
expression = new BinaryExpression(BinaryExpression.Operator.LSHIFT, expression, additive());
|
||||||
@ -618,13 +632,13 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression additive() {
|
private Expression additive() {
|
||||||
Expression result = multiplicative();
|
Expression result = multiplicative();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.PLUS)) {
|
if (match(TokenType.PLUS)) {
|
||||||
result = new BinaryExpression(BinaryExpression.Operator.ADD, result, multiplicative());
|
result = new BinaryExpression(BinaryExpression.Operator.ADD, result, multiplicative());
|
||||||
@ -648,13 +662,13 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression multiplicative() {
|
private Expression multiplicative() {
|
||||||
Expression result = unary();
|
Expression result = unary();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (match(TokenType.STAR)) {
|
if (match(TokenType.STAR)) {
|
||||||
result = new BinaryExpression(BinaryExpression.Operator.MULTIPLY, result, unary());
|
result = new BinaryExpression(BinaryExpression.Operator.MULTIPLY, result, unary());
|
||||||
@ -674,10 +688,10 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression unary() {
|
private Expression unary() {
|
||||||
if (match(TokenType.PLUSPLUS)) {
|
if (match(TokenType.PLUSPLUS)) {
|
||||||
return new UnaryExpression(UnaryExpression.Operator.INCREMENT_PREFIX, primary());
|
return new UnaryExpression(UnaryExpression.Operator.INCREMENT_PREFIX, primary());
|
||||||
@ -699,14 +713,14 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return primary();
|
return primary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression primary() {
|
private Expression primary() {
|
||||||
if (match(TokenType.LPAREN)) {
|
if (match(TokenType.LPAREN)) {
|
||||||
Expression result = expression();
|
Expression result = expression();
|
||||||
consume(TokenType.RPAREN);
|
consume(TokenType.RPAREN);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match(TokenType.COLONCOLON)) {
|
if (match(TokenType.COLONCOLON)) {
|
||||||
// ::method reference
|
// ::method reference
|
||||||
final String functionName = consume(TokenType.WORD).getText();
|
final String functionName = consume(TokenType.WORD).getText();
|
||||||
@ -729,7 +743,7 @@ public final class Parser {
|
|||||||
if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.LPAREN)) {
|
if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.LPAREN)) {
|
||||||
return functionChain(new ValueExpression(consume(TokenType.WORD).getText()));
|
return functionChain(new ValueExpression(consume(TokenType.WORD).getText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Expression qualifiedNameExpr = qualifiedName();
|
final Expression qualifiedNameExpr = qualifiedName();
|
||||||
if (qualifiedNameExpr != null) {
|
if (qualifiedNameExpr != null) {
|
||||||
// variable(args) || arr["key"](args) || obj.key(args)
|
// variable(args) || arr["key"](args) || obj.key(args)
|
||||||
@ -745,7 +759,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return qualifiedNameExpr;
|
return qualifiedNameExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lookMatch(0, TokenType.LBRACKET)) {
|
if (lookMatch(0, TokenType.LBRACKET)) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
@ -754,12 +768,12 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return value();
|
return value();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression qualifiedName() {
|
private Expression qualifiedName() {
|
||||||
// var || var.key[index].key2
|
// var || var.key[index].key2
|
||||||
final Token current = get(0);
|
final Token current = get(0);
|
||||||
if (!match(TokenType.WORD)) return null;
|
if (!match(TokenType.WORD)) return null;
|
||||||
|
|
||||||
final List<Expression> indices = variableSuffix();
|
final List<Expression> indices = variableSuffix();
|
||||||
if (indices == null || indices.isEmpty()) {
|
if (indices == null || indices.isEmpty()) {
|
||||||
return new VariableExpression(current.getText());
|
return new VariableExpression(current.getText());
|
||||||
@ -786,7 +800,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return indices;
|
return indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression value() {
|
private Expression value() {
|
||||||
final Token current = get(0);
|
final Token current = get(0);
|
||||||
if (match(TokenType.NUMBER)) {
|
if (match(TokenType.NUMBER)) {
|
||||||
@ -816,7 +830,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
throw new ParseException("Unknown expression: " + current);
|
throw new ParseException("Unknown expression: " + current);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Number createNumber(String text, int radix) {
|
private Number createNumber(String text, int radix) {
|
||||||
// Double
|
// Double
|
||||||
if (text.contains(".")) {
|
if (text.contains(".")) {
|
||||||
@ -829,7 +843,7 @@ public final class Parser {
|
|||||||
return Long.parseLong(text, radix);
|
return Long.parseLong(text, radix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Token consume(TokenType type) {
|
private Token consume(TokenType type) {
|
||||||
final Token current = get(0);
|
final Token current = get(0);
|
||||||
if (type != current.getType()) {
|
if (type != current.getType()) {
|
||||||
@ -838,7 +852,7 @@ public final class Parser {
|
|||||||
pos++;
|
pos++;
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean match(TokenType type) {
|
private boolean match(TokenType type) {
|
||||||
final Token current = get(0);
|
final Token current = get(0);
|
||||||
if (type != current.getType()) {
|
if (type != current.getType()) {
|
||||||
@ -847,11 +861,11 @@ public final class Parser {
|
|||||||
pos++;
|
pos++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean lookMatch(int pos, TokenType type) {
|
private boolean lookMatch(int pos, TokenType type) {
|
||||||
return get(pos).getType() == type;
|
return get(pos).getType() == type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Token get(int relativePosition) {
|
private Token get(int relativePosition) {
|
||||||
final int position = pos + relativePosition;
|
final int position = pos + relativePosition;
|
||||||
if (position >= size) return EOF;
|
if (position >= size) return EOF;
|
||||||
|
@ -69,6 +69,7 @@ public enum TokenType {
|
|||||||
DOTDOT, // ..
|
DOTDOT, // ..
|
||||||
STARSTAR, // **
|
STARSTAR, // **
|
||||||
QUESTIONCOLON, // ?:
|
QUESTIONCOLON, // ?:
|
||||||
|
QUESTIONQUESTION, // ??
|
||||||
|
|
||||||
TILDE, // ~
|
TILDE, // ~
|
||||||
CARET, // ^
|
CARET, // ^
|
||||||
|
@ -10,19 +10,21 @@ import com.annimon.ownlang.lib.Value;
|
|||||||
* @author aNNiMON
|
* @author aNNiMON
|
||||||
*/
|
*/
|
||||||
public final class ConditionalExpression implements Expression {
|
public final class ConditionalExpression implements Expression {
|
||||||
|
|
||||||
public enum Operator {
|
public enum Operator {
|
||||||
EQUALS("=="),
|
EQUALS("=="),
|
||||||
NOT_EQUALS("!="),
|
NOT_EQUALS("!="),
|
||||||
|
|
||||||
LT("<"),
|
LT("<"),
|
||||||
LTEQ("<="),
|
LTEQ("<="),
|
||||||
GT(">"),
|
GT(">"),
|
||||||
GTEQ(">="),
|
GTEQ(">="),
|
||||||
|
|
||||||
AND("&&"),
|
AND("&&"),
|
||||||
OR("||");
|
OR("||"),
|
||||||
|
|
||||||
|
NULL_COALESCE("??");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private Operator(String name) {
|
private Operator(String name) {
|
||||||
@ -33,7 +35,7 @@ public final class ConditionalExpression implements Expression {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Expression expr1, expr2;
|
public final Expression expr1, expr2;
|
||||||
public final Operator operation;
|
public final Operator operation;
|
||||||
|
|
||||||
@ -45,17 +47,24 @@ 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;
|
||||||
if (value1.type() == Types.NUMBER) {
|
if (value1.type() == Types.NUMBER) {
|
||||||
number1 = value1.asNumber();
|
number1 = value1.asNumber();
|
||||||
@ -64,23 +73,42 @@ public final class ConditionalExpression implements Expression {
|
|||||||
number1 = value1.compareTo(value2);
|
number1 = value1.compareTo(value2);
|
||||||
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
|
||||||
public void accept(Visitor visitor) {
|
public void accept(Visitor visitor) {
|
||||||
visitor.visit(this);
|
visitor.visit(this);
|
||||||
|
27
src/test/resources/expressions/nullCoalesce.own
Normal file
27
src/test/resources/expressions/nullCoalesce.own
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user