From cb5af974a443ed44f95a8772745cef2428f70c89 Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Sat, 15 Jan 2022 14:51:44 -0500 Subject: [PATCH 01/10] POC "rest" pattern in destructuring Add support for using [& rest] to match the remaining values in an array or tuple when destructuring. the rest pattern is implemented by pushing remaining values in the rhs to the stack once & is found on the lhs. Then tuple is called and the result is assigned to the next symbol on the lhs. This commit DOES NOT implement handling for malformed patterns. --- src/core/specials.c | 39 +++++++++++++++++++++++++++++++++++++++ test/suite0001.janet | 15 +++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/core/specials.c b/src/core/specials.c index 9ccf8879..55ae14b0 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -154,6 +154,45 @@ static int destructure(JanetCompiler *c, for (int32_t i = 0; i < len; i++) { JanetSlot nextright = janetc_farslot(c); Janet subval = values[i]; + + if (!janet_cstrcmp(janet_unwrap_symbol(subval), "&")) { + JanetSlot argi = janetc_farslot(c); + JanetSlot arg = janetc_farslot(c); + JanetSlot len = janetc_farslot(c); + + janetc_emit_si(c, JOP_LOAD_INTEGER, argi, i, 0); + janetc_emit_ss(c, JOP_LENGTH, len, right, 0); + + // loop condition + // reuse arg slot for the condition result + int32_t label_loop_start = janetc_emit_sss(c, JOP_EQUALS, arg, argi, len, 0); + int32_t label_loop_cond_jump = janetc_emit_si(c, JOP_JUMP_IF, arg, 0, 0); + + // loop body + janetc_emit_sss(c, JOP_GET, arg, right, argi, 0); + janetc_emit_s(c, JOP_PUSH, arg, 0); + janetc_emit_ssi(c, JOP_ADD_IMMEDIATE, argi, argi, 1, 0); + + // loop + // jump back to the start of the loop + int32_t label_loop_loop = janet_v_count(c->buffer); + janetc_emit(c, JOP_JUMP); + int32_t label_loop_exit = janet_v_count(c->buffer); + + c->buffer[label_loop_cond_jump] |= (label_loop_exit - label_loop_cond_jump) << 16; + c->buffer[label_loop_loop] |= (label_loop_start - label_loop_loop) << 8; + + janetc_freeslot(c, argi); + janetc_freeslot(c, arg); + janetc_freeslot(c, len); + + janetc_emit_s(c, JOP_MAKE_TUPLE, nextright, 1); + + leaf(c, janet_unwrap_symbol(values[i + 1]), nextright, attr); + janetc_freeslot(c, nextright); + break; + } + if (i < 0x100) { janetc_emit_ssu(c, JOP_GET_INDEX, nextright, right, (uint8_t) i, 1); } else { diff --git a/test/suite0001.janet b/test/suite0001.janet index 958449ce..3fc0888f 100644 --- a/test/suite0001.janet +++ b/test/suite0001.janet @@ -137,6 +137,21 @@ (assert (= a 1) "dictionary destructuring 3") (assert (= b 2) "dictionary destructuring 4") (assert (= c 4) "dictionary destructuring 5 - expression as key")) +(let [test-tuple [:a :b 1 2]] + (def [a b one two] test-tuple) + (assert (= a :a) "tuple destructuring 1") + (assert (= b :b) "tuple destructuring 2") + (assert (= two 2) "tuple destructuring 3")) +(let [test-tuple [:a :b 1 2]] + (def [a & rest] test-tuple) + (assert (= a :a) "tuple destructuring 4 - rest") + (assert (= rest [:b 1 2]) "tuple destructuring 5 - rest")) +(do + (def [a b & rest] [:a :b nil :d]) + (assert (= a :a) "tuple destructuring 6 - rest") + (assert (= b :b) "tuple destructuring 7 - rest") + (pp rest) + (assert (= rest [nil :d]) "tuple destructuring 8 - rest")) # Marshal From 0d3167416693d0433712f0d21a4bf6a39870deaa Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Wed, 19 Jan 2022 12:49:02 -0500 Subject: [PATCH 02/10] remove debug print in test suite0001 --- test/suite0001.janet | 1 - 1 file changed, 1 deletion(-) diff --git a/test/suite0001.janet b/test/suite0001.janet index 3fc0888f..3faf2963 100644 --- a/test/suite0001.janet +++ b/test/suite0001.janet @@ -150,7 +150,6 @@ (def [a b & rest] [:a :b nil :d]) (assert (= a :a) "tuple destructuring 6 - rest") (assert (= b :b) "tuple destructuring 7 - rest") - (pp rest) (assert (= rest [nil :d]) "tuple destructuring 8 - rest")) # Marshal From db631097b17ef5d75455559ab5aabad25ee90de9 Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Wed, 19 Jan 2022 13:29:34 -0500 Subject: [PATCH 03/10] add support for & _ to match macro This commit adds support for using & _ syntax to bind the remaining values in an array in the match macro. The commit also adds a few tests for the new syntax in suite0008 --- src/boot/boot.janet | 30 ++++++++++++++++++++++++------ test/suite0008.janet | 4 ++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 7da63078..d7b1378e 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1751,10 +1751,18 @@ (break) # match data structure template - (or isarr (= t :struct) (= t :table)) + (or (= t :struct) (= t :table)) + (eachp [i sub-pattern] pattern + (visit-pattern-1 b2g s i sub-pattern)) + + isarr (do - (when isarr (get-length-sym s)) + (get-length-sym s) (eachp [i sub-pattern] pattern + (when (= sub-pattern '&) + # TODO: check that & is followed by something + (put b2g (pattern (inc i)) @[[slice s i]]) + (break)) (visit-pattern-1 b2g s i sub-pattern))) # match global unification @@ -1774,14 +1782,24 @@ (def isarr (or (= t :array) (and (= t :tuple) (= (tuple/type pattern) :brackets)))) (when isarr (array/push anda (get-length-sym s)) - (array/push anda [<= (length pattern) (get-length-sym s)])) + (def pattern-len + (if-let [ rest-idx (find-index (fn [x] (= x '&)) pattern) ] + rest-idx + (length pattern))) + (array/push anda [<= pattern-len (get-length-sym s)])) (cond # match data structure template - (or isarr (= t :struct) (= t :table)) + (or (= t :struct) (= t :table)) (eachp [i sub-pattern] pattern - (when (not isarr) - (array/push anda [not= nil (get-sym s i)])) + (array/push anda [not= nil (get-sym s i)]) + (visit-pattern-2 anda gun preds s i sub-pattern)) + + isarr + (eachp [i sub-pattern] pattern + # stop recursing to sub-patterns if the rest sigil is found + (when (= sub-pattern '&) + (break)) (visit-pattern-2 anda gun preds s i sub-pattern)) # match local binding diff --git a/test/suite0008.janet b/test/suite0008.janet index a53b16a4..c93ddafc 100644 --- a/test/suite0008.janet +++ b/test/suite0008.janet @@ -106,6 +106,10 @@ (assert (= nil (match {:a :hi} {:a a :b b} a)) "match 3") (assert (= nil (match [1 2] [a b c] a)) "match 4") (assert (= 2 (match [1 2] [a b] b)) "match 5") +(assert (= [2 :a :b] (match [1 2 :a :b] [o & rest] rest)) "match 6") +(assert (= [] (match @[:a] @[x & r] r :fallback)) "match 7") +(assert (= :fallback (match @[1] @[x y & r] r :fallback)) "match 8") +(assert (= [1 2 3 4] (match @[1 2 3 4] @[x y z & r] [x y z ;r] :fallback)) "match 9") # And/or checks From a8e49d084b7b16a10db28ab684883d1f1b291be1 Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Wed, 19 Jan 2022 13:55:05 -0500 Subject: [PATCH 04/10] add checks for & _ destructuring pattern This commit adds three checks to ensure & rest patterns are valid: 1. When checking for '& ensure the value is a symbol before unwrapping 2. Make sure '& is followed by a value 3. Make sure the value following '& is a symbol --- src/core/specials.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/specials.c b/src/core/specials.c index 55ae14b0..75c95858 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -155,7 +155,17 @@ static int destructure(JanetCompiler *c, JanetSlot nextright = janetc_farslot(c); Janet subval = values[i]; - if (!janet_cstrcmp(janet_unwrap_symbol(subval), "&")) { + if (janet_type(subval) == JANET_SYMBOL && !janet_cstrcmp(janet_unwrap_symbol(subval), "&")) { + if (i + 1 >= len) { + janetc_cerror(c, "expected symbol following '& in destructuring pattern"); + return 1; + } + + if (janet_type(values[i + 1]) != JANET_SYMBOL) { + janetc_error(c, janet_formatc("expected symbol following '& in destructuring pattern, found %q", values[i + 1])); + return 1; + } + JanetSlot argi = janetc_farslot(c); JanetSlot arg = janetc_farslot(c); JanetSlot len = janetc_farslot(c); From 71cffc973dd52dcf353be6b04b1f521d3c6681aa Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Wed, 19 Jan 2022 14:01:28 -0500 Subject: [PATCH 05/10] add test: destructure with a nested tuple before & This test ensures rest patterns work when preceded by a more complicated pattern. --- test/suite0001.janet | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/suite0001.janet b/test/suite0001.janet index 3faf2963..f4d9523c 100644 --- a/test/suite0001.janet +++ b/test/suite0001.janet @@ -151,6 +151,12 @@ (assert (= a :a) "tuple destructuring 6 - rest") (assert (= b :b) "tuple destructuring 7 - rest") (assert (= rest [nil :d]) "tuple destructuring 8 - rest")) +(do + (def [[a b] x & rest] [[1 2] :a :c :b :a]) + (assert (= a 1) "tuple destructuring 9 - rest") + (assert (= b 2) "tuple destructuring 10 - rest") + (assert (= x :a) "tuple destructuring 11 - rest") + (assert (= rest [:c :b :a]) "tuple destructuring 12 - rest")) # Marshal From 62608bec03d264ba23c23c8036772ae1f7846d05 Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Thu, 20 Jan 2022 08:12:05 -0500 Subject: [PATCH 06/10] use janet_checktype over janet_type and == In destructure janet_type(_) == JANET_SYMBOL was used to check if a value was a symbol. This commit replaces that with the janet_checktype function, because that function is used for the same purpose in other places. --- src/core/specials.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/specials.c b/src/core/specials.c index 75c95858..ff8a1179 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -155,13 +155,13 @@ static int destructure(JanetCompiler *c, JanetSlot nextright = janetc_farslot(c); Janet subval = values[i]; - if (janet_type(subval) == JANET_SYMBOL && !janet_cstrcmp(janet_unwrap_symbol(subval), "&")) { + if (janet_checktype(subval, JANET_SYMBOL) && !janet_cstrcmp(janet_unwrap_symbol(subval), "&")) { if (i + 1 >= len) { janetc_cerror(c, "expected symbol following '& in destructuring pattern"); return 1; } - if (janet_type(values[i + 1]) != JANET_SYMBOL) { + if (!janet_checktype(values[i + 1], JANET_SYMBOL)) { janetc_error(c, janet_formatc("expected symbol following '& in destructuring pattern, found %q", values[i + 1])); return 1; } From 4118d581af6a8220c4e89628942fbb339e70c938 Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Thu, 20 Jan 2022 08:52:37 -0500 Subject: [PATCH 07/10] error if '& is followed by 2+ items in destructure The current destructure pattern ends when '& is encountered. This commit adds an error if it is followed by more than a symbol to bind the array to. Although its not critical since the extra items can be ignored, they're a sign of some kind of mistake so its best to complain. --- src/core/specials.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/specials.c b/src/core/specials.c index ff8a1179..19e41179 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -161,6 +161,20 @@ static int destructure(JanetCompiler *c, return 1; } + if (i + 2 < len) { + int32_t num_extra = len - i - 1; + Janet* extra = janet_tuple_begin(num_extra); + janet_tuple_flag(extra) |= JANET_TUPLE_FLAG_BRACKETCTOR; + + for (int32_t j = 0; j < num_extra; ++j) { + extra[j] = values[j + i + 1]; + } + + janetc_error(c, janet_formatc("expected a single symbol follow '& in destructuring pattern, found %q", janet_wrap_tuple(janet_tuple_end(extra)))); + return 1; + } + + if (!janet_checktype(values[i + 1], JANET_SYMBOL)) { janetc_error(c, janet_formatc("expected symbol following '& in destructuring pattern, found %q", values[i + 1])); return 1; From 651e12cfe46d249ab996cdf25052ce2a1d80176e Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Thu, 20 Jan 2022 08:54:56 -0500 Subject: [PATCH 08/10] test nested '& destructure and empty rest array This commit adds two new tests for destructure patterns with '&: - Test that the rest array can be empty - Test that & can be nested --- test/suite0001.janet | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/suite0001.janet b/test/suite0001.janet index f4d9523c..fd04de8b 100644 --- a/test/suite0001.janet +++ b/test/suite0001.janet @@ -157,6 +157,19 @@ (assert (= b 2) "tuple destructuring 10 - rest") (assert (= x :a) "tuple destructuring 11 - rest") (assert (= rest [:c :b :a]) "tuple destructuring 12 - rest")) +(do + (def [a b & rest] [:a :b]) + (assert (= a :a) "tuple destructuring 13 - rest") + (assert (= b :b) "tuple destructuring 14 - rest") + (assert (= rest []) "tuple destructuring 15 - rest")) + +(do + (def [[a b & r1] c & r2] [[:a :b 1 2] :c 3 4]) + (assert (= a :a) "tuple destructuring 16 - rest") + (assert (= b :b) "tuple destructuring 17 - rest") + (assert (= c :c) "tuple destructuring 18 - rest") + (assert (= r1 [1 2]) "tuple destructuring 19 - rest") + (assert (= r2 [3 4]) "tuple destructuring 20 - rest")) # Marshal From 82688b9a4425c6091f5a6f991a02217a6d68c5b0 Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Thu, 20 Jan 2022 09:16:02 -0500 Subject: [PATCH 09/10] add checks to & _ pattern in match macro This commit adds 2 checks for & rest pattern in the match macro: - & is followed by exactly 1 item - & is followed by a symbol --- src/boot/boot.janet | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index d7b1378e..d00cef17 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1760,7 +1760,15 @@ (get-length-sym s) (eachp [i sub-pattern] pattern (when (= sub-pattern '&) - # TODO: check that & is followed by something + (when (<= (length pattern) (inc i)) + (errorf "expected symbol following & in pattern")) + + (when (< (+ i 2) (length pattern)) + (errorf "expected a single symbol follow '& in pattern, found %q" (slice pattern (inc i)))) + + (when (not= (type (pattern (inc i))) :symbol) + (errorf "expected symbol following & in pattern, found %q" (pattern (inc i)))) + (put b2g (pattern (inc i)) @[[slice s i]]) (break)) (visit-pattern-1 b2g s i sub-pattern))) From 64127680002f2843e14a4325e1a0eaf0544303fc Mon Sep 17 00:00:00 2001 From: Ian Shehadeh Date: Thu, 20 Jan 2022 09:19:21 -0500 Subject: [PATCH 10/10] Add match documentation for & rest pattern --- src/boot/boot.janet | 1 + 1 file changed, 1 insertion(+) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index d00cef17..050743bc 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1678,6 +1678,7 @@ * array or bracket tuple -- an array or bracket tuple will match only if all of its elements match the corresponding elements in `x`. + Use `& rest` at the end of an array or bracketed tuple to bind all remaining values to `rest`. * table or struct -- a table or struct will match if all values match with the corresponding values in `x`.