From 1a06266987c780465d8f6d7449d994f4eded0fd5 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 29 Nov 2020 19:09:11 -0700 Subject: [PATCH] List destructuring --- src/kiss/SpecialForms.hx | 33 ++++++++++++++++++++++--------- src/test/cases/BasicTestCase.hx | 4 ++++ src/test/cases/BasicTestCase.kiss | 15 +++++++++++++- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/kiss/SpecialForms.hx b/src/kiss/SpecialForms.hx index a2d1eff2..aea73fe6 100644 --- a/src/kiss/SpecialForms.hx +++ b/src/kiss/SpecialForms.hx @@ -64,8 +64,7 @@ class SpecialForms { EBinop(OpAssign, convert(args[0]), convert(args[1])).withContextPos(); }; - // TODO allow var bindings to destructure lists and key-value pairs - function toVar(nameExp:ReaderExp, valueExp:ReaderExp, isFinal:Bool, convert:ExprConversion) { + function toVar(nameExp:ReaderExp, valueExp:ReaderExp, isFinal:Bool, convert:ExprConversion):Var { return { name: switch (nameExp.def) { case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}): @@ -83,6 +82,23 @@ class SpecialForms { }; } + function toVars(namesExp:ReaderExp, valueExp:ReaderExp, isFinal:Bool, convert:ExprConversion):Array { + return switch (namesExp.def) { + case Symbol(_) | TypedExp(_, {pos: _, def: Symbol(_)}): + [toVar(namesExp, valueExp, isFinal, convert)]; + case ListExp(nameExps): + var idx = 0; + [ + for (nameExp in nameExps) + toVar(nameExp, + CallExp(Symbol("nth").withPosOf(valueExp), [valueExp, Symbol(Std.string(idx++)).withPosOf(valueExp)]).withPosOf(valueExp), + isFinal, convert) + ]; + default: + throw CompileError.fromExp(namesExp, "Can only bind variables to a symbol or list of symbols for destructuring"); + }; + } + map["deflocal"] = (wholeExp:ReaderExp, args:Array, convert:ExprConversion) -> { wholeExp.checkNumArgs(2, 3, "(deflocal [optional :type] [variable] [optional: &mut] [value])"); var valueIndex = 1; @@ -93,7 +109,7 @@ class SpecialForms { default: true; }; - EVars([toVar(args[0], args[valueIndex], isFinal, convert)]).withContextPos(); + EVars(toVars(args[0], args[valueIndex], isFinal, convert)).withContextPos(); }; map["let"] = (wholeExp:ReaderExp, args:Array, convert:ExprConversion) -> { @@ -114,10 +130,10 @@ class SpecialForms { throw CompileError.fromExp(args[bindingListIndex], 'let bindings should be a list expression with an even number of sub expressions'); }; var bindingPairs = bindingList.groups(2); - var varDefs = [ - for (bindingPair in bindingPairs) - toVar(bindingPair[0], bindingPair[1], isFinal, convert) - ]; + var varDefs = []; + for (bindingPair in bindingPairs) { + varDefs = varDefs.concat(toVars(bindingPair[0], bindingPair[1], isFinal, convert)); + } var body = args.slice(bindingListIndex + 1); if (body.length == 0) { @@ -151,7 +167,6 @@ class SpecialForms { }).withContextPos(); }; - // TODO will this return null if there are no catches? It probably should, for last-expression return semantics map["try"] = (wholeExp:ReaderExp, args:Array, convert:ExprConversion) -> { wholeExp.checkNumArgs(1, null, "(try [thing] [catches...])"); var tryKissExp = args[0]; @@ -179,7 +194,7 @@ class SpecialForms { }; default: throw CompileError.fromExp(catchKissExp, - 'expressions following the first expression in a (try... ) should all be (catch... ) expressions'); + 'expressions following the first expression in a (try... ) should all be (catch [[error]] [body...]) expressions'); } } ]).withContextPos(); diff --git a/src/test/cases/BasicTestCase.hx b/src/test/cases/BasicTestCase.hx index 1f63791d..7bb3843b 100644 --- a/src/test/cases/BasicTestCase.hx +++ b/src/test/cases/BasicTestCase.hx @@ -198,4 +198,8 @@ class BasicTestCase extends Test { function testQuickNths() { _testQuickNths(); } + + function testListDestructuring() { + _testListDestructuring(); + } } diff --git a/src/test/cases/BasicTestCase.kiss b/src/test/cases/BasicTestCase.kiss index 9e525ea3..7e5ade9e 100644 --- a/src/test/cases/BasicTestCase.kiss +++ b/src/test/cases/BasicTestCase.kiss @@ -163,4 +163,17 @@ (Assert.equals 8 (eighth myListOfTen)) (Assert.equals 9 (ninth myListOfTen)) (Assert.equals 10 (tenth myListOfTen)) - (Assert.equals 10 (last myListOfTen))) \ No newline at end of file + (Assert.equals 10 (last myListOfTen))) + +(defun _testListDestructuring [] + (deflocal [a b c d e f g h i j] myListOfTen) + (Assert.equals 1 a) + (Assert.equals 2 b) + (Assert.equals 3 c) + (Assert.equals 4 d) + (Assert.equals 5 e) + (Assert.equals 6 f) + (Assert.equals 7 g) + (Assert.equals 8 h) + (Assert.equals 9 i) + (Assert.equals 10 j)) \ No newline at end of file