diff --git a/src/main/java/serene/simple/DefSpecialForm.java b/src/main/java/serene/simple/DefSpecialForm.java new file mode 100644 index 0000000..7377157 --- /dev/null +++ b/src/main/java/serene/simple/DefSpecialForm.java @@ -0,0 +1,15 @@ +package serene.simple; + +public class DefSpecialForm extends SpecialForm { + public DefSpecialForm(ListNode listNode) { + super(listNode); + } + + @Override + public Object eval(Scope scope) { + SymbolNode sym = (SymbolNode) this.node.rest.first; + scope.putValue(sym.name, + this.node.rest.rest.first.eval(scope)); + return ListNode.EMPTY; + } +} diff --git a/src/main/java/serene/simple/FnSpecialForm.java b/src/main/java/serene/simple/FnSpecialForm.java new file mode 100644 index 0000000..a95a9fa --- /dev/null +++ b/src/main/java/serene/simple/FnSpecialForm.java @@ -0,0 +1,40 @@ +package serene.simple; + +import java.util.function.Function; + +public class FnSpecialForm extends SpecialForm { + public FnSpecialForm(ListNode paramsAndBody) { + super(paramsAndBody); + } + + @Override + public Object eval(final Scope parentScope) { + final ListNode formalParams = (ListNode) this.node.rest.far; + final ListNode body = this.node.rest.rest; + return new Function() { + @Override + public Object apply(Object... args) { + Scope scope = new Scope(parentScope); + if (args.length != formalParams.length()) { + throw new RuntimeException(String.format("Wrong number of arguments. Expected: %s, Got: %s.", + formalParams.length(), + args.length)); + } + // Map parameter values to formal parameter names + int i = 0; + for (Node param : formalParams) { + SymbolNode paramSymbol = (SymbolNode) param; + scope.putValue(paramSymbol.name, args[i]); + i++; + } + + // Evaluate body + Object output = null; + for (Node node : body) { + output = node.eval(scope); + } + return output; + } + }; + } +} diff --git a/src/main/java/serene/simple/FunctionNode.java b/src/main/java/serene/simple/FunctionNode.java new file mode 100644 index 0000000..0f80ff0 --- /dev/null +++ b/src/main/java/serene/simple/FunctionNode.java @@ -0,0 +1,8 @@ +package serene.simple; + +public class FunctionNode extends Node { + @Override + public Object eval(Scope scope) { + return this; + } +} diff --git a/src/main/java/serene/simple/ListNode.java b/src/main/java/serene/simple/ListNode.java new file mode 100644 index 0000000..527382d --- /dev/null +++ b/src/main/java/serene/simple/ListNode.java @@ -0,0 +1,18 @@ +package serene.simple; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class ListNode extends Node implements Iterable { + @Override + public Object eval(Scope scope) { + Function f = (Function) this.first.eval(scope); + List args = new ArrayList(); + + for (Node node : this.first) { + args.add(node.eval(scope)); + } + return f.apply(args.toArray()); + } +} diff --git a/src/main/java/serene/simple/Node.java b/src/main/java/serene/simple/Node.java new file mode 100644 index 0000000..2b38a71 --- /dev/null +++ b/src/main/java/serene/simple/Node.java @@ -0,0 +1,5 @@ +package serene.simple; + +public abstract class Node { + public abstract Object eval(Scope scope); +} diff --git a/src/main/java/serene/simple/NumberNode.java b/src/main/java/serene/simple/NumberNode.java new file mode 100644 index 0000000..8d85e8b --- /dev/null +++ b/src/main/java/serene/simple/NumberNode.java @@ -0,0 +1,14 @@ +package serene.simple; + +public class NumberNode extends Node { + private final Long value; + + public NumberNode(Long v) { + this.value = v; + } + + @Override + public Object eval(Scope scope) { + return this.value; + } +} diff --git a/src/main/java/serene/simple/Reader.java b/src/main/java/serene/simple/Reader.java index 449308e..1596df1 100644 --- a/src/main/java/serene/simple/Reader.java +++ b/src/main/java/serene/simple/Reader.java @@ -1,5 +1,6 @@ package serene.simple; +import java.io.EOFException; import java.io.IOException; import java.io.InputStreamReader; import java.io.PushbackReader; @@ -11,13 +12,13 @@ public class Reader { char c = (char) inputStream.read(); inputStream.unread(c); - if (c == "(") { + if (c == '(') { return readList(inputStream); } else if (Character.isDigit(c)) { return readNumber(inputStream); } - else if (c == ")") { + else if (c == ')') { throw new IllegalArgumentException("Unmatch paranthesis.") } else { @@ -40,5 +41,28 @@ public class Reader { nodes.add(readNode(inputStream)); skipWhitespaces(inputStream); c = (char) inputStream.read(); + } + } + + private static Node readList(PushbackReader inputStream) throws IOException { + char opening = (char) inputStream.read(); + assert opening == '(' : "Lists must start with a '('"; + + List nodes = new ArrayList(); + + do { + skipWhitespaces(inputStream); + char c = (char) inputStream.read(); + + if (c == ')') { + break; + } else if ((byte) c == -1) { + throw new EOFException("EOF reached before closing of list"); + } else { + inputStream.unread(c); + nodes.add(readNode(inputStream)); + } + } while(true); + return SpecialForm(ListNode.list(nodes)); } } diff --git a/src/main/java/serene/simple/Scope.java b/src/main/java/serene/simple/Scope.java new file mode 100644 index 0000000..93b9ca5 --- /dev/null +++ b/src/main/java/serene/simple/Scope.java @@ -0,0 +1,33 @@ +package serene.simple; + +import java.util.HashMap; + +public class Scope { + private final HashMap symbolsMapping = new HashMap(); + private final Scope parent; + + public Scope() { + this(null); + } + + public Scope(Scope parent) { + this.parent = parent; + } + + public Object lookupSymbol(String symbolName) { + if (this.symbolsMapping.containsKey(symbolName)) { + return this.symbolsMapping.get(symbolName); + } + else if (this.parent != null) { + return this.parent.lookupSymbol(symbolName); + } + else { + throw new RuntimeException(String.format("Variable '%s' is not defined in this scope.", + symbolName)); + } + } + + public void insertSymbol(String symbolName, Object symbolValue) { + this.symbolsMapping.put(symbolName, symbolValue); + } +} diff --git a/src/main/java/serene/simple/SpecialForm.java b/src/main/java/serene/simple/SpecialForm.java new file mode 100644 index 0000000..f7b285b --- /dev/null +++ b/src/main/java/serene/simple/SpecialForm.java @@ -0,0 +1,30 @@ +package serene.simple; + + +public class SpecialForm extends Node { + private static final SymbolNode DEF = new SymbolNode("def"); + private static final SymbolNode FN = new SymbolNode("fn"); + private static final SymbolNode IF = new SymbolNode("if"); + private static final SymbolNode QUOTE = new SymbolNode("quote"); + + private final ListNode node; + + public SpecialForm(ListNode node) { + this.node = node; + } + + public static Node check(ListNode l) { + if (l == ListNode.EMPTY) { + return l; + } else if (l.first.equals(DEF)) { + return new DefSpecialForm(l); + } else if (l.first.equals(FN)) { + return new FnSpecialForm(l); + } else if (l.first.equals(IF)) { + return new IfSpecialForm(l); + } else if (l.first.equals(QUOTE)) { + return new QuoteSpecialForm(l); + } + return l; + } +} diff --git a/src/main/java/serene/simple/SymbolNode.java b/src/main/java/serene/simple/SymbolNode.java new file mode 100644 index 0000000..af75eda --- /dev/null +++ b/src/main/java/serene/simple/SymbolNode.java @@ -0,0 +1,14 @@ +package serene.simple; + +public class SymbolNode extends Node { + private final String name; + + public SymbolNode(String name) { + this.name = name; + } + + @Override + public Object eval(Scope scope) { + return scope.lookupSymbol(this.name); + } +}