Skip to content

Instantly share code, notes, and snippets.

@gigamonkey
Created September 26, 2024 22:00
Show Gist options
  • Save gigamonkey/1d8fc9e9f2422f9a44b29d00c7efd63c to your computer and use it in GitHub Desktop.
Save gigamonkey/1d8fc9e9f2422f9a44b29d00c7efd63c to your computer and use it in GitHub Desktop.
Simple arithmetic sexps parsing and evaluating.
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