Skip to content
Snippets Groups Projects
Commit 7b3f3b69 authored by Dominic Daniel Krämer's avatar Dominic Daniel Krämer
Browse files

Merge branch 'dominicsbranch' into 'main'

Dominicsbranch

See merge request !13
parents 5e1ee50f 16856cd8
No related branches found
No related tags found
1 merge request!13Dominicsbranch
1+(x^2)-(3*x)
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
......@@ -32,17 +32,25 @@ public class Application {
}
//Iterate through every String
for (String expression: expressions) {
System.out.println("Expression: " + expression);
for (String expressionString: expressions) {
System.out.println("Expression: " + expressionString);
//Usage of the lexer, parser & evaluator
try {
//Create a list of tokens with the lexer
List<Lexer.Token> tokens = Lexer.lex(expression);
List<Lexer.Token> tokens = Lexer.lex(expressionString);
System.out.println("Lexer created tokens:");
//prints out each token to the console
for(Lexer.Token token: tokens) {
System.out.println(token.toString());
}
//Parse all tokens with the parser and saves the result into the variable 'ast'
Parser parser = new Parser();
Parser.Expression ast = parser.parse(tokens);
//Prints the ast out with an ASTPrinter
//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) + "'");
System.out.println();
}
catch (Exception e) {
e.printStackTrace();
......@@ -51,22 +59,10 @@ public class Application {
}
/*
//Iterates through every String and parses them into multiple tokens
for (String value: expressions) {
Parser parser = new Parser();
try {
Parser.Expression exp = parser.parse(tokens);
ASTPrinter printer = new ASTPrinter();
Evaluator evaluator = new Evaluator();
System.out.println("Printing: '" + printer.visit(exp) + "'");
String s = printer.visit(exp);
System.out.println("Evaluating: '" + evaluator.visit(exp) + "'");
SwingFunctionPlotter.plotFunction(s);
}
catch (ParserException e) {
System.out.println(e.getMessage());
}
System.out.println();
}*/
}
}
......@@ -93,7 +93,9 @@ public class Lexer {
index++;
}
}
if(result.isEmpty()) {
throw new LexerException("SyntaxError: lexer couldn't create any tokens out of the input");
}
return result;
}
}
......@@ -7,10 +7,17 @@ import java.util.List;
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
public abstract class Expression {
public abstract <T> T accept(Visitor<T> visitor);
}
//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;
......@@ -31,6 +38,8 @@ public class Parser {
}
}
//This class represents a variable, with it holds a String 'variableName' that contains a character
//of the alphabet
public class Variable extends Expression {
String variableName;
public Variable(String i, Boolean capsuled) {
......@@ -41,10 +50,13 @@ public class Parser {
}
}
//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
public class Number extends Value {
public String digits;
public Number(String i) {
......@@ -55,6 +67,7 @@ public class Parser {
}
}
//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;
......@@ -69,7 +82,7 @@ public class Parser {
}
//starting method of the parser
//parses a list of tokens
//parses a list of tokens and returns the AST (Abstract Syntax Tree)
public Expression parse(List<Lexer.Token> list) throws ParserException {
if(list.isEmpty()) {
throw new ParserException("empty token list");
......@@ -103,102 +116,158 @@ public class Parser {
return ast;
}
//checks if a String only contains an allowed operator with parseCharacter(...)
private boolean parseOperator(String operator) throws ParserException {
if(operator.length()>1) {
throw new ParserException("SyntaxError: invalid length for an operator: " + operator);
//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
private BinaryOperation parseBinaryOperation(List<Lexer.Token> ts) throws ParserException {
if(ts.isEmpty()) {
throw new ParserException("SyntaxError: empty token list");
}
if(!parseCharacter(operator, "+-*/^")) {
throw new ParserException("SyntaxError: invalid operator character: " + operator);
if(ts.size()==1) {
return null;
}
//Index will be determined in the next if/else statement
//It will indicate the position of the operator that separates the left- and right expression
int index;
int tokensInBracket = parseBrackets(ts);
boolean capsuled;
//If the result of parseBrackets(ts)+1 is the same as ts.size the current BinaryOperation is capsuled
if(tokensInBracket+1==ts.size()) {
//removes the tokens with the capsuling brackets
ts.remove(tokensInBracket);
ts.remove(0);
capsuled = true;
//Checks again for brackets
tokensInBracket = parseBrackets(ts);
//If no bracket is following, the operator needs to be at position 1 of ts
if(tokensInBracket==-1) {
index = 1;
}
else {
//If brackets are following, the operator is at position tokensInBracket+1
index = tokensInBracket+1;
}
}
//If tokensInBracket is -1 this expression is not capsuled and the operator needs to be at position 1 of ts
else if (tokensInBracket==-1) {
capsuled = false;
index = 1;
}
//If both other cases were not fulfilled an inner expression is capsuled
//Then this BinaryOperation is not capsuled, no brackets need to be removed and the operator is at
//position tokensInBracket+1
else {
capsuled = false;
index = tokensInBracket+1;
}
//Checks if the operator of this BinaryOperation is permitted
parseOperator(ts.get(index).getData());
List<Lexer.Token> leftList = new LinkedList<>();
//Splits the token list ts into a left and right expression
for(int iterator = 0; iterator<index; iterator++) {
leftList.add(ts.remove(0));
}
return true;
Expression leftExpression = parseExpression(leftList);
//Operator is now at position 0
String operator = ts.remove(0).getData();
Expression rightExpression = parseExpression(ts);
return new BinaryOperation(leftExpression, operator, rightExpression, capsuled);
}
//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
private int parseBrackets(List<Lexer.Token> ts) throws ParserException{
if(ts.isEmpty()) {
throw new ParserException("SyntaxError: empty token list");
}
if(parseCharacter(ts.get(0).getData(), "(")) {
int index;
int lBrackets = 0;
int rBrackets = 0;
boolean found = false;
//Iterates through every token, until the closing bracket got found
for(index=1; index<ts.size(); index++) {
if(parseCharacter(ts.get(index).getData(), "(")) {
lBrackets += 1;
}
else if(parseCharacter(ts.get(index).getData(), ")")) {
rBrackets += 1;
//If more ')' got found then '(' the bracket got closed correctly and the loop can be interrupted
if(rBrackets>lBrackets) {
found = true;
break;
}
}
}
//If no matching closing bracket was found a ParserException will be thrown
if(!found) {
throw new ParserException("SyntaxError: brackets never got closed");
}
//If the closing bracket was placed immediately after the opening bracket a ParserException will be thrown
if(index==1) {
throw new ParserException("SyntaxError: no value inside brackets");
}
//Returns the index of the closing bracket
return index;
}
return -1;
}
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;
}
int index = 0;
int tokensInBracket = parseBrackets(ts);
boolean capsuled;
if(tokensInBracket+1==ts.size()) {
ts.remove(tokensInBracket);
ts.remove(0);
capsuled = true;
index = 1;
}
else if (tokensInBracket==-1) {
capsuled = false;
parseOperator(ts.get(1).getData());
index = 1;
//called by parseBinaryOperation
//checks if a String only contains an allowed operator with parseCharacter
//if not: throws a ParserException
private void parseOperator(String operator) throws ParserException {
if(operator.isEmpty()) {
throw new ParserException("RuntimeError: empty String instead of an operator (+, -, *, /, ^");
}
else {
capsuled = false;
index = tokensInBracket+1;
if(operator.length()>1) {
throw new ParserException("SyntaxError: invalid length for an operator: " + operator);
}
List<Lexer.Token> leftList = new LinkedList<>();
for(int iterator = 0; iterator<index; iterator++) {
leftList.add(ts.remove(0));
if(!parseCharacter(operator, "+-*/^")) {
throw new ParserException("SyntaxError: invalid operator character: " + operator);
}
Expression leftExpression = parseExpression(leftList);
String operator = ts.remove(0).getData();
Expression rightExpression = parseExpression(ts);
return new BinaryOperation(leftExpression, operator, rightExpression, capsuled);
}
//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
private Variable parseVariable(List<Lexer.Token> ts) throws ParserException {
if (ts.isEmpty()) {
throw new ParserException("SyntaxError: empty token list");
}
//If the TokenType is != VARIABLE the token is not a variable and null gets returned
if (ts.get(0).getType() != Lexer.TokenType.VARIABLE) {
return null;
}
if (ts.get(0).getData().length()>1) {
throw new ParserException("Invalid size for a variable!");
}
String data = ts.remove(0).getData();
//If the token is empty, throw a ParserException
if (data.isEmpty()) {
throw new ParserException("SyntaxError: empty token");
}
//Checks if the variable is a character of the alphabet, if not the token is not valid
if(!parseCharacter(data,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")) {
throw new ParserException("Invalid variable character: " + data);
}
return new Variable(data, false);
}
//called by method parseException(...)
//parses a token/list of tokens into a value
//called by method parseException
//Checks if a token is a value:
//Yes: returns a new Number or Decimal that gets created with the received token
//No: returns null
private Value parseValue(List<Lexer.Token> ts) throws ParserException {
if (ts.isEmpty()) {
throw new ParserException("SyntaxError: empty token list");
......@@ -217,12 +286,13 @@ public class Parser {
return parseNumber(data, false);
}
//called by method parseValue(...)
//parses a decimal of a list of tokens & a string
//called by method parseValue
//parses a decimal of a string
private Decimal parseDecimal(String data) throws ParserException {
if(data.isEmpty()) {
throw new ParserException("SyntaxError: empty data");
}
//Separates the String into the number before- and after the dot
int dot = data.indexOf('.');
String number1 = data.substring(0,dot);
String number2 = data.substring(dot+1);
......@@ -231,30 +301,41 @@ public class Parser {
return new Decimal(beforeDot, afterDot);
}
//called by method parseValue(...)
//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
private Number parseNumber(String data, boolean afterDot) throws ParserException{
if (data.isEmpty()) {
throw new ParserException("RuntimeException: empty token");
}
//if the number only contains the digit 0, it can be returned already
if (data.startsWith("0") && data.length() == 1) {
return new Number(data);
}
if(afterDot) {
//Checks if all digits of the number are permitted
//If afterDot is false, the first digit is not allowed to be 0
if(!afterDot) {
parseDigitWithoutZero(data.substring(0,1));
parseDigit(data.substring(1));
if (data.length()>1) {
parseDigit(data.substring(1));
}
}
//Checks if all digits of the number are permitted
else {
parseDigit(data);
}
return new Number(data);
}
//called by method parseNumber(...)
//checks if a String only contains numbers(including zero) with parseCharacter(...)
//called by method parseNumber
//checks if a String only contains numbers(including zero) with parseCharacter
//if not, a ParserException is thrown
private void parseDigit(String data) throws ParserException {
for(int index=0; index<data.length(); index++) {
if(data.isEmpty()) {
throw new ParserException("RuntimeError: empty String instead of a digit(0-9)");
}
String character = Character.toString(data.charAt(index));
if(!parseCharacter(character, "0123456789")) {
throw new ParserException("SyntaxError: unexpected character: " + character);
......@@ -262,9 +343,13 @@ public class Parser {
}
}
//called by method parseNumber(...)
//checks if a String only contains numbers(excluding zero) with parseCharacter(...)
//called by method parseNumber
//checks if a String only contains numbers(excluding zero) with parseCharacter
//if not, a ParserException is thrown
private void parseDigitWithoutZero(String data) throws ParserException {
if(data.isEmpty()) {
throw new ParserException("RuntimeError: empty String instead of a digit(1-9)");
}
for(int index=0; index<data.length(); index++) {
String character = Character.toString(data.charAt(index));
if(!parseCharacter(character, "123456789")) {
......@@ -273,8 +358,8 @@ public class Parser {
}
}
//called by methods parseOperator(...), parseDigit(...), parseDigitWithoutZero(...)
//checks if a certain string can be found in a string of allowed character
//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
private boolean parseCharacter(String data, String allowedCharacters) throws ParserException {
if(data.isEmpty()) {
throw new ParserException("RuntimeError: empty String");
......
package test;
import main.Lexer;
import main.Parser;
import org.junit.Test;
import java.util.LinkedList;
import java.util.List;
public class ParserTest {
@Test
void testParseVariable() {
List<Lexer.Token> tokens = new LinkedList<>();
tokens.add(new Lexer.Token(Lexer.TokenType.VARIABLE,"x"));
var parser = new Parser();
try {
var ast = parser.parse(tokens);
}
catch(Exception e) {
e.printStackTrace();
}
}
@Test
void testWrongVariableInput() {
List<Lexer.Token> tokens = new LinkedList<>();
tokens.add(new Lexer.Token(Lexer.TokenType.VARIABLE,"xy"));
var parser = new Parser();
try {
var ast = parser.parse(tokens);
}
catch(Exception e) {
e.printStackTrace();
}
}
@Test
void testParseNumber() {
List<Lexer.Token> tokens = new LinkedList<>();
tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"23"));
var parser = new Parser();
try {
var ast = parser.parse(tokens);
}
catch(Exception e) {
e.printStackTrace();
}
}
@Test
void testWrongNumberInput() {
List<Lexer.Token> tokens = new LinkedList<>();
tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"02.4"));
var parser = new Parser();
try {
var ast = parser.parse(tokens);
}
catch(Exception e) {
e.printStackTrace();
}
}
@Test
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"));
tokens.add(new Lexer.Token(Lexer.TokenType.SPECIAL,"*"));
tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"2"));
tokens.add(new Lexer.Token(Lexer.TokenType.SPECIAL,")"));
tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"*"));
tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"3"));
var parser = new Parser();
try {
var ast = parser.parse(tokens);
}
catch(Exception e) {
e.printStackTrace();
}
}
@Test
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"));
tokens.add(new Lexer.Token(Lexer.TokenType.SPECIAL,"*"));
tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"2"));
tokens.add(new Lexer.Token(Lexer.TokenType.SPECIAL,")"));
tokens.add(new Lexer.Token(Lexer.TokenType.NUMBER,"3"));
var parser = new Parser();
try {
var ast = parser.parse(tokens);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment