Skip to content

Commit

Permalink
seq (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
lilactown authored Aug 17, 2022
1 parent feadde3 commit 5405fad
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 23 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ extra performance and small bundle size.
- `println` is a synonym for `console.log`
- `pr-str` and `prn` coerce values to a string using `JSON.stringify`

### seqs

Clava does not implement Clojure seqs. Instead it uses the JavaScript
[iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)
to work with collections. What this means in practice is the following:

- `seq` takes a collection and returns an Iterable of that collection, or nil if it's empty
- `iterable` takes a collection and returns an Iterable of that collection, even if it's empty
- `seqable?` can be used to check if you can call either one

Most collections are iterable already, so `seq` and `iterable` will simply
return them; an exception are objects created via `{:a 1}`, where `seq` and
`iterable` will return the result of `Object.entries`.

`first`, `rest`, `map`, `reduce` et al. call `iterable` on the collection before
processing, and functions that typically return seqs instead return an array of
the results.

## Open questions

- TC39 records and tuples are immutable but not widely supported. It's not yet sure how they will fit within clava.
Expand Down
49 changes: 29 additions & 20 deletions core.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export function assoc_BANG_(m, k, v, ...kvs) {
if (kvs.length % 2 !== 0) {
throw new Error(
"Illegal argument: assoc expects an odd number of arguments."
);
throw new Error('Illegal argument: assoc expects an odd number of arguments.');
}

if (m instanceof Map) {
Expand All @@ -19,7 +17,7 @@ export function assoc_BANG_(m, k, v, ...kvs) {
}
} else {
throw new Error(
"Illegal argument: assoc! expects a Map, Array, or Object as the first argument."
'Illegal argument: assoc! expects a Map, Array, or Object as the first argument.'
);
}

Expand All @@ -29,7 +27,7 @@ export function assoc_BANG_(m, k, v, ...kvs) {
export function assoc(o, k, v, ...kvs) {
if (!(o instanceof Object)) {
throw new Error(
"Illegal argument: assoc expects a Map, Array, or Object as the first argument."
'Illegal argument: assoc expects a Map, Array, or Object as the first argument.'
);
}

Expand Down Expand Up @@ -100,11 +98,11 @@ function assoc_in_with(f, fname, o, keys, value) {
}

export function assoc_in(o, keys, value) {
return assoc_in_with(assoc, "assoc-in", o, keys, value);
return assoc_in_with(assoc, 'assoc-in', o, keys, value);
}

export function assoc_in_BANG_(o, keys, value) {
return assoc_in_with(assoc_BANG_, "assoc-in!", o, keys, value);
return assoc_in_with(assoc_BANG_, 'assoc-in!', o, keys, value);
}

export function conj_BANG_(...xs) {
Expand All @@ -130,7 +128,7 @@ export function conj_BANG_(...xs) {
}
} else {
throw new Error(
"Illegal argument: conj! expects a Set, Array, Map, or Object as the first argument."
'Illegal argument: conj! expects a Set, Array, Map, or Object as the first argument.'
);
}

Expand Down Expand Up @@ -167,7 +165,7 @@ export function conj(...xs) {
}

throw new Error(
"Illegal argument: conj expects a Set, Array, Map, or Object as the first argument."
'Illegal argument: conj expects a Set, Array, Map, or Object as the first argument.'
);
}

Expand Down Expand Up @@ -217,31 +215,44 @@ export function get(coll, key, otherwise = undefined) {
return key in coll ? coll[key] : otherwise;
}

export function iterable_QMARK_(x) {
export function seqable_QMARK_(x) {
// String is iterable but doesn't allow `m in s`
return x instanceof String || Symbol.iterator in x;
return typeof x === 'string' || x === null || x === undefined || Symbol.iterator in x;
}

export function iterable(x) {
if (iterable_QMARK_(x)) {
// nil puns to empty iterable, support passing nil to first/rest/reduce, etc.
if (x === null || x === undefined) {
return [];
}
if (seqable_QMARK_(x)) {
return x;
}
return Object.entries(x);
}

export function seq(x) {
let iter = iterable(x);
// return nil for terminal checking
if (iter.length === 0 || iter.size === 0) {
return null;
}
return iter;
}

export function first(coll) {
// destructuring uses iterable protocol
let [first] = coll;
let [first] = iterable(coll);
return first;
}

export function second(coll) {
let [_, v] = coll;
let [_, v] = iterable(coll);
return v;
}

export function rest(coll) {
let [_, ...rest] = coll;
let [_, ...rest] = iterable(coll);
return rest;
}

Expand Down Expand Up @@ -293,7 +304,7 @@ export function map(f, coll) {
}

export function str(...xs) {
return xs.join("");
return xs.join('');
}

export function not(expr) {
Expand All @@ -307,13 +318,11 @@ export function nil_QMARK_(v) {
export const PROTOCOL_SENTINEL = {};

function pr_str_1(x) {
return JSON.stringify(x, (_key, value) =>
value instanceof Set ? [...value] : value
);
return JSON.stringify(x, (_key, value) => (value instanceof Set ? [...value] : value));
}

export function pr_str(...xs) {
return xs.map(pr_str_1).join(" ");
return xs.map(pr_str_1).join(' ');
}

export function prn(...xs) {
Expand Down
2 changes: 1 addition & 1 deletion resources/clava/core.edn
Original file line number Diff line number Diff line change
@@ -1 +1 @@
#{reduce first dissoc atom dec map assoc_in reset_BANG_ rest PROTOCOL_SENTINEL reduced_QMARK_ range disj iterable conj get disj_BANG_ vec second prn assoc_BANG_ println Atom nth nil_QMARK_ assoc_in_BANG_ iterable_QMARK_ dissoc_BANG_ not pr_str swap_BANG_ vector mapv subvec reduced inc str apply map_indexed deref assoc conj_BANG_ re_matches}
#{seq reduce first dissoc atom dec map assoc_in reset_BANG_ rest PROTOCOL_SENTINEL reduced_QMARK_ range disj seqable_QMARK_ iterable conj get disj_BANG_ vec second prn assoc_BANG_ println Atom nth nil_QMARK_ assoc_in_BANG_ dissoc_BANG_ not pr_str swap_BANG_ vector mapv subvec reduced inc str apply map_indexed deref assoc conj_BANG_ re_matches}
42 changes: 40 additions & 2 deletions test/clava/compiler_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -528,14 +528,26 @@
(is (eq 3 (jsv! '(get {"my-key" 1} "bad-key" 3))))))

(deftest first-test
(is (= nil (jsv! '(first nil))))
(is (= nil (jsv! '(first []))))
(is (= nil (jsv! '(first #{}))))
(is (= nil (jsv! '(first {}))))
(is (= nil (jsv! '(first (js/Map. [])))))
(is (= 1 (jsv! '(first [1 2 3]))))
(is (= 1 (jsv! '(first #{1 2 3}))))
(is (eq #js [1 2] (jsv! '(first (js/Map. [[1 2] [3 4]]))))))
(is (eq #js [1 2] (jsv! '(first (js/Map. [[1 2] [3 4]])))))
(is (eq "a" (jsv! '(first "abc")))))

(deftest rest-test
(is (eq () (jsv! '(rest nil))))
(is (eq () (jsv! '(rest []))))
(is (eq () (jsv! '(rest #{}))))
(is (eq () (jsv! '(rest {}))))
(is (eq () (jsv! '(rest (js/Map. [])))))
(is (eq #js [2 3] (jsv! '(rest [1 2 3]))))
(is (eq #{2 3} (jsv! '(rest #{1 2 3}))))
(is (eq #js [#js [3 4]] (jsv! '(rest (js/Map. [[1 2] [3 4]]))))))
(is (eq #js [#js [3 4]] (jsv! '(rest (js/Map. [[1 2] [3 4]])))))
(is (eq '("b" "c") (jsv! '(rest "abc")))))

(deftest reduce-test
(testing "no val"
Expand Down Expand Up @@ -595,5 +607,31 @@
(is (jsv! '(reduced? (reduced 5))))
(is (= 4 (jsv! '(deref (reduced 4))))))

(deftest seq-test
(is (= "abc" (jsv! '(seq "abc"))))
(is (eq '(1 2 3) (jsv! '(seq [1 2 3]))))
(is (eq '([:a 1] [:b 2]) (jsv! '(seq {:a 1 :b 2}))))
(is (eq (js/Set. [1 2 3])
(jsv! '(seq #{1 2 3}))))
(is (eq (js/Map. #js[#js[1 2] #js[3 4]])
(jsv! '(seq (js/Map. [[1 2] [3 4]])))))
(testing "empty"
(is (= nil (jsv! '(seq nil))))
(is (= nil (jsv! '(seq []))))
(is (= nil (jsv! '(seq {}))))
(is (= nil (jsv! '(seq #{}))))
(is (= nil (jsv! '(seq (js/Map.))))))
(is (eq #js [0 2 4 6 8]
(jsv! '(loop [evens []
nums (range 10)]
(if-some [x (first nums)]
(recur (if (case x
(0 2 4 6 8 10) true
false)
(conj evens x)
evens)
(rest nums))
evens))))))

(defn init []
(cljs.test/run-tests 'clava.compiler-test))

0 comments on commit 5405fad

Please sign in to comment.