From dab730e3eb6ea13d6a8844f93a93bcb567f2d892 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Wed, 25 Nov 2020 11:32:04 -0700 Subject: [PATCH] make immutability default everywhere --- src/kiss/FieldForms.hx | 1 - src/kiss/Macros.hx | 1 + src/kiss/SpecialForms.hx | 80 +++++++++++++++++-------------- src/test/cases/BasicTestCase.hx | 4 ++ src/test/cases/BasicTestCase.kiss | 13 ++++- 5 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/kiss/FieldForms.hx b/src/kiss/FieldForms.hx index 513888f5..5eedb8d3 100644 --- a/src/kiss/FieldForms.hx +++ b/src/kiss/FieldForms.hx @@ -44,7 +44,6 @@ class FieldForms { }; } - // TODO make immutability the default static function varOrProperty(formName:String, position:Position, args:Array, convert:ExprConversion):Field { if (args.length < 2 || args.length > 3) { throw '$formName with $args at $position has wrong number of arguments'; diff --git a/src/kiss/Macros.hx b/src/kiss/Macros.hx index 1dd82272..f48a40e3 100644 --- a/src/kiss/Macros.hx +++ b/src/kiss/Macros.hx @@ -63,6 +63,7 @@ class Macros { CallExp(Symbol("begin").withPos(args[0].pos), [ CallExp(Symbol("deflocal").withPos(args[0].pos), [ TypedExp("Any", uniqueVarSymbol).withPos(args[0].pos), + MetaExp("mut").withPos(args[0].pos), Symbol("null").withPos(args[0].pos) ]).withPos(args[0].pos), CallExp(Symbol("cond").withPos(args[0].pos), [ diff --git a/src/kiss/SpecialForms.hx b/src/kiss/SpecialForms.hx index 54fddf2c..60722c9b 100644 --- a/src/kiss/SpecialForms.hx +++ b/src/kiss/SpecialForms.hx @@ -42,35 +42,55 @@ class SpecialForms { ENew(Helpers.parseTypePath(classType), args.slice(1).map(convert)).withContextPos(); }; - // TODO this isn't tested and doesn't give an arg length warning + // TODO this doesn't give an arg length warning map["set"] = (args:Array, convert:ExprConversion) -> { EBinop(OpAssign, convert(args[0]), convert(args[1])).withContextPos(); }; - // TODO this isn't tested, immutable-default, or DRY with (let... ) or (defvar... ) and doesn't give an arg length warning + // TODO allow var bindings to destructure lists and key-value pairs + function toVar(nameExp:ReaderExp, valueExp:ReaderExp, isFinal:Bool, convert:ExprConversion) { + return { + name: switch (nameExp.def) { + case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}): + name; + default: + throw '$nameExp should be a symbol or typed symbol'; + }, + type: switch (nameExp.def) { + case TypedExp(type, _): + Helpers.parseComplexType(type); + default: null; + }, + isFinal: isFinal, + expr: convert(valueExp) + }; + } + + // TODO this doesn't give an arg length warning map["deflocal"] = (args:Array, convert:ExprConversion) -> { - EVars([ - { - name: switch (args[0].def) { - case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}): - name; - default: - throw 'first element of (deflocal... ) with $args should be a symbol or typed symbol'; - }, - type: switch (args[0].def) { - case TypedExp(type, _): - Helpers.parseComplexType(type); - default: null; - }, - isFinal: false, - expr: convert(args[1]) - } - ]).withContextPos(); + var valueIndex = 1; + var isFinal = switch (args[1].def) { + case MetaExp("mut"): + valueIndex += 1; + false; + default: + true; + }; + EVars([toVar(args[0], args[valueIndex], isFinal, convert)]).withContextPos(); }; - // TODO refactor out EVar generation and allow var bindings to destructure lists and key-value pairs + // TODO this doesn't have an arg length check map["let"] = (args:Array, convert:ExprConversion) -> { - var bindingList = switch (args[0].def) { + var bindingListIndex = 0; + // If the first arg of (let... ) is &mut, make the bindings mutable. + var isFinal = switch (args[0].def) { + case MetaExp("mut"): + bindingListIndex += 1; + false; + default: + true; + }; + var bindingList = switch (args[bindingListIndex].def) { case ListExp(bindingExps) if (bindingExps.length > 0 && bindingExps.length % 2 == 0): bindingExps; default: @@ -79,24 +99,10 @@ class SpecialForms { var bindingPairs = bindingList.groups(2); var varDefs = [ for (bindingPair in bindingPairs) - { - name: switch (bindingPair[0].def) { - case Symbol(name) | TypedExp(_, {pos: _, def: Symbol(name)}): - name; - default: - throw 'first element of binding pair $bindingPair should be a symbol or typed symbol'; - }, - type: switch (bindingPair[0].def) { - case TypedExp(type, _): - Helpers.parseComplexType(type); - default: null; - }, - isFinal: true, // Let's give (let...) variable immutability a try - expr: convert(bindingPair[1]) - } + toVar(bindingPair[0], bindingPair[1], isFinal, convert) ]; - var body = args.slice(1); + var body = args.slice(bindingListIndex + 1); if (body.length == 0) { throw '(let....) expression with bindings $bindingPairs needs a body'; } diff --git a/src/test/cases/BasicTestCase.hx b/src/test/cases/BasicTestCase.hx index 6f38c7f7..725886e3 100644 --- a/src/test/cases/BasicTestCase.hx +++ b/src/test/cases/BasicTestCase.hx @@ -166,6 +166,10 @@ class BasicTestCase extends Test { Assert.equals(null, BasicTestCase.myCondFallthrough); } + function testSetAndDeflocal() { + Assert.equals("another thing", BasicTestCase.mySetLocal()); + } + function testOr() { Assert.equals(5, BasicTestCase.myOr1); } diff --git a/src/test/cases/BasicTestCase.kiss b/src/test/cases/BasicTestCase.kiss index 851b0679..d3a870b2 100644 --- a/src/test/cases/BasicTestCase.kiss +++ b/src/test/cases/BasicTestCase.kiss @@ -101,7 +101,11 @@ :String c "stuff"] (Assert.equals 5 a) (Assert.equals 6 b) - (Assert.equals "stuff" c))) + (Assert.equals "stuff" c)) + (let &mut [a "str1"] + (Assert.equals "str1" a) + (set a "str2") + (Assert.equals "str2" a))) (defvar myConstructedString (new String "sup")) @@ -124,4 +128,9 @@ (defvar myCondFallthrough (cond (false "not this"))) -(defvar myOr1 (or null 5)) \ No newline at end of file +(defvar myOr1 (or null 5)) + +(defun mySetLocal [] + (deflocal loc &mut "one thing") + (set loc "another thing") + loc) \ No newline at end of file