Skip to content

Commit

Permalink
Small argument passing improvements and improved docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
FourteenBrush committed Nov 15, 2023
1 parent 49dd638 commit c151659
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 27 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ assert result == 4;
```

Functions cannot be overloaded, but you can define a function that accepts a variable amount of arguments.
PS: don't actually do this, there is already a built-in sum function:

```java
/*
* This inserts a function called 'add' that returns the sum of all of its arguments.
Expand All @@ -110,6 +110,7 @@ ExpressionParser.insertFunction("add", 2, 10, ctx -> {
double result = ExpressionParser.parse("add(1, 2, 4)");
assert result == 7;
```
PS: don't actually do this, there is already a built-in sum function.

For more complex functions, take a look at the method that accepts a `Symbol` and pass in a
[FunctionCallSite](core/src/main/java/me/fourteendoggo/mathexpressionparser/function/FunctionCallSite.java).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void insertFunction(String name, DoubleBinaryOperator fn) {
* @see #insertFunction(String, int, int, ToDoubleFunction)
*/
public void insertFunction(String name, int numArgs, ToDoubleFunction<FunctionContext> fn) {
insertSymbol(new FunctionCallSite(name, numArgs, numArgs, fn));
insertFunction(name, numArgs, numArgs, fn);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

/**
* A placeholder for an invokable function.
* @see FunctionContext
* Invocation happens via {@link #apply(FunctionContext)}.
* @see FunctionContext on how to deal with parameters.
*/
public class FunctionCallSite implements Symbol {
private final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void add(double value) {
}

/**
* Either eturns an unsigned int or fails.
* Either returns an unsigned int or fails.
* @see #getBoundedInt(int, int, int)
*/
public int getUnsignedInt(int index, int max) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import java.util.function.Supplier;

// TODO: allow more different chars as symbol name

/**
* An efficient lookup tree for {@link Symbol}s.
*/
public class SymbolLookup {
private static final int CHILDREN_WIDTH = 26;
private final IntPredicate characterValidator;
Expand All @@ -31,6 +35,12 @@ public void insert(Symbol symbol) {
Assert.isFalse(lastNode instanceof ValueHoldingNode, "symbol %s was already inserted", symbol.getName());
}

/**
* Looks up a {@link Symbol} in the given char buffer, starting at the given position.
* @param buf the char buffer supplied by the tokenizer.
* @param pos the position to start looking at.
* @return a {@link Symbol} or null if not found.
*/
public Symbol lookup(char[] buf, int pos) {
Node node = root;
char current;
Expand Down Expand Up @@ -74,10 +84,11 @@ public Node insert(char value, Node node) {
}

private int indexOrThrow(char value) {
Assert.isTrue(characterValidator.test(value), "character " + value + " is not allowed to be used");
Assert.isTrue(characterValidator.test(value), "character %s is not allowed to be used", value);
return value - 'a';
}

// FIXME: more tree like string representation
@Override
public String toString() {
StringBuilder sb = new StringBuilder(100);
Expand All @@ -99,6 +110,9 @@ public String toString() {
}
}

/**
* A node that holds a {@link Symbol}, this node can still have child nodes.
*/
private class ValueHoldingNode extends Node {
private final Symbol symbol;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

/**
* Represents a parsed expression in a solvable form. <br>
* @see this#solve()
* @see TokenList#solve()
*/
public class TokenList {
private LinkedCalculation head, tail;
Expand Down Expand Up @@ -45,21 +45,21 @@ private void checkType(TokenType type) {
* Simple expressions like 1+1 or 1*3+1 will be solved in one pass.
* <h2>Algorithm explained:</h2>
* The algorithm works with a linked list of calculations, each calculation object is a
* wrapper for an operator and its surrounding operands, this causes overlapping with operands but is perfectly fine <br/>
* wrapper for an operator and its surrounding operands, this causes overlapping with operands but is perfectly fine. <br/>
* <p/>
* To solve, we start at the head of the list and take two calculations. <br/>
* If the first calculation has an operator with a higher or the same priority,
* we can safely solve the first calculation and apply its result to the left of the second calculation
* we can safely solve the first calculation and set its result to the left of the second calculation
* and unlink that first calculation. <br/>
* For example: <br/>
* <p/>
* Expression before: 3 * 4 + 2 <br/>
* Transformed to calculations: [3 * 4] <-> [4 + 2]</-> <br/>
* Resulting calculation after applying this algorithm would be: [12 + 2]
* Transformed to calculations: [3 * 4] -> [4 + 2] <br/>
* Resulting calculation after applying this algorithm would be: [12 + 2] (left calculation could be solved).
* <p/>
* This algorithm is repeated until there is only one calculation left, which is then solved and returned. <br/>
* This might involve looping multiple times over the expression, keeping higher priority operators
* at the beginning reduces this overhead because the first calculation can always be solved.
* at the beginning reduces this small 'overhead' because the first calculation can always be solved.
*
* @return the result of the expression
*/
Expand All @@ -71,16 +71,16 @@ public double solve() {
LinkedCalculation first = head;
LinkedCalculation second = first.next;

// x op y op was previously throwing a npe
Assert.notNull(second.right, "unexpected operator at the end");
// `x operator y operator` was previously throwing a npe
Assert.notNull(second.right, "unexpected trailing operator");

if (first.mayExecuteFirst()) {
// 2*3+2
// f.e. 2*3+2
double leftOperand = first.solve();
double secondOperand = second.right.getValue();
yield second.operator.apply(leftOperand, secondOperand);
}
// 2+3*2
// f.e. 2+3*2
double firstOperand = first.left.getValue();
double secondOperand = second.solve();
yield first.operator.apply(firstOperand, secondOperand);
Expand All @@ -95,7 +95,7 @@ public double solve() {
}

/**
* Solves a chain of calculations, leaves one calculation behind which can then be solved.
* Shortens a chain of calculations, leaves one calculation behind which can then be solved.
*/
private void shorten() {
// lets take {2+3}<->{3*4}<->{4+2} as example (expression: 2+3*4+2) and {3*4} as current
Expand Down Expand Up @@ -174,7 +174,7 @@ private static class LinkedCalculation {
public LinkedCalculation(LinkedCalculation prev, Operator operator) {
this(prev, prev.right);
this.operator = operator;
// might want to call clone() on prev.right because Operand is mutable
// FIXME: might want to call clone() on prev.right because Operand is mutable
// current solving algorithm doesn't cause any issues with it yet
}

Expand Down Expand Up @@ -215,7 +215,7 @@ public boolean isComplete() {
/**
* Tries to simplify this calculation, effectively checking if its operator priority is {@link Operator#HIGHEST_PRIORITY}. <br>
* Then solving the calculation and marking it as "incomplete" again to allow further adding of tokens.
* @return true, if this calculation can be simplified, false otherwise
* @return true, if this calculation could be simplified, false otherwise
*/
public boolean simplify() {
if (operator.getPriority() != Operator.HIGHEST_PRIORITY) {
Expand Down Expand Up @@ -244,7 +244,7 @@ public double tryToSolve() {
if (operator == null) {
return left.getValue();
}
Assert.notNull(right, "unexpected operator at the end");
Assert.notNull(right, "unexpected trailing operator");
return solve();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package me.fourteendoggo.mathexpressionparser.token;

public enum TokenType {
/**
* @see Operator
*/
OPERATOR,
/**
* @see Operand
*/
OPERAND,
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ private char advanceOrThrow() {
return advance();
}

private char currentOrThrow(String message) {
Assert.isTrue(pos < source.length, message);
private char currentOrThrow(String fmt, Object... placeholders) {
Assert.isTrue(pos < source.length, fmt, placeholders);
return source[pos];
}

Expand All @@ -156,8 +156,8 @@ private boolean match(char c) {
return true;
}

private void matchOrThrow(char expected, String message) {
Assert.isTrue(match(expected), message);
private void matchOrThrow(char expected, String fmt, Object... placeholders) {
Assert.isTrue(match(expected), fmt, placeholders);
}

private Tokenizer branchOff(IntPredicate newLoopCondition, int newPos) {
Expand Down Expand Up @@ -257,13 +257,14 @@ private Operand readSymbol() {
}

private Operand readFunctionCall(FunctionCallSite function) {
matchOrThrow('(', "missing opening parenthesis for function call");
String functionName = function.getName();
matchOrThrow('(', "missing opening parenthesis for function %s", functionName);

FunctionContext parameters = function.allocateParameters();
char maybeClosingParenthesis = currentOrThrow("missing closing parenthesis for function call " + function.getName());
char maybeClosingParenthesis = currentOrThrow("missing closing parenthesis for function call ", functionName);
if (maybeClosingParenthesis != ')') { // arguments were provided
// TODO: when calling f.e. exit( ), the space gets interpreted as parameters too
Assert.isTrue(function.supportsArgs(), "did not expect any parameters for function %s", function.getName());
Assert.isTrue(function.supportsArgs(), "did not expect any parameters for function %s", functionName);

// do while doesn't really work here due to that + 1
Tokenizer paramTokenizer = branchOff(Utility::isValidArgument, pos);
Expand All @@ -275,7 +276,7 @@ private Operand readFunctionCall(FunctionCallSite function) {
}
pos = paramTokenizer.pos; // move to ')' (or something else if there's an error)
}
matchOrThrow(')', "missing closing parenthesis for function call " + function.getName());
matchOrThrow(')', "missing closing parenthesis for function %s", functionName);

return new Operand(function.apply(parameters));
}
Expand Down

0 comments on commit c151659

Please sign in to comment.