Skip to content

Commit

Permalink
Finish initial parser rewrite (untested)
Browse files Browse the repository at this point in the history
  • Loading branch information
boxbeam committed Oct 6, 2023
1 parent 09a6715 commit 0b49081
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 308 deletions.
298 changes: 55 additions & 243 deletions src/redempt/crunch/ExpressionCompiler.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
package redempt.crunch;

import redempt.crunch.data.CharTree;
import redempt.crunch.data.FastNumberParsing;
import redempt.crunch.data.Pair;
import redempt.crunch.data.TokenList;
import redempt.crunch.data.TokenList.Node;
import redempt.crunch.exceptions.ExpressionCompilationException;
import redempt.crunch.functional.ArgumentList;
import redempt.crunch.functional.ExpressionEnv;
import redempt.crunch.functional.Function;
import redempt.crunch.functional.FunctionCall;
import redempt.crunch.token.*;

import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;

class ExpressionCompiler {

private static final char VAR_CHAR = '$';
Expand All @@ -24,53 +16,86 @@ static CompiledExpression compile(String expression, ExpressionEnv env) {
if (expression == null || env == null) {
throw new ExpressionCompilationException(null, "Expression and environment may not be null");
}
CompiledExpression exp = new CompiledExpression();
Value val = compileValue(expression, exp, env, 0, false).getFirst();
exp.setValue(val);
return exp;
ExpressionParser parser = new ExpressionParser(expression, env);
return parser.parse();
}

private static Value parseExpression(Parser parser, ExpressionEnv env) {
List<Token> tokens = new ArrayList<>();

return null;
private static Value parseExpression(ExpressionParser parser, ExpressionEnv env) {
ShuntingYard tokens = new ShuntingYard();
tokens.addValue(parseTerm(parser, env));
parser.whitespace();
while (!parser.isAtEnd() && parser.peek() != ')') {
Token token = env.getNamedTokens().getWith(parser);
if (!(token instanceof BinaryOperator)) {
throw new ExpressionCompilationException(parser, "Expected binary operator");
}
tokens.addOperator((BinaryOperator) token);
parser.whitespace();
tokens.addValue(parseTerm(parser, env));
}
return tokens.finish();
}

private static Value parseOptionalNestedExpression(Parser parser, ExpressionEnv env) {
if (parser.peek() != '(') {
return null;
}
parser.advanceCursor();
private static Value parseNestedExpression(ExpressionParser parser, ExpressionEnv env) {
parser.expectChar('(');
parser.whitespace();
Value expression = parseExpression(parser, env);
parser.expectChar(')');
return expression;
}

private static Value parseTerm(Parser parser, ExpressionEnv env) {
Value nested = parseOptionalNestedExpression(parser, env);
if (nested != null) {
return nested;
private static Value parseTerm(ExpressionParser parser, ExpressionEnv env) {
switch (parser.peek()) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
return parseLiteral(parser);
case '(':
return parseNestedExpression(parser, env);
}
Token token = env.getNamedTokens().getWith(parser);
if (token == null) {
throw new ExpressionCompilationException(parser, "Expected value");
}
if (token instanceof Value) {
return (Value) token;
}
return parseLeadingOperation(parser, env, token);
}

private static Value parseLeadingOperation(Parser parser, ExpressionEnv env, Token operation) {
switch (operation.getType()) {
private static LiteralValue parseLiteral(ExpressionParser parser) {
int start = parser.cur;
char c;
while (Character.isDigit(c = parser.peek()) || c == '.') {
parser.advanceCursor();
}
return new LiteralValue(FastNumberParsing.parseInt(parser.str, start, parser.cur));
}

private static Value parseLeadingOperation(ExpressionParser parser, ExpressionEnv env, Token token) {
if (token instanceof Value) {
return (Value) token;
}
switch (token.getType()) {
case UNARY_OPERATOR:
return new UnaryOperation((UnaryOperator) operation, parseTerm(parser, env));
return new UnaryOperation((UnaryOperator) token, parseTerm(parser, env));
case FUNCTION:
Function function = (Function) operation;
Function function = (Function) token;
ArgumentList args = parseArgumentList(parser, env, function.getArgCount());
return new FunctionCall(function, args.getArguments());
}
throw new ExpressionCompilationException(parser, "Expected leading operation");
}

private static ArgumentList parseArgumentList(Parser parser, ExpressionEnv env, int args) {
private static ArgumentList parseArgumentList(ExpressionParser parser, ExpressionEnv env, int args) {
parser.expectChar('(');
parser.whitespace();
Value[] values = new Value[args];
Expand All @@ -89,218 +114,5 @@ private static ArgumentList parseArgumentList(Parser parser, ExpressionEnv env,
parser.expectChar(')');
return new ArgumentList(values);
}

private static Pair<Value, Integer> compileValue(String expression, CompiledExpression exp, ExpressionEnv env, int begin, boolean parenthetical) {
CharTree<Token> namedTokens = env.getNamedTokens();
TokenList tokens = new TokenList();
Pair<Token, Integer> firstOp = namedTokens.getFrom(expression, begin);
boolean op = firstOp.getFirst() != null && firstOp.getFirst().getType() == TokenType.BINARY_OPERATOR;
boolean closed = false;
int tokenStart = begin;
char[] chars = expression.toCharArray();
int i;
loop:
for (i = begin; i < expression.length(); i++) {
char c = chars[i];
switch (c) {
case '(':
if (tokens.size() > 0 && tokens.tail().token.getType() == TokenType.FUNCTION) {
Pair<ArgumentList, Integer> args = compileArgumentList(expression, exp, env, i + 1);
tokens.add(args.getFirst());
i += args.getSecond();
tokenStart = i;
op = true;
continue;
}
if (!op && tokenStart != i) {
tokens.add(compileToken(expression, tokenStart, i, exp));
}
if (tokens.tail() != null && tokens.tail().token instanceof Value) {
tokens.add(BinaryOperator.MULTIPLY);
}
Pair<Value, Integer> inner = compileValue(expression, exp, env, i + 1, true);
i += inner.getSecond() + 1;
tokens.add(inner.getFirst());
tokenStart = i;
op = true;
continue;
case ' ':
if (!op && tokenStart != i) {
tokens.add(compileToken(expression, tokenStart, i, exp));
tokenStart = i + 1;
} else {
tokenStart++;
}
continue;
case ')':
case ',':
if (!parenthetical) {
throw new ExpressionCompilationException("Unbalanced parenthesis");
}
closed = true;
break loop;
}
Pair<Token, Integer> namedToken = namedTokens.getFrom(expression, i);
if (namedToken.getFirst() != null) {
Token token = namedToken.getFirst();
if (token.getType() == TokenType.VARIABLE) {
Variable var = ((Variable) token).getClone();
var.expression = exp;
token = var;
}
if (!op && tokenStart != i) {
tokens.add(compileToken(expression, tokenStart, i, exp));
}
if (!(token.getType() == TokenType.BINARY_OPERATOR && !((BinaryOperator) token).isUnary())
&& tokens.tail() != null && tokens.tail().token instanceof Value) {
tokens.add(BinaryOperator.MULTIPLY);
}
if (token == BinaryOperator.SUBTRACT && (tokens.size() == 0 || !(tokens.tail().token instanceof Value))) {
token = BinaryOperator.NEGATE;
}
op = token.getType() == TokenType.BINARY_OPERATOR;
i += namedToken.getSecond() - 1;
tokenStart = i + 1;
tokens.add(token);
continue;
}
op = false;
}
if (parenthetical && !closed) {
throw new ExpressionCompilationException("Unbalanced parenthesis");
}
if (tokenStart < i && i <= expression.length() && !op) {
tokens.add(compileToken(expression, tokenStart, i, exp));
}
return new Pair<>(reduceTokens(tokens), i - begin);
}

private static Pair<ArgumentList, Integer> compileArgumentList(String expression, CompiledExpression exp, ExpressionEnv env, int start) {
List<Value> values = new ArrayList<>();
int i = start;
loop:
while (i < expression.length() && expression.charAt(i) != ')') {
Pair<Value, Integer> result = compileValue(expression, exp, env, i, true);
i += result.getSecond() + 1;
values.add(result.getFirst());
switch (expression.charAt(i - 1)) {
case ')':
break loop;
case ',':
break;
default:
throw new ExpressionCompilationException("Function argument lists must be separated by commas");
}
}
if (values.size() == 0) {
i++;
}
if (expression.charAt(i - 1) != ')') {
throw new ExpressionCompilationException("Unbalanced parenthesis");
}
Value[] valueArray = values.toArray(new Value[values.size()]);
return new Pair<>(new ArgumentList(valueArray), i - start);
}

private static class OperatorList extends ArrayList<Node> {}

private static Value reduceTokens(TokenList tokens) {
OperatorList[] priorities = new OperatorList[11];
for (Node node = tokens.head(); node != null; node = node.next) {
Token token = node.token;
if (token.getType() == TokenType.FUNCTION) {
createFunctionCall(node);
continue;
}
if (token.getType() == TokenType.BINARY_OPERATOR) {
BinaryOperator op = (BinaryOperator) token;
OperatorList ops = priorities[op.getPriority()];
if (ops == null) {
ops = new OperatorList();
priorities[op.getPriority()] = ops;
}
ops.add(node);
}
}
for (int i = priorities.length - 1; i >= 0; i--) {
OperatorList list = priorities[i];
if (list == null) {
continue;
}
list.forEach(ExpressionCompiler::createOperation);
}
Token token = tokens.head().token;
if (!(token instanceof Value)) {
throw new ExpressionCompilationException("Token is not a value: " + token.toString());
}
if (tokens.size() > 1) {
StringJoiner joiner = new StringJoiner(", ");
tokens.forEach(t -> joiner.add(t.toString()));
throw new ExpressionCompilationException("Adjacent values have no operators between them: " + joiner.toString());
}
return (Value) tokens.head().token;
}

private static void createFunctionCall(Node node) {
if (node.next == null) {
throw new ExpressionCompilationException("Function must be followed by argument list");
}
Token next = node.next.token;
if (next.getType() != TokenType.ARGUMENT_LIST) {
throw new ExpressionCompilationException("Function must be followed by argument list");
}
Function func = (Function) node.token;
ArgumentList list = (ArgumentList) next;
if (list.getArguments().length != func.getArgCount()) {
throw new ExpressionCompilationException("Function '" + func.getName() + "' takes " + func.getArgCount() + " args, but got " + list.getArguments().length);
}
node.removeAfter();
node.token = new FunctionCall(func, list.getArguments());
}

private static void createOperation(Node node) {
BinaryOperator op = (BinaryOperator) node.token;
if (node.next == null) {
throw new ExpressionCompilationException("Operator " + op + " has no following operand");
}
if (op.isUnary()) {
Token next = node.next.token;
node.removeAfter();
if (next.getType() == TokenType.BINARY_OPERATOR) {
throw new ExpressionCompilationException("Adjacent operators have no values to operate on");
}
if (next.getType() == TokenType.LITERAL_VALUE && op.canInline()) {
Value literal = (Value) next;
node.token = new LiteralValue(op.operate(literal.getValue()));
return;
}
node.token = new BinaryOperation(op, (Value) next);
return;
}
if (node.prev == null) {
throw new ExpressionCompilationException("Operator " + op + " has no leading operand");
}
Token next = node.next.token;
node.removeAfter();
Token prev = node.prev.token;
node.removeBefore();
if (prev.getType() == TokenType.BINARY_OPERATOR || next.getType() == TokenType.BINARY_OPERATOR) {
throw new ExpressionCompilationException("Adjacent operators have no values to operate on");
}
if (prev.getType() == TokenType.LITERAL_VALUE && next.getType() == TokenType.LITERAL_VALUE && op.canInline()) {
Value lit1 = (Value) prev;
Value lit2 = (Value) next;
node.token = new LiteralValue(op.operate(lit1.getValue(), lit2.getValue()));
return;
}
node.token = new BinaryOperation(op, (Value) prev, (Value) next);
}

private static Token compileToken(String str, int start, int end, CompiledExpression exp) {
if (str.charAt(start) == VAR_CHAR) {
return new Variable(exp, FastNumberParsing.parseInt(str, start + 1, end) - 1);
}
return new LiteralValue(FastNumberParsing.parseDouble(str, start, end));
}

}
Loading

0 comments on commit 0b49081

Please sign in to comment.