diff --git a/build.gradle b/build.gradle index c6bd69c..0a3fb45 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'application' + id "net.java.openjdk.shinyafox.jshell.gradle.plugin" version "1.0.4" } repositories { @@ -21,3 +22,16 @@ jar { attributes 'Main-Class': 'serene.simple.Main' } } + +// run { +// standardInput = System.in +// } + +task repl(type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + + main = 'serene.simple.Main' + standardInput = System.in + + +} diff --git a/src/main/java/serene/simple/DefSpecialForm.java b/src/main/java/serene/simple/DefSpecialForm.java index 7377157..d6ba9a5 100644 --- a/src/main/java/serene/simple/DefSpecialForm.java +++ b/src/main/java/serene/simple/DefSpecialForm.java @@ -7,9 +7,9 @@ public class DefSpecialForm extends SpecialForm { @Override public Object eval(Scope scope) { - SymbolNode sym = (SymbolNode) this.node.rest.first; - scope.putValue(sym.name, - this.node.rest.rest.first.eval(scope)); + SymbolNode sym = (SymbolNode) this.node.rest().first(); + scope.insertSymbol(sym.name, + this.node.rest().rest().first().eval(scope)); return ListNode.EMPTY; } } diff --git a/src/main/java/serene/simple/FalseNode.java b/src/main/java/serene/simple/FalseNode.java new file mode 100644 index 0000000..18d8ee2 --- /dev/null +++ b/src/main/java/serene/simple/FalseNode.java @@ -0,0 +1,10 @@ +package serene.simple; + +public class FalseNode extends Node { + public boolean isTruthy = false; + + @Override + public Object eval(Scope scope) { + return false; + } +} diff --git a/src/main/java/serene/simple/FnSpecialForm.java b/src/main/java/serene/simple/FnSpecialForm.java index a95a9fa..bd3ed1e 100644 --- a/src/main/java/serene/simple/FnSpecialForm.java +++ b/src/main/java/serene/simple/FnSpecialForm.java @@ -9,22 +9,28 @@ public class FnSpecialForm extends SpecialForm { @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() { + final ListNode formalParams = (ListNode) this.node.rest().first(); + final ListNode body = this.node.rest().rest(); + + return new Function() { @Override + public Object apply(Object arg) { + Object args[] = {arg}; + return this.apply(args); + } + public Object apply(Object... args) { Scope scope = new Scope(parentScope); - if (args.length != formalParams.length()) { + if (args.length != formalParams.length) { throw new RuntimeException(String.format("Wrong number of arguments. Expected: %s, Got: %s.", - formalParams.length(), + 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]); + scope.insertSymbol(paramSymbol.name, args[i]); i++; } diff --git a/src/main/java/serene/simple/IfSpecialForm.java b/src/main/java/serene/simple/IfSpecialForm.java new file mode 100644 index 0000000..d007ea0 --- /dev/null +++ b/src/main/java/serene/simple/IfSpecialForm.java @@ -0,0 +1,25 @@ +package serene.simple; + + +public class IfSpecialForm extends SpecialForm { + + private Node pred; + private Node ifNode; + private Node elseNode; + + public IfSpecialForm(ListNode l) { + super(l); + this.pred = l.rest().first(); + this.ifNode = l.rest().rest().first(); + this.elseNode = l.rest().rest().rest().first(); + } + + @Override + public Object eval(final Scope scope) { + Object result = this.pred.eval(scope); + if (result == null) { + return this.elseNode.eval(scope); + } + return this.ifNode.eval(scope); + } +} diff --git a/src/main/java/serene/simple/ListNode.java b/src/main/java/serene/simple/ListNode.java index 527382d..46a5ca1 100644 --- a/src/main/java/serene/simple/ListNode.java +++ b/src/main/java/serene/simple/ListNode.java @@ -1,18 +1,97 @@ package serene.simple; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.function.Function; +import static java.util.Arrays.asList; + +public class ListNode extends Node implements Iterable { + public static final ListNode EMPTY = new ListNode<>(); + + public final T first; + public final ListNode rest; + public final int length; + + public ListNode() { + this.first = null; + this.rest = null; + this.length = 0; + } + + public ListNode(T f, ListNode r) { + this.first = f; + this.rest = r; + this.length = r.length + 1; + } + + @SafeVarargs + public static ListNode list(T... objs) { + return list(asList(objs)); + } + + public static ListNode list(List objs) { + ListNode l = (ListNode) EMPTY; + for (int i = objs.size() - 1; i >= 0; i--) { + l = l.cons(objs.get(i)); + } + return l; + } + + public ListNode cons(T node) { + return new ListNode(node, this); + } -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) { + for (T node : this.rest) { args.add(node.eval(scope)); } return f.apply(args.toArray()); } + + public T first() { + if (this != EMPTY) { + return this.first; + } + return null; + } + + public ListNode rest() { + if (this != EMPTY) { + return this.rest; + } + return (ListNode) EMPTY; + } + + + @Override + public Iterator iterator() { + return new Iterator() { + private ListNode l = ListNode.this; + + @Override + public boolean hasNext() { + return this.l != EMPTY; + } + + @Override + public T next() { + if (this.l == EMPTY) { + return null; + } + T first = this.l.first; + this.l = this.l.rest; + return first; + } + + @Override + public void remove() { + throw new SereneException("Iterator is immutable"); + } + }; + } } diff --git a/src/main/java/serene/simple/Main.java b/src/main/java/serene/simple/Main.java index ab608ee..75ca1d5 100644 --- a/src/main/java/serene/simple/Main.java +++ b/src/main/java/serene/simple/Main.java @@ -1,6 +1,9 @@ package serene.simple; import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Scanner; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.Console; import java.io.FileInputStream; @@ -10,19 +13,25 @@ public class Main { public static void main(String[] args) throws IOException { if (args.length == 0) { startRepl(); + return; } runSerene(args[0]); } - private static void startRepl() { + private static void startRepl() throws IOException { Scope rootScope = Scope.getRootScope(); - Console console = System.console(); + //Console console = System.console(); + //BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + Scanner in = new Scanner(System.in); + System.out.println("Serene 'simple' v0.1.0"); while(true) { - String inputData = console.readLine("serene-> "); + System.out.print("serene-> "); + //String inputData = reader.readLine(); + String inputData = in.nextLine(); - if (expr == null) break; + if (inputData == null) break; ByteArrayInputStream inputStream = new ByteArrayInputStream(inputData.getBytes()); ListNode nodes = Reader.read(inputStream); @@ -37,6 +46,7 @@ public class Main { System.out.println(result); } } + in.close(); } diff --git a/src/main/java/serene/simple/NilNode.java b/src/main/java/serene/simple/NilNode.java new file mode 100644 index 0000000..e0a5380 --- /dev/null +++ b/src/main/java/serene/simple/NilNode.java @@ -0,0 +1,10 @@ +package serene.simple; + +public class NilNode extends Node { + public boolean isTruthy = false; + + @Override + public Object eval(Scope scope) { + return this; + } +} diff --git a/src/main/java/serene/simple/Node.java b/src/main/java/serene/simple/Node.java index 2b38a71..e035ef3 100644 --- a/src/main/java/serene/simple/Node.java +++ b/src/main/java/serene/simple/Node.java @@ -1,5 +1,7 @@ package serene.simple; public abstract class Node { + public boolean isTruthy = true; + public abstract Object eval(Scope scope); } diff --git a/src/main/java/serene/simple/QuoteSpecialForm.java b/src/main/java/serene/simple/QuoteSpecialForm.java new file mode 100644 index 0000000..4a33c83 --- /dev/null +++ b/src/main/java/serene/simple/QuoteSpecialForm.java @@ -0,0 +1,13 @@ +package serene.simple; + + +public class QuoteSpecialForm extends SpecialForm { + public QuoteSpecialForm(ListNode node) { + super(node); + } + + @Override + public Object eval(final Scope scope) { + return this.node; + } +} diff --git a/src/main/java/serene/simple/Reader.java b/src/main/java/serene/simple/Reader.java index 1596df1..a965a55 100644 --- a/src/main/java/serene/simple/Reader.java +++ b/src/main/java/serene/simple/Reader.java @@ -2,8 +2,10 @@ package serene.simple; import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.PushbackReader; +import java.util.ArrayList; import java.util.List; @@ -19,29 +21,31 @@ public class Reader { return readNumber(inputStream); } else if (c == ')') { - throw new IllegalArgumentException("Unmatch paranthesis.") + throw new IllegalArgumentException("Unmatch paranthesis."); } else { return readSymbol(inputStream); } } - public static ListNode read(InputStream inputStream) throws IOException { + public static ListNode read(InputStream inputStream) throws IOException { return read(new PushbackReader(new InputStreamReader(inputStream))); } - public static ListNode read(PushbackReader inputStream) throws IOException { + public static ListNode read(PushbackReader inputStream) throws IOException { List nodes = new ArrayList(); - skipWhitespaces(inputStream); + skipWhiteSpaces(inputStream); - char c = inputStream.read(); + char c = (char) inputStream.read(); while ((byte) c != -1) { inputStream.unread(c); nodes.add(readNode(inputStream)); - skipWhitespaces(inputStream); + skipWhiteSpaces(inputStream); c = (char) inputStream.read(); } + + return ListNode.list(nodes); } private static Node readList(PushbackReader inputStream) throws IOException { @@ -51,7 +55,7 @@ public class Reader { List nodes = new ArrayList(); do { - skipWhitespaces(inputStream); + skipWhiteSpaces(inputStream); char c = (char) inputStream.read(); if (c == ')') { @@ -63,6 +67,51 @@ public class Reader { nodes.add(readNode(inputStream)); } } while(true); - return SpecialForm(ListNode.list(nodes)); + + return SpecialForm.check(ListNode.list(nodes)); + } + + private static Node readSymbol(PushbackReader inputStream) throws IOException { + + String str = ""; + + while (true) { + int ch = inputStream.read(); + if(isWhiteSpace(ch)) { + inputStream.unread(ch); + break; + }; + str = str + (char) ch; + } + + return new SymbolNode(str); + } + + private static Node readNumber(PushbackReader inputStream) throws IOException { + + String number = ""; + + while (true) { + int ch = inputStream.read(); + if(isWhiteSpace(ch)) { + inputStream.unread(ch); + break; + }; + number = number + (char) ch; + } + + return new NumberNode(Long.parseLong(number, 10)); + } + + private static void skipWhiteSpaces(PushbackReader inputStream) throws IOException { + int ch = inputStream.read(); + while (isWhiteSpace(ch)) { + ch = inputStream.read(); + } + inputStream.unread(ch); + } + + private static boolean isWhiteSpace(int ch) { + return (ch == ' ' || ch == '\t' || ch == '\f' || ch == '\r' || ch == '\n'); } } diff --git a/src/main/java/serene/simple/Scope.java b/src/main/java/serene/simple/Scope.java index 93b9ca5..272fc4a 100644 --- a/src/main/java/serene/simple/Scope.java +++ b/src/main/java/serene/simple/Scope.java @@ -5,6 +5,7 @@ import java.util.HashMap; public class Scope { private final HashMap symbolsMapping = new HashMap(); private final Scope parent; + private static final Scope root = new Scope(null); public Scope() { this(null); @@ -30,4 +31,8 @@ public class Scope { public void insertSymbol(String symbolName, Object symbolValue) { this.symbolsMapping.put(symbolName, symbolValue); } + + public static Scope getRootScope() { + return Scope.root; + } } diff --git a/src/main/java/serene/simple/SereneException.java b/src/main/java/serene/simple/SereneException.java new file mode 100644 index 0000000..c4f87c2 --- /dev/null +++ b/src/main/java/serene/simple/SereneException.java @@ -0,0 +1,10 @@ +package serene.simple; + + +class SereneException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public SereneException(String message) { + super(message); + } +} diff --git a/src/main/java/serene/simple/SpecialForm.java b/src/main/java/serene/simple/SpecialForm.java index f7b285b..4406450 100644 --- a/src/main/java/serene/simple/SpecialForm.java +++ b/src/main/java/serene/simple/SpecialForm.java @@ -7,13 +7,13 @@ public class SpecialForm extends Node { private static final SymbolNode IF = new SymbolNode("if"); private static final SymbolNode QUOTE = new SymbolNode("quote"); - private final ListNode node; + public final ListNode node; - public SpecialForm(ListNode node) { + public SpecialForm(ListNode node) { this.node = node; } - public static Node check(ListNode l) { + public static Node check(ListNode l) { if (l == ListNode.EMPTY) { return l; } else if (l.first.equals(DEF)) { @@ -27,4 +27,9 @@ public class SpecialForm extends Node { } return l; } + + @Override + public Object eval(Scope scope) { + throw new SereneException("Can't use SpecialForm directly"); + } } diff --git a/src/main/java/serene/simple/SymbolNode.java b/src/main/java/serene/simple/SymbolNode.java index af75eda..d19ef8b 100644 --- a/src/main/java/serene/simple/SymbolNode.java +++ b/src/main/java/serene/simple/SymbolNode.java @@ -1,7 +1,7 @@ package serene.simple; public class SymbolNode extends Node { - private final String name; + public final String name; public SymbolNode(String name) { this.name = name; diff --git a/src/main/java/serene/simple/TrueNode.java b/src/main/java/serene/simple/TrueNode.java new file mode 100644 index 0000000..0d671af --- /dev/null +++ b/src/main/java/serene/simple/TrueNode.java @@ -0,0 +1,8 @@ +package serene.simple; + +public class TrueNode extends Node { + @Override + public Object eval(Scope scope) { + return true; + } +}