After reading Peter Norvig's post (How to Write a (Lisp) Interpreter (in Python)), I thought I'd have a go at doing the same thing in Java 8:
Lispy.java
package org.adrianwalker.lispy;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public final class Lispy {
private class Env extends HashMap<String, Object> {
private final Env outer;
public Env(final List<String> params, final List<Object> args, final Env outer) {
IntStream.range(0, params.size()).forEach(i -> put(params.get(i), args.get(i)));
this.outer = outer;
}
public Env find(final Object var) {
return this.containsKey(var) ? this : outer.find(var);
}
}
private Env addGlobal(final Env env) {
env.put("+", (Function<List<Double>, Double>) (List<Double> args) -> args.get(0) + args.get(1));
env.put("-", (Function<List<Double>, Double>) (List<Double> args) -> args.get(0) - args.get(1));
env.put("*", (Function<List<Double>, Double>) (List<Double> args) -> args.get(0) * args.get(1));
env.put("/", (Function<List<Double>, Double>) (List<Double> args) -> args.get(0) / args.get(1));
env.put(">", (Function<List<Double>, Boolean>) (List<Double> args) -> args.get(0) > args.get(1));
env.put("<", (Function<List<Double>, Boolean>) (List<Double> args) -> args.get(0) < args.get(1));
env.put(">=", (Function<List<Double>, Boolean>) (List<Double> args) -> args.get(0) >= args.get(1));
env.put("<=", (Function<List<Double>, Boolean>) (List<Double> args) -> args.get(0) <= args.get(1));
env.put("=", (Function<List<Double>, Boolean>) (List<Double> args) -> Objects.equals(args.get(0), args.get(1)));
env.put("equal?", (Function<List<Double>, Boolean>) (List<Double> args) -> Objects.equals(args.get(0), args.get(1)));
env.put("eq?", (Function<List<Object>, Boolean>) (List<Object> args) -> args.get(0).getClass().isInstance(args.get(1)));
env.put("length", (Function<List<Object>, Integer>) (List<Object> args) -> args.size());
env.put("cons", (Function<List<Object>, List<Object>>) (List<Object> args) -> Stream.concat(Stream.of(args.get(0)), Stream.of(args.get(1))).collect(Collectors.toList()));
env.put("car", (Function<List<Object>, Object>) (List<Object> args) -> ((List) args.get(0)).get(0));
env.put("cdr", (Function<List<Object>, List<Object>>) (List<Object> args) -> ((List) args.get(0)).subList(1, ((List) args.get(0)).size()));
env.put("append", (Function<List<Object>, List<Object>>) (List<Object> args) -> (List) Stream.concat(((List) args.get(0)).stream(), ((List) args.get(1)).stream()).collect(Collectors.toList()));
env.put("list", (Function<List<Object>, List<Object>>) (List<Object> args) -> args);
env.put("list?", (Function<List<Object>, Boolean>) (List<Object> args) -> args.get(0) instanceof List);
env.put("null?", (Function<List<Object>, Boolean>) (List<Object> args) -> ((List) args.get(0)).isEmpty());
env.put("symbol?", (Function<List<Object>, Boolean>) (List<Object> args) -> args.get(0) instanceof String);
return env;
}
private final Env globalEnv = addGlobal(new Env(Collections.EMPTY_LIST, Collections.EMPTY_LIST, null));
private Object eval(final Object x, final Env env) {
if (x instanceof String) {
return env.find(x).get(x);
} else if (!(x instanceof List)) {
return x;
} else if (((List) x).get(0).equals("quote")) {
return ((List) x).get(1);
} else if (((List) x).get(0).equals("if")) {
return eval(((Boolean) eval(((List) x).get(1), env)) ? ((List) x).get(2) : ((List) x).get(3), env);
} else if (((List) x).get(0).equals("set!")) {
env.find(((List) x).get(1)).put((String) ((List) x).get(1), eval(((List) x).get(2), env));
} else if (((List) x).get(0).equals("define")) {
env.put((String) ((List) x).get(1), eval(((List) x).get(2), env));
} else if (((List) x).get(0).equals("lambda")) {
return (Function<List<Object>, Object>) (List<Object> args) -> eval(((List) x).get(2), new Env((List) ((List) x).get(1), args, env));
} else if (((List) x).get(0).equals("begin")) {
Object val = null;
for (Object exp : ((List) x).subList(1, ((List) x).size())) {
val = eval(exp, env);
}
return val;
} else {
List exps = (List) ((List) x).stream().map(exp -> eval(exp, env)).collect(Collectors.toList());
Function proc = (Function) exps.get(0);
return proc.apply(exps.subList(1, exps.size()));
}
return null;
}
private Object read(final String s) {
return readFrom(new ArrayDeque<>(Arrays.asList(tokenize(s))));
}
private String[] tokenize(final String s) {
return s.replace("(", " ( ").replace(")", " ) ").trim().split("\\s+");
}
private Object readFrom(final Deque<String> tokens) {
if (tokens.isEmpty()) {
throw new RuntimeException("unexpected EOF while reading");
}
String token = tokens.pop();
if ("(".equals(token)) {
List l = new ArrayList();
while (!tokens.peek().equals(")")) {
l.add(readFrom(tokens));
}
tokens.pop();
return l;
} else if (")".equals(token)) {
throw new RuntimeException("unexpected )");
} else {
return atom(token);
}
}
private Object atom(final String token) {
try {
return Double.valueOf(token);
} catch (final NumberFormatException nfe) {
return token;
}
}
private String toString(final Object exp) {
return exp instanceof List ? "(" + ((List) exp).stream().map(x -> toString(x)).collect(Collectors.joining(" ")) + ")" : exp.toString();
}
private void repl() {
while (true) {
System.out.print("lispy> ");
Object val = eval(read(new Scanner(System.in).nextLine()), globalEnv);
if (null != val) {
System.out.println(toString(val));
}
}
}
public static void main(final String[] args) {
new Lispy().repl();
}
}
Source Code
- Code available in GitHub - lispy