diff --git a/expressions.txt b/expressions.txt index bcba8954c44f26fe3d42dd1d3162ff0e75ad841d..ba3d6148e53d59ee0444357ada31b3a4c16ce1bd 100644 --- a/expressions.txt +++ b/expressions.txt @@ -1,6 +1,7 @@ -2*(4^2) -(4+2)*4 -(4+2*(4-2)) -((3+2)*(12+2)) -1+(x^2.22)-(3*x) -2*x+0.5*(x^2+4)-1 \ No newline at end of file +1+1 +2*4+x +2*5+2 +2+5*2 +2*(5+2) +20*(5^2) +(x^2)+50*x+2 \ No newline at end of file diff --git a/src/main/ASTPrinter.java b/src/main/ASTPrinter.java index 10669f24f21640f8f627bac93c2015ec2e3a16a8..2b7c72b951031b7a2a33f2b075d4faee227cf15c 100644 --- a/src/main/ASTPrinter.java +++ b/src/main/ASTPrinter.java @@ -1,13 +1,35 @@ package main; +import java.util.List; + +/** + * <h1>The ASTPrinter</h1> + * The ASTPrinter class creates a String out of an AST(represented through an Expression object) + * It prints it in a form the Evaluator class can work with it, it isn't primarily designed for + * returning a String that is perfect for human readability (e.g. Pow(x,3) would be printed instead of x^3) + * + * @version 1.0 + * @since 09.01.2023 + */ public class ASTPrinter implements Visitor<String>{ + + /** + * Builds a String representation of a given BinaryOperation + * It gets a String representation of its left and right Expressions (which may result in a + * recursive call) and combines them into one String together with the operator + * + * @param binaryOP The BinaryOperation that should be printed into a String + * @return String Returns the String that was build + */ @Override public String visit(final Parser.BinaryOperation binaryOP) { StringBuffer sb = new StringBuffer(); + //adds left bracket if the BinaryOperation is capsuled if(binaryOP.capsuled) { sb.append("("); } if(binaryOP.operator.contains("^")) { + //Exponents will be printed in the form of Pow(basis, exponent) so the Evaluator can work with it sb.append("Pow("); sb.append(binaryOP.leftExpression.accept(this)); sb.append(","); @@ -15,36 +37,53 @@ public class ASTPrinter implements Visitor<String>{ sb.append(")"); } else { + //Adds the String representations of the left expression, operator and right expression sb.append(binaryOP.leftExpression.accept(this)); sb.append(binaryOP.operator); sb.append(binaryOP.rightExpression.accept(this)); } - + //adds right bracket if the BinaryOperation is capsuled if(binaryOP.capsuled) { sb.append(")"); } return sb.toString(); } + + /** + * Returns the result of ast.accept(this), which will be the core values of a Value(digits), + * Variable(variableName) or BinaryOperation(will lead to a recursive call) + * @param ast The Expression + * @return ast.accept(this) + */ + public String visit(final Parser.Expression ast) { + if(ast==null) { + throw new RuntimeException("AST is null"); + } + return ast.accept(this); + } + + /** + * @see #visit(Parser.Expression) + */ @Override public String visit(Parser.Variable variable) { return variable.variableName; } + /** + * @see #visit(Parser.Expression) + */ @Override public String visit(Parser.Number number) { return number.digits; } + /** + * @see #visit(Parser.Expression) + */ @Override public String visit(Parser.Decimal decimal) { return decimal.beforeDot.digits + "." + decimal.afterDot.digits; } - - public String visit(final Parser.Expression ast) { - if(ast==null) { - throw new RuntimeException("AST is null"); - } - return ast.accept(this); - } } diff --git a/src/main/Application.java b/src/main/Application.java index 466f25baab48d96e8921b81867c1023640695c29..dd30e0237d237ddb4514d4ba8396be7c6dfa9078 100644 --- a/src/main/Application.java +++ b/src/main/Application.java @@ -1,10 +1,6 @@ package main; -import Exceptions.LexerException; -import Exceptions.ParserException; - import java.io.BufferedReader; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.LinkedList; @@ -40,8 +36,10 @@ public class Application { List<Lexer.Token> tokens = Lexer.lex(expressionString); System.out.println("Lexer created tokens:"); //prints out each token to the console + int i = 0; for(Lexer.Token token: tokens) { - System.out.println(token.toString()); + System.out.println("[" + i + "]" + token.toString()); + i++; } //Parse all tokens with the parser and saves the result into the variable 'ast' Parser parser = new Parser(); @@ -50,6 +48,8 @@ public class Application { //the result can later be used to show the ast in a function plotter ASTPrinter printer = new ASTPrinter(); System.out.println("Printing: '" + printer.visit(ast) + "'"); + Evaluator evaluator = new Evaluator(); + System.out.println(evaluator.visit(ast, 20)); System.out.println(); } catch (Exception e) { diff --git a/src/main/Evaluator.java b/src/main/Evaluator.java index 7f51f3ca78b76b92717ad16310dc3b3bc0a560c4..62a37d883ebde622fb6df7821f1b14ac1d18e262 100644 --- a/src/main/Evaluator.java +++ b/src/main/Evaluator.java @@ -1,50 +1,84 @@ package main; +import java.util.LinkedList; +import java.util.List; + public class Evaluator implements Visitor<Double>{ + private int x; + @Override public Double visit(final Parser.BinaryOperation binaryOP) { - double result = binaryOP.leftExpression.accept(this); - Double rOperand = binaryOP.rightExpression.accept(this); - if(rOperand==null) { + int range = 20; + List<Double> results = new LinkedList<>(); + for(int index=-20; index<=range; index++) { } + double result = binaryOP.leftExpression.accept(this); + double rOperand; + switch (binaryOP.operator.charAt(0)) { case '+': + rOperand = binaryOP.rightExpression.accept(this); result += rOperand; break; case '-': + rOperand = binaryOP.rightExpression.accept(this); result -= rOperand; break; case '*': - rOperand *= rOperand; - result *= rOperand; + if(binaryOP.rightExpression instanceof Parser.BinaryOperation) {; + if(((Parser.BinaryOperation) binaryOP.rightExpression).capsuled) { + rOperand = binaryOP.rightExpression.accept(this); + result *= rOperand; + } + else { + //*************************************************************************************** + result = ((Parser.BinaryOperation) binaryOP.rightExpression).accept(this, result, "*"); + } + } + else { + rOperand = binaryOP.rightExpression.accept(this); + result *= rOperand; + } break; case '/': - if (rOperand != 0) { - result /= rOperand; + if (binaryOP.rightExpression instanceof Parser.BinaryOperation) { + if(binaryOP.capsuled) { + rOperand = binaryOP.rightExpression.accept(this); + result /= rOperand; + } + else { + //*************************************************************************************** + result = ((Parser.BinaryOperation) binaryOP.rightExpression).accept(this, result, "/"); + } } else { - result = result; - //throw new ArithmeticException("Arithmetic Error: division by zero detected"); + rOperand = binaryOP.rightExpression.accept(this); + if(rOperand!=0) { + result /= rOperand; + } + else { + throw new ArithmeticException("Arithmetic Error: division by zero detected"); + } } break; case '^': + rOperand = binaryOP.rightExpression.accept(this); for(int i=1; i<rOperand; i++) { result = result*result; } break; default: - throw new RuntimeException("SematicError: unknown operand found in AST: " - + binaryOP.operator); + throw new RuntimeException("SemanticError: unknown operand found in AST: " + binaryOP.operator); } return result; } @Override public Double visit(final Parser.Variable variable) { - return 0.0; + return variable.value * x; } @Override @@ -57,10 +91,8 @@ public class Evaluator implements Visitor<Double>{ return Double.valueOf(decimal.afterDot.digits + "." + decimal.beforeDot.digits); } - public double visit(final Parser.Expression ast) { - if(ast==null) { - return 0.0; - } + public Double visit(final Parser.Expression ast, int x) { + this.x = x; return ast.accept(this); } } diff --git a/src/main/Lexer.java b/src/main/Lexer.java index 8b8b4ba2e2f51d1b53d776abf917c7d3550d2de9..b76e27d8a754b223e06351de59391893e1b6b3a1 100644 --- a/src/main/Lexer.java +++ b/src/main/Lexer.java @@ -5,15 +5,26 @@ import Exceptions.LexerException; import java.util.LinkedList; import java.util.List; +/** + * <h1>The Lexer</h1> + * The Lexer class crates a List of Token objects out of a String + * + * @version 1.0 + * @since 09.01.2023 + */ public class Lexer { - //enum TokenType that can either be a number, variable or a special character (like * or / etc.) + /** + * enum TokenType that can either be a number, variable or a special character (like * or / etc.) + */ public static enum TokenType { NUMBER, VARIABLE, SPECIAL } - //tokens with a TokenType that represents their type and a String with the actual data - //has for both values a getter method and overrides toString with its fitting values + /** + * tokens with a TokenType that represents their type and a String with the actual data + * has for both values a getter method and overrides toString with its fitting values + */ public static class Token { protected TokenType type; protected String data; @@ -36,7 +47,12 @@ public class Lexer { } } - //creates a list of tokens from a given String + /** + * creates a list of tokens from a given String + * @param input The String with the input + * @return result The List of Token objects + * @throws LexerException If the String was empty, or it couldn't create any result out of it + */ public static List<Token> lex(String input) throws Exception{ if(input.isEmpty()) { throw new LexerException("SyntaxError: lexer received empty String"); diff --git a/src/main/Parser.java b/src/main/Parser.java index e29e094a41aa98f6a70b70e0867c25957a6b2124..73327dd15d8f99895299add833b2a35bcd6a1af6 100644 --- a/src/main/Parser.java +++ b/src/main/Parser.java @@ -5,19 +5,31 @@ import Exceptions.ParserException; import java.util.LinkedList; import java.util.List; +/** + * <h1>The Parser</h1> + * The Parser class parses a list of Tokens into an Expression that represents an AST + * + * @version 1.0 + * @since 09.01.2023 + */ public class Parser { - //This abstract class is only used for heredity - //It represents an expression in the form of an Abstract Syntax Tree(AST) - //A method 'accept' is implemented, it is used for allowing other classes to interact with it - //through the visiting pattern + /** + * This abstract class is only used for heredity + * It represents an expression in the form of an Abstract Syntax Tree(AST) + * A method 'accept' is implemented, it is used for allowing other classes to interact with it + * through the visiting pattern + */ public abstract class Expression { public abstract <T> T accept(Visitor<T> visitor); + public abstract <T> T accept(Visitor<T> visitor, double leftValue, String operator); } - //This class represents a binary operation - //it contains a left expression, an operator, and a right expression - //if this operation is capsuled by brackets, the value of 'capsuled' will be true, else it will be false + /** + * This class represents a binary operation + * it contains a left expression, an operator, and a right expression + * if this operation is capsuled by brackets, the value of 'capsuled' will be true, else it will be false + */ public class BinaryOperation extends Expression { Expression leftExpression; String operator; @@ -29,34 +41,53 @@ public class Parser { this.rightExpression = rightExpression; this.capsuled = capsuled; } + @Override public <T> T accept(Visitor<T> visitor) { return visitor.visit(this); } - public <T> T accept(Visitor<T> vistor, String operator) { - return vistor.visit(this); + public <T> T accept(Visitor<T> visitor, double leftValue, String operator) { + leftExpression.accept(visitor, leftValue, operator); + return visitor.visit(this); } } - //This class represents a variable, with it holds a String 'variableName' that contains a character - //of the alphabet + /** + * This class represents a variable, with it holds a String 'variableName' that contains a character + * of the alphabet + */ public class Variable extends Expression { + double value; String variableName; - public Variable(String i, Boolean capsuled) { - this.variableName = i; + public Variable(String i) { + this.value = 1.0; + variableName = i; } public <T> T accept(Visitor<T> visitor) { return visitor.visit(this); } + public <T> T accept(Visitor<T> visitor, double leftValue, String operator) { + if(operator.equals("*")) { + this.value = leftValue; + } + else { + this.value = 1/leftValue; + } + return visitor.visit(this); + } } - //This abstract class is only used for heredity - //A value can either be a number or a decimal + /** + * This abstract class is only used for heredity + * A value can either be a number or a decimal + */ private abstract class Value extends Expression { } - //This class represents a number, it contains a String 'digits' that holds it value + /** + * This class represents a number, it contains a String 'digits' that holds it value + */ public class Number extends Value { public String digits; public Number(String i) { @@ -65,9 +96,20 @@ public class Parser { public <T> T accept(Visitor<T> visitor) { return visitor.visit(this); } + public <T> T accept(Visitor<T> visitor, double leftValue, String operator) { + if(operator.equals("*")) { + this.digits = String.valueOf((leftValue * Double.parseDouble(this.digits))); + } + else { + this.digits = String.valueOf((leftValue / Integer.parseInt(this.digits))); + } + return visitor.visit(this); + } } - //This class represents a decimal, it contains two numbers: the one before- and the one after the dot + /** + * This class represents a decimal, it contains two numbers: the one before- and the one after the dot + */ public class Decimal extends Value { public Number beforeDot; public Number afterDot; @@ -79,10 +121,20 @@ public class Parser { public <T> T accept(Visitor<T> visitor) { return visitor.visit(this); } + public <T> T accept(Visitor<T> visitor, double leftValue, String operator) { + beforeDot.accept(visitor, leftValue, operator); + afterDot.accept(visitor, leftValue, operator); + return visitor.visit(this); + } } - //starting method of the parser - //parses a list of tokens and returns the AST (Abstract Syntax Tree) + /** + * starting method of the parser that parses a list of tokens into an AST (Abstract Syntax Tree) + * @param list The list of tokens + * @return Expression Returns the Expression object that represents the AST + * @throws ParserException If the list of tokens was empty or not all tokens could be used in + * the process due to wrong syntax/semantic + */ public Expression parse(List<Lexer.Token> list) throws ParserException { if(list.isEmpty()) { throw new ParserException("empty token list"); @@ -97,8 +149,12 @@ public class Parser { return ast; } - //called by method parse(...) - //parses a list of tokens into an expression + /** + * parses a list of tokens into an expression (maybe with recursive calls through parseBinaryOperation(...) + * @param ts The list of tokens + * @return The expression object that got parsed + * @throws ParserException If the list of tokens was empty + */ private Expression parseExpression(List<Lexer.Token> ts) throws ParserException { if(ts.isEmpty()) { throw new ParserException("SyntaxError: empty token list"); @@ -116,12 +172,14 @@ public class Parser { return ast; } - //called by parseExpression - //checks if there are multiple tokens left, which indicates that they build up a BinaryOperation - //It splits up the tokens into a left and right expression and with a recursive call of parseExpression - //each part will be determined, until all BinaryOperations consist of finite left and right expressions - //Then a new BinaryOperation will be returned, which saves the left- & right expression and the operator - //between them + /** + * checks if there are multiple tokens left, which indicates that they build up a BinaryOperation + * It splits up the tokens into a left and right expression and with a recursive call of parseExpression + * each part will be determined, until all BinaryOperations consist of finite left and right expressions + * @param ts The list of tokens + * @return null If the list does not contain a BinaryOperation (anymore) or: BinaryOperation The crated BinaryOP + * @throws ParserException If the list of tokens was empty + * */ private BinaryOperation parseBinaryOperation(List<Lexer.Token> ts) throws ParserException { if(ts.isEmpty()) { throw new ParserException("SyntaxError: empty token list"); @@ -178,10 +236,13 @@ public class Parser { } - //called by parseBinaryOperator - //checks if the first token is a bracket - //If yes: checks if the bracket gets closed correctly and returns the index of the closing bracket - //If no: returns -1 + /** + * checks if the first token is a bracket + * @param ts The List of tokens + * @return int The position of the token with the closing bracket or -1 if the first token wasn't a bracket + * @throws ParserException If the list of tokens was empty, a bracket never gets closed or there isn't any + * value between the brackets + */ private int parseBrackets(List<Lexer.Token> ts) throws ParserException{ if(ts.isEmpty()) { throw new ParserException("SyntaxError: empty token list"); @@ -207,7 +268,7 @@ public class Parser { } //If no matching closing bracket was found a ParserException will be thrown if(!found) { - throw new ParserException("SyntaxError: brackets never got closed"); + throw new ParserException("SyntaxError: bracket never got closed"); } //If the closing bracket was placed immediately after the opening bracket a ParserException will be thrown if(index==1) { @@ -220,9 +281,11 @@ public class Parser { } - //called by parseBinaryOperation - //checks if a String only contains an allowed operator with parseCharacter - //if not: throws a ParserException + /** + * Checks if a String only contains an allowed operator + * @param operator The string that represents the operator + * @throws ParserException If the operator token was too big, small or consisted of invalid characters + */ private void parseOperator(String operator) throws ParserException { if(operator.isEmpty()) { throw new ParserException("RuntimeError: empty String instead of an operator (+, -, *, /, ^"); @@ -235,10 +298,13 @@ public class Parser { } } - //called by parseExpression - //Checks if a token is a variable: - //Yes: returns a new Variable that gets created with the received token - //No: returns null + /** + * Checks if a token is a variable: + * @param ts List of tokens + * @return null It the token isn't a variable or: Variable A new Variable that gets created with the received token + * @throws ParserException If the list of tokens was empty or the size of the token was too big + * or too small for a variable + */ private Variable parseVariable(List<Lexer.Token> ts) throws ParserException { if (ts.isEmpty()) { throw new ParserException("SyntaxError: empty token list"); @@ -260,11 +326,17 @@ public class Parser { if(!parseCharacter(data,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")) { throw new ParserException("Invalid variable character: " + data); } - return new Variable(data, false); + return new Variable(data); } - //called by method parseException + /** + * Checks if a token is a value and if so creates and returns a new Number or Decimal object + * @param ts The List of the tokens + * @return null If the token isn't a value/number or: Value The created Number or Decimal Object + * @throws ParserException If the list of tokens is empty or + */ + //Checks if a token is a value: //Yes: returns a new Number or Decimal that gets created with the received token //No: returns null @@ -286,8 +358,12 @@ public class Parser { return parseNumber(data, false); } - //called by method parseValue - //parses a decimal of a string + + /** + * parses a String into a Decimal object that consists of two numbers + * @param data The String that contains the value + * @return Decimal Returns the new Decimal object + */ private Decimal parseDecimal(String data) throws ParserException { if(data.isEmpty()) { throw new ParserException("SyntaxError: empty data"); @@ -301,9 +377,12 @@ public class Parser { return new Decimal(beforeDot, afterDot); } - //called by method parseValue or parseDecimal - //parses a String into a number - //the parameter afterDot will be set on 'true' if the number is used after a dot of a decimal + /** + * parses a String into a Number object + * @param data The String that contains the value + * @param afterDot If this Number will be used after a dot in a Decimal object + * @return Number Returns the new Number object + */ private Number parseNumber(String data, boolean afterDot) throws ParserException{ if (data.isEmpty()) { throw new ParserException("RuntimeException: empty token"); @@ -328,9 +407,11 @@ public class Parser { return new Number(data); } - //called by method parseNumber - //checks if a String only contains numbers(including zero) with parseCharacter - //if not, a ParserException is thrown + /** + * checks if a String only contains numbers(including zero) + * @param data The String that gets checked + * @exception ParserException If the data is empty or does contain invalid characters + */ private void parseDigit(String data) throws ParserException { for(int index=0; index<data.length(); index++) { if(data.isEmpty()) { @@ -343,9 +424,11 @@ public class Parser { } } - //called by method parseNumber - //checks if a String only contains numbers(excluding zero) with parseCharacter - //if not, a ParserException is thrown + /** + * checks if a String only contains numbers(excluding zero) + * @param data The String that gets checked + * @exception ParserException If the data is empty or does contain invalid characters + */ private void parseDigitWithoutZero(String data) throws ParserException { if(data.isEmpty()) { throw new ParserException("RuntimeError: empty String instead of a digit(1-9)"); @@ -358,8 +441,13 @@ public class Parser { } } - //called by multiple methods e.g. parserOperator, parseDigit - //checks if a certain string can be found in a String of allowed characters and returns the result + /** + * checks if a certain string can be found in a String of allowed characters + * @param data The String + * @param allowedCharacters The String of allowed characters + * @return boolean Returns if data was found in allowedCharacters or not + * @exception ParserException If the data is empty + */ private boolean parseCharacter(String data, String allowedCharacters) throws ParserException { if(data.isEmpty()) { throw new ParserException("RuntimeError: empty String"); diff --git a/src/main/Visitor.java b/src/main/Visitor.java index 88d87ae51a88b7e6f0edcfd8bf0213dbd26461e1..246556f5df36ecd0223edba5394d3ed0b8656167 100644 --- a/src/main/Visitor.java +++ b/src/main/Visitor.java @@ -1,5 +1,7 @@ package main; +import java.util.List; + public interface Visitor<T>{ public T visit(final Parser.BinaryOperation binOp); public T visit(final Parser.Variable variable); diff --git a/src/test/LexerTest.java b/src/test/LexerTest.java index 54356a6ec5573f4dc76a3cbd25d98d0a648e5ff9..aaceea7b4eb3bc47239590bd6653f2766697809a 100644 --- a/src/test/LexerTest.java +++ b/src/test/LexerTest.java @@ -3,16 +3,16 @@ package test; import org.junit.Test; import main.*; -import java.io.IOException; import java.util.LinkedList; import java.util.List; +import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.*; -class LexerTest { + class LexerTest { @Test - void disassembleFirstStringWithLexer() { + public void disassembleFirstStringWithLexer() { String s = "3+x*(2^4)"; List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"3")); @@ -33,7 +33,7 @@ class LexerTest { } @Test - void disassembleSecondStringWithLexer() { + public void disassembleSecondStringWithLexer() { String s = "1 + (x^2 )- (3*x)"; List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"1")); diff --git a/src/test/ParserTest.java b/src/test/ParserTest.java index 2d70d2edda5239923794264e4d64b10ed8ad0982..e58b0776d575e182d04c607242e7e157b3c8e976 100644 --- a/src/test/ParserTest.java +++ b/src/test/ParserTest.java @@ -7,10 +7,10 @@ import org.junit.Test; import java.util.LinkedList; import java.util.List; -public class ParserTest { +class ParserTest { @Test - void testParseVariable() { + public void testParseVariable() { List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.VARIABLE,"x")); var parser = new Parser(); @@ -23,7 +23,7 @@ public class ParserTest { } @Test - void testWrongVariableInput() { + public void testWrongVariableInput() { List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.VARIABLE,"xy")); var parser = new Parser(); @@ -36,7 +36,7 @@ public class ParserTest { } @Test - void testParseNumber() { + public void testParseNumber() { List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"23")); var parser = new Parser(); @@ -49,7 +49,7 @@ public class ParserTest { } @Test - void testWrongNumberInput() { + public void testWrongNumberInput() { List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"02.4")); var parser = new Parser(); @@ -62,7 +62,7 @@ public class ParserTest { } @Test - void testParseBinaryOperation() { + public void testParseBinaryOperation() { List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.SPECIAL,"(")); tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"5")); @@ -81,7 +81,7 @@ public class ParserTest { } @Test - void testParseWrongBinaryOperation() { + public void testParseWrongBinaryOperation() { List<Lexer.Token> tokens = new LinkedList<>(); tokens.add(new Lexer.Token(Lexer.TokenType.SPECIAL,"(")); tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"5"));