diff --git a/expressions.txt b/expressions.txt index e06786f8951366a167d5dbc357fae99c9036adca..f46bda32ccf334ee798b2b135980269a6f6628de 100644 --- a/expressions.txt +++ b/expressions.txt @@ -1,3 +1 @@ -1 + xy^2 - (31 * x)y -1 + x^2 - (3 * x) -2x + 0,5x^2 - 1 \ No newline at end of file +x+2/5223+y+44+4+2^12/222 \ No newline at end of file diff --git a/src/Application.java b/src/Application.java index dedb78af92d61ca2c689a4d199698c80185aaab5..f8ac1ee4a8efcb6326fb7f33612862193a4dc8fb 100644 --- a/src/Application.java +++ b/src/Application.java @@ -2,36 +2,50 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; public class Application { public static void main(String[] args) { + //LinkedList<String> expressions = new LinkedList<>() + //LinkedList<String> expressions = new List<>(); + List<String> expressions = new LinkedList<>(); + //Reads the file "expressions.txt" and adds its values to a list BufferedReader reader; String datapath = "expressions.txt"; try { - System.out.println("Reading the file:"); + System.out.println("Reading the file...\n"); reader = new BufferedReader(new FileReader(datapath)); String line = reader.readLine(); while(line != null) { - System.out.println(line); + expressions.add(line); line = reader.readLine(); } - System.out.println(); reader.close(); } catch (IOException e ) { e.printStackTrace(); } - //List with a fitting String to be parsed into multiple tokens - List<String> test = Arrays.asList("1 + xy^2 - (31 * x)y"); - //Iterates through every String - for (String value: test) { + + //Iterates through every String and parses them into multiple tokens + for (String value: expressions) { + System.out.println(value); //creates a list of tokens out of the String List<Lexer.Token> tokens = Lexer.lex(value); + //prints each token out (with .toString()) for(Lexer.Token singleToken: tokens) { System.out.println(singleToken.toString()); } + Parser parser = new Parser(); + try { + parser.parse(tokens); + System.out.println(); + } + catch (ParserException e) { + System.out.println(e.getMessage()); + } + System.out.println(); } } } diff --git a/src/Lexer.java b/src/Lexer.java index a61e4cc3cdac280f042f1796efbf7314d887961d..a65c71b0985fbd51f4fc9a793228e2bd2baf31a9 100644 --- a/src/Lexer.java +++ b/src/Lexer.java @@ -47,6 +47,9 @@ public class Lexer { if(endIndex<input.length()) { while(Character.isDigit(input.charAt(endIndex)) && endIndex<input.length()) { endIndex += 1; + if(endIndex==input.length()) { + break; + } } } Token token = new Token(TokenType.NUMBER, input.substring(index,endIndex)); diff --git a/src/Parser.java b/src/Parser.java index 7e341d139f3c641d49ecf816ceb2290bca194520..85c780cd9be5f090a934fb42c37d54075032de2e 100644 --- a/src/Parser.java +++ b/src/Parser.java @@ -4,29 +4,45 @@ import java.util.Optional; public class Parser { - private abstract interface Expression { + private abstract class Expression { } - private abstract class Value implements Expression { + private class BinaryOperation extends Expression { + Expression leftExpression; + String operator; + Expression rightExpression; + public BinaryOperation(Expression leftExpression, String operator, Expression rightExpression) { + this.leftExpression = leftExpression; + this.operator = operator; + this.rightExpression = rightExpression; + } + } + + private class Variable extends Expression { + String variableName; + public Variable(String i) { + this.variableName = i; + } + } + + private abstract class Value extends Expression { } private class Number extends Value { - public String original; + public String digits; public Number(String i) { - this.original = i; + this.digits = i; } } private class Decimal extends Value { public Number beforeDot; - public char dot; public Number afterDot; public Decimal(Number i1, Number i2) { this.beforeDot = i1; - dot = '.'; this.afterDot = i2; } } @@ -42,9 +58,8 @@ public class Parser { Expression ast = parseExpression(ts); if(!ts.isEmpty()) { - throw new ParserException("SyntaxError: " + ts.size() + " Token(s) left"); + throw new ParserException("RuntimeError: " + ts.size() + " token(s) left"); } - return ast; } @@ -52,75 +67,111 @@ public class Parser { //parses a list of tokens into an expression private Expression parseExpression(List<Lexer.Token> ts) throws ParserException { if(ts.isEmpty()) { - throw new ParserException("empty token list"); + throw new ParserException("SyntaxError: empty token list"); + } + Expression ast; + ast = parseBinaryOperation(ts); + if(ast==null) { + ast = parseVariable(ts); + if(ast==null) { + ast = parseValue(ts); + } + if(ast==null) { + throw new ParserException("SyntaxError: invalid character"); + } } - - return parseValue(ts).orElseGet(() -> null); + return ast; } //checks if a String only contains an allowed operator with parseCharacter(...) - private void parseOperator(String operator) throws ParserException { + private Boolean parseOperator(String operator) throws ParserException { if(operator.length()>1) { - throw new ParserException("RuntimeException: invalid length for an operator: " + operator); + throw new ParserException("SyntaxError: invalid length for an operator: " + operator); + } + if(!parseCharacter(operator, "+-*/^")) { + throw new ParserException("SyntaxError: invalid operator character: " + operator); } - parseCharacter(operator, "+-*/^"); + return true; } - //called by methods parseOperator(...), parseDigit(...), parseDigitWithoutZero(...) - //checks if a certain string can be found in a string of allowed character - //if yes: return true - //if not: a ParserException is thrown - private Boolean parseCharacter(String data, String allowedCharacters) throws ParserException { - if(!allowedCharacters.contains(data)) { - throw new ParserException("SyntaxError: Invalid character: " + data); + private BinaryOperation parseBinaryOperation(List<Lexer.Token> ts) throws ParserException { + if(ts.isEmpty()) { + throw new ParserException("SyntaxError: empty token list"); + } + if(ts.size()==1) { + return null; + } + BinaryOperation operation; + if(ts.get(1).getType()!= Lexer.TokenType.SPECIAL) { + throw new ParserException("SyntaxError: unexpected operator: " + ts.get(1).getData()); + } + List<Lexer.Token> leftList = new LinkedList<>(); + leftList.add(ts.remove(0)); + Expression leftExpression = parseExpression(leftList); + String operator = ts.remove(0).getData(); + parseOperator(operator); + Expression rightExpression = parseExpression(ts); + return new BinaryOperation(leftExpression, operator, rightExpression); + } + + private Variable parseVariable(List<Lexer.Token> ts) throws ParserException { + if (ts.isEmpty()) { + throw new ParserException("SyntaxError: empty token list"); } - if(data.isEmpty()) { - throw new ParserException("RuntimeException: empty String"); + if (ts.get(0).getType() != Lexer.TokenType.VARIABLE) { + return null; } - return true; + + String data = ts.remove(0).getData(); + if (data.isEmpty()) { + throw new ParserException("SyntaxError: empty token"); + } + + if(!parseCharacter(data,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")) { + throw new ParserException("Invalid variable character: " + data); + } + return new Variable(data); } //called by method parseException(...) - //parses a list of tokens into a value - private Optional<Expression> parseValue(List<Lexer.Token> ts) throws ParserException { + //parses a token/list of tokens into a value + private Value parseValue(List<Lexer.Token> ts) throws ParserException { if (ts.isEmpty()) { - throw new ParserException("RuntimeException: empty token list"); + throw new ParserException("SyntaxError: empty token list"); } if (ts.get(0).getType() != Lexer.TokenType.NUMBER) { - return Optional.empty(); + return null; } - - String data = ts.remove(0).getData(); + String data = ts.get(0).getData(); if (data.isEmpty()) { - throw new ParserException("RuntimeException: empty token"); + throw new ParserException("SyntaxError: empty token"); } - if (ts.size()>0) { + Value value = null; + if (ts.size()>1) { //if the next token is of TokenType.SPECIAL, check if it's a comma //if it is, create a decimal if(ts.get(0).getType() == Lexer.TokenType.SPECIAL) { - if(ts.size()>1) { - if(parseComma(ts.get(0).getData())) { - return Optional.of(parseDecimal(ts, data)); - } + if(parseComma(ts)) { + value = parseDecimal(ts); } } } - //if the next token wasn't a comma, create a number - return Optional.of(parseNumber(data)); + if(value==null) { + value = parseNumber(ts.remove(0).getData()); + } + return value; } //called by method parseValue(...) //parses a decimal of a list of tokens & a string - private Expression parseDecimal(List<Lexer.Token> ts, String data) throws ParserException { - if(ts.size()<1) { - throw new ParserException("RuntimeException: empty token list"); - } - if(ts.get(0).getType() != Lexer.TokenType.SPECIAL) { - throw new ParserException("SyntaxException: expected a comma"); + private Decimal parseDecimal(List<Lexer.Token> ts) throws ParserException { + String data = ts.remove(0).getData(); + if(data.isEmpty()) { + throw new ParserException("SyntaxError: empty data"); } - if(ts.get(1).getType() != Lexer.TokenType.NUMBER) { - throw new ParserException("SyntaxException: expected a number after a comma"); + if(ts.size()==0) { + throw new ParserException("SyntaxError: no tokens left to create Decimal"); } Number beforeDot = (Number) parseNumber(data); ts.remove(0); @@ -131,7 +182,7 @@ public class Parser { //called by method parseValue(...) //parses a String into a number - private Expression parseNumber(String data) throws ParserException{ + private Number parseNumber(String data) throws ParserException{ if (data.isEmpty()) { throw new ParserException("RuntimeException: empty token"); } @@ -149,7 +200,10 @@ public class Parser { //checks if a String only contains numbers(including zero) with parseCharacter(...) private void parseDigit(String data) throws ParserException { for(int index=1; index<data.length(); index++) { - parseCharacter(Character.toString(data.charAt(index)), "0123456789"); + String character = Character.toString(data.charAt(index)); + if(!parseCharacter(character, "0123456789")) { + throw new ParserException("SyntaxError: unexpected character: " + character); + } } } @@ -157,11 +211,40 @@ public class Parser { //checks if a String only contains numbers(excluding zero) with parseCharacter(...) private void parseDigitWithoutZero(String data) throws ParserException { for(int index=1; index<data.length(); index++) { - parseCharacter(Character.toString(data.charAt(index)),"123456789"); + String character = Character.toString(data.charAt(index)); + if(!parseCharacter(character, "123456789")) { + throw new ParserException("SyntaxError: unexpected character: " + character); + } } } - private Boolean parseComma(String data) throws ParserException { - return true; + private Boolean parseComma(List<Lexer.Token> ts) throws ParserException { + String data = ts.get(0).getData(); + if(parseCharacter(data, ".")) { + if(ts.get(1).getType()!= Lexer.TokenType.NUMBER) { + throw new ParserException("SyntaxError: no number after comma"); + } + return true; + } + return false; + } + + private Boolean parseBracket(List<Lexer.Token> ts) throws ParserException { + String data = ts.get(0).getData(); + if(parseCharacter(data, "(")) { + return true; + } + else { + return false; + } + } + + //called by methods parseOperator(...), parseDigit(...), parseDigitWithoutZero(...) + //checks if a certain string can be found in a string of allowed character + private Boolean parseCharacter(String data, String allowedCharacters) throws ParserException { + if(data.isEmpty()) { + throw new ParserException("RuntimeError: empty String"); + } + return (allowedCharacters.contains(data)); } }