Created
September 26, 2024 22:00
-
-
Save gigamonkey/1d8fc9e9f2422f9a44b29d00c7efd63c to your computer and use it in GitHub Desktop.
Simple arithmetic sexps parsing and evaluating.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.function.Predicate; | |
public class Sexps { | |
// Thing that can be evaluated. | |
public static interface Expression { | |
public double evaluate(); | |
} | |
public record Number(double evaluate) implements Expression {} | |
public record Symbol(String name) implements Expression { | |
public double evaluate() { | |
throw new RuntimeException("Variables not implemented yet."); | |
} | |
} | |
public record Addition(List<Expression> children) implements Expression { | |
public double evaluate() { | |
double value = 0.0; | |
for (Expression expr : children) { | |
value += expr.evaluate(); | |
} | |
return value; | |
} | |
} | |
public record Subtraction(List<Expression> children) implements Expression { | |
public double evaluate() { | |
double value = children.getFirst().evaluate(); | |
for (Expression expr : children.subList(1, children.size())) { | |
value -= expr.evaluate(); | |
} | |
return value; | |
} | |
} | |
public record Multiplication(List<Expression> children) implements Expression { | |
public double evaluate() { | |
double value = 1.0; | |
for (Expression expr : children) { | |
value *= expr.evaluate(); | |
} | |
return value; | |
} | |
} | |
public record Division(List<Expression> children) implements Expression { | |
public double evaluate() { | |
double value = children.getFirst().evaluate(); | |
for (Expression expr : children.subList(1, children.size())) { | |
value /= expr.evaluate(); | |
} | |
return value; | |
} | |
} | |
private static class Reader { | |
private record Result(boolean ok, Expression expression, int newPosition) {} | |
// Successful parse | |
private Result ok(Expression expression, int pos) { | |
return new Result(true, expression, pos); | |
} | |
// Unsucessful parse | |
private Result fail(int pos) { | |
return new Result(false, null, pos); | |
} | |
private int peek(String s, int pos) { | |
return pos < s.length() ? s.charAt(pos) : -1; | |
} | |
public Expression read(String s) { | |
var r = readAt(s, whitespace(s, 0)); | |
if (r.ok) { | |
return r.expression; | |
} else { | |
throw new RuntimeException("Can't read expression from \"" + s + "\""); | |
} | |
} | |
private Result readAt(String s, int pos) { | |
Result r; | |
if ((r = readList(s, pos)).ok) { | |
return r; | |
} else if ((r = readNumber(s, pos)).ok) { | |
return r; | |
} else { | |
return readSymbol(s, pos); | |
} | |
} | |
private Result readList(String s, int pos) { | |
if (peek(s, pos) == '(') { | |
// First item of list needs to be a symbol in this language. | |
var r = readSymbol(s, whitespace(s, pos + 1)); | |
if (r.ok) { | |
Symbol symbol = (Symbol) r.expression; | |
List<Expression> children = new ArrayList<>(); | |
while (true) { | |
int next = peek(s, r.newPosition()); | |
if (next == ')') { | |
break; | |
} else if (next == -1) { | |
throw new RuntimeException("Unexpected end of input at: " + pos); | |
} else { | |
r = readAt(s, r.newPosition()); | |
if (r.ok) { | |
children.add(r.expression); | |
} else { | |
return r; | |
} | |
} | |
} | |
Expression expr = switch (symbol.name()) { | |
case "+" -> new Addition(children); | |
case "-" -> new Subtraction(children); | |
case "*" -> new Multiplication(children); | |
case "/" -> new Division(children); | |
default -> throw new RuntimeException("Unknown operator " + symbol); | |
}; | |
// Eat the closing ')' and any whitespace | |
return ok(expr, whitespace(s, r.newPosition() + 1)); | |
} else { | |
return r; | |
} | |
} else { | |
return fail(pos); | |
} | |
} | |
private Result readNumber(String s, int pos) { | |
int newPos = consume(this::numberChar, s, pos); | |
if (newPos > pos) { | |
double value = Double.parseDouble(s.substring(pos, newPos)); | |
return ok(new Number(value), whitespace(s, newPos)); | |
} else { | |
return fail(pos); | |
} | |
} | |
private Result readSymbol(String s, int pos) { | |
int newPos = consume(this::symbolChar, s, pos); | |
if (newPos > pos) { | |
String value = s.substring(pos, newPos); | |
return ok(new Symbol(value), whitespace(s, newPos)); | |
} else { | |
return fail(pos); | |
} | |
} | |
private int whitespace(String s, int pos) { | |
return consume(Character::isWhitespace, s, pos); | |
} | |
private int consume(Predicate<Integer> p, String s, int pos) { | |
int newPos = pos; | |
while (p.test(peek(s, newPos))) { | |
newPos++; | |
} | |
return newPos; | |
} | |
private boolean numberChar(int c) { | |
return Character.isDigit(c) || c == '.'; | |
} | |
private boolean symbolChar(int c) { | |
return !Character.isWhitespace(c) && c != '(' && c != ')'; | |
} | |
} | |
public static void main(String[] args) { | |
Reader r = new Reader(); | |
Expression e = r.read(args[0]); | |
System.out.println(args[0]); | |
System.out.println(e); | |
System.out.println(e.evaluate()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment