From 28668461916c7f27a823fcbb9c828921f8036842 Mon Sep 17 00:00:00 2001 From: Mads Ravn Date: Mon, 23 Oct 2023 21:31:35 +0200 Subject: [PATCH] Booleans and Grouped Expressions --- .../madsravn/interpreter/ast/BooleanType.java | 29 +++ .../interpreter/ast/InfixExpression.java | 12 ++ .../madsravn/interpreter/parser/Parser.java | 20 +- .../interpreter/parser/ParserTest.java | 191 ++++++++++++++++-- 4 files changed, 235 insertions(+), 17 deletions(-) create mode 100644 src/main/java/dk/madsravn/interpreter/ast/BooleanType.java diff --git a/src/main/java/dk/madsravn/interpreter/ast/BooleanType.java b/src/main/java/dk/madsravn/interpreter/ast/BooleanType.java new file mode 100644 index 0000000..a49b523 --- /dev/null +++ b/src/main/java/dk/madsravn/interpreter/ast/BooleanType.java @@ -0,0 +1,29 @@ +package dk.madsravn.interpreter.ast; + +import dk.madsravn.interpreter.tokens.Token; + +public class BooleanType implements IExpression { + private Token token; + private boolean value; + + public BooleanType(Token token, boolean value) { + this.token = token; + this.value = value; + } + @Override + public void expressionNode() {} + + @Override + public String tokenLiteral() { + return token.getLiteral(); + } + + public boolean getValue() { + return value; + } + + @Override + public String string() { + return token.getLiteral(); + } +} diff --git a/src/main/java/dk/madsravn/interpreter/ast/InfixExpression.java b/src/main/java/dk/madsravn/interpreter/ast/InfixExpression.java index c99aec1..dc8b322 100644 --- a/src/main/java/dk/madsravn/interpreter/ast/InfixExpression.java +++ b/src/main/java/dk/madsravn/interpreter/ast/InfixExpression.java @@ -15,6 +15,18 @@ public InfixExpression(Token token, IExpression left, String operator, IExpressi this.right = right; } + public String getOperator(){ + return operator; + } + + public IExpression getRight() { + return right; + } + + public IExpression getLeft() { + return left; + } + @Override public void expressionNode() {} diff --git a/src/main/java/dk/madsravn/interpreter/parser/Parser.java b/src/main/java/dk/madsravn/interpreter/parser/Parser.java index 2f73967..1ddb9aa 100644 --- a/src/main/java/dk/madsravn/interpreter/parser/Parser.java +++ b/src/main/java/dk/madsravn/interpreter/parser/Parser.java @@ -78,14 +78,26 @@ private IExpression getPrefixExpression() { return parseIntegerLiteral(); case BANG, MINUS: return parsePrefixExpression(); + case TRUE, FALSE: + return parseBoolean(); + case LPAREN: + return parseGroupedExpression(); default: - // TODO: Add tests for these - //noPrefixParseFunctionError(currentToken.getType()); + noPrefixParseFunctionError(currentToken.getType()); // TODO: This is ugly return null; } } + private IExpression parseGroupedExpression() { + nextToken(); + IExpression expression = parseExpression(LOWEST); + if (!expectPeekType(RPAREN)) { + return null; + } + return expression; + } + private IExpression parseExpression(PrecedenceEnum precedence) { IExpression left = getPrefixExpression(); if (left == null) { @@ -148,7 +160,9 @@ private IExpression parseInfixExpression(IExpression left) { return new InfixExpression(token, left, token.getLiteral(), right); } - + private BooleanType parseBoolean() { + return new BooleanType(currentToken, currentTokenType(TokenType.TRUE)); + } private void noPrefixParseFunctionError(TokenType tokenType) { errors.add("No prefix parse function found for " + tokenType); diff --git a/src/test/java/dk/madsravn/interpreter/parser/ParserTest.java b/src/test/java/dk/madsravn/interpreter/parser/ParserTest.java index bac0dfb..13c885e 100644 --- a/src/test/java/dk/madsravn/interpreter/parser/ParserTest.java +++ b/src/test/java/dk/madsravn/interpreter/parser/ParserTest.java @@ -19,7 +19,7 @@ public void TestLetStatements() { """; Lexer lexer = new Lexer(input); Parser parser = new Parser(lexer); - checkForParseErrors(parser); + checkForParseErrors(parser, input); Program program = parser.parseProgram(); assertNotNull(program, "program is null"); @@ -47,7 +47,7 @@ public void TestReturnStatements() { """; Lexer lexer = new Lexer(input); Parser parser = new Parser(lexer); - checkForParseErrors(parser); + checkForParseErrors(parser, input); Program program = parser.parseProgram(); @@ -71,7 +71,7 @@ public void TestLetStatementWithErrors() { // TODO: Should the errors be on the program or on the parser? Program program = parser.parseProgram(); - assertEquals(parser.getErrors().size(), 3); + assertEquals(parser.getErrors().size(), 4); } @Test @@ -114,7 +114,7 @@ public void testIntegerLiteralExpression() { Lexer lexer = new Lexer(input); Parser parser = new Parser(lexer); Program program = parser.parseProgram(); - checkForParseErrors(parser); + checkForParseErrors(parser, input); assertEquals(program.getStatementsLength(), 1); IStatement statement = program.getStatements().get(0); @@ -127,11 +127,162 @@ public void testIntegerLiteralExpression() { assertEquals(integerLiteral.tokenLiteral(), "5"); } - private class PrefixData { + + + // TODO: InfixDataBoolean and InfixDataInteger needs to be merged somehow + private class InfixDataBoolean { + public String input; + public String operator; + public boolean leftValue; + public boolean rightValue; + public InfixDataBoolean(String input, boolean leftValue, String operator, boolean rightValue) { + this.input = input; + this.leftValue = leftValue; + this.operator = operator; + this.rightValue = rightValue; + } + } + + @Test + public void testParsingBooleanInfixExpressions() { + List inputs = Arrays.asList( + new InfixDataBoolean("true == true;", true, "==", true), + new InfixDataBoolean("true == false;", true, "==", false), + new InfixDataBoolean("false == true;", false, "==", true), + new InfixDataBoolean("false == false;", false, "==", false), + new InfixDataBoolean("true != true;", true, "!=", true), + new InfixDataBoolean("true != false;", true, "!=", false), + new InfixDataBoolean("false != true;", false, "!=", true), + new InfixDataBoolean("false != false;", false, "!=", false) + ); + for(InfixDataBoolean infixData : inputs) { + Lexer lexer = new Lexer(infixData.input); + Parser parser = new Parser(lexer); + Program program = parser.parseProgram(); + checkForParseErrors(parser, infixData.input); + + assertEquals(program.getStatementsLength(), 1); + assertTrue(program.getStatements().get(0) instanceof ExpressionStatement); + ExpressionStatement statement = (ExpressionStatement) program.getStatements().get(0); + assertTrue(statement.getExpression() instanceof InfixExpression); + InfixExpression infixExpression = (InfixExpression) statement.getExpression(); + assertEquals(infixExpression.getOperator(), infixData.operator); + + // TODO: testIntegerLiteral method for later + assertTrue(infixExpression.getRight() instanceof BooleanType); + BooleanType rightBooleanLiteral = (BooleanType) infixExpression.getRight(); + + assertEquals(rightBooleanLiteral.getValue(), infixData.rightValue); + assertEquals(rightBooleanLiteral.tokenLiteral(), "" + infixData.rightValue); + + assertTrue(infixExpression.getLeft() instanceof BooleanType); + BooleanType leftBooleanLiteral = (BooleanType) infixExpression.getLeft(); + + assertEquals(leftBooleanLiteral.getValue(), infixData.leftValue); + assertEquals(leftBooleanLiteral.tokenLiteral(), "" + infixData.leftValue); + } + + } + + private class InfixDataInteger { + public String input; + public String operator; + public int leftValue; + public int rightValue; + public InfixDataInteger(String input, int leftValue, String operator, int rightValue) { + this.input = input; + this.leftValue = leftValue; + this.operator = operator; + this.rightValue = rightValue; + } + } + + @Test + public void testParsingIntegerInfixExpressions() { + List inputs = Arrays.asList( + new InfixDataInteger("5 + 5;", 5, "+", 5), + new InfixDataInteger("5 - 5;", 5, "-", 5), + new InfixDataInteger("5 * 5;", 5, "*", 5), + new InfixDataInteger("5 / 5;", 5, "/", 5), + new InfixDataInteger("5 > 5;", 5, ">", 5), + new InfixDataInteger("5 < 5;", 5, "<", 5), + new InfixDataInteger("5 == 5;", 5, "==", 5), + new InfixDataInteger("5 != 5;", 5, "!=", 5) + ); + for(InfixDataInteger infixData : inputs) { + Lexer lexer = new Lexer(infixData.input); + Parser parser = new Parser(lexer); + Program program = parser.parseProgram(); + checkForParseErrors(parser, infixData.input); + + assertEquals(program.getStatementsLength(), 1); + assertTrue(program.getStatements().get(0) instanceof ExpressionStatement); + ExpressionStatement statement = (ExpressionStatement) program.getStatements().get(0); + assertTrue(statement.getExpression() instanceof InfixExpression); + InfixExpression infixExpression = (InfixExpression) statement.getExpression(); + assertEquals(infixExpression.getOperator(), infixData.operator); + + // TODO: testIntegerLiteral method for later + assertTrue(infixExpression.getRight() instanceof IntegerLiteral); + IntegerLiteral rightIntegerLiteral = (IntegerLiteral) infixExpression.getRight(); + + assertEquals(rightIntegerLiteral.getValue(), infixData.rightValue); + assertEquals(rightIntegerLiteral.tokenLiteral(), "" + infixData.rightValue); + + assertTrue(infixExpression.getLeft() instanceof IntegerLiteral); + IntegerLiteral leftIntegerLiteral = (IntegerLiteral) infixExpression.getLeft(); + + assertEquals(leftIntegerLiteral.getValue(), infixData.leftValue); + assertEquals(leftIntegerLiteral.tokenLiteral(), "" + infixData.leftValue); + } + + } + + private class PrefixDataBoolean + { + public String input; + public String operator; + public boolean value; + public PrefixDataBoolean(String input, String operator, boolean value) { + this.input = input; + this.operator = operator; + this.value = value; + } + } + + @Test + public void testParsingBooleanPrefixExpression() { + List inputs = Arrays.asList( + new PrefixDataBoolean("!true;", "!", true), + new PrefixDataBoolean("!false;", "!", false)); + for(PrefixDataBoolean prefixData : inputs) { + Lexer lexer = new Lexer(prefixData.input); + Parser parser = new Parser(lexer); + Program program = parser.parseProgram(); + checkForParseErrors(parser, prefixData.input); + + assertEquals(program.getStatementsLength(), 1); + assertTrue(program.getStatements().get(0) instanceof ExpressionStatement); + ExpressionStatement statement = (ExpressionStatement) program.getStatements().get(0); + assertTrue(statement.getExpression() instanceof PrefixExpression); + PrefixExpression prefixExpression = (PrefixExpression) statement.getExpression(); + assertEquals(prefixExpression.getOperator(), prefixData.operator); + + // TODO: testIntegerLiteral method for later + assertTrue(prefixExpression.getRight() instanceof BooleanType); + BooleanType booleanLiteral = (BooleanType) prefixExpression.getRight(); + + assertEquals(booleanLiteral .getValue(), prefixData.value); + assertEquals(booleanLiteral .tokenLiteral(), "" + prefixData.value); + } + } + + private class PrefixDataInteger + { public String input; public String operator; public int integerValue; - public PrefixData(String input, String operator, int integerValue) { + public PrefixDataInteger(String input, String operator, int integerValue) { this.input = input; this.operator = operator; this.integerValue = integerValue; @@ -139,13 +290,15 @@ public PrefixData(String input, String operator, int integerValue) { } @Test - public void testParsingPrefixExpression() { - List inputs = Arrays.asList(new PrefixData("!5;", "!", 5), new PrefixData("-15;", "-", 15)); - for(PrefixData prefixData : inputs) { + public void testParsingIntegerPrefixExpression() { + List inputs = Arrays.asList( + new PrefixDataInteger("!5;", "!", 5), + new PrefixDataInteger("-15;", "-", 15)); + for(PrefixDataInteger prefixData : inputs) { Lexer lexer = new Lexer(prefixData.input); Parser parser = new Parser(lexer); Program program = parser.parseProgram(); - checkForParseErrors(parser); + checkForParseErrors(parser, prefixData.input); assertEquals(program.getStatementsLength(), 1); assertTrue(program.getStatements().get(0) instanceof ExpressionStatement); @@ -186,7 +339,17 @@ public void testOperatorPrecedenceParsing() { new OperatorPrecedenceParsing("3 + 4; -5 * 5", "(3 + 4)((-5) * 5)"), new OperatorPrecedenceParsing("5 < 4 == 3 < 4", "((5 < 4) == (3 < 4))"), new OperatorPrecedenceParsing("5 > 4 != 3 > 4", "((5 > 4) != (3 > 4))"), - new OperatorPrecedenceParsing("3 + 4 * 5 == 3 * 1 + 4 * 5", "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))") + new OperatorPrecedenceParsing("3 + 4 * 5 == 3 * 1 + 4 * 5", "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))"), + new OperatorPrecedenceParsing("true", "true"), + new OperatorPrecedenceParsing("false", "false"), + new OperatorPrecedenceParsing("3 > 5 == false", "((3 > 5) == false)"), + new OperatorPrecedenceParsing("3 < 5 == true", "((3 < 5) == true)"), + new OperatorPrecedenceParsing("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)"), + new OperatorPrecedenceParsing("(5 + 5) * 2", "((5 + 5) * 2)"), + new OperatorPrecedenceParsing("2 / (5 + 5)", "(2 / (5 + 5))"), + new OperatorPrecedenceParsing("-(5 + 5)", "(-(5 + 5))"), + new OperatorPrecedenceParsing("!(true == true)", "(!(true == true))") + ); for (OperatorPrecedenceParsing operatorPrecedenceParsing : operatorPrecedenceParsingList) { @@ -194,13 +357,13 @@ public void testOperatorPrecedenceParsing() { Lexer lexer = new Lexer(input); Parser parser = new Parser(lexer); Program program = parser.parseProgram(); - checkForParseErrors(parser); + checkForParseErrors(parser, operatorPrecedenceParsing.input); assertEquals(program.string(), operatorPrecedenceParsing.expected); } } - private void checkForParseErrors(Parser parser) { - assertEquals(parser.getErrors().size(), 0, "There should not be any errors: " + parser.getErrors()); + private void checkForParseErrors(Parser parser, String input) { + assertEquals(parser.getErrors().size(), 0, "Input [" + input + "] should not gives errors. There should not be any errors: " + parser.getErrors()); } }