Skip to content

Commit

Permalink
Add token parser
Browse files Browse the repository at this point in the history
  • Loading branch information
karl-zschiebsch committed Sep 18, 2023
1 parent a21e44e commit 84c547d
Show file tree
Hide file tree
Showing 18 changed files with 472 additions and 19 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The Geometry feature offers a collection of classes and methods for performing g

### Proto

The Proto feature provides classes and utilities for handling protocol data. It includes functionality for serialization and deserialization of protocol buffers, as well as other related operations. This feature is beneficial for applications that communicate using
The Proto feature provides classes and utilities for handling protocol entries. It includes functionality for serialization and deserialization of protocol buffers, as well as other related operations. This feature is beneficial for applications that communicate using

## Contributing

Expand Down
51 changes: 51 additions & 0 deletions src/main/java/io/scvis/BaseOperator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.scvis;

import javax.annotation.Nonnull;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;

public class BaseOperator implements Operator {

public static final Operator ADD = new BaseOperator((a, b) ->
new Constant(a.get().doubleValue() + b.get().doubleValue()), 1) {
@Override
public boolean zeroAsLeft() {
return true;
}
};
public static final Operator SUBTRACT = new BaseOperator((a, b) ->
new Constant(a.get().doubleValue() - b.get().doubleValue()), 1) {
@Override
public boolean zeroAsLeft() {
return true;
}
};
public static final Operator MULTIPLY = new BaseOperator((a, b) ->
new Constant(a.get().doubleValue() * b.get().doubleValue()), 2);
public static final Operator DIVIDE = new BaseOperator((a, b) ->
new Constant(a.get().doubleValue() / b.get().doubleValue()), 2);
public static final Operator MOD = new BaseOperator((a, b) ->
new Constant(a.get().doubleValue() % b.get().doubleValue()), 3);
public static final Operator POW = new BaseOperator((a, b) ->
new Constant(Math.pow(a.get().doubleValue(), b.get().doubleValue())), 4);

private final @Nonnull BiFunction<Value, Value, Value> function;

private final int priority;

public BaseOperator(@Nonnull BinaryOperator<Value> function, int priority) {
this.function = function;
this.priority = priority;
}

@Nonnull
@Override
public Value evaluate(@Nonnull Value left, @Nonnull Value right) {
return function.apply(left, right);
}

@Override
public int priority() {
return priority;
}
}
19 changes: 19 additions & 0 deletions src/main/java/io/scvis/Brackets.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.scvis;

import javax.annotation.Nonnull;
import java.util.List;

public class Brackets implements Value {

private final @Nonnull Number value;

public Brackets(@Nonnull List<Operator> operators, @Nonnull List<Object> tokens) {
value = new TokenEvaluator(operators, tokens).evaluate();
}

@Nonnull
@Override
public Number get() {
return value;
}
}
23 changes: 23 additions & 0 deletions src/main/java/io/scvis/Constant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.scvis;

import javax.annotation.Nonnull;
import java.util.Map;

public class Constant implements Value {
private final @Nonnull Number value;

public static final Map<String, Constant> CONSTANT_MAP = Map.of(
"e", new Constant(Math.E), "pi", new Constant(Math.PI));

public static final Constant ZERO = new Constant(0.0);

public Constant(@Nonnull Number value) {
this.value = value;
}

@Nonnull
@Override
public Number get() {
return value;
}
}
29 changes: 29 additions & 0 deletions src/main/java/io/scvis/Function.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.scvis;

import javax.annotation.Nonnull;
import java.util.Map;
import java.util.function.DoubleFunction;

public class Function implements Value {

private final @Nonnull Number value;

public static final @Nonnull Map<String, DoubleFunction<Number>> DOUBLE_FUNCTION_MAP = Map.of(
"sin", Math::sin, "cos", Math::cos, "tan", Math::tan,
"asin", Math::asin, "acos", Math::acos, "atan", Math::atan,
"sqrt", Math::sqrt, "abs", Math::abs, "signum", Math::signum
);

public Function(@Nonnull String name, @Nonnull Brackets brackets) throws NoSuchMethodException {
DoubleFunction<Number> function = DOUBLE_FUNCTION_MAP.get(name);
if (function == null)
throw new NoSuchMethodException("No method found for " + name);
value = function.apply(brackets.get().doubleValue());
}

@Nonnull
@Override
public Number get() {
return value;
}
}
20 changes: 20 additions & 0 deletions src/main/java/io/scvis/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.scvis;

import javax.annotation.Nonnull;

public interface Operator extends Comparable<Operator> {

@Nonnull Value evaluate(@Nonnull Value left, @Nonnull Value right);

default boolean zeroAsLeft() {
return false;
}

int priority();

@Override
default int compareTo(@Nonnull Operator o) {
return o.priority() - priority();
}

}
46 changes: 46 additions & 0 deletions src/main/java/io/scvis/TokenEvaluator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.scvis;

import javax.annotation.Nonnull;
import java.util.*;

public class TokenEvaluator {

private final @Nonnull List<Operator> operators;
private final @Nonnull List<Object> tokens;

public TokenEvaluator(@Nonnull List<Operator> operators, @Nonnull List<Object> tokens) {
this.operators = operators;
Collections.sort(operators);
this.tokens = tokens;
}

private void eliminate() {
for (Operator operator : operators) {
int index = tokens.indexOf(operator);
if (index == 0) {
if (operator.zeroAsLeft()) {
Value evaluated = operator.evaluate(Constant.ZERO, (Value) tokens.get(1));
tokens.remove(1);
tokens.remove(0);
tokens.add(0, evaluated);
} else {
throw new UnsupportedOperationException("Operator does not allow zero as left!");
}
} else {
Value evaluated = operator.evaluate((Value) tokens.get(index - 1), (Value) tokens.get(index + 1));
for (int i = 1; i > -2; i--) {
tokens.remove(index + i);
}
tokens.add(index - 1, evaluated);
}
}
}

public Number evaluate() {
eliminate();
if (tokens.size() != 1) {
throw new ArithmeticException("False number of tokens are left after elimination: " + tokens.size());
}
return ((Value) tokens.get(0)).get();
}
}
129 changes: 129 additions & 0 deletions src/main/java/io/scvis/TokenParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package io.scvis;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class TokenParser {

private static final @Nonnull Map<Character, Operator> CHARACTER_OPERATOR_MAP = Map.of(
'+', BaseOperator.ADD, '-', BaseOperator.SUBTRACT, '*', BaseOperator.MULTIPLY,
'/', BaseOperator.DIVIDE, '%', BaseOperator.MOD, '^', BaseOperator.POW);

private int pos;

private final @Nonnull List<Operator> operators = new ArrayList<>();

private final @Nonnull List<Object> tokens = new ArrayList<>();

public void tokenize(String string) throws ParseException, NoSuchMethodException {
char[] chars = string.toCharArray();
pos = 0; // reset position
while (pos < chars.length) {
char c = chars[pos];
if (Character.isDigit(c) || c == '.') {
tokens.add(parseNumber(chars));
} else if (Character.isAlphabetic(c)) {
tokens.add(parseText(chars));
} else if (CHARACTER_OPERATOR_MAP.containsKey(c)) {
Operator operator = CHARACTER_OPERATOR_MAP.get(c);
tokens.add(operator);
operators.add(operator);
} else if (c == '(') {
tokens.add(parseBrackets(chars));
} else if (!Character.isSpaceChar(c)) {
throw new ParseException("Could not parse char: ", pos);
}
pos++;
}
}

@CheckReturnValue
@Nonnull
private Value parseVar(String name) throws NoSuchMethodException {
Constant constant = Constant.CONSTANT_MAP.get(name.toLowerCase());
if (constant == null)
throw new NoSuchMethodException("No variable found for: " + name);
return constant;
}

@CheckReturnValue
@Nonnull
private Brackets parseBrackets(char[] chars) throws ParseException, NoSuchMethodException {
StringBuilder body = new StringBuilder("(");
int open = 1;
pos++;
while (open > 0) {
char c = chars[pos];
if (c == '(') open++;
else if (c == ')') open--;
body.append(c);
pos++;
}
pos--;
TokenParser parser = new TokenParser();
parser.tokenize(body.substring(1, body.length() - 1));
return new Brackets(parser.operators, parser.tokens);
}

@CheckReturnValue
@Nonnull
private Value parseFunction(String name, char[] chars) throws ParseException, NoSuchMethodException {
return new Function(name, parseBrackets(chars));
}

@CheckReturnValue
@Nonnull
private Value parseText(char[] chars) throws ParseException, NoSuchMethodException {
StringBuilder build = new StringBuilder();
while (pos < chars.length && (Character.isAlphabetic(chars[pos]) || Character.isDigit(chars[pos]))) {
build.append(chars[pos]);
pos++;
}
if (pos < chars.length && chars[pos] == '(') {
return parseFunction(build.toString(), chars);
} else {
pos--;
return parseVar(build.toString());
}
}

@CheckReturnValue
@Nonnull
private Value parseNumber(char[] chars) throws ParseException {
double build = 0;
int real = -1;
while (pos < chars.length) {
char c = chars[pos];
if (Character.isDigit(c)) {
if (real > -1) {
build += Character.digit(c, 10) / Math.pow(10, pos - real);
} else {
build = build * 10.0 + Character.digit(c, 10);
}
} else if (c == '.') {
if (real > -1)
throw new ParseException("Could not parse number: ", pos);
real = pos;
} else {
pos--;
break;
}
pos++;
}
return new Constant(build);
}

@Nonnull
public List<Operator> getOperators() {
return operators;
}

@Nonnull
public List<Object> getTokens() {
return tokens;
}
}
8 changes: 8 additions & 0 deletions src/main/java/io/scvis/Value.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.scvis;

import javax.annotation.Nonnull;

public interface Value {

@Nonnull Number get();
}
Loading

0 comments on commit 84c547d

Please sign in to comment.