summaryrefslogtreecommitdiff
path: root/init.fnl
diff options
context:
space:
mode:
authorAndrey Listopadov <andreyorst@gmail.com>2022-08-21 18:03:25 +0000
committerAndrey Listopadov <andreyorst@gmail.com>2022-08-21 18:03:25 +0000
commit9bbe5ddf93c7c8b17a73318bc89dd1330f4f3f59 (patch)
tree7d358804b1bcb5ab4f1368d2d60eb2993f4de926 /init.fnl
parent58f91092e2831421aa88be36e9dfa6dd153fd212 (diff)
release v1.0.0
Almost complete rewrite of the library, complete with lazy sequences, immutable tables, transients, transducers, better equality semantics, and more correct code generation in macros.
Diffstat (limited to 'init.fnl')
-rw-r--r--init.fnl2993
1 files changed, 1893 insertions, 1100 deletions
diff --git a/init.fnl b/init.fnl
index a230450..361208b 100644
--- a/init.fnl
+++ b/init.fnl
@@ -1,94 +1,91 @@
-(local module-info
- {:_MODULE_NAME "cljlib"
- :_DESCRIPTION "Fennel-cljlib - functions from Clojure's core.clj implemented on top
-of Fennel.
-
-This library contains a set of functions providing functions that
-behave similarly to Clojure's equivalents. Library itself has nothing
-Fennel specific so it should work on Lua, e.g:
-
-``` lua
-Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio
-> clj = require\"cljlib\"
-> table.concat(clj.mapv(function (x) return x * x end, {1, 2, 3}), \" \")
--- 1 4 9
-```
-
-This example is mapping an anonymous `function' over a table,
-producing new table and concatenating it with `\" \"`.
-
-However this library also provides Fennel-specific set of
-[macros](./macros.md), that provides additional facilities like
-`defn' or `defmulti' which extend the language allowing writing code
-that looks and works mostly like Clojure.
-
-Each function in this library is created with `defn', which is a
-special macros for creating multi-arity functions. So when you see
-function signature like `(foo [x])`, this means that this is function
-`foo', that accepts exactly one argument `x'. In contrary, functions
-created with `fn' will produce `(foo x)` signature (`x' is not inside
-brackets).
-
-Functions, which signatures look like `(foo ([x]) ([x y]) ([x y &
-zs]))`, it is a multi-arity function, which accepts either one, two,
-or three-or-more arguments. Each `([...])` represents different body
-of a function which is chosen by checking amount of arguments passed
-to the function. See [Clojure's doc section on multi-arity
-functions](https://clojure.org/guides/learn/functions#_multi_arity_functions).
-
-## Compatibility
-This library is mainly developed with Lua 5.4, and tested against
-Lua 5.2, 5.3, 5.4, and LuaJIT 2.1.0-beta3. Note, that in lua 5.2 and
-LuaJIT equality semantics are a bit different from Lua 5.3 and Lua 5.4.
-Main difference is that when comparing two tables, they must have
-exactly the same `__eq` metamethods, so comparing hash sets with hash
-sets will work, but comparing sets with other tables works only in
-Lua5.3+. Another difference is that Lua 5.2 and LuaJIT don't have
-inbuilt UTF-8 library, therefore `seq' function will not work for
-non-ASCII strings."})
-
-(local core {})
-
-(local insert table.insert)
-(local _unpack (or table.unpack _G.unpack))
-
-(import-macros {: defn : into : empty
- : when-let : if-let : when-some : if-some}
- ;; tricky relative require to make it work from
- ;; anywhere as (require :cljlib) and as well
- ;; (import-macros cljm :cljlib)
- (if ... (if (= ... :init) :init-macros ...) :init-macros))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Utility functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(defn core.apply
- "Apply `f' to the argument list formed by prepending intervening
-arguments to `args', and `f' must support variadic amount of
+(import-macros
+ {: defn
+ : defn-
+ : ns
+ : def
+ : fn*
+ : if-let
+ : if-some
+ : cond}
+ (if ... (if (= ... :init) :init-macros ...) :init-macros))
+
+(ns core
+ "MIT License
+
+Copyright (c) 2022 Andrey Listopadov
+
+Permission is hereby granted‚ free of charge‚ to any person obtaining a copy
+of this software and associated documentation files (the “Software”)‚ to deal
+in the Software without restriction‚ including without limitation the rights
+to use‚ copy‚ modify‚ merge‚ publish‚ distribute‚ sublicense‚ and/or sell
+copies of the Software‚ and to permit persons to whom the Software is
+furnished to do so‚ subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”‚ WITHOUT WARRANTY OF ANY KIND‚ EXPRESS OR
+IMPLIED‚ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY‚
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM‚ DAMAGES OR OTHER
+LIABILITY‚ WHETHER IN AN ACTION OF CONTRACT‚ TORT OR OTHERWISE‚ ARISING FROM‚
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE."
+ (:require [lazy-seq :as lazy]
+ [itable :as itable]))
+
+;;; Utility functions
+
+(fn unpack* [x ...]
+ (if (core.seq? x)
+ (lazy.unpack x)
+ (itable.unpack x ...)))
+
+(fn pack* [...]
+ (doto [...] (tset :n (select "#" ...))))
+
+(fn pairs* [t]
+ (match (getmetatable t)
+ {:__pairs p} (p t)
+ _ (pairs t)))
+
+(fn ipairs* [t]
+ (match (getmetatable t)
+ {:__ipairs i} (i t)
+ _ (ipairs t)))
+
+(fn length* [t]
+ (match (getmetatable t)
+ {:__len l} (l t)
+ _ (length t)))
+
+(defn apply
+ "Apply `f` to the argument list formed by prepending intervening
+arguments to `args`, and `f` must support variadic amount of
arguments.
# Examples
-Applying `add' to different amount of arguments:
+Applying `add` to different amount of arguments:
``` fennel
(assert-eq (apply add [1 2 3 4]) 10)
(assert-eq (apply add 1 [2 3 4]) 10)
(assert-eq (apply add 1 2 3 4 5 6 [7 8 9]) 45)
```"
- ([f args] (f (_unpack args)))
- ([f a args] (f a (_unpack args)))
- ([f a b args] (f a b (_unpack args)))
- ([f a b c args] (f a b c (_unpack args)))
+ ([f args] (f (unpack* args)))
+ ([f a args] (f a (unpack* args)))
+ ([f a b args] (f a b (unpack* args)))
+ ([f a b c args] (f a b c (unpack* args)))
([f a b c d & args]
(let [flat-args []
- len (- (length args) 1)]
+ len (- (length* args) 1)]
(for [i 1 len]
(tset flat-args i (. args i)))
- (each [i a (ipairs (. args (+ len 1)))]
+ (each [i a (pairs* (. args (+ len 1)))]
(tset flat-args (+ i len) a))
- (f a b c d (_unpack flat-args)))))
+ (f a b c d (unpack* flat-args)))))
-(defn core.add
+(defn add
"Sum arbitrary amount of numbers."
([] 0)
([a] a)
@@ -97,7 +94,7 @@ Applying `add' to different amount of arguments:
([a b c d] (+ a b c d))
([a b c d & rest] (apply add (+ a b c d) rest)))
-(defn core.sub
+(defn sub
"Subtract arbitrary amount of numbers."
([] 0)
([a] (- a))
@@ -106,7 +103,7 @@ Applying `add' to different amount of arguments:
([a b c d] (- a b c d))
([a b c d & rest] (apply sub (- a b c d) rest)))
-(defn core.mul
+(defn mul
"Multiply arbitrary amount of numbers."
([] 1)
([a] a)
@@ -115,7 +112,7 @@ Applying `add' to different amount of arguments:
([a b c d] (* a b c d))
([a b c d & rest] (apply mul (* a b c d) rest)))
-(defn core.div
+(defn div
"Divide arbitrary amount of numbers."
([a] (/ 1 a))
([a b] (/ a b))
@@ -123,7 +120,7 @@ Applying `add' to different amount of arguments:
([a b c d] (/ a b c d))
([a b c d & rest] (apply div (/ a b c d) rest)))
-(defn core.le
+(defn le
"Returns true if nums are in monotonically non-decreasing order"
([a] true)
([a b] (<= a b))
@@ -133,7 +130,7 @@ Applying `add' to different amount of arguments:
(<= b c))
false)))
-(defn core.lt
+(defn lt
"Returns true if nums are in monotonically decreasing order"
([a] true)
([a b] (< a b))
@@ -143,7 +140,7 @@ Applying `add' to different amount of arguments:
(< b c))
false)))
-(defn core.ge
+(defn ge
"Returns true if nums are in monotonically non-increasing order"
([a] true)
([a b] (>= a b))
@@ -153,7 +150,7 @@ Applying `add' to different amount of arguments:
(>= b c))
false)))
-(defn core.gt
+(defn gt
"Returns true if nums are in monotonically increasing order"
([a] true)
([a b] (> a b))
@@ -163,521 +160,1363 @@ Applying `add' to different amount of arguments:
(> b c))
false)))
-(defn core.inc "Increase number `x' by one" [x] (+ x 1))
-(defn core.dec "Decrease number `x' by one" [x] (- x 1))
-
-(local utility-doc-order
- [:apply :add :sub :mul :div :le :lt :ge :gt :inc :dec])
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;; Tests and predicates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(fn fast-table-type [tbl]
- (-?> tbl getmetatable (. :cljlib/type)))
-
-(defn core.map?
- "Check whether `tbl' is an associative table.
-
-Non empty associative tables are tested for two things:
-- `next' returns the key-value pair,
-- key, that is returned by the `next' is not equal to `1`.
-
-Empty tables can't be analyzed with this method, and `map?' will
-return `false'. If you need this test pass for empty table, see
-`hash-map' for creating tables that have additional
-metadata attached for this test to work.
-
-# Examples
-Non empty tables:
-
-``` fennel
-(assert-is (map? {:a 1 :b 2}))
-
-(local some-table {:key :value})
-(assert-is (map? some-table))
-```
-
-Empty tables:
-
-``` fennel
-(local some-table {})
-(assert-not (map? some-table))
-```
-
-Empty tables created with `hash-map' will pass the test:
-
-``` fennel
-(local some-table (hash-map))
-(assert-is (map? some-table))
-```"
- [tbl]
- (if (= (type tbl) :table)
- (if-let [t (fast-table-type tbl)]
- (= t :table)
- (let [(k _) (next tbl)]
- (and (not= k nil)
- (not= k 1))))))
+(defn inc
+ "Increase number `x` by one"
+ [x]
+ (+ x 1))
-(defn core.vector?
- "Check whether `tbl' is an sequential table.
+(defn dec
+ "Decrease number `x` by one"
+ [x]
+ (- x 1))
-Non empty sequential tables are tested for two things:
-- `next' returns the key-value pair,
-- key, that is returned by the `next' is equal to `1`.
+(defn class
+ "Return cljlib type of the `x`, or lua type."
+ [x]
+ (match (type x)
+ :table (match (getmetatable x)
+ {:cljlib/type t} t
+ _ :table)
+ t t))
-Empty tables can't be analyzed with this method, and `vector?' will
-always return `false'. If you need this test pass for empty table,
-see `vector' for creating tables that have additional
-metadata attached for this test to work.
+(defn constantly
+ "Returns a function that takes any number of arguments and returns `x`."
+ [x]
+ (fn [] x))
-# Examples
-Non empty vector:
+(defn complement
+ "Takes a function `f` and returns the function that takes the same
+amount of arguments as `f`, has the same effect, and returns the
+oppisite truth value."
+ [f]
+ (fn*
+ ([] (not (f)))
+ ([a] (not (f a)))
+ ([a b] (not (f a b)))
+ ([a b & cs] (not (apply f a b cs)))))
-``` fennel
-(assert-is (vector? [1 2 3 4]))
+(defn identity
+ "Returns its argument."
+ [x]
+ x)
-(local some-table [1 2 3])
-(assert-is (vector? some-table))
-```
+(defn comp
+ "Compose functions."
+ ([] identity)
+ ([f] f)
+ ([f g]
+ (fn*
+ ([] (f (g)))
+ ([x] (f (g x)))
+ ([x y] (f (g x y)))
+ ([x y z] (f (g x y z)))
+ ([x y z & args] (f (apply g x y z args)))))
+ ([f g & fs]
+ (core.reduce comp (core.cons f (core.cons g fs)))))
-Empty tables:
+(defn eq
+ "Comparison function.
-``` fennel
-(local some-table [])
-(assert-not (vector? some-table))
-```
+Accepts arbitrary amount of values, and does the deep comparison. If
+values implement `__eq` metamethod, tries to use it, by checking if
+first value is equal to second value, and the second value is equal to
+the first value. If values are not equal and are tables does the deep
+comparison. Tables as keys are supported."
+ ([] true)
+ ([_] true)
+ ([a b]
+ (if (and (= a b) (= b a))
+ true
+ (= :table (type a) (type b))
+ (do (var (res count-a) (values true 0))
+ (each [k v (pairs* a) :until (not res)]
+ (set res (eq v (do (var (res done) (values nil nil))
+ (each [k* v (pairs* b) :until done]
+ (when (eq k* k)
+ (set (res done) (values v true))))
+ res)))
+ (set count-a (+ count-a 1)))
+ (when res
+ (let [count-b (accumulate [res 0 _ _ (pairs* b)]
+ (+ res 1))]
+ (set res (= count-a count-b))))
+ res)
+ false))
+ ([a b & cs]
+ (and (eq a b) (apply eq b cs))))
-Empty tables created with `vector' will pass the test:
+(fn deep-index [tbl key]
+ "This function uses the `eq` function to compare keys of the given
+table `tbl` and the given `key`. Several other functions also reuse
+this indexing method, such as sets."
+ (accumulate [res nil
+ k v (pairs* tbl)
+ :until res]
+ (when (eq k key)
+ v)))
+
+(fn deep-newindex [tbl key val]
+ "This function uses the `eq` function to compare keys of the given
+table `tbl` and the given `key`. If the key is found it's being
+set, if not a new key is set."
+ (var done false)
+ (when (= :table (type key))
+ (each [k _ (pairs* tbl) :until done]
+ (when (eq k key)
+ (rawset tbl k val)
+ (set done true))))
+ (when (not done)
+ (rawset tbl key val)))
+
+(defn memoize
+ "Returns a memoized version of a referentially transparent function.
+The memoized version of the function keeps a cache of the mapping from
+arguments to results and, when calls with the same arguments are
+repeated often, has higher performance at the expense of higher memory
+use."
+ [f]
+ (let [memo (setmetatable {} {:__index deep-index})]
+ (fn* [& args]
+ (match (. memo args)
+ res (unpack* res 1 res.n)
+ _ (let [res (pack* (f ...))]
+ (tset memo args res)
+ (unpack* res 1 res.n))))))
-``` fennel
-(local some-table (vector))
-(assert-is (vector? some-table))
-```"
- [tbl]
- (if (= (type tbl) :table)
- (if-let [t (fast-table-type tbl)]
- (= t :seq)
- (let [(k _) (next tbl)]
- (and (not= k nil) (= k 1))))))
-
-(defn core.multifn?
- "Test if `mf' is an instance of `multifn'.
+(defn deref
+ "Dereference an object."
+ [x]
+ (match (getmetatable x)
+ {:cljlib/deref f} (f x)
+ _ (error "object doesn't implement cljlib/deref metamethod" 2)))
-`multifn' is a special kind of table, created with `defmulti' macros
-from `macros.fnl'."
- [mf]
- (= (. (or (getmetatable mf) {}) :cljlib/type) :multifn))
+(defn empty
+ "Get an empty variant of a given collection."
+ [x]
+ (match (getmetatable x)
+ {:cljlib/empty f} (f)
+ _ (match (type x)
+ :table []
+ :string ""
+ _ (error (.. "don't know how to create empty variant of type " _)))))
-(defn core.set?
- "Test if `s` is either instance of a `hash-set' or `ordered-set'."
- [s]
- (match (. (or (getmetatable s) {}) :cljlib/type)
- :cljlib/ordered-set :cljlib/ordered-set
- :cljlib/hash-set :cljlib/hash-set
- _ false))
+;;;Tests and predicates
-(defn core.nil?
- "Test if `x' is nil."
+(defn nil?
+ "Test if `x` is nil."
([] true)
([x] (= x nil)))
-(defn core.zero?
- "Test if `x' is equal to zero."
+(defn zero?
+ "Test if `x` is equal to zero."
[x]
(= x 0))
-(defn core.pos?
- "Test if `x' is greater than zero."
+(defn pos?
+ "Test if `x` is greater than zero."
[x]
(> x 0))
-(defn core.neg?
- "Test if `x' is less than zero."
+(defn neg?
+ "Test if `x` is less than zero."
[x]
(< x 0))
-(defn core.even?
- "Test if `x' is even."
+(defn even?
+ "Test if `x` is even."
[x]
(= (% x 2) 0))
-(defn core.odd?
- "Test if `x' is odd."
+(defn odd?
+ "Test if `x` is odd."
[x]
(not (even? x)))
-(defn core.string?
- "Test if `x' is a string."
+(defn string?
+ "Test if `x` is a string."
[x]
(= (type x) :string))
-(defn core.boolean?
- "Test if `x' is a Boolean"
+(defn boolean?
+ "Test if `x` is a Boolean"
[x]
(= (type x) :boolean))
-(defn core.true?
- "Test if `x' is `true'"
+(defn true?
+ "Test if `x` is `true`"
[x]
(= x true))
-(defn core.false?
- "Test if `x' is `false'"
+(defn false?
+ "Test if `x` is `false`"
[x]
(= x false))
-(defn core.int?
- "Test if `x' is a number without floating point data.
+(defn int?
+ "Test if `x` is a number without floating point data.
-Number is rounded with `math.floor' and compared with original number."
+Number is rounded with `math.floor` and compared with original number."
[x]
(and (= (type x) :number)
(= x (math.floor x))))
-(defn core.pos-int?
- "Test if `x' is a positive integer."
+(defn pos-int?
+ "Test if `x` is a positive integer."
[x]
(and (int? x)
(pos? x)))
-(defn core.neg-int?
- "Test if `x' is a negative integer."
+(defn neg-int?
+ "Test if `x` is a negative integer."
[x]
(and (int? x)
(neg? x)))
-(defn core.double?
- "Test if `x' is a number with floating point data."
+(defn double?
+ "Test if `x` is a number with floating point data."
[x]
(and (= (type x) :number)
(not= x (math.floor x))))
-(defn core.empty?
+(defn empty?
"Check if collection is empty."
[x]
(match (type x)
- :table (= (next x) nil)
+ :table
+ (match (getmetatable x)
+ {:cljlib/type :seq}
+ (nil? (core.seq x))
+ (where (or nil {:cljlib/type nil}))
+ (let [(next*) (pairs* x)]
+ (= (next* x) nil)))
:string (= x "")
+ :nil true
_ (error "empty?: unsupported collection")))
-(defn core.not-empty
- "If `x' is empty, returns `nil', otherwise `x'."
+(defn not-empty
+ "If `x` is empty, returns `nil`, otherwise `x`."
[x]
(if (not (empty? x))
x))
-(local predicate-doc-order
- [:map? :vector? :multifn? :set? :nil? :zero? :pos?
- :neg? :even? :odd? :string? :boolean? :true? :false?
- :int? :pos-int? :neg-int? :double? :empty? :not-empty])
+(defn map?
+ "Check whether `x` is an associative table.
+
+Non-empty tables are tested by calling `next`. If the length of the
+table is greater than zero, the last integer key is passed to the
+`next`, and if `next` returns a key, the table is considered
+associative. If the length is zero, `next` is called with what `paris`
+returns for the table, and if the key is returned, table is considered
+associative.
+
+Empty tables can't be analyzed with this method, and `map?` will
+always return `false`. If you need this test pass for empty table,
+see `hash-map` for creating tables that have additional metadata
+attached for this test to work.
+
+# Examples
+Non empty map:
+
+``` fennel
+(assert-is (map? {:a 1 :b 2}))
+```
+
+Empty tables don't pass the test:
+
+``` fennel
+(assert-not (map? {}))
+```
+
+Empty tables created with `hash-map` will pass the test:
+``` fennel
+(assert-is (map? (hash-map)))
+```"
+ [x]
+ (if (= :table (type x))
+ (match (getmetatable x)
+ {:cljlib/type :hash-map} true
+ {:cljlib/type :sorted-map} true
+ (where (or nil {:cljlib/type nil}))
+ (let [len (length* x)
+ (nxt t k) (pairs* x)]
+ (not= nil (nxt t (if (= len 0) k len))))
+ _ false)
+ false))
+
+(defn vector?
+ "Check whether `tbl` is an sequential table.
-;;;;;;;;;;;;;;;;;;;;;; Sequence manipulation functions ;;;;;;;;;;;;;;;;;;;;;;;;;
+Non empty sequential tables are tested for two things:
+- `next` returns the key-value pair,
+- key, that is returned by the `next` is equal to `1`.
-(defn core.vector
+Empty tables can't be analyzed with this method, and `vector?` will
+always return `false`. If you need this test pass for empty table,
+see `vector` for creating tables that have additional
+metadata attached for this test to work.
+
+# Examples
+Non empty vector:
+
+``` fennel
+(assert-is (vector? [1 2 3 4]))
+```
+
+Empty tables don't pass the test:
+
+``` fennel
+(assert-not (vector? []))
+```
+
+Empty tables created with `vector` will pass the test:
+
+``` fennel
+(assert-is (vector? (vector)))
+```"
+ [x]
+ (if (= :table (type x))
+ (match (getmetatable x)
+ {:cljlib/type :vector} true
+ (where (or nil {:cljlib/type nil}))
+ (let [len (length* x)
+ (nxt t k) (pairs* x)]
+ (if (not= nil (nxt t (if (= len 0) k len))) false
+ (> len 0) true
+ false))
+ _ false)
+ false))
+
+(defn set?
+ "Check if object is a set."
+ [x]
+ (match (getmetatable x)
+ {:cljlib/type :hash-set} true
+ _ false))
+
+(defn seq?
+ "Check if object is a sequence."
+ [x]
+ (lazy.seq? x))
+
+(defn some?
+ "Returns true if x is not nil, false otherwise."
+ [x]
+ (not= x nil))
+
+;;; Vector
+
+(fn vec->transient [immutable]
+ (fn [vec]
+ (var len (length vec))
+ (->> {:__index (fn [_ i]
+ (if (<= i len)
+ (. vec i)))
+ :__len #len
+ :cljlib/type :transient
+ :cljlib/conj #(error "can't `conj` onto transient vector, use `conj!`")
+ :cljlib/assoc #(error "can't `assoc` onto transient vector, use `assoc!`")
+ :cljlib/dissoc #(error "can't `dissoc` onto transient vector, use `dissoc!`")
+ :cljlib/conj! (fn [tvec v]
+ (set len (+ len 1))
+ (doto tvec (tset len v)))
+ :cljlib/assoc! (fn [tvec ...]
+ (let [len (length tvec)]
+ (for [i 1 (select "#" ...) 2]
+ (let [(k v) (select i ...)]
+ (if (<= 1 i len)
+ (tset tvec i v)
+ (error (.. "index " i " is out of bounds"))))))
+ tvec)
+ :cljlib/pop! (fn [tvec]
+ (if (= len 0)
+ (error "transient vector is empty" 2)
+ (let [val (table.remove tvec)]
+ (set len (- len 1))
+ tvec)))
+ :cljlib/dissoc! #(error "can't `dissoc!` with a transient vector")
+ :cljlib/persistent! (fn [tvec]
+ (let [v (fcollect [i 1 len] (. tvec i))]
+ (while (> len 0)
+ (table.remove tvec)
+ (set len (- len 1)))
+ (setmetatable tvec
+ {:__index #(error "attempt to use transient after it was persistet")
+ :__newindex #(error "attempt to use transient after it was persistet")})
+ (immutable (itable v))))}
+ (setmetatable {}))))
+
+(fn vec* [v]
+ (match (getmetatable v)
+ mt (doto mt
+ (tset :cljlib/type :vector)
+ (tset :cljlib/editable true)
+ (tset :cljlib/conj
+ (fn [t v] (vec* (itable.insert t v))))
+ (tset :cljlib/empty
+ (fn [] (vec* (itable []))))
+ (tset :cljlib/transient (vec->transient vec*))
+ (tset :__fennelview (fn [coll view inspector indent]
+ (if (empty? coll)
+ "[]"
+ (let [lines (icollect [_ v (ipairs coll)]
+ (.. " " (view v inspector indent)))]
+ (tset lines 1 (.. "[" (string.gsub (or (. lines 1) "") "^%s+" "")))
+ (tset lines (length lines) (.. (. lines (length lines)) "]"))
+ lines)))))
+ nil (vec* (setmetatable v {})))
+ v)
+
+(defn vec
+ "Coerce collection `coll` to a vector."
+ [coll]
+ (cond (empty? coll) (vec* (itable []))
+ (vector? coll) (vec* (itable coll))
+ :else (-> coll
+ core.seq
+ lazy.pack
+ (doto (tset :n nil))
+ itable
+ vec*)))
+
+(defn vector
"Constructs sequential table out of it's arguments.
-Sets additional metadata for function `vector?' to work.
+Sets additional metadata for function `vector?` to work.
# Examples
``` fennel
-(local v (vector 1 2 3 4))
+(def :private v (vector 1 2 3 4))
(assert-eq v [1 2 3 4])
```"
[& args]
- (setmetatable args {:cljlib/type :seq}))
+ (vec args))
+
+(defn nth
+ "Returns the value at the `index`. `get` returns `nil` if `index` out
+of bounds, `nth` raises an error unless `not-found` is supplied.
+`nth` also works for strings and sequences."
+ ([coll i]
+ (if (vector? coll)
+ (if (or (< i 1) (< (length* coll) i))
+ (error (string.format "index %d is out of bounds" i))
+ (. coll i))
+ (string? coll)
+ (nth (vec coll) i)
+ (seq? coll)
+ (nth (vec coll) i)
+ :else
+ (error "expected an indexed collection")))
+ ([coll i not-found]
+ (assert (int? i) "expected an integer key")
+ (if (vector? coll)
+ (or (. coll i) not-found)
+ (string? coll)
+ (nth (vec coll) i not-found)
+ (seq? coll)
+ (nth (vec coll) i not-found)
+ :else
+ (error "expected an indexed collection"))))
-(defn core.seq
- "Create sequential table.
+;;; Sequences
-Transforms original table to sequential table of key value pairs
-stored as sequential tables in linear time. If `col' is an
-associative table, returns sequential table of vectors with key and
-value. If `col' is sequential table, returns its shallow copy. If
-`col' is string, return sequential table of its codepoints.
+(defn- seq*
+ "Add cljlib sequence meta-info."
+ [x]
+ (match (getmetatable x)
+ mt (doto mt
+ (tset :cljlib/type :seq)
+ (tset :cljlib/conj
+ (fn [s v] (core.cons v s)))
+ (tset :cljlib/empty #(core.list))))
+ x)
+
+(defn seq
+ "Construct a sequnce from the given collection `coll`. If `coll` is an
+associative table, returns sequence of vectors with key and
+value. If `col` is sequential table, returns its shallow copy. If
+`col` is string, return sequential table of its codepoints.
# Examples
-Sequential tables remain as is:
+Sequential tables are transformed to sequences:
``` fennel
-(seq [1 2 3 4])
-;; [1 2 3 4]
+(seq [1 2 3 4]) ;; @seq(1 2 3 4)
```
Associative tables are transformed to format like this `[[key1 value1]
... [keyN valueN]]` and order is non deterministic:
``` fennel
-(seq {:a 1 :b 2 :c 3})
-;; [[:b 2] [:a 1] [:c 3]]
-```
+(seq {:a 1 :b 2 :c 3}) ;; @seq([:b 2] [:a 1] [:c 3])
+```"
+ [coll]
+ (seq* (match (getmetatable coll)
+ {:cljlib/seq f} (f coll)
+ _ (cond (lazy.seq? coll) (lazy.seq coll)
+ (map? coll) (lazy.map vec coll)
+ :else (lazy.seq coll)))))
+
+(defn rseq
+ "Returns, in possibly-constant time, a seq of the items in `rev` in reverse order.
+Input must be traversable with `ipairs`. Doesn't work in constant
+time if `rev` implements a linear-time `__len` metamethod, or invoking
+Lua `#` operator on `rev` takes linar time. If `t` is empty returns
+`nil`.
-See `into' macros for transforming this back to associative table.
-Additionally you can use `conj' and `apply' with
-`hash-map':
+# Examples
``` fennel
-(apply conj (hash-map) [:c 3] [[:a 1] [:b 2]])
-;; => {:a 1 :b 2 :c 3}
+(def :private v [1 2 3])
+(def :private r (rseq v))
+
+(assert-eq (reverse v) r)
```"
- [col]
- (let [res (empty [])]
- (match (type col)
- :table (let [m (or (getmetatable col) {})]
- (when-some [_ ((or m.cljlib/next next) col)]
- (var assoc? false)
- (let [assoc-res (empty [])]
- (each [k v (pairs col)]
- (if (and (not assoc?)
- (map? col))
- (set assoc? true))
- (insert res v)
- (insert assoc-res [k v]))
- (if assoc? assoc-res res))))
- :string (if _G.utf8
- (let [char _G.utf8.char]
- (each [_ b (_G.utf8.codes col)]
- (insert res (char b)))
- res)
- (do (io.stderr:write
- "WARNING: utf8 module unavailable, seq function will not work for non-unicode strings\n")
- (each [b (col:gmatch ".")]
- (insert res b))
- res))
- :nil nil
- _ (error (.. "expected table, string or nil, got " (type col)) 2))))
-
-(defn core.kvseq
- "Transforms any table `col' to key-value sequence."
- [col]
- (let [res (empty [])]
- (match (type col)
- :table (let [m (or (getmetatable col) {})]
- (when-some [_ ((or m.cljlib/next next) col)]
- (each [k v (pairs col)]
- (insert res [k v]))
- res))
- :string (if _G.utf8
- (let [char _G.utf8.char]
- (each [i b (_G.utf8.codes col)]
- (insert res [i (char b)]))
- res)
- (do (io.stderr:write
- "WARNING: utf8 module unavailable, seq function will not work for non-unicode strings\n")
- (for [i 1 (length col)]
- (insert res [i (col:sub i i)]))
- res))
- :nil nil
- _ (error (.. "expected table, string or nil, got " (type col)) 2))))
-
-(defn core.first
- "Return first element of a table. Calls `seq' on its argument."
- [col]
- (when-some [col (seq col)]
- (. col 1)))
-
-(defn core.rest
- "Returns table of all elements of a table but the first one. Calls
- `seq' on its argument."
- [col]
- (if-some [col (seq col)]
- (vector (_unpack col 2))
- (empty [])))
-
-(defn core.last
- "Returns the last element of a table. Calls `seq' on its argument."
- [col]
- (when-some [col (seq col)]
- (var (i v) (next col))
- (while i
- (local (_i _v) (next col i))
- (if _i (set v _v))
- (set i _i))
- v))
-
-(defn core.butlast
- "Returns everything but the last element of a table as a new
- table. Calls `seq' on its argument."
- [col]
- (when-some [col (seq col)]
- (table.remove col (length col))
- (when (not (empty? col))
- col)))
-
-(defn core.conj
- "Insert `x' as a last element of a table `tbl'.
-
-If `tbl' is a sequential table or empty table, inserts `x' and
-optional `xs' as final element in the table.
-
-If `tbl' is an associative table, that satisfies `map?' test,
-insert `[key value]` pair into the table.
+ [rev]
+ (seq* (lazy.rseq rev)))
+
+(defn lazy-seq
+ "Create lazy sequence from the result of calling a function `f`.
+Delays execution of `f` until sequence is consumed. `f` must return a
+sequence or a vector."
+ [f]
+ (seq* (lazy.lazy-seq f)))
-Mutates `tbl'.
+(defn first
+ "Return first element of a `coll`. Calls `seq` on its argument."
+ [coll]
+ (lazy.first (seq coll)))
+
+(defn rest
+ "Returns a sequence of all elements of a `coll` but the first one.
+Calls `seq` on its argument."
+ [coll]
+ (seq* (lazy.rest (seq coll))))
+
+(defn- next*
+ "Return the tail of a sequence.
+
+If the sequence is empty, returns nil."
+ [s]
+ (seq* (lazy.next s)))
+
+(doto core (tset :next next*)) ; luajit doesn't like next redefinition
+
+(defn count
+ "Count amount of elements in the sequence."
+ [s]
+ (lazy.count s))
+
+(defn cons
+ "Construct a cons cell.
+Prepends new `head` to a `tail`, which must be either a table,
+sequence, or nil.
# Examples
-Adding to sequential tables:
``` fennel
-(conj [] 1 2 3 4)
-;; => [1 2 3 4]
-(conj [1 2 3] 4 5)
-;; => [1 2 3 4 5]
-```
+(assert-eq [0 1] (cons 0 [1]))
+(assert-eq (list 0 1 2 3) (cons 0 (cons 1 (list 2 3))))
+```"
+ [head tail]
+ (seq* (lazy.cons head tail)))
-Adding to associative tables:
+(fn list
+ [...]
+ "Create eager sequence of provided values.
+
+# Examples
``` fennel
-(conj {:a 1} [:b 2] [:c 3])
-;; => {:a 1 :b 2 :c 3}
+(local l (list 1 2 3 4 5))
+(assert-eq [1 2 3 4 5] l)
+```"
+ (seq* (lazy.list ...)))
+
+(set core.list list)
+
+(defn list*
+ "Creates a new sequence containing the items prepended to the rest,
+the last of which will be treated as a sequence.
+
+# Examples
+
+``` fennel
+(local l (list* 1 2 3 [4 5]))
+(assert-eq [1 2 3 4 5] l)
+```"
+ [& args]
+ (seq* (apply lazy.list* args)))
+
+(defn last
+ "Returns the last element of a `coll`. Calls `seq` on its argument."
+ [coll]
+ (match (next* coll)
+ coll* (last coll*)
+ _ (first coll)))
+
+(defn butlast
+ "Returns everything but the last element of the `coll` as a new
+ sequence. Calls `seq` on its argument."
+ [coll]
+ (seq (lazy.drop-last coll)))
+
+(defn map
+ "Returns a lazy sequence consisting of the result of applying `f` to
+the set of first items of each `coll`, followed by applying `f` to the
+set of second items in each `coll`, until any one of the `colls` is
+exhausted. Any remaining items in other `colls` are ignored. Function
+`f` should accept number-of-colls arguments. Returns a transducer when
+no collection is provided.
+
+# Examples
+
+```fennel
+(map #(+ $ 1) [1 2 3]) ;; => @seq(2 3 4)
+(map #(+ $1 $2) [1 2 3] [4 5 6]) ;; => @seq(5 7 9)
+(def :private res (map #(+ $ 1) [:a :b :c])) ;; will raise an error only when realized
+```"
+ ([f]
+ (fn* [rf]
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (rf result (f input)))
+ ([result input & inputs]
+ (rf result (apply f input inputs))))))
+ ([f coll]
+ (seq* (lazy.map f coll)))
+ ([f coll & colls]
+ (seq* (apply lazy.map f coll colls))))
+
+(defn mapv
+ "Returns a vector consisting of the result of applying `f` to the
+set of first items of each `coll`, followed by applying `f` to the set
+of second items in each coll, until any one of the `colls` is exhausted.
+Any remaining items in other colls are ignored. Function `f` should
+accept number-of-colls arguments."
+ ([f coll]
+ (->> coll
+ (core.transduce (map f)
+ core.conj!
+ (core.transient (vector)))
+ core.persistent!))
+ ([f coll & colls] (vec (apply map f coll colls))))
+
+(defn map-indexed
+ "Returns a lazy sequence consisting of the result of applying `f` to 1
+and the first item of `coll`, followed by applying `f` to 2 and the
+second item in `coll`, etc, until `coll` is exhausted. Returns a
+transducer when no collection is provided."
+ ([f]
+ (fn* [rf]
+ (var i -1)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (set i (+ i 1))
+ (rf result (f i input))))))
+ ([f coll]
+ (seq* (lazy.map-indexed f coll))))
+
+(defn mapcat
+ "Apply `concat` to the result of calling `map` with `f` and
+collections `colls`. Returns a transducer when no collection is
+provided."
+ ([f]
+ (comp (map f) core.cat))
+ ([f & colls]
+ (seq* (apply lazy.mapcat f colls))))
+
+(defn filter
+ "Returns a lazy sequence of the items in `coll` for which
+`pred` returns logical true. Returns a transducer when no collection
+is provided."
+ ([pred]
+ (fn* [rf]
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (if (pred input)
+ (rf result input)
+ result)))))
+ ([pred coll]
+ (seq* (lazy.filter pred coll))))
+
+(defn filterv
+ "Returns a vector of the items in `coll` for which
+`pred` returns logical true."
+ [pred coll]
+ (vec (filter pred coll)))
+
+(defn every?
+ "Test if every item in `coll` satisfies the `pred`."
+ [pred coll]
+ (lazy.every? pred coll))
+
+(defn some
+ "Test if any item in `coll` satisfies the `pred`."
+ [pred coll]
+ (lazy.some? pred coll))
+
+(defn not-any?
+ "Test if no item in `coll` satisfy the `pred`."
+ [pred coll]
+ (some #(not (pred $)) coll))
+
+(defn range
+ "Returns lazy sequence of of numbers from `lower` to `upper` with optional `step`."
+ ([] (seq* (lazy.range)))
+ ([upper] (seq* (lazy.range upper)))
+ ([lower upper] (seq* (lazy.range lower upper)))
+ ([lower upper step] (seq* (lazy.range lower upper step))))
+
+(defn concat
+ "Return a lazy sequence of concatenated `colls`."
+ [& colls]
+ (seq* (apply lazy.concat colls)))
+
+(defn reverse
+ "Returns a lazy sequnce with same items as in `coll` but in reverse order."
+ [coll]
+ (seq* (lazy.reverse coll)))
+
+(defn take
+ "Returns a lazy sequence of the first `n` items in `coll`, or all items if
+there are fewer than `n`."
+ ([n]
+ (fn* [rf]
+ (var n n)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (let [result (if (< 0 n)
+ (rf result input)
+ result)]
+ (set n (- n 1))
+ (if (not (< 0 n))
+ (core.ensure-reduced result)
+ result))))))
+ ([n coll]
+ (seq* (lazy.take n coll))))
+
+(defn take-while
+ "Take the elements from the collection `coll` until `pred` returns logical
+false for any of the elemnts. Returns a lazy sequence. Returns a
+transducer when no collection is provided."
+ ([pred]
+ (fn* [rf]
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (if (pred input)
+ (rf result input)
+ (core.reduced result))))))
+ ([pred coll]
+ (seq* (lazy.take-while pred coll))))
+
+(defn drop
+ "Drop `n` elements from collection `coll`, returning a lazy sequence
+of remaining elements. Returns a transducer when no collection is
+provided."
+ ([n]
+ (fn* [rf]
+ (var nv n)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (let [n nv]
+ (set nv (- nv 1))
+ (if (pos? n)
+ result
+ (rf result input)))))))
+ ([n coll]
+ (seq* (lazy.drop n coll))))
+
+(defn drop-while
+ "Drop the elements from the collection `coll` until `pred` returns logical
+false for any of the elemnts. Returns a lazy sequence. Returns a
+transducer when no collection is provided."
+ ([pred]
+ (fn* [rf]
+ (var dv true)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (let [drop? dv]
+ (if (and drop? (pred input))
+ result
+ (do
+ (set dv nil)
+ (rf result input))))))))
+ ([pred coll]
+ (seq* (lazy.drop-while pred coll))))
+
+(defn drop-last
+ "Return a lazy sequence from `coll` without last `n` elements."
+ ([] (seq* (lazy.drop-last)))
+ ([coll] (seq* (lazy.drop-last coll)))
+ ([n coll] (seq* (lazy.drop-last n coll))))
+
+(defn take-last
+ "Return a sequence of last `n` elements of the `coll`."
+ [n coll]
+ (seq* (lazy.take-last n coll)))
+
+(defn take-nth
+ "Return a lazy sequence of every `n` item in `coll`. Returns a
+transducer when no collection is provided."
+ ([n]
+ (fn* [rf]
+ (var iv -1)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (set iv (+ iv 1))
+ (if (= 0 (% iv n))
+ (rf result input)
+ result)))))
+ ([n coll]
+ (seq* (lazy.take-nth n coll))))
+
+(defn split-at
+ "Return a table with sequence `coll` being split at `n`"
+ [n coll]
+ (vec (lazy.split-at n coll)))
+
+(defn split-with
+ "Return a table with sequence `coll` being split with `pred`"
+ [pred coll]
+ (vec (lazy.split-with pred coll)))
+
+(defn nthrest
+ "Returns the nth rest of `coll`, `coll` when `n` is 0.
+
+# Examples
+
+``` fennel
+(assert-eq (nthrest [1 2 3 4] 3) [4])
+(assert-eq (nthrest [1 2 3 4] 2) [3 4])
+(assert-eq (nthrest [1 2 3 4] 1) [2 3 4])
+(assert-eq (nthrest [1 2 3 4] 0) [1 2 3 4])
```
+"
+ [coll n]
+ (seq* (lazy.nthrest coll n)))
+
+(defn nthnext
+ "Returns the nth next of `coll`, (seq coll) when `n` is 0."
+ [coll n]
+ (lazy.nthnext coll n))
+
+(defn keep
+ "Returns a lazy sequence of the non-nil results of calling `f` on the
+items of the `coll`. Returns a transducer when no collection is
+provided."
+ ([f]
+ (fn* [rf]
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (let [v (f input)]
+ (if (nil? v)
+ result
+ (rf result v)))))))
+ ([f coll]
+ (seq* (lazy.keep f coll))))
+
+(defn keep-indexed
+ "Returns a lazy sequence of the non-nil results of (f index item) in
+the `coll`. Note, this means false return values will be included.
+`f` must be free of side-effects. Returns a transducer when no
+collection is provided."
+ ([f]
+ (fn* [rf]
+ (var iv -1)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (set iv (+ iv 1))
+ (let [v (f iv input)]
+ (if (nil? v)
+ result
+ (rf result v)))))))
+ ([f coll]
+ (seq* (lazy.keep-indexed f coll))))
+
+(defn partition
+ "Given a collection `coll`, returns a lazy sequence of lists of `n`
+items each, at offsets `step` apart. If `step` is not supplied,
+defaults to `n`, i.e. the partitions do not overlap. If a `pad`
+collection is supplied, use its elements as necessary to complete last
+partition upto `n` items. In case there are not enough padding
+elements, return a partition with less than `n` items."
+ ([n coll] (map seq* (lazy.partition n coll)))
+ ([n step coll] (map seq* (lazy.partition n step coll)))
+ ([n step pad coll] (map seq* (lazy.partition n step pad coll))))
+
+(fn array []
+ (var len 0)
+ (->> {:__len #len
+ :__index {:clear (fn [self]
+ (while (not= 0 len)
+ (tset self len nil)
+ (set len (- len 1))
+ self))
+ :add (fn [self val]
+ (set len (+ len 1))
+ (tset self len val)
+ self)}}
+ (setmetatable [])))
+
+(defn partition-by
+ "Applies `f` to each value in `coll`, splitting it each time `f`
+returns a new value. Returns a lazy seq of partitions. Returns a
+transducer, if collection is not supplied."
+ ([f]
+ (fn* [rf]
+ (let [a (array)
+ none {}]
+ (var pv none)
+ (fn*
+ ([] (rf))
+ ([result]
+ (rf (if (empty? a)
+ result
+ (let [v (vec a)]
+ (a:clear)
+ (core.unreduced (rf result v))))))
+ ([result input]
+ (let [pval pv
+ val (f input)]
+ (set pv val)
+ (if (or (= pval none)
+ (= val pval))
+ (do
+ (a:add input)
+ result)
+ (let [v (vec a)]
+ (a:clear)
+ (let [ret (rf result v)]
+ (when (not (core.reduced? ret))
+ (a:add input))
+ ret)))))))))
+ ([f coll]
+ (map seq* (lazy.partition-by f coll))))
+
+(defn partition-all
+ "Given a collection `coll`, returns a lazy sequence of lists like
+`partition`, but may include partitions with fewer than n items at the
+end. Accepts addiitonal `step` argument, similarly to `partition`.
+Returns a transducer, if collection is not supplied."
+ ([n]
+ (fn* [rf]
+ (let [a (array)]
+ (fn*
+ ([] (rf))
+ ([result]
+ (rf (if (= 0 (length a))
+ result
+ (let [v (vec a)]
+ (a:clear)
+ (core.unreduced (rf result v))))))
+ ([result input]
+ (a:add input)
+ (if (= n (length a))
+ (let [v (vec a)]
+ (a:clear)
+ (rf result v))
+ result))))))
+ ([n coll]
+ (map seq* (lazy.partition-all n coll)))
+ ([n step coll]
+ (map seq* (lazy.partition-all n step coll))))
+
+(defn reductions
+ "Returns a lazy seq of the intermediate values of the reduction (as
+per reduce) of `coll` by `f`, starting with `init`."
+ ([f coll] (seq* (lazy.reductions f coll)))
+ ([f init coll] (seq* (lazy.reductions f init coll))))
+
+(defn contains?
+ "Test if `elt` is in the `coll`. May be a linear search depending on the type of the collection."
+ [coll elt]
+ (lazy.contains? coll elt))
+
+(defn distinct
+ "Returns a lazy sequence of the elements of the `coll` without
+duplicates. Comparison is done by equality. Returns a transducer when
+no collection is provided."
+ ([]
+ (fn* [rf]
+ (let [seen (setmetatable {} {:__index deep-index})]
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (if (. seen input)
+ result
+ (do
+ (tset seen input true)
+ (rf result input))))))))
+ ([coll]
+ (seq* (lazy.distinct coll))))
+
+(defn dedupe
+ "Returns a lazy sequence removing consecutive duplicates in coll.
+Returns a transducer when no collection is provided."
+ ([]
+ (fn* [rf]
+ (let [none {}]
+ (var pv none)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (let [prior pv]
+ (set pv input)
+ (if (= prior input)
+ result
+ (rf result input))))))))
+ ([coll] (core.sequence (dedupe) coll)))
+
+(defn random-sample
+ "Returns items from coll with random probability of prob (0.0 -
+1.0). Returns a transducer when no collection is provided."
+ ([prob]
+ (filter (fn [] (< (math.random) prob))))
+ ([prob coll]
+ (filter (fn [] (< (math.random) prob)) coll)))
+
+(defn doall
+ "Realize whole lazy sequence `seq`.
+
+Walks whole sequence, realizing each cell. Use at your own risk on
+infinite sequences."
+ [seq]
+ (seq* (lazy.doall seq)))
+
+(defn dorun
+ "Realize whole sequence `seq` for side effects.
+
+Walks whole sequence, realizing each cell. Use at your own risk on
+infinite sequences."
+ [seq]
+ (lazy.dorun seq))
+
+(defn line-seq
+ "Accepts a `file` handle, and creates a lazy sequence of lines using
+`lines` metamethod.
-Note, that passing literal empty associative table `{}` will not work:
+# Examples
+
+Lazy sequence of file lines may seem similar to an iterator over a
+file, but the main difference is that sequence can be shared onve
+realized, and iterator can't. Lazy sequence can be consumed in
+iterator style with the `doseq` macro.
+
+Bear in mind, that since the sequence is lazy it should be realized or
+truncated before the file is closed:
+
+```fennel
+(let [lines (with-open [f (io.open \"init.fnl\" :r)]
+ (line-seq f))]
+ ;; this errors because only first line was realized, but the file
+ ;; was closed before the rest of lines were cached
+ (assert-not (pcall next lines)))
+```
+
+Sequence is realized with `doall` before file was closed and can be shared:
``` fennel
-(conj {} [:a 1] [:b 2])
-;; => [[:a 1] [:b 2]]
-(conj (hash-map) [:a 1] [:b 2])
-;; => {:a 1 :b 2}
+(let [lines (with-open [f (io.open \"init.fnl\" :r)]
+ (doall (line-seq f)))]
+ (assert-is (pcall next lines)))
```
-See `hash-map' for creating empty associative tables."
- ([] (empty []))
- ([tbl] tbl)
- ([tbl x]
- (when-some [x x]
- (let [tbl (or tbl (empty []))]
- (if (map? tbl)
- (tset tbl (. x 1) (. x 2))
- (tset tbl (+ 1 (length tbl)) x))))
- tbl)
- ([tbl x & xs]
- (apply conj (conj tbl x) xs)))
-
-(defn core.disj
- "Remove key `k' from set `s'."
- ([s] (if (set? s) s
- (error "expected either hash-set or ordered-set as first argument" 2)))
- ([s k]
- (if (set? s)
- (doto s (tset k nil))
- (error "expected either hash-set or ordered-set as first argument" 2)))
- ([s k & ks]
- (apply disj (disj s k) ks)))
-
-(fn consj [...]
- "Like conj but joins at the front. Modifies `tbl'."
- (let [[tbl x & xs] [...]]
- (if (nil? x) tbl
- (consj (doto tbl (insert 1 x)) (_unpack xs)))))
-
-(defn core.cons
- "Insert `x' to `tbl' at the front. Calls `seq' on `tbl'."
- [x tbl]
- (if-some [x x]
- (doto (or (seq tbl) (empty []))
- (insert 1 x))
- tbl))
-
-(defn core.concat
- "Concatenate tables."
- ([] nil)
- ([x] (or (seq x) (empty [])))
- ([x y] (let [to (or (seq x) (empty []))
- from (or (seq y) (empty []))]
- (each [_ v (ipairs from)]
- (insert to v))
- to))
- ([x y & xs]
- (apply concat (concat x y) xs)))
-
-(defn core.reduce
- "Reduce collection `col' using function `f' and optional initial value `val'.
-
-`f' should be a function of 2 arguments. If val is not supplied,
-returns the result of applying f to the first 2 items in coll, then
-applying f to that result and the 3rd item, etc. If coll contains no
-items, f must accept no arguments as well, and reduce returns the
-result of calling f with no arguments. If coll has only 1 item, it is
-returned and f is not called. If val is supplied, returns the result
-of applying f to val and the first item in coll, then applying f to
-that result and the 2nd item, etc. If coll contains no items, returns
-val and f is not called. Calls `seq' on `col'.
-
-Early termination is possible with the use of `reduced'
-function.
+Infinite files can't be fully realized, but can be partially realized
+with `take`:
+
+``` fennel
+(let [lines (with-open [f (io.open \"/dev/urandom\" :r)]
+ (doall (take 3 (line-seq f))))]
+ (assert-is (pcall next lines)))
+```"
+ [file]
+ (seq* (lazy.line-seq file)))
+
+(defn iterate
+ "Returns an infinete lazy sequence of x, (f x), (f (f x)) etc."
+ [f x]
+ (seq* (lazy.iterate f x)))
+
+(defn remove
+ "Returns a lazy sequence of the items in the `coll` without elements
+for wich `pred` returns logical true. Returns a transducer when no
+collection is provided."
+ ([pred]
+ (filter (complement pred)))
+ ([pred coll]
+ (seq* (lazy.remove pred coll))))
+
+(defn cycle
+ "Create a lazy infinite sequence of repetitions of the items in the
+`coll`."
+ [coll]
+ (seq* (lazy.cycle coll)))
+
+(defn repeat
+ "Takes a value `x` and returns an infinite lazy sequence of this value.
# Examples
-Reduce sequence of numbers with `add'
``` fennel
-(reduce add [1 2 3 4])
-;; => 10
-(reduce add 10 [1 2 3 4])
-;; => 20
+(assert-eq 10 (accumulate [res 0
+ _ x (pairs (take 10 (repeat 1)))]
+ (+ res x)))
```"
- ([f col]
- (let [col (or (seq col) (empty []))]
- (match (length col)
- 0 (f)
- 1 (. col 1)
- 2 (f (. col 1) (. col 2))
- _ (let [[a b & rest] col]
- (reduce f (f a b) rest)))))
- ([f val col]
- (let [m (getmetatable val)]
- (if (and m
- m.cljlib/reduced
- (= m.cljlib/reduced.status :ready))
- m.cljlib/reduced.val
- (let [col (or (seq col) (empty []))]
- (let [[x & xs] col]
- (if (nil? x)
- val
- (reduce f (f val x) xs))))))))
-
-(defn core.reduced
- "Wraps `x' in such a way so `reduce' will terminate early
-with this value.
+ [x]
+ (seq* (lazy.repeat x)))
+
+(defn repeatedly
+ "Takes a function `f` and returns an infinite lazy sequence of
+function applications. Rest arguments are passed to the function."
+ [f & args]
+ (seq* (apply lazy.repeatedly f args)))
+
+(defn tree-seq
+ "Returns a lazy sequence of the nodes in a tree, via a depth-first walk.
+
+`branch?` must be a function of one arg that returns true if passed a
+node that can have children (but may not). `children` must be a
+function of one arg that returns a sequence of the children. Will
+only be called on nodes for which `branch?` returns true. `root` is
+the root node of the tree.
# Examples
-Stop reduction is result is higher than `10`:
+
+For the given tree `[\"A\" [\"B\" [\"D\"] [\"E\"]] [\"C\" [\"F\"]]]`:
+
+ A
+ / \\
+ B C
+ / \\ \\
+ D E F
+
+Calling `tree-seq` with `next` as the `branch?` and `rest` as the
+`children` returns a flat representation of a tree:
``` fennel
-(reduce (fn [res x]
- (if (>= res 10)
- (reduced res)
- (+ res x)))
- [1 2 3])
-;; => 6
-
-(reduce (fn [res x]
- (if (>= res 10)
- (reduced res)
- (+ res x)))
- [1 2 3 4 :nil])
-;; => 10
-```
+(assert-eq (map first (tree-seq next rest [\"A\" [\"B\" [\"D\"] [\"E\"]] [\"C\" [\"F\"]]]))
+ [\"A\" \"B\" \"D\" \"E\" \"C\" \"F\"])
+```"
+ [branch? children root]
+ (seq* (lazy.tree-seq branch? children root)))
+
+(defn interleave
+ "Returns a lazy sequence of the first item in each sequence, then the
+second one, until any sequence exhausts."
+ ([] (seq* (lazy.interleave)))
+ ([s] (seq* (lazy.interleave s)))
+ ([s1 s2] (seq* (lazy.interleave s1 s2)))
+ ([s1 s2 & ss] (seq* (apply lazy.interleave s1 s2 ss))))
+
+(defn interpose
+ "Returns a lazy sequence of the elements of `coll` separated by
+`separator`. Returns a transducer when no collection is provided."
+ ([sep]
+ (fn* [rf]
+ (var started false)
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (if started
+ (let [sepr (rf result sep)]
+ (if (core.reduced? sepr)
+ sepr
+ (rf sepr input)))
+ (do
+ (set started true)
+ (rf result input)))))))
+ ([separator coll]
+ (seq* (lazy.interpose separator coll))))
+
+(defn halt-when
+ "Returns a transducer that ends transduction when `pred` returns `true`
+for an input. When `retf` is supplied it must be a `fn` of 2 arguments
+- it will be passed the (completed) result so far and the input that
+triggered the predicate, and its return value (if it does not throw an
+exception) will be the return value of the transducer. If `retf` is
+not supplied, the input that triggered the predicate will be
+returned. If the predicate never returns `true` the transduction is
+unaffected."
+ ([pred]
+ (halt-when pred nil))
+ ([pred retf]
+ (fn* [rf]
+ (let [halt (setmetatable {} {:__fennelview #"#<halt>"})]
+ (fn*
+ ([] (rf))
+ ([result]
+ (if (and (map? result) (contains? result halt))
+ result.value
+ (rf result)))
+ ([result input]
+ (if (pred input)
+ (core.reduced {halt true :value (if retf (retf (rf result) input) input)})
+ (rf result input))))))))
+
+(defn realized?
+ "Check if sequence's first element is realized."
+ [s]
+ (lazy.realized? s))
+
+(defn keys
+ "Returns a sequence of the map's keys, in the same order as `seq`."
+ [coll]
+ (assert (or (map? coll) (empty? coll)) "expected a map")
+ (if (empty? coll)
+ (lazy.list)
+ (lazy.keys coll)))
+
+(defn vals
+ "Returns a sequence of the table's values, in the same order as `seq`."
+ [coll]
+ (assert (or (map? coll) (empty? coll)) "expected a map")
+ (if (empty? coll)
+ (lazy.list)
+ (lazy.vals coll)))
+
+(defn find
+ "Returns the map entry for `key`, or `nil` if key not present in `coll`."
+ [coll key]
+ (assert (or (map? coll) (empty? coll)) "expected a map")
+ (match (. coll key)
+ v [key v]))
+
+(defn sort
+ "Returns a sorted sequence of the items in `coll`. If no `comparator`
+is supplied, uses `<`."
+ ([coll]
+ (match (seq coll)
+ s (seq (itable.sort (vec s)))
+ _ (list)))
+ ([comparator coll]
+ (match (seq coll)
+ s (seq (itable.sort (vec s) comparator))
+ _ (list))))
+
+;;; Reduce
+
+(defn reduce
+ "`f` should be a function of 2 arguments. If `val` is not supplied,
+returns the result of applying `f` to the first 2 items in `coll`,
+then applying `f` to that result and the 3rd item, etc. If `coll`
+contains no items, f must accept no arguments as well, and reduce
+returns the result of calling `f` with no arguments. If `coll` has
+only 1 item, it is returned and `f` is not called. If `val` is
+supplied, returns the result of applying `f` to `val` and the first
+item in `coll`, then applying `f` to that result and the 2nd item,
+etc. If `coll` contains no items, returns `val` and `f` is not
+called. Early termination is supported via `reduced`.
+
+# Examples
+
+``` fennel
+(defn- add
+ ([] 0)
+ ([a] a)
+ ([a b] (+ a b))
+ ([a b & cs] (apply add (+ a b) cs)))
+;; no initial value
+(assert-eq 10 (reduce add [1 2 3 4]))
+;; initial value
+(assert-eq 10 (reduce add 1 [2 3 4]))
+;; empty collection - function is called with 0 args
+(assert-eq 0 (reduce add []))
+(assert-eq 10.3 (reduce math.floor 10.3 []))
+;; collection with a single element doesn't call a function unless the
+;; initial value is supplied
+(assert-eq 10.3 (reduce math.floor [10.3]))
+(assert-eq 7 (reduce add 3 [4]))
+```"
+ ([f coll] (lazy.reduce f (seq coll)))
+ ([f val coll] (lazy.reduce f val (seq coll))))
+
+(defn reduced
+ "Terminates the `reduce` early with a given `value`.
-Note that in second example we had `:nil` in the array, which is not a
-valid number, but we've terminated right before we've reached it."
+# Examples
+
+``` fennel
+(assert-eq :NaN
+ (reduce (fn [acc x]
+ (if (not= :number (type x))
+ (reduced :NaN)
+ (+ acc x)))
+ [1 2 :3 4 5]))
+```"
+ [value]
+ (doto (lazy.reduced value)
+ (-> getmetatable (tset :cljlib/deref #$.value))))
+
+(defn reduced?
+ "Returns true if `x` is the result of a call to reduced"
[x]
- (setmetatable
- {} {:cljlib/reduced {:status :ready
- :val x}}))
+ (lazy.reduced? x))
-(defn core.reduce-kv
- "Reduces an associative table using function `f' and initial value `val'.
+(defn unreduced
+ [x]
+ (if (reduced? x) (deref x) x))
-`f' should be a function of 3 arguments. Returns the result of
-applying `f' to `val', the first key and the first value in `tbl',
-then applying `f' to that result and the 2nd key and value, etc. If
-`tbl' contains no entries, returns `val' and `f' is not called. Note
+(defn ensure-reduced
+ "If x is already reduced?, returns it, else returns (reduced x)"
+ [x]
+ (if (reduced? x)
+ x
+ (reduced x)))
+
+(defn- preserving-reduced [rf]
+ (fn* [a b]
+ (let [ret (rf a b)]
+ (if (reduced? ret)
+ (reduced ret)
+ ret))))
+
+(defn cat
+ "A transducer which concatenates the contents of each input, which must be a
+ collection, into the reduction."
+ [rf]
+ (let [rrf (preserving-reduced rf)]
+ (fn*
+ ([] (rf))
+ ([result] (rf result))
+ ([result input]
+ (reduce rrf result input)))))
+
+(defn reduce-kv
+ "Reduces an associative table using function `f` and initial value `val`.
+
+`f` should be a function of 3 arguments. Returns the result of
+applying `f` to `val`, the first key and the first value in `tbl`,
+then applying `f` to that result and the 2nd key and value, etc. If
+`tbl` contains no entries, returns `val` and `f` is not called. Note
that reduce-kv is supported on sequential tables and strings, where
the keys will be the ordinals.
-Early termination is possible with the use of `reduced'
+Early termination is possible with the use of `reduced`
function.
# Examples
@@ -693,7 +1532,7 @@ Reduce associative table by adding values from all keys:
;; => 8
```
-Reduce table by adding values from keys that start with letter `a':
+Reduce table by adding values from keys that start with letter `a`:
``` fennel
(local t {:a1 1
@@ -705,432 +1544,638 @@ Reduce table by adding values from keys that start with letter `a':
0 t)
;; => 3
```"
- [f val tbl]
- (var res val)
- (each [_ [k v] (ipairs (or (kvseq tbl) (empty [])))]
- (set res (f res k v))
- (match (getmetatable res)
- m (if (and m.cljlib/reduced
- (= m.cljlib/reduced.status :ready))
- (do (set res m.cljlib/reduced.val)
- (lua :break)))))
- res)
-
-(defn core.mapv
- "Maps function `f' over one or more collections.
-
-Accepts arbitrary amount of collections, calls `seq' on each of it.
-Function `f' must take the same amount of arguments as the amount of
-tables, passed to `mapv'. Applies `f' over first value of each
-table. Then applies `f' to second value of each table. Continues until
-any of the tables is exhausted. All remaining values are
-ignored. Returns a sequential table of results.
+ [f val s]
+ (if (map? s)
+ (reduce (fn [res [k v]] (f res k v)) val (seq s))
+ (reduce (fn [res [k v]] (f res k v)) val (map vector (drop 1 (range)) (seq s)))))
+
+(defn completing
+ "Takes a reducing function `f` of 2 args and returns a function
+suitable for transduce by adding an arity-1 signature that calls
+`cf` (default - `identity`) on the result argument."
+ ([f] (completing f identity))
+ ([f cf]
+ (fn*
+ ([] (f))
+ ([x] (cf x))
+ ([x y] (f x y)))))
+
+(defn transduce
+ "`reduce` with a transformation of `f` (`xform`). If `init` is not
+supplied, `f` will be called to produce it. f should be a reducing
+step function that accepts both 1 and 2 arguments, if it accepts only
+2 you can add the arity-1 with `completing`. Returns the result of
+applying (the transformed) `xform` to `init` and the first item in
+`coll`, then applying `xform` to that result and the 2nd item, etc. If
+`coll` contains no items, returns `init` and `f` is not called. Note
+that certain transforms may inject or skip items."
+ ([xform f coll] (transduce xform f (f) coll))
+ ([xform f init coll]
+ (let [f (xform f)]
+ (f (reduce f init (seq coll))))))
+
+(defn sequence
+ "Coerces coll to a (possibly empty) sequence, if it is not already
+one. Will not force a lazy seq. `(sequence nil)` yields an empty list,
+When a transducer is supplied, returns a lazy sequence of applications
+of the transform to the items in `coll`, i.e. to the set of first
+items of each `coll`, followed by the set of second items in each
+`coll`, until any one of the `colls` is exhausted. Any remaining
+items in other `colls` are ignored. The transform should accept
+number-of-colls arguments"
+ ([coll]
+ (if (seq? coll) coll
+ (or (seq coll) (list))))
+ ([xform coll]
+ (let [f (xform (completing #(cons $2 $1)))]
+ (or ((fn step [coll]
+ (if-some [s (seq coll)]
+ (let [res (f nil (first s))]
+ (cond (reduced? res) (f (deref res))
+ (seq? res) (concat res (lazy-seq #(step (rest s))))
+ :else (step (rest s))))
+ (f nil)))
+ coll)
+ (list))))
+ ([xform coll & colls]
+ (let [f (xform (completing #(cons $2 $1)))]
+ (or ((fn step [colls]
+ (if (every? seq colls)
+ (let [res (apply f nil (map first colls))]
+ (cond (reduced? res) (f (deref res))
+ (seq? res) (concat res (lazy-seq #(step (map rest colls))))
+ :else (step (map rest colls))))
+ (f nil)))
+ (cons coll colls))
+ (list)))))
+
+;;; Hash map
+
+(fn map->transient [immutable]
+ (fn [map]
+ (let [removed (setmetatable {} {:__index deep-index})]
+ (->> {:__index (fn [_ k]
+ (if (not (. removed k))
+ (. map k)))
+ :cljlib/type :transient
+ :cljlib/conj #(error "can't `conj` onto transient map, use `conj!`")
+ :cljlib/assoc #(error "can't `assoc` onto transient map, use `assoc!`")
+ :cljlib/dissoc #(error "can't `dissoc` onto transient map, use `dissoc!`")
+ :cljlib/conj! (fn [tmap [k v]]
+ (if (= nil v)
+ (tset removed k true)
+ (tset removed k nil))
+ (doto tmap (tset k v)))
+ :cljlib/assoc! (fn [tmap ...]
+ (for [i 1 (select "#" ...) 2]
+ (let [(k v) (select i ...)]
+ (tset tmap k v)
+ (if (= nil v)
+ (tset removed k true)
+ (tset removed k nil))))
+ tmap)
+ :cljlib/dissoc! (fn [tmap ...]
+ (for [i 1 (select "#" ...)]
+ (let [k (select i ...)]
+ (tset tmap k nil)
+ (tset removed k true)))
+ tmap)
+ :cljlib/persistent! (fn [tmap]
+ (let [t (collect [k v (pairs tmap)
+ :into (collect [k v (pairs map)]
+ (values k v))]
+ (values k v))]
+ (each [k (pairs removed)]
+ (tset t k nil))
+ (each [_ k (ipairs (icollect [k (pairs* tmap)] k))]
+ (tset tmap k nil))
+ (setmetatable tmap
+ {:__index #(error "attempt to use transient after it was persistet")
+ :__newindex #(error "attempt to use transient after it was persistet")})
+ (immutable (itable t))))}
+ (setmetatable {})))))
+
+(fn hash-map* [x]
+ "Add cljlib hash-map meta-info."
+ (match (getmetatable x)
+ mt (doto mt
+ (tset :cljlib/type :hash-map)
+ (tset :cljlib/editable true)
+ (tset :cljlib/conj
+ (fn [t [k v] ...]
+ (apply core.assoc
+ t k v
+ (accumulate [kvs [] _ [k v] (ipairs* [...])]
+ (doto kvs
+ (table.insert k)
+ (table.insert v))))))
+ (tset :cljlib/transient (map->transient hash-map*))
+ (tset :cljlib/empty #(hash-map* (itable {}))))
+ _ (hash-map* (setmetatable x {})))
+ x)
+
+(defn assoc
+ "Associate `val` under a `key`.
+Accepts extra keys and values.
# Examples
-Map `string.upcase' over the string:
``` fennel
-(mapv string.upper \"string\")
-;; => [\"S\" \"T\" \"R\" \"I\" \"N\" \"G\"]
-```
+(assert-eq {:a 1 :b 2} (assoc {:a 1} :b 2))
+(assert-eq {:a 1 :b 2} (assoc {:a 1 :b 1} :b 2))
+(assert-eq {:a 1 :b 2 :c 3} (assoc {:a 1 :b 1} :b 2 :c 3))
+```"
+ ([tbl]
+ (hash-map* (itable {})))
+ ([tbl k v]
+ (assert (or (nil? tbl) (map? tbl) (empty? tbl)) "expected a map")
+ (assert (not (nil? k)) "attempt to use nil as key")
+ (hash-map* (itable.assoc (or tbl {}) k v)))
+ ([tbl k v & kvs]
+ (assert (or (nil? tbl) (map? tbl) (empty? tbl)) "expected a map")
+ (assert (not (nil? k)) "attempt to use nil as key")
+ (hash-map* (apply itable.assoc (or tbl {}) k v kvs))))
+
+(defn assoc-in
+ "Associate `val` into set of immutable nested tables `t`, via given `key-seq`.
+Returns a new immutable table. Returns a new immutable table.
+
+# Examples
-Map `mul' over two tables:
+Replace value under nested keys:
``` fennel
-(mapv mul [1 2 3 4] [1 0 -1])
-;; => [1 0 -3]
+(assert-eq
+ {:a {:b {:c 1}}}
+ (assoc-in {:a {:b {:c 0}}} [:a :b :c] 1))
```
-Basic `zipmap' implementation:
+Create new entries as you go:
``` fennel
-(import-macros {: into} :init-macros)
-(fn zipmap [keys vals]
- (into {} (mapv vector keys vals)))
-
-(zipmap [:a :b :c] [1 2 3 4])
-;; => {:a 1 :b 2 :c 3}
+(assert-eq
+ {:a {:b {:c 1}} :e 2}
+ (assoc-in {:e 2} [:a :b :c] 1))
```"
- ([f col]
- (local res (empty []))
- (each [_ v (ipairs (or (seq col) (empty [])))]
- (when-some [tmp (f v)]
- (insert res tmp)))
- res)
- ([f col1 col2]
- (let [res (empty [])
- col1 (or (seq col1) (empty []))
- col2 (or (seq col2) (empty []))]
- (var (i1 v1) (next col1))
- (var (i2 v2) (next col2))
- (while (and i1 i2)
- (when-some [tmp (f v1 v2)]
- (insert res tmp))
- (set (i1 v1) (next col1 i1))
- (set (i2 v2) (next col2 i2)))
- res))
- ([f col1 col2 col3]
- (let [res (empty [])
- col1 (or (seq col1) (empty []))
- col2 (or (seq col2) (empty []))
- col3 (or (seq col3) (empty []))]
- (var (i1 v1) (next col1))
- (var (i2 v2) (next col2))
- (var (i3 v3) (next col3))
- (while (and i1 i2 i3)
- (when-some [tmp (f v1 v2 v3)]
- (insert res tmp))
- (set (i1 v1) (next col1 i1))
- (set (i2 v2) (next col2 i2))
- (set (i3 v3) (next col3 i3)))
- res))
- ([f col1 col2 col3 & cols]
- (let [step (fn step [cols]
- (if (->> cols
- (mapv #(not= (next $) nil))
- (reduce #(and $1 $2)))
- (cons (mapv #(. (or (seq $) (empty [])) 1) cols)
- (step (mapv #(do [(_unpack $ 2)]) cols)))
- (empty [])))
- res (empty [])]
- (each [_ v (ipairs (step (consj cols col3 col2 col1)))]
- (when-some [tmp (apply f v)]
- (insert res tmp)))
- res)))
-
-(defn core.filter
- "Returns a sequential table of the items in `col' for which `pred'
- returns logical true."
- [pred col]
- (if-let [col (seq col)]
- (let [f (. col 1)
- r [(_unpack col 2)]]
- (if (pred f)
- (cons f (filter pred r))
- (filter pred r)))
- (empty [])))
-
-(defn core.every?
- "Test if every item in `tbl' satisfies the `pred'."
- [pred tbl]
- (if (empty? tbl) true
- (pred (. tbl 1)) (every? pred [(_unpack tbl 2)])
- false))
+ [tbl key-seq val]
+ (assert (or (nil? tbl) (map? tbl) (empty? tbl)) "expected a map or nil")
+ (hash-map* (itable.assoc-in tbl key-seq val)))
-(defn core.some
- "Test if any item in `tbl' satisfies the `pred'."
- [pred tbl]
- (when-let [tbl (seq tbl)]
- (or (pred (. tbl 1)) (some pred [(_unpack tbl 2)]))))
-
-(defn core.not-any?
- "Test if no item in `tbl' satisfy the `pred'."
- [pred tbl]
- (some #(not (pred $)) tbl))
-
-(defn core.range
- "return range of of numbers from `lower' to `upper' with optional `step'."
- ([upper] (range 0 upper 1))
- ([lower upper] (range lower upper 1))
- ([lower upper step]
- (let [res (empty [])]
- (for [i lower (- upper step) step]
- (insert res i))
- res)))
-
-(defn core.reverse
- "Returns table with same items as in `tbl' but in reverse order."
- [tbl]
- (when-some [tbl (seq tbl)]
- (reduce consj (empty []) tbl)))
-
-(defn core.take
- "Returns a sequence of the first `n' items in `col', or all items if
-there are fewer than `n'."
- [n col]
- (if (= n 0)
- []
- (pos-int? n)
- (if-let [s (seq col)]
- (cons (first s) (take (dec n) (rest s)))
- nil)
- (error "expected positive integer as first argument" 2)))
-
-(defn core.nthrest
- "Returns the nth rest of `col', `col' when `n' is 0.
+(defn update
+ "Update table value stored under `key` by calling a function `f` on
+that value. `f` must take one argument, which will be a value stored
+under the key in the table.
# Examples
+Same as `assoc` but accepts function to produce new value based on key value.
+
``` fennel
-(assert-eq (nthrest [1 2 3 4] 3) [4])
-(assert-eq (nthrest [1 2 3 4] 2) [3 4])
-(assert-eq (nthrest [1 2 3 4] 1) [2 3 4])
-(assert-eq (nthrest [1 2 3 4] 0) [1 2 3 4])
-```
-"
- [col n]
- [(_unpack col (inc n))])
+(assert-eq
+ {:data \"THIS SHOULD BE UPPERCASE\"}
+ (update {:data \"this should be uppercase\"} :data string.upper))
+```"
+ [tbl key f]
+ (assert (or (nil? tbl) (map? tbl) (empty? tbl)) "expected a map")
+ (hash-map* (itable.update tbl key f)))
-(defn core.partition
- "Returns a sequence of sequences of `n' items each, at offsets step
-apart. If `step' is not supplied, defaults to `n', i.e. the partitions
-do not overlap. If a `pad' collection is supplied, use its elements as
-necessary to complete last partition up to `n' items. In case there
-are not enough padding elements, return a partition with less than `n'
-items.
-# Examples
-Partition sequence into sub-sequences of size 3:
+(defn update-in
+ "Update table value stored under set of immutable nested tables, via
+given `key-seq` by calling a function `f` on the value stored under the
+last key. `f` must take one argument, which will be a value stored
+under the key in the table. Returns a new immutable table.
-``` fennel
-(assert-eq (partition 3 [1 2 3 4 5 6]) [[1 2 3] [4 5 6]])
-```
+# Examples
-When collection doesn't have enough elements, partition will not include those:
+Same as `assoc-in` but accepts function to produce new value based on key value.
``` fennel
-(assert-eq (partition 3 [1 2 3 4]) [[1 2 3]])
-```
+(fn capitalize-words [s]
+ (pick-values 1
+ (s:gsub \"(%a)([%w_`]*)\" #(.. ($1:upper) ($2:lower)))))
-Partitions can overlap if step is supplied:
+(assert-eq
+ {:user {:name \"John Doe\"}}
+ (update-in {:user {:name \"john doe\"}} [:user :name] capitalize-words))
+```"
+ [tbl key-seq f]
+ (assert (or (nil? tbl) (map? tbl) (empty? tbl)) "expected a map or nil")
+ (hash-map* (itable.update-in tbl key-seq f)))
-``` fennel
-(assert-eq (partition 2 1 [1 2 3 4]) [[1 2] [2 3] [3 4]])
-```
+(defn hash-map
+ "Create associative table from `kvs` represented as sequence of keys
+and values"
+ [& kvs]
+ (apply assoc {} kvs))
-Additional padding can be used to supply insufficient elements:
+(defn get
+ "Get value from the table by accessing it with a `key`.
+Accepts additional `not-found` as a marker to return if value wasn't
+found in the table."
+ ([tbl key] (get tbl key nil))
+ ([tbl key not-found]
+ (assert (or (map? tbl) (empty? tbl)) "expected a map")
+ (or (. tbl key) not-found)))
-``` fennel
-(assert-eq (partition 3 3 [3 2 1] [1 2 3 4]) [[1 2 3] [4 3 2]])
-```"
- ([n col]
- (partition n n col))
- ([n step col]
- (if-let [s (seq col)]
- (let [p (take n s)]
- (if (= n (length p))
- (cons p (partition n step (nthrest s step)))
- nil))
- nil))
- ([n step pad col]
- (if-let [s (seq col)]
- (let [p (take n s)]
- (if (= n (length p))
- (cons p (partition n step pad (nthrest s step)))
- [(take n (concat p pad))]))
- nil)))
-
-(local sequence-doc-order
- [:vector :seq :kvseq :first :rest :last :butlast
- :conj :disj :cons :concat :reduce :reduced :reduce-kv
- :mapv :filter :every? :some :not-any? :range :reverse :take
- :nthrest :partition])
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Equality ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(var eq nil)
+(defn get-in
+ "Get value from nested set of tables by providing key sequence.
+Accepts additional `not-found` as a marker to return if value wasn't
+found in the table."
+ ([tbl keys] (get-in tbl keys nil))
+ ([tbl keys not-found]
+ (assert (or (map? tbl) (empty? tbl)) "expected a map")
+ (var (res t done) (values tbl tbl nil))
+ (each [_ k (ipairs* keys) :until done]
+ (match (. t k)
+ v (set (res t) (values v v))
+ _ (set (res done) (values not-found true))))
+ res))
-(fn deep-index [tbl key]
- "This function uses the pre-declared `eq', which we set later on,
-because `eq' requires this function internally. Several other
-functions also reuse this indexing method, such as sets."
- (var res nil)
- (each [k v (pairs tbl)]
- (when (eq k key)
- (set res v)
- (lua :break)))
- res)
+(defn dissoc
+ "Remove `key` from table `tbl`. Optionally takes more `keys`."
+ ([tbl] tbl)
+ ([tbl key]
+ (assert (or (map? tbl) (empty? tbl)) "expected a map")
+ (hash-map* (doto tbl (tset key nil))))
+ ([tbl key & keys]
+ (apply dissoc (dissoc tbl key) keys)))
-(defn _eq
- "Deep compare values.
+(defn merge
+ "Merge `maps` rght to left into a single hash-map."
+ [& maps]
+ (when (some identity maps)
+ (->> maps
+ (reduce (fn [a b] (collect [k v (pairs* b) :into a]
+ (values k v)))
+ {})
+ itable
+ hash-map*)))
+
+(defn frequencies
+ "Return a table of unique entries from table `t` associated to amount
+of their appearances.
# Examples
-`eq' can compare both primitive types, tables, and user defined types
-that have `__eq` metamethod.
+Count each entry of a random letter:
``` fennel
-(assert-is (eq 42 42))
-(assert-is (eq [1 2 3] [1 2 3]))
-(assert-is (eq (hash-set :a :b :c) (hash-set :a :b :c)))
-(assert-is (eq (hash-set :a :b :c) (ordered-set :c :b :a)))
-```
+(let [fruits [:banana :banana :apple :strawberry :apple :banana]]
+ (assert-eq (frequencies fruits)
+ {:banana 3
+ :apple 2
+ :strawberry 1}))
+```"
+ [t]
+ (hash-map* (itable.frequencies t)))
-Deep comparison is used for tables which use tables as keys:
+(defn group-by
+ "Group table items in an associative table under the keys that are
+results of calling `f` on each element of sequential table `t`.
+Elements that the function call resulted in `nil` returned in a
+separate table.
+
+# Examples
+
+Group rows by their date:
``` fennel
-(assert-is (eq {[1 2 3] {:a [1 2 3]} {:a 1} {:b 2}}
- {{:a 1} {:b 2} [1 2 3] {:a [1 2 3]}}))
-(assert-is (eq {{{:a 1} {:b 1}} {{:c 3} {:d 4}} [[1] [2 [3]]] {:a 2}}
- {[[1] [2 [3]]] {:a 2} {{:a 1} {:b 1}} {{:c 3} {:d 4}}}))
+(local rows
+ [{:date \"2007-03-03\" :product \"pineapple\"}
+ {:date \"2007-03-04\" :product \"pizza\"}
+ {:date \"2007-03-04\" :product \"pineapple pizza\"}
+ {:date \"2007-03-05\" :product \"bananas\"}])
+
+(assert-eq (group-by #(. $ :date) rows)
+ {\"2007-03-03\"
+ [{:date \"2007-03-03\" :product \"pineapple\"}]
+ \"2007-03-04\"
+ [{:date \"2007-03-04\" :product \"pizza\"}
+ {:date \"2007-03-04\" :product \"pineapple pizza\"}]
+ \"2007-03-05\"
+ [{:date \"2007-03-05\" :product \"bananas\"}]})
```"
- ([x] true)
- ([x y]
- (if (= x y)
- true
- (and (= (type x) :table) (= (type y) :table))
- (do (var [res count-a count-b] [true 0 0])
- (each [k v (pairs x)]
- (set res (eq v (deep-index y k)))
- (set count-a (+ count-a 1))
- (when (not res) (lua :break)))
- (when res
- (each [_ _ (pairs y)]
- (set count-b (+ count-b 1)))
- (set res (= count-a count-b)))
- res)
- :else
- false))
- ([x y & xs]
- (and (eq x y) (apply eq x xs))))
+ [f t]
+ (hash-map* (pick-values 1 (itable.group-by f t))))
+
+(defn zipmap
+ "Return an associative table with the `keys` mapped to the
+corresponding `vals`."
+ [keys vals]
+ (hash-map* (itable (lazy.zipmap keys vals))))
+
+(defn replace
+ "Given a map of replacement pairs and a vector/collection `coll`,
+returns a vector/seq with any elements `=` a key in `smap` replaced
+with the corresponding `val` in `smap`. Returns a transducer when no
+collection is provided."
+ ([smap]
+ (map #(if-let [e (find smap $)] (. e 2) $)))
+ ([smap coll]
+ (if (vector? coll)
+ (->> coll
+ (reduce (fn [res v]
+ (if-let [e (find smap v)]
+ (doto res (table.insert (. e 2)))
+ (doto res (table.insert v))))
+ [])
+ itable
+ vec*)
+ (map #(if-let [e (find smap $)] (. e 2) $) coll))))
+
+;;; Conj
+
+(defn conj
+ "Insert `x` as a last element of a table `tbl`.
+
+If `tbl` is a sequential table or empty table, inserts `x` and
+optional `xs` as final element in the table.
+
+If `tbl` is an associative table, that satisfies `map?` test,
+insert `[key value]` pair into the table.
-(set eq _eq)
-(set core.eq _eq)
+Mutates `tbl`.
-;;;;;;;;;;;;;;;;;;;;;; Function manipulation functions ;;;;;;;;;;;;;;;;;;;;;;;;;
+# Examples
+Adding to sequential tables:
-(defn core.identity "Returns its argument." [x] x)
+``` fennel
+(conj [] 1 2 3 4)
+;; => [1 2 3 4]
+(conj [1 2 3] 4 5)
+;; => [1 2 3 4 5]
+```
-(defn core.comp
- "Compose functions."
- ([] identity)
- ([f] f)
- ([f g]
- (defn
- ([] (f (g)))
- ([x] (f (g x)))
- ([x y] (f (g x y)))
- ([x y z] (f (g x y z)))
- ([x y z & args] (f (g x y z (_unpack args))))))
- ([f g & fs]
- (reduce comp (consj fs g f))))
+Adding to associative tables:
-(defn core.complement
- "Takes a function `f' and returns the function that takes the same
-amount of arguments as `f', has the same effect, and returns the
-oppisite truth value."
- [f]
- (defn
- ([] (not (f)))
- ([a] (not (f a)))
- ([a b] (not (f a b)))
- ([a b & cs] (not (apply f a b cs)))))
+``` fennel
+(conj {:a 1} [:b 2] [:c 3])
+;; => {:a 1 :b 2 :c 3}
+```
-(defn core.constantly
- "Returns a function that takes any number of arguments and returns `x'."
- [x]
- (fn [] x))
+Note, that passing literal empty associative table `{}` will not work:
-(defn core.memoize
- "Returns a memoized version of a referentially transparent function.
-The memoized version of the function keeps a cache of the mapping from
-arguments to results and, when calls with the same arguments are
-repeated often, has higher performance at the expense of higher memory
-use."
- [f]
- (let [memo (setmetatable {} {:__index deep-index})]
- (fn [...]
- (let [args [...]]
- (if-some [res (. memo args)]
- res
- (let [res (f ...)]
- (tset memo args res)
- res))))))
+``` fennel
+(conj {} [:a 1] [:b 2])
+;; => [[:a 1] [:b 2]]
+(conj (hash-map) [:a 1] [:b 2])
+;; => {:a 1 :b 2}
+```
-(local function-manipulation-doc-order
- [:identity :comp :complement :constantly :memoize])
+See `hash-map` for creating empty associative tables."
+ ([] (vector))
+ ([s] s)
+ ([s x]
+ (match (getmetatable s)
+ {:cljlib/conj f} (f s x)
+ _ (if (vector? s) (vec* (itable.insert s x))
+ (map? s) (apply assoc s x)
+ (nil? s) (cons x s)
+ (empty? s) (vector x)
+ (error "expected collection, got" (type s)))))
+ ([s x & xs]
+ (apply conj (conj s x) xs)))
+
+(defn disj
+ "Returns a new set type, that does not contain the
+specified `key` or `keys`."
+ ([Set] Set)
+ ([Set key]
+ (match (getmetatable Set)
+ {:cljlib/type :hash-set :cljlib/disj f} (f Set key)
+ _ (error (.. "disj is not supported on " (class Set)) 2)))
+ ([Set key & keys]
+ (match (getmetatable Set)
+ {:cljlib/type :hash-set :cljlib/disj f} (apply f Set key keys)
+ _ (error (.. "disj is not supported on " (class Set)) 2))))
+
+;;; Transients
+
+(defn transient
+ "Returns a new, transient version of the collection."
+ [coll]
+ (match (getmetatable coll)
+ {:cljlib/editable true :cljlib/transient f} (f coll)
+ _ (error "expected editable collection" 2)))
+
+(defn conj!
+ "Adds `x` to the transient collection, and return `coll`."
+ ([] (transient (vec* [])))
+ ([coll] coll)
+ ([coll x]
+ (match (getmetatable coll)
+ {:cljlib/type :transient :cljlib/conj! f} (f coll x)
+ {:cljlib/type :transient} (error "unsupported transient operation" 2)
+ _ (error "expected transient collection" 2))
+ coll))
+
+(defn assoc!
+ "Remove `k`from transient map, and return `map`."
+ [map k & ks]
+ (match (getmetatable map)
+ {:cljlib/type :transient :cljlib/dissoc! f} (apply f map k ks)
+ {:cljlib/type :transient} (error "unsupported transient operation" 2)
+ _ (error "expected transient collection" 2))
+ map)
+
+(defn dissoc!
+ "Remove `k`from transient map, and return `map`."
+ [map k & ks]
+ (match (getmetatable map)
+ {:cljlib/type :transient :cljlib/dissoc! f} (apply f map k ks)
+ {:cljlib/type :transient} (error "unsupported transient operation" 2)
+ _ (error "expected transient collection" 2))
+ map)
+
+(defn disj!
+ "disj[oin]. Returns a transient set of the same type, that does not
+contain `key`."
+ ([Set] Set)
+ ([Set key & ks]
+ (match (getmetatable Set)
+ {:cljlib/type :transient :cljlib/disj! f} (apply f Set key ks)
+ {:cljlib/type :transient} (error "unsupported transient operation" 2)
+ _ (error "expected transient collection" 2))))
+
+(defn pop!
+ "Removes the last item from a transient vector. If the collection is
+empty, raises an error Returns coll"
+ [coll]
+ (match (getmetatable coll)
+ {:cljlib/type :transient :cljlib/pop! f} (f coll)
+ {:cljlib/type :transient} (error "unsupported transient operation" 2)
+ _ (error "expected transient collection" 2)))
+
+(defn persistent!
+ "Returns a new, persistent version of the transient collection. The
+transient collection cannot be used after this call, any such use will
+raise an error."
+ [coll]
+ (match (getmetatable coll)
+ {:cljlib/type :transient :cljlib/persistent! f} (f coll)
+ _ (error "expected transient collection" 2)))
+
+;;; Into
+
+(defn into
+ "Returns a new coll consisting of to-coll with all of the items of
+ from-coll conjoined. A transducer may be supplied.
+# Examples
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Hash table extras ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Thransofmr a hash-map into a sequence of key-value pairs:
-(defn core.assoc
- "Associate key `k' with value `v' in `tbl'."
- ([tbl k v]
- (assert (not (nil? k)) "attempt to use nil as key")
- (setmetatable
- (doto tbl (tset k v))
- {:cljlib/type :table}))
- ([tbl k v & kvs]
- (when (not= (% (length kvs) 2) 0)
- (.. "no value supplied for key " (tostring (. kvs (length kvs)))))
- (assert (not (nil? k)) "attempt to use nil as key")
- (tset tbl k v)
- (var [k v] [nil nil])
- (var (i k) (next kvs))
- (while i
- (set (i v) (next kvs i))
- (tset tbl k v)
- (set (i k) (next kvs i)))
- (setmetatable tbl {:cljlib/type :table})))
-
-(defn core.hash-map
- "Create associative table from `kvs' represented as sequence of keys
-and values"
- ([] (empty {}))
- ([& kvs] (apply assoc {} kvs)))
+```fennel
+(assert-eq [[:a 1]] (into (vector) {:a 1}))
+```
-(defn core.get
- "Get value from the table by accessing it with a `key'.
-Accepts additional `not-found' as a marker to return if value wasn't
-found in the table."
- ([tbl key] (get tbl key nil))
- ([tbl key not-found]
- (if-some [res (. tbl key)]
- res
- not-found)))
+ Construct a hash-map from a sequence of key-value pairs:
-(defn core.get-in
- "Get value from nested set of tables by providing key sequence.
-Accepts additional `not-found' as a marker to return if value wasn't
-found in the table."
- ([tbl keys] (get-in tbl keys nil))
- ([tbl keys not-found]
- (var res tbl)
- (var t tbl)
- (each [_ k (ipairs keys)]
- (if-some [v (. t k)]
- (set [res t] [v v])
- (set res not-found)))
- res))
+```fennel
+(assert-eq {:a 1 :b 2 :c 3}
+ (into (hash-map) [[:a 1] [:b 2] [:c 3]]))
+```"
+ ([] (vector))
+ ([to] to)
+ ([to from]
+ (match (getmetatable to)
+ {:cljlib/editable true}
+ (persistent! (reduce conj! (transient to) from))
+ _ (reduce conj to from)))
+ ([to xform from]
+ (match (getmetatable to)
+ {:cljlib/editable true}
+ (persistent! (transduce xform conj! (transient to) from))
+ _ (transduce xform conj to from))))
+
+;;; Hash Set
-(defn core.keys
- "Returns a sequence of the table's keys, in the same order as `seq'."
- [tbl]
- (let [res []]
- (each [k _ (pairs tbl)]
- (insert res k))
- res))
-
-(defn core.vals
- "Returns a sequence of the table's values, in the same order as `seq'."
- [tbl]
- (let [res []]
- (each [_ v (pairs tbl)]
- (insert res v))
- res))
-
-(defn core.find
- "Returns the map entry for `key', or `nil' if key not present in `tbl'."
- [tbl key]
- (when-some [v (. tbl key)]
- [key v]))
-
-(defn core.dissoc
- "Remove `key' from table `tbl'. Optionally takes more `keys`."
- ([tbl] tbl)
- ([tbl key]
- (doto tbl (tset key nil)))
- ([tbl key & keys]
- (apply dissoc (dissoc tbl key) keys)))
+(fn viewset [Set view inspector indent]
+ (if (. inspector.seen Set)
+ (.. "@set" (. inspector.seen Set) "{...}")
+ (let [prefix (.. "@set"
+ (if (inspector.visible-cycle? Set)
+ (. inspector.seen Set) "")
+ "{")
+ set-indent (length prefix)
+ indent-str (string.rep " " set-indent)
+ lines (icollect [v (pairs* Set)]
+ (.. indent-str
+ (view v inspector (+ indent set-indent) true)))]
+ (tset lines 1 (.. prefix (string.gsub (or (. lines 1) "") "^%s+" "")))
+ (tset lines (length lines) (.. (. lines (length lines)) "}"))
+ lines)))
-(local hash-table-doc-order
- [:assoc :hash-map :get :get-in :keys :vals :find :dissoc])
+(fn hash-set->transient [immutable]
+ (fn [hset]
+ (let [removed (setmetatable {} {:__index deep-index})]
+ (->> {:__index (fn [_ k]
+ (if (not (. removed k)) (. hset k)))
+ :cljlib/type :transient
+ :cljlib/conj #(error "can't `conj` onto transient set, use `conj!`")
+ :cljlib/disj #(error "can't `disj` a transient set, use `disj!`")
+ :cljlib/assoc #(error "can't `assoc` onto transient set, use `assoc!`")
+ :cljlib/dissoc #(error "can't `dissoc` onto transient set, use `dissoc!`")
+ :cljlib/conj! (fn [thset v]
+ (if (= nil v)
+ (tset removed v true)
+ (tset removed v nil))
+ (doto thset (tset v v)))
+ :cljlib/assoc! #(error "can't `assoc!` onto transient set")
+ :cljlib/assoc! #(error "can't `dissoc!` a transient set")
+ :cljlib/disj! (fn [thset ...]
+ (for [i 1 (select "#" ...)]
+ (let [k (select i ...)]
+ (tset thset k nil)
+ (tset removed k true)))
+ thset)
+ :cljlib/persistent! (fn [thset]
+ (let [t (collect [k v (pairs thset)
+ :into (collect [k v (pairs hset)]
+ (values k v))]
+ (values k v))]
+ (each [k (pairs removed)]
+ (tset t k nil))
+ (each [_ k (ipairs (icollect [k (pairs* thset)] k))]
+ (tset thset k nil))
+ (setmetatable thset
+ {:__index #(error "attempt to use transient after it was persistet")
+ :__newindex #(error "attempt to use transient after it was persistet")})
+ (immutable (itable t))))}
+ (setmetatable {})))))
+
+(fn hash-set* [x]
+ (match (getmetatable x)
+ mt (doto mt
+ (tset :cljlib/type :hash-set)
+ (tset :cljlib/conj
+ (fn [s v ...]
+ (hash-set*
+ (itable.assoc
+ s v v
+ (unpack* (let [res []]
+ (each [ _ v (ipairs [...])]
+ (table.insert res v)
+ (table.insert res v))
+ res))))))
+ (tset :cljlib/disj
+ (fn [s k ...]
+ (let [to-remove
+ (collect [_ k (ipairs [...])
+ :into (->> {:__index deep-index}
+ (setmetatable {k true}))]
+ k true)]
+ (hash-set*
+ (itable.assoc {}
+ (unpack*
+ (let [res []]
+ (each [_ v (pairs s)]
+ (when (not (. to-remove v))
+ (table.insert res v)
+ (table.insert res v)))
+ res)))))))
+ (tset :cljlib/empty #(hash-set* (itable {})))
+ (tset :cljlib/editable true)
+ (tset :cljlib/transient (hash-set->transient hash-set*))
+ (tset :cljlib/seq (fn [s] (map #(if (vector? $) (. $ 1) $) s)))
+ (tset :__fennelview viewset)
+ (tset :__fennelrest (fn [s i]
+ (var j 1)
+ (let [vals []]
+ (each [v (pairs* s)]
+ (if (>= j i)
+ (table.insert vals v)
+ (set j (+ j 1))))
+ (core.hash-set (unpack* vals))))))
+ _ (hash-set* (setmetatable x {})))
+ x)
+
+(defn hash-set
+ "Create hash set.
+Set is a collection of unique elements, which sore purpose is only to
+tell you if something is in the set or not."
+ [& xs]
+ (let [Set (collect [_ val (pairs* xs)
+ :into (->> {:__newindex deep-newindex}
+ (setmetatable {}))]
+ (values val val))]
+ (hash-set* (itable Set))))
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Multimethods ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Multimethods
-(defn core.remove-method
+(defn multifn?
+ "Test if `mf' is an instance of `multifn'.
+
+`multifn' is a special kind of table, created with `defmulti' macros
+from `macros.fnl'."
+ [mf]
+ (match (getmetatable mf)
+ {:cljlib/type :multifn} true
+ _ false))
+
+(defn remove-method
"Remove method from `multimethod' for given `dispatch-value'."
[multimethod dispatch-value]
(if (multifn? multimethod)
@@ -1138,7 +2183,7 @@ found in the table."
(error (.. (tostring multimethod) " is not a multifn") 2))
multimethod)
-(defn core.remove-all-methods
+(defn remove-all-methods
"Removes all of the methods of `multimethod'"
[multimethod]
(if (multifn? multimethod)
@@ -1147,7 +2192,7 @@ found in the table."
(error (.. (tostring multimethod) " is not a multifn") 2))
multimethod)
-(defn core.methods
+(defn methods
"Given a `multimethod', returns a map of dispatch values -> dispatch fns"
[multimethod]
(if (multifn? multimethod)
@@ -1157,7 +2202,7 @@ found in the table."
m)
(error (.. (tostring multimethod) " is not a multifn") 2)))
-(defn core.get-method
+(defn get-method
"Given a `multimethod' and a `dispatch-value', returns the dispatch
`fn' that would apply to that value, or `nil' if none apply and no
default."
@@ -1167,260 +2212,8 @@ default."
(. multimethod :default))
(error (.. (tostring multimethod) " is not a multifn") 2)))
-(local multimethods-doc-order
- [:remove-method :remove-all-methods :methods :get-method])
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Sets ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+core
-(fn viewset [Set view inspector indent]
- (if (. inspector.seen Set)
- (.. "@set" (. inspector.seen Set) "{...}")
- (let [prefix (.. "@set"
- (if (inspector.visible-cycle? Set)
- (. inspector.seen Set) "")
- "{")
- set-indent (length prefix)
- indent-str (string.rep " " set-indent)
- lines (icollect [v (pairs Set)]
- (.. indent-str
- (view v inspector (+ indent set-indent) true)))]
- (tset lines 1 (.. prefix (string.gsub (or (. lines 1) "") "^%s+" "")))
- (tset lines (length lines) (.. (. lines (length lines)) "}"))
- lines)))
-
-(fn ordered-set-newindex [Set]
- "`__newindex` metamethod for ordered-set.
-
-Updates order of all items when some key removed from set."
- (fn [t k v]
- (if (= nil v)
- (let [k (. Set k)]
- (each [key index (pairs Set)]
- (if (= index k) (tset Set key nil)
- (> index k) (tset Set key (- index 1)))))
- (if (not (. Set v))
- (tset Set v (+ 1 (length t)))))))
-
-(fn hash-set-newindex [Set]
- "`__newindex` metamethod for hash-set."
- (fn [t k v]
- (if (= nil v)
- (each [key _ (pairs Set)]
- (when (eq key k)
- (tset Set key nil)
- (lua :break)))
- (if (not (. Set v))
- (tset Set v true)))))
-
-(fn set-length [Set]
- "`__len` metamethod for set data structure."
- (fn []
- (var len 0)
- (each [_ _ (pairs Set)]
- (set len (+ 1 len)))
- len))
-
-(fn set-eq [s1 s2]
- "`__eq` metamethod for set data structure."
- (var [size res] [0 true])
- (each [i k (pairs s1)]
- (set size (+ size 1))
- (if res (set res (. s2 k))
- (lua :break)))
- (and res (= size (length s2))))
-
-(fn set->iseq [Set]
- (collect [v k (pairs Set)]
- (values k v)))
-
-(fn ordered-set-pairs [Set]
- "Returns stateless `ipairs' iterator for ordered sets."
- (fn []
- (var i 0)
- (var iseq nil)
- (fn set-next [t _]
- (when (not iseq)
- (set iseq (set->iseq Set)))
- (set i (+ i 1))
- (let [v (. iseq i)]
- (values v v)))
- (values set-next Set nil)))
-
-(fn hash-set-pairs [Set]
- "Returns stateful `ipairs' iterator for hashed sets."
- (fn []
- (fn iter [t k]
- (let [v (next t k)]
- (values v v)))
- (values iter Set nil)))
-
-(fn into-set [Set tbl]
- "Transform `tbl' into `Set`"
- (each [_ v (pairs (or (seq tbl) []))]
- (conj Set v))
- Set)
-
-;; Sets are bootstrapped upon previous functions.
-
-(defn core.ordered-set
- "Create ordered set.
-
-Set is a collection of unique elements, which sore purpose is only to
-tell you if something is in the set or not.
-
-`ordered-set' is follows the argument insertion order, unlike sorted
-sets, which apply some sorting algorithm internally. New items added
-at the end of the set. Ordered set supports removal of items via
-`tset' and `disj'. To add element to the ordered set use
-`tset' or `conj'. Both operations modify the set.
-
-**Note**: Hash set prints as `@set{a b c}`, but this construct is not
-supported by the Fennel reader, so you can't create sets with this
-syntax. Use `ordered-set' function instead.
-
-Below are some examples of how to create and manipulate sets.
-
-## Create ordered set:
-Ordered sets are created by passing any amount of elements desired to
-be in the set:
-
-``` fennel
-(ordered-set)
-;; => @set{}
-(ordered-set :a :c :b)
-;; => @set{:a :c :b}
-```
-
-Duplicate items are not added:
-
-``` fennel
-(ordered-set :a :c :a :a :a :a :c :b)
-;; => @set{:a :c :b}
-```
-
-## Check if set contains desired value:
-Sets are functions of their keys, so simply calling a set with a
-desired key will either return the key, or `nil':
-
-``` fennel
-(local oset (ordered-set [:a :b :c] [:c :d :e] :e :f))
-(oset [:a :b :c])
-;; => [\"a\" \"b\" \"c\"]
-(. oset :e)
-;; \"e\"
-(oset [:a :b :f])
-;; => nil
-```
-
-## Add items to existing set:
-To add element to the set use `conj' or `tset'
-
-``` fennel
-(local oset (ordered-set :a :b :c))
-(conj oset :d :e)
-;; => @set{:a :b :c :d :e}
-```
-
-### Remove items from the set:
-To add element to the set use `disj' or `tset'
-
-``` fennel
-(local oset (ordered-set :a :b :c))
-(disj oset :b)
-;; => @set{:a :c}
-(tset oset :a nil)
-oset
-;; => @set{:c}
-```
-
-## Equality semantics
-Both `ordered-set' and `hash-set' implement `__eq` metamethod,
-and are compared for having the same keys without particular order and
-same size:
-
-``` fennel
-(assert-eq (ordered-set :a :b) (ordered-set :b :a))
-(assert-ne (ordered-set :a :b) (ordered-set :b :a :c))
-(assert-eq (ordered-set :a :b) (hash-set :a :b))
-```"
- [& xs]
- (let [Set (setmetatable {} {:__index deep-index})
- set-pairs (ordered-set-pairs Set)]
- (var i 1)
- (each [_ val (ipairs xs)]
- (when (not (. Set val))
- (tset Set val i)
- (set i (+ 1 i))))
- (setmetatable {}
- {:cljlib/type :cljlib/ordered-set
- :cljlib/next #(next Set $2)
- :cljlib/into into-set
- :cljlib/empty #(ordered-set)
- :__eq set-eq
- :__call #(if (. Set $2) $2 nil)
- :__len (set-length Set)
- :__index #(if (. Set $2) $2 nil)
- :__newindex (ordered-set-newindex Set)
- :__pairs set-pairs
- :__name "ordered set"
- :__fennelview viewset})))
-
-(defn core.hash-set
- "Create hash set.
-
-Set is a collection of unique elements, which sore purpose is only to
-tell you if something is in the set or not.
-
-Hash set differs from ordered set in that the keys are do not have any
-particular order. New items are added at the arbitrary position by
-using `conj' or `tset' functions, and items can be removed
-with `disj' or `tset' functions. Rest semantics are the same
-as for `ordered-set'
-
-**Note**: Hash set prints as `@set{a b c}`, but this construct is not
-supported by the Fennel reader, so you can't create sets with this
-syntax. Use `hash-set' function instead."
- [& xs]
- (let [Set (setmetatable {} {:__index deep-index})
- set-pairs (hash-set-pairs Set)]
- (each [_ val (ipairs xs)]
- (when (not (. Set val))
- (tset Set val true)))
- (setmetatable {}
- {:cljlib/type :cljlib/hash-set
- :cljlib/next #(next Set $2)
- :cljlib/into into-set
- :cljlib/empty #(hash-set)
- :__eq set-eq
- :__call #(if (. Set $2) $2 nil)
- :__len (set-length Set)
- :__index #(if (. Set $2) $2 nil)
- :__newindex (hash-set-newindex Set)
- :__pairs set-pairs
- :__name "hash set"
- :__fennelview viewset})))
-
-(local set-doc-order
- [:ordered-set :hash-set])
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;; Module info and export ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(set module-info._DOC_ORDER (concat utility-doc-order
- [:eq]
- predicate-doc-order
- sequence-doc-order
- function-manipulation-doc-order
- hash-table-doc-order
- multimethods-doc-order
- set-doc-order))
-
-(setmetatable core {:__index module-info})
-
-
-;; LocalWords: cljlib Clojure's clj lua PUC mapv concat Clojure fn zs
-;; LocalWords: defmulti multi arity eq metadata prepending variadic
-;; LocalWords: args tbl LocalWords memoized referentially Andrey
-;; LocalWords: Orst codepoints Listopadov metamethods nums multifn
-;; LocalWords: stateful LuaJIT
+;; Local Variables:
+;; eval: (add-to-list 'imenu-generic-expression `(nil "\\s(\\(?:defn-?\\|fn\\*\\)[[:space:]]+\\(\\(?:\\sw\\|\\s_\\|-\\|_\\)+\\)" 1))
+;; End: