From 62f351ec629be873074f904948dc9f2b435e5a7e Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Sun, 11 Feb 2018 14:17:49 -0500 Subject: [PATCH] Add function for displaying execution context - Use lambda character in addition to keyword --- lisp/finance/interest-compounder.lisp | 2 +- lisp/random/array.lisp | 48 ++++++++-------- src/function/builtin/SYMBOLS.java | 29 ++++++++++ src/function/builtin/special/LAMBDA.java | 6 +- src/sexpression/Symbol.java | 4 +- src/stream/SafeInputStream.java | 7 ++- src/table/ExecutionContext.java | 12 ++++ src/table/FunctionTable.java | 2 + src/table/SymbolTable.java | 42 +++++++++++++- test/function/builtin/EVALTest.java | 8 +++ test/function/builtin/SYMBOLSTest.java | 57 +++++++++++++++++++ test/function/builtin/special/LAMBDATest.java | 14 +++++ .../LispCommentRemovingInputStreamTest.java | 8 +++ test/sexpression/SExpressionTest.java | 3 +- 14 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 src/function/builtin/SYMBOLS.java create mode 100644 test/function/builtin/SYMBOLSTest.java diff --git a/lisp/finance/interest-compounder.lisp b/lisp/finance/interest-compounder.lisp index 558aa2b..1f7a0b3 100644 --- a/lisp/finance/interest-compounder.lisp +++ b/lisp/finance/interest-compounder.lisp @@ -19,7 +19,7 @@ (if (> years 0) (setq years-passed (+ years-passed years)))) - (:compound-interest (years) + (:compound-interest (years) (if (> years 0) (begin (setq principal diff --git a/lisp/random/array.lisp b/lisp/random/array.lisp index 6532fc0..f18b8b3 100644 --- a/lisp/random/array.lisp +++ b/lisp/random/array.lisp @@ -10,32 +10,32 @@ accumulator (recur (- index 1) (cons index accumulator)))))) - (defmacro array (length) - (let* ((this (gensym)) - (index-prefix (fuse this 'index)) + (defun array (length) + (let* ((this) (indices (call static :create-indices length)) - (index-bindings (map (lambda (i) `(,(fuse index-prefix i))) indices)) - (scope `((,this) ,@index-bindings))) + (index-bindings (map (λ (i) (list (fuse 'index i))) indices))) - `(let ,scope - (setq ,this - (dlambda - (:get (i) - (eval (fuse ',index-prefix i))) + (eval + `(let ,index-bindings - (:set (i value) - (if (and (< i ,length) (> i -1)) - (set (fuse ',index-prefix i) value) - (call ,this :get i))) ;; show error + (setq this + (dlambda + (:get (i) + (eval (fuse 'index i))) - (:length () - ,length) + (:set (i value) + (if (and (< i ,length) (> i -1)) + (set (fuse 'index i) value) + (call this :get i))) ;; show error - (t () - ((lambda (length accumulator) - (if (< length 1) - accumulator - (recur - (- length 1) - (cons (call ,this :get (- length 1)) accumulator)))) - ,length nil)))))))) + (:length () + ,length) + + (t () + ((λ (size accumulator) + (if (< size 1) + accumulator + (recur + (- size 1) + (cons (call this :get (- size 1)) accumulator)))) + ,length nil))))))))) diff --git a/src/function/builtin/SYMBOLS.java b/src/function/builtin/SYMBOLS.java new file mode 100644 index 0000000..38b91be --- /dev/null +++ b/src/function/builtin/SYMBOLS.java @@ -0,0 +1,29 @@ +package function.builtin; + +import function.ArgumentValidator; +import function.FunctionNames; +import function.LispFunction; +import sexpression.Cons; +import sexpression.SExpression; +import table.ExecutionContext; + +@FunctionNames({ "SYMBOLS" }) +public class SYMBOLS extends LispFunction { + + private ArgumentValidator argumentValidator; + private ExecutionContext executionContext; + + public SYMBOLS(String name) { + this.argumentValidator = new ArgumentValidator(name); + this.argumentValidator.setExactNumberOfArguments(0); + this.executionContext = ExecutionContext.getInstance(); + } + + @Override + public SExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); + + return executionContext.toList(); + } + +} diff --git a/src/function/builtin/special/LAMBDA.java b/src/function/builtin/special/LAMBDA.java index 601896b..b33eb5c 100644 --- a/src/function/builtin/special/LAMBDA.java +++ b/src/function/builtin/special/LAMBDA.java @@ -11,14 +11,14 @@ import sexpression.LambdaExpression; import sexpression.SExpression; import sexpression.Symbol; -@FunctionNames({ "LAMBDA" }) +@FunctionNames({ "LAMBDA", "Λ" }) public class LAMBDA extends LispSpecialFunction { public static boolean isLambdaExpression(SExpression sexpr) { if (sexpr.isCons()) { - SExpression first = ((Cons) sexpr).getFirst(); + String first = ((Cons) sexpr).getFirst().toString(); - return "LAMBDA".equals(first.toString()); + return "LAMBDA".equals(first) || "Λ".equals(first); } return false; diff --git a/src/sexpression/Symbol.java b/src/sexpression/Symbol.java index 811e3b0..ae6e032 100644 --- a/src/sexpression/Symbol.java +++ b/src/sexpression/Symbol.java @@ -1,5 +1,7 @@ package sexpression; +import java.util.Locale; + @DisplayName("symbol") public class Symbol extends Atom { @@ -10,7 +12,7 @@ public class Symbol extends Atom { } public Symbol(String text) { - super(text.toUpperCase()); + super(text.toUpperCase(Locale.ROOT)); } @Override diff --git a/src/stream/SafeInputStream.java b/src/stream/SafeInputStream.java index f8f46d6..e817994 100644 --- a/src/stream/SafeInputStream.java +++ b/src/stream/SafeInputStream.java @@ -1,14 +1,17 @@ package stream; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; public class SafeInputStream { - private InputStream underlyingStream; + private InputStreamReader underlyingStream; public SafeInputStream(InputStream underlyingStream) { - this.underlyingStream = underlyingStream; + this.underlyingStream = new InputStreamReader(underlyingStream, UTF_8); } public int read() { diff --git a/src/table/ExecutionContext.java b/src/table/ExecutionContext.java index f23bf1f..6a24be7 100644 --- a/src/table/ExecutionContext.java +++ b/src/table/ExecutionContext.java @@ -1,8 +1,11 @@ package table; +import static sexpression.Nil.NIL; + import java.util.Stack; import function.LispFunction; +import sexpression.Cons; import sexpression.SExpression; public class ExecutionContext { @@ -48,6 +51,15 @@ public class ExecutionContext { return null; } + public Cons toList() { + Cons symbols = NIL; + + for (SymbolTable t = scope; t != null; t = t.getParent()) + symbols = new Cons(t.toList(), symbols); + + return symbols; + } + public void pushFunctionCall(LispFunction function) { functionCalls.push(new LispFunctionRecurInfo(function)); } diff --git a/src/table/FunctionTable.java b/src/table/FunctionTable.java index 7f2d3c0..8839e06 100644 --- a/src/table/FunctionTable.java +++ b/src/table/FunctionTable.java @@ -19,6 +19,7 @@ import function.builtin.GENSYM; import function.builtin.LOAD; import function.builtin.PRINT; import function.builtin.SET; +import function.builtin.SYMBOLS; import function.builtin.SYMBOL_FUNCTION; import function.builtin.cons.CONS; import function.builtin.cons.FIRST; @@ -106,6 +107,7 @@ public class FunctionTable { allBuiltIns.add(SET.class); allBuiltIns.add(SETQ.class); allBuiltIns.add(SYMBOL_FUNCTION.class); + allBuiltIns.add(SYMBOLS.class); } public static LispFunction lookupFunction(String functionName) { diff --git a/src/table/SymbolTable.java b/src/table/SymbolTable.java index cca909f..ec1bcb2 100644 --- a/src/table/SymbolTable.java +++ b/src/table/SymbolTable.java @@ -1,8 +1,16 @@ package table; -import java.util.HashMap; +import static function.builtin.cons.LIST.makeList; +import static sexpression.Nil.NIL; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import sexpression.Cons; import sexpression.SExpression; +import sexpression.Symbol; public class SymbolTable { @@ -38,4 +46,36 @@ public class SymbolTable { return parent == null; } + public Cons toList() { + Cons context = NIL; + + for (Entry binding : getSortedBindings()) + context = append(context, makeList(makeSymbolValuePair(binding))); + + return context; + } + + private Set> getSortedBindings() { + return new TreeMap<>(table).entrySet(); + } + + private Cons makeSymbolValuePair(Entry binding) { + return new Cons(new Symbol(binding.getKey()), makeList(binding.getValue())); + } + + // TODO - extract into built in append function + private Cons append(Cons firstList, Cons secondList) { + if (firstList.isNull()) + return secondList; // FIXME - copy + + Cons appendedList = firstList; // FIXME - copy + Cons appender = appendedList; + + for (; !((Cons) appender.getRest()).isNull(); appender = (Cons) appender.getRest()) {} + + appender.setRest(secondList); // FIXME - copy + + return appendedList; + } + } diff --git a/test/function/builtin/EVALTest.java b/test/function/builtin/EVALTest.java index be83f06..ae0fe66 100644 --- a/test/function/builtin/EVALTest.java +++ b/test/function/builtin/EVALTest.java @@ -38,6 +38,14 @@ public class EVALTest extends SymbolAndFunctionCleaner { assertSExpressionsMatch(parseString("()"), evaluateString(input)); } + @Test + public void evalUsesCurrentLexicalEnvironment() { + String input = "(let ((x 1)) (eval '(+ x 1)))"; + + assertSExpressionsMatch(parseString("2"), evaluateString(input)); + + } + @Test public void lookupKeywordSymbol() { String symbol = ":symbol"; diff --git a/test/function/builtin/SYMBOLSTest.java b/test/function/builtin/SYMBOLSTest.java new file mode 100644 index 0000000..88d87c3 --- /dev/null +++ b/test/function/builtin/SYMBOLSTest.java @@ -0,0 +1,57 @@ +package function.builtin; + +import static testutil.TestUtilities.assertSExpressionsMatch; +import static testutil.TestUtilities.evaluateString; + +import org.junit.Test; + +import function.ArgumentValidator.TooManyArgumentsException; +import testutil.SymbolAndFunctionCleaner; + +public class SYMBOLSTest extends SymbolAndFunctionCleaner { + + @Test + public void noSymbols() { + assertSExpressionsMatch(evaluateString("'(nil)"), evaluateString("(symbols)")); + } + + @Test + public void globalSymbol() { + evaluateString("(setq x 10)"); + assertSExpressionsMatch(evaluateString("'(((x 10)))"), evaluateString("(symbols)")); + } + + @Test + public void multipleSymbolsSorted() { + evaluateString("(setq x 10)"); + evaluateString("(setq a 20)"); + evaluateString("(setq y 30)"); + evaluateString("(setq w 40)"); + evaluateString("(setq e 50)"); + + assertSExpressionsMatch(evaluateString("'(((a 20) (e 50) (w 40) (x 10) (y 30)))"), evaluateString("(symbols)")); + } + + @Test + public void fullExecutionContext() { + evaluateString("(setq x 10)"); + evaluateString("(setq y 30)"); + + assertSExpressionsMatch(evaluateString("'(((x 10) (y 30)) ((a 100) (q 99) (z nil)))"), + evaluateString("(let ((q 99) (a 100) (z)) (symbols))")); + } + + @Test + public void updateSymbolInLet() { + evaluateString("(setq x 10)"); + evaluateString("(setq y 30)"); + + assertSExpressionsMatch(evaluateString("'(((x 10) (y 30)) ((q 99) (x 1) (z 2)))"), + evaluateString("(let ((q 99) (x 100) (z 2)) (setq x 1) (symbols))")); + } + + @Test(expected = TooManyArgumentsException.class) + public void symbolsWithTooManyArguments() { + evaluateString("(symbols 1)"); + } +} diff --git a/test/function/builtin/special/LAMBDATest.java b/test/function/builtin/special/LAMBDATest.java index a251d1a..81a6bf7 100644 --- a/test/function/builtin/special/LAMBDATest.java +++ b/test/function/builtin/special/LAMBDATest.java @@ -29,6 +29,13 @@ public class LAMBDATest extends SymbolAndFunctionCleaner { assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input)); } + @Test + public void lambdaSymbol() { + String input = "(λ (x) x)"; + + assertSExpressionsMatch(parseString("(LAMBDA (X) X)"), evaluateString(input)); + } + @Test public void lambdaWithNoBody() { String input = "(lambda ())"; @@ -108,6 +115,13 @@ public class LAMBDATest extends SymbolAndFunctionCleaner { assertSExpressionsMatch(new LispNumber("205"), evaluateString(input)); } + @Test + public void anonymousLambdaCallWithSymbol() { + String input = "((λ (x) (+ x 1)) 3)"; + + assertSExpressionsMatch(new LispNumber("4"), evaluateString(input)); + } + @Test(expected = TooFewArgumentsException.class) public void anonymousLambdaCallWithTooFewArguments() { evaluateString("((lambda (x) x))"); diff --git a/test/scanner/LispCommentRemovingInputStreamTest.java b/test/scanner/LispCommentRemovingInputStreamTest.java index 07fb893..7d4d6e7 100644 --- a/test/scanner/LispCommentRemovingInputStreamTest.java +++ b/test/scanner/LispCommentRemovingInputStreamTest.java @@ -219,4 +219,12 @@ public class LispCommentRemovingInputStreamTest { } } + @Test + public void readUnicodeCharacter() { + String input = "λ"; + String expectedResult = "λ"; + + assertEquals(expectedResult, getLispCommentRemovingInputStreamResult(input)); + } + } diff --git a/test/sexpression/SExpressionTest.java b/test/sexpression/SExpressionTest.java index 710657a..49be3a6 100644 --- a/test/sexpression/SExpressionTest.java +++ b/test/sexpression/SExpressionTest.java @@ -9,6 +9,7 @@ import static testutil.TestUtilities.assertSExpressionsMatch; import static testutil.TestUtilities.makeList; import java.math.BigInteger; +import java.util.Locale; import org.junit.Test; @@ -53,7 +54,7 @@ public class SExpressionTest { public void symbol_ToString() { String input = "symbol"; - assertSExpressionMatchesString(input.toUpperCase(), new Symbol(input)); + assertSExpressionMatchesString(input.toUpperCase(Locale.ROOT), new Symbol(input)); } @Test