From 5bf187555012925bbd464b86ca49f7bd37e2c51c Mon Sep 17 00:00:00 2001 From: Andrey Orst Date: Tue, 10 Nov 2020 23:26:08 +0300 Subject: feature(core): breaking change of project structure --- Makefile | 2 +- cljlib-macros.fnl | 535 ++++++++++++++++++++++++++++++++++++++++++++++++ cljlib.fnl | 594 +++++++++++++++++++++++++++++++++++++++++++++++++++++ core.fnl | 595 ------------------------------------------------------ macros/core.fnl | 263 ------------------------ macros/fn.fnl | 281 -------------------------- test/core.fnl | 5 +- test/fn.fnl | 3 +- test/macros.fnl | 6 +- test/test.fnl | 2 +- 10 files changed, 1136 insertions(+), 1150 deletions(-) create mode 100644 cljlib-macros.fnl create mode 100644 cljlib.fnl delete mode 100644 core.fnl delete mode 100644 macros/core.fnl delete mode 100644 macros/fn.fnl diff --git a/Makefile b/Makefile index e044f01..d6363d2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ LUA ?= lua -FNLSOURCES = core.fnl test/core.fnl test/macros.fnl test/fn.fnl +FNLSOURCES = cljlib.fnl test/core.fnl test/macros.fnl test/fn.fnl LUASOURCES = $(FNLSOURCES:.fnl=.lua) all: $(LUASOURCES) diff --git a/cljlib-macros.fnl b/cljlib-macros.fnl new file mode 100644 index 0000000..908fd44 --- /dev/null +++ b/cljlib-macros.fnl @@ -0,0 +1,535 @@ +(local unpack (or table.unpack _G.unpack)) +(local insert table.insert) +(local concat table.concat) +(local sort table.sort) +(local gsub string.gsub) +(local meta-enabled (pcall _SCOPE.specials.doc (list (sym :doc) (sym :doc)) _SCOPE _CHUNK)) + +(fn with-meta [val meta] + (if (not meta-enabled) val + `(let [val# ,val + (res# fennel#) (pcall require :fennel)] + (if res# + (each [k# v# (pairs ,meta)] + (fennel#.metadata:set val# k# v#))) + val#))) + +(fn gen-arglist-doc [args] + (if (list? (. args 1)) + (let [arglist [] + open (if (> (length args) 1) "\n [" "") + close (if (= open "") "" "]")] + (each [i v (ipairs args)] + (insert + arglist + (.. open (concat (gen-arglist-doc v) " ") close))) + arglist) + + (sequence? (. args 1)) + (let [arglist []] + (each [_ v (ipairs (. args 1))] + (insert arglist (tostring v))) + arglist))) + +(fn multisym->sym [s] + (if (multi-sym? s) + (values (sym (gsub (tostring s) ".*[.]" "")) true) + (values s false))) + +(fn string? [x] + (= (type x) "string")) + +(fn has-amp? [args] + ;; Check if arglist has `&' and return its position of `false'. + ;; Performs additional checks for `&' and `...' usage in arglist. + (var res false) + (each [i s (ipairs args)] + (if (= (tostring s) "&") + (if res (assert-compile false "only one `&' can be specified in arglist." args) + (set res i)) + (= (tostring s) "...") + (assert-compile false "use of `...' in `fn*' is not permitted. Use `&' if you want a vararg." args) + (and res (> i (+ res 1))) + (assert-compile false "only one `more' argument can be supplied after `&' in arglist." args))) + res) + +(fn gen-arity [[args & body]] + ;; Forms three values, representing data needed to create dispatcher: + ;; + ;; - the length of arglist; + ;; - the body of the function we generate; + ;; - position of `&' in the arglist if any. + (assert-compile (sequence? args) "fn*: expected parameters table. + +* Try adding function parameters as a list of identifiers in brackets." args) + (values (length args) + (list 'let [args ['...]] (list 'do (unpack body))) + (has-amp? args))) + +(fn contains? [tbl x] + (var res false) + (each [i v (ipairs tbl)] + (if (= v x) + (do (set res i) + (lua :break)))) + res) + +(fn grows-by-one-or-equal? [tbl] + (let [t []] + (each [_ v (ipairs tbl)] (insert t v)) + (sort t) + (var prev nil) + (each [_ cur (ipairs t)] + (if prev + (when (and (not= (+ prev 1) cur) + (not= prev cur)) + (lua "return false"))) + (set prev cur)) + prev)) + +(fn arity-dispatcher [len fixed body& name] + ;; Forms an `if' expression with all fixed arities first, then `&' + ;; arity, if present, and default error message as last arity. + ;; + ;; `len' is a symbol, that represents the length of the current argument + ;; list, and is computed at runtime. + ;; + ;; `fixed' is a table of arities with fixed amount of arguments. + ;; These are put in this `if' as: `(= len fixed-len)', where + ;; `fixed-len' is the length of current arity arglist, computed with + ;; `gen-arity'. + ;; + ;; `body&' stores size of fixed part of arglist, that is, everything + ;; up until `&', and the body itself. When `body&' provided, the + ;; `(>= len more-len)' is added to the resulting `if' expression. + ;; + ;; Lastly the catchall branch is added to `if' expression, which + ;; ensures that only valid amount of arguments were passed to + ;; function, which are defined by previous branches. + (let [bodies '(if) + lengths []] + (var max nil) + (each [fixed-len body (pairs (doto fixed))] + (when (or (not max) (> fixed-len max)) + (set max fixed-len)) + (insert lengths fixed-len) + (insert bodies (list '= len fixed-len)) + (insert bodies body)) + (when body& + (let [[more-len body arity] body&] + (assert-compile (not (and max (<= more-len max))) "fn*: arity with `&' must have more arguments than maximum arity without `&'. + +* Try adding more arguments before `&'" arity) + (insert lengths (- more-len 1)) + (insert bodies (list '>= len (- more-len 1))) + (insert bodies body))) + (if (not (and (grows-by-one-or-equal? lengths) + (contains? lengths 0))) + (insert bodies (list 'error + (.. "wrong argument amount" + (if name (.. " for " name) "")) 2))) + bodies)) + +(fn single-arity-body [args fname] + ;; Produces arglist and body for single-arity function. + ;; For more info check `gen-arity' documentation. + (let [[args & body] args + (arity body amp) (gen-arity [args (unpack body)])] + `(let [len# (select :# ...)] + ,(arity-dispatcher + 'len# + (if amp {} {arity body}) + (if amp [amp body]) + fname)))) + +(fn multi-arity-body [args fname] + ;; Produces arglist and all body forms for multi-arity function. + ;; For more info check `gen-arity' documentation. + (let [bodies {} ;; bodies of fixed arity + bodies& []] ;; bodies where arglist contains `&' + (each [_ arity (ipairs args)] + (let [(n body amp) (gen-arity arity)] + (if amp + (insert bodies& [amp body arity]) + (tset bodies n body)))) + (assert-compile (<= (length bodies&) 1) + "fn* must have only one arity with `&':" + (. bodies& (length bodies&))) + `(let [len# (select :# ...)] + ,(arity-dispatcher + 'len# + bodies + (if (not= (next bodies&) nil) + (. bodies& 1)) + fname)))) + +(fn fn* [name doc? ...] + "Create (anonymous) function of fixed arity. +Supports multiple arities by defining bodies as lists: + +Named function of fixed arity 2: +(fn* f [a b] (+ a b)) + +Function of fixed arities 1 and 2: +(fn* ([x] x) + ([x y] (+ x y))) + +Named function of 2 arities, one of which accepts 0 arguments, and the +other one or more arguments: +(fn* f + ([] nil) + ([x & xs] + (print x) + (f (unpack xs)))) + +Note, that this function is recursive, and calls itself with less and +less amount of arguments until there's no arguments, and the +zero-arity body is called. + +Named functions accept additional documentation string before the +argument list: + +(fn* cube + \"raise `x' to power of 3\" + [x] + (^ x 3)) + +(fn* greet + \"greet a `person', optionally specifying default `greeting'.\" + ([person] (print (.. \"Hello, \" person \"!\"))) + ([greeting person] (print (.. greeting \", \" person \"!\")))) + +Argument lists follow the same destruction rules as in `let'. +Variadic arguments with `...' are not supported. + +If function name contains namespace part, defines local variable +without namespace part, then creates function with this name, sets +this function to the namespace, and returns it. This roughly means, +that instead of writing this: + +(local namespace {}) +(fn f [x] + (if (> x 0) (f (- x 1)))) +(set namespace.f f) +(fn g [x] (f (* x 100))) +(set namespace.g g) + +It is possible to write: + +(local namespace {}) +(fn* namespace.f [x] + (if (> x 0) (f (- x 1)))) +(fn* namespace.g [x] (f (* x 100))) + +Note that it is still possible to call `f' and `g' in current scope +without namespace part. `Namespace' will hold both functions as `f' +and `g' respectively." + (assert-compile (not (string? name)) "fn* expects symbol, vector, or list as first argument" name) + (let [docstring (if (string? doc?) doc? nil) + (name-wo-namespace namespaced?) (multisym->sym name) + fname (if (sym? name-wo-namespace) (tostring name-wo-namespace)) + args (if (sym? name-wo-namespace) + (if (string? doc?) [...] [doc? ...]) + [name-wo-namespace doc? ...]) + arglist-doc (gen-arglist-doc args) + [x] args + + body (if (sequence? x) (single-arity-body args fname) + (list? x) (multi-arity-body args fname) + (assert-compile false "fn*: expected parameters table. + +* Try adding function parameters as a list of identifiers in brackets." x))] + (if (sym? name-wo-namespace) + (if namespaced? + `(local ,name-wo-namespace + (do + (fn ,name-wo-namespace [...] ,docstring ,body) + (set ,name ,name-wo-namespace) + ,(with-meta name-wo-namespace `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) + `(local ,name ,(with-meta `(fn ,name [...] ,docstring ,body) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) + (with-meta `(fn [...] ,docstring ,body) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring})))) + +(fn fn& [name doc? args ...] + "Create (anonymous) function. +Works the same as plain `fn' except supports automatic declaration of +namespaced functions. See `fn*' for more info." + (assert-compile (not (string? name)) "fn* expects symbol, vector, or list as first argument" name) + (let [docstring (if (string? doc?) doc? nil) + (name-wo-namespace namespaced?) (multisym->sym name) + arg-list (if (sym? name-wo-namespace) + (if (string? doc?) args doc?) + name-wo-namespace) + arglist-doc (gen-arglist-doc arg-list) + body (if (sym? name) + (if (string? doc?) + [doc? ...] + [args ...]) + [doc? args ...])] + (if (sym? name-wo-namespace) + (if namespaced? + `(local ,name-wo-namespace + (do + (fn ,name-wo-namespace ,arg-list ,(unpack body)) + (set ,name ,name-wo-namespace) + ,(with-meta name-wo-namespace `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) + `(local ,name ,(with-meta `(fn ,name ,arg-list ,(unpack body)) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) + (with-meta `(fn ,arg-list ,(unpack body)) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring})))) + +(fn check-bindings [bindings] + (and (assert-compile (sequence? bindings) "expected binding table" []) + (assert-compile (= (length bindings) 2) "expected exactly two forms in binding vector." bindings))) + +(fn if-let [...] + (let [[bindings then else] [...]] + (check-bindings bindings) + (let [[form test] bindings] + `(let [tmp# ,test] + (if tmp# + (let [,form tmp#] + ,then) + ,else))))) + +(fn when-let [...] + (let [[bindings & body] [...]] + (check-bindings bindings) + (let [[form test] bindings] + `(let [tmp# ,test] + (if tmp# + (let [,form tmp#] + ,(unpack body))))))) + +(fn if-some [...] + (let [[bindings then else] [...]] + (check-bindings bindings) + (let [[form test] bindings] + `(let [tmp# ,test] + (if (= tmp# nil) + ,else + (let [,form tmp#] + ,then)))))) + +(fn when-some [...] + (let [[bindings & body] [...]] + (check-bindings bindings) + (let [[form test] bindings] + `(let [tmp# ,test] + (if (= tmp# nil) + nil + (let [,form tmp#] + ,(unpack body))))))) + + +(fn table-type [tbl] + (if (sequence? tbl) :seq + (table? tbl) :table + :else)) + +(fn table-type-fn [] + `(fn [tbl#] + (let [t# (type tbl#)] + (if (= t# :table) + (let [meta# (getmetatable tbl#) + table-type# (and meta# (. meta# :cljlib/table-type))] + (if table-type# table-type# + (let [(k# _#) (next tbl#)] + (if (and (= (type k#) :number) (= k# 1)) :seq + (= k# nil) :empty + :table)))) + :else)))) + +(fn seq-fn [] + `(fn [tbl#] + (var assoc# false) + (let [res# [] + insert# table.insert] + (each [k# v# (pairs tbl#)] + (if (and (not assoc#) + (not (= (type k#) :number))) + (set assoc# true)) + (insert# res# [k# v#])) + (if assoc# res# tbl#)))) + +(fn empty [tbl] + (let [table-type (table-type tbl)] + (if (= table-type :seq) `(setmetatable {} {:cljlib/table-type :seq}) + (= table-type :table) `(setmetatable {} {:cljlib/table-type :table}) + `(setmetatable {} {:cljlib/table-type (,(table-type-fn) ,tbl)})))) + +(fn into [to from] + (let [to-type (table-type to) + from-type (table-type from)] + (if (and (= to-type :seq) (= from-type :seq)) + `(let [to# ,to + insert# table.insert] + (each [_# v# (ipairs ,from)] + (insert# to# v#)) + to#) + (= to-type :seq) + `(let [to# ,to + seq# ,(seq-fn) + insert# table.insert] + (each [_# v# (ipairs (seq# ,from))] + (insert# to# v#)) + to#) + (and (= to-type :table) (= from-type :seq)) + `(let [to# ,to] + (each [_# [k# v#] (ipairs ,from)] + (tset to# k# v#)) + to#) + (and (= to-type :table) (= from-type :table)) + `(let [to# ,to + from# ,from] + (each [k# v# (pairs from#)] + (tset to# k# v#)) + to#) + (= to-type :table) + `(let [to# ,to + from# ,from] + (match (,(table-type-fn) from#) + :seq (each [_# [k# v#] (ipairs from#)] + (tset to# k# v#)) + :table (each [k# v# (pairs from#)] + (tset to# k# v#)) + :else (error "expected table as second argument")) + to#) + `(let [to# ,to + from# ,from + insert# table.insert + table-type# ,(table-type-fn) + seq# ,(seq-fn)] + (match (table-type# to#) + :seq (each [_# v# (ipairs (seq# from#))] + (insert# to# v#)) + :table (match (table-type# from#) + :seq (each [_# [k# v#] (ipairs from#)] + (tset to# k# v#)) + :table (each [k# v# (pairs from#)] + (tset to# k# v#)) + :else (error "expected table as second argument")) + ;; If we could not deduce type, it means that + ;; we've got empty table. We use will default + ;; to sequential table, because it will never + ;; break when converting into + :empty (each [_# v# (ipairs (seq# from#))] + (insert# to# v#)) + :else (error "expected table as first argument")) + to#)))) + +(fn first [tbl] + (. tbl 1)) + +(fn rest [tbl] + [(unpack tbl 2)]) + +(fn string? [x] + (= (type x) :string)) + +(fn when-meta [...] + (when meta-enabled `(do ,...))) + +(fn meta [v] + (when-meta + `(let [(res# fennel#) (pcall require :fennel)] + (if res# (. fennel#.metadata ,v))))) + +(fn eq-fn [] + `(fn eq# [a# b#] + (if (and (= (type a#) :table) (= (type b#) :table)) + (do (var [res# count-a# count-b#] [true 0 0]) + (each [k# v# (pairs a#)] + (set res# (eq# v# (. b# k#))) + (set count-a# (+ count-a# 1)) + (when (not res#) (lua :break))) + (when res# + (each [_# _# (pairs b#)] + (set count-b# (+ count-b# 1))) + (set res# (and res# (= count-a# count-b#)))) + res#) + (= a# b#)))) + +(fn defmulti [...] + (let [[name & opts] [...] + docstring (if (string? (first opts)) (first opts)) + opts (if docstring (rest opts) opts) + dispatch-fn (first opts)] + (if (in-scope? name) + nil + `(local ,name + (let [multimethods# {}] + (setmetatable + ,(with-meta {} {:fnl/docstring docstring}) + {:__call + (fn [_# ...] + ,docstring + (let [dispatch-value# (,dispatch-fn ...) + (res# view#) (pcall require :fennelview)] + ((or (. multimethods# dispatch-value#) + (. multimethods# :default) + (error (.. "No method in multimethod '" + ,(tostring name) + "' for dispatch value: " + ((if res# view# tostring) dispatch-value#)) + 2)) ...))) + :multimethods (setmetatable multimethods# + {:__index + (fn [tbl# key#] + (let [eq# ,(eq-fn)] + (var res# nil) + (each [k# v# (pairs tbl#)] + (when (eq# k# key#) + (set res# v#) + (lua :break))) + res#))})})))))) + +(fn defmethod [...] + (let [[multifn dispatch-val & fn-tail] [...]] + `(let [multifn# ,multifn] + (tset (. (getmetatable multifn#) :multimethods) + ,dispatch-val + (fn* ,(unpack fn-tail))) + multifn#))) + +(fn def [...] + (let [[attr-map name expr] (match (select :# ...) + 2 [{} ...] + 3 [...] + _ (error "wa")) + attr-map (if (table? attr-map) attr-map + (string? attr-map) {attr-map true} + (error "def: expected keyword or literal table as first argument" 2)) + (s multi) (multisym->sym name) + docstring (or (. attr-map :doc) + (. attr-map :fnl/docstring)) + f (if (. attr-map :dynamic) 'var 'local)] + (if multi + `(,f ,s (do (,f ,s ,expr) + (set ,name ,s) + ,(with-meta s {:fnl/docstring docstring}))) + `(,f ,name ,(with-meta expr {:fnl/docstring docstring}))))) + +(fn defonce [...] + (let [[attr-map name expr] (match (select :# ...) + 2 [{} ...] + 3 [...] + _ (error "wa"))] + (if (in-scope? name) + nil + (def attr-map name expr)))) + +;; LocalWords: arglist fn runtime arities arity multi destructuring +;; LocalWords: docstring Variadic LocalWords +{: fn* + : fn& + : if-let + : when-let + : if-some + : when-some + : empty + : into + : when-meta + : with-meta + : meta + : defmulti + : defmethod + : def + : defonce} diff --git a/cljlib.fnl b/cljlib.fnl new file mode 100644 index 0000000..14b5869 --- /dev/null +++ b/cljlib.fnl @@ -0,0 +1,594 @@ +(local core {}) + +(local insert table.insert) +(local unpack (or table.unpack _G.unpack)) +(require-macros :cljlib-macros) + +(fn* core.vector + "Constructs sequential table out of it's arguments." + [& args] + (setmetatable args {:cljlib/table-type :seq})) + +(fn* core.apply + "Apply `f' to the argument list formed by prepending intervening +arguments to `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 (empty [])] + (for [i 1 (- (length args) 1)] + (insert flat-args (. args i))) + (each [_ a (ipairs (. args (length args)))] + (insert flat-args a)) + (f a b c d (unpack flat-args))))) + +(fn fast-table-type [tbl] + (let [m (getmetatable tbl)] + (if-let [t (and m (. m :cljlib/table-type))] + t))) + +;; predicate functions +(fn& core.map? + "Check whether `tbl' is an associative table." + [tbl] + (if (= (type tbl) :table) + (if-let [t (fast-table-type tbl)] + (= t :table) + (let [(k _) (next tbl)] + (and (not= k nil) + (or (not= (type k) :number) + (not= k 1))))))) + +(fn& core.seq? + "Check whether `tbl' is an sequential table." + [tbl] + (if (= (type tbl) :table) + (if-let [t (fast-table-type tbl)] + (= t :seq) + (let [(k _) (next tbl)] + (and (not= k nil) (= (type k) :number) (= k 1)))))) + + +(fn& core.nil? + "Test if value is nil." + [x] + (= x nil)) + +(fn& core.zero? + "Test if value is zero." + [x] + (= x 0)) + +(fn& core.pos? + "Test if `x' is greater than zero." + [x] + (> x 0)) + +(fn& core.neg? + "Test if `x' is less than zero." + [x] + (< x 0)) + +(fn& core.even? + "Test if value is even." + [x] + (= (% x 2) 0)) + +(fn& core.odd? + "Test if value is odd." + [x] + (not (even? x))) + +(fn& core.string? + "Test if `x' is a string." + [x] + (= (type x) :string)) + +(fn& core.boolean? + "Test if `x' is a Boolean" + [x] + (= (type x) :boolean)) + +(fn& core.true? + "Test if `x' is `true'" + [x] + (= x true)) + +(fn& core.false? + "Test if `x' is `false'" + [x] + (= x false)) + +(fn& core.int? + "Test if `x' is a number without floating point data." + [x] + (and (= (type x) :number) + (= x (math.floor x)))) + +(fn& core.pos-int? + "Test if `x' is a positive integer." + [x] + (and (int? x) + (pos? x))) + +(fn& core.neg-int? + "Test if `x' is a negetive integer." + [x] + (and (int? x) + (neg? x))) + +(fn& core.double? + "Test if `x' is a number with floating point data." + [x] + (and (= (type x) :number) + (not= x (math.floor x)))) + +(fn& core.empty? + "Check if collection is empty." + [x] + (match (type x) + :table (= (next x) nil) + :string (= x "") + _ (error "empty?: unsupported collection"))) + +(fn& core.not-empty + "If `x' is empty, returns `nil', otherwise `x'." + [x] + (if (not (empty? x)) + x)) + +;; sequence manipulating functions + +(fn& core.seq + "Create sequential table. +Transforms original table to sequential table of key value pairs +stored as sequential tables in linear time. If `tbl' is an +associative table, returns `[[key1 value1] ... [keyN valueN]]' table. +If `tbl' is sequential table, returns its shallow copy." + [tbl] + (when-some [_ (and tbl (next tbl))] + (var assoc? false) + (let [assoc (empty []) + seq (empty [])] + (each [k v (pairs tbl)] + (if (and (not assoc?) + (not (= (type k) :number))) + (set assoc? true)) + (insert assoc [k v]) + (tset seq k v)) + (if assoc? assoc seq)))) + +(macro safe-seq [tbl] + "Create sequential table, or empty table if `seq' returned `nil'." + `(or (seq ,tbl) (empty []))) + +(fn& core.first + "Return first element of a table. Calls `seq' on its argument." + [tbl] + (when-some [tbl (seq tbl)] + (. tbl 1))) + +(fn& core.rest + "Returns table of all elements of a table but the first one. Calls + `seq' on its argument." + [tbl] + (if-some [tbl (seq tbl)] + (vector (unpack tbl 2)) + (empty []))) + +(fn& core.last + "Returns the last element of a table. Calls `seq' on its argument." + [tbl] + (when-some [tbl (seq tbl)] + (var (i v) (next tbl)) + (while i + (local (_i _v) (next tbl i)) + (if _i (set v _v)) + (set i _i)) + v)) + +(fn& core.butlast + "Returns everything but the last element of a table as a new + table. Calls `seq' on its argument." + [tbl] + (when-some [tbl (seq tbl)] + (table.remove tbl (length tbl)) + (when (not (empty? tbl)) + tbl))) + + +(fn* core.conj + "Insert `x' as a last element of indexed table `tbl'. Modifies `tbl'" + ([] (empty [])) + ([tbl] tbl) + ([tbl x] + (when-some [x x] + (let [tbl (or tbl (empty []))] + (if (map? tbl) + (tset tbl (. x 1) (. x 2)) + (insert tbl x)))) + tbl) + ([tbl x & xs] + (apply conj (conj tbl x) xs))) + +(fn* consj + "Like conj but joins at the front. Modifies `tbl'." + ([] (empty [])) + ([tbl] tbl) + ([tbl x] + (when-some [x x] + (doto tbl (insert 1 x)))) + ([tbl x & xs] + (apply consj (consj tbl x) xs))) + +(fn& core.cons + "Insert `x' to `tbl' at the front. Modifies `tbl'." + [x tbl] + (if-some [x x] + (doto (safe-seq tbl) + (insert 1 x)) + tbl)) + +(fn* core.concat + "Concatenate tables." + ([] nil) + ([x] (safe-seq x)) + ([x y] (let [to (safe-seq x) + from (safe-seq y)] + (each [_ v (ipairs from)] + (insert to v)) + to)) + ([x y & xs] + (apply concat (concat x y) xs))) + +(fn* core.reduce + "Reduce indexed table using function `f' and optional initial value `val'. + +([f table]) +([f val table]) + +`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." + ([f tbl] + (let [tbl (safe-seq tbl)] + (match (length tbl) + 0 (f) + 1 (. tbl 1) + 2 (f (. tbl 1) (. tbl 2)) + _ (let [[a b & rest] tbl] + (reduce f (f a b) rest))))) + ([f val tbl] + (let [tbl (safe-seq tbl)] + (let [[x & xs] tbl] + (if (nil? x) + val + (reduce f (f val x) xs)))))) + +(fn* core.reduce-kv + "Reduces an associative table using function `f' and initial value `val'. + +([f val table]) + +`f' should be a function of 3 arguments. Returns the result of +applying `f' to `val', the first key and the first value in coll, then +applying `f' to that result and the 2nd key and value, etc. If coll +contains no entries, returns `val' and `f' is not called. Note that +reduce-kv is supported on vectors, where the keys will be the +ordinals." [f val tbl] + (var res val) + (each [_ [k v] (pairs (safe-seq tbl))] + (set res (f res k v))) + res) + +(fn* core.mapv + "Maps function `f' over one or more tables. + +Accepts arbitrary amount of tables, calls `seq' on each of it. +Function `f' must take the same amount of parameters 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 table of results." + ([f tbl] + (local res (empty [])) + (each [_ v (ipairs (safe-seq tbl))] + (when-some [tmp (f v)] + (insert res tmp))) + res) + ([f t1 t2] + (let [res (empty []) + t1 (safe-seq t1) + t2 (safe-seq t2)] + (var (i1 v1) (next t1)) + (var (i2 v2) (next t2)) + (while (and i1 i2) + (when-some [tmp (f v1 v2)] + (insert res tmp)) + (set (i1 v1) (next t1 i1)) + (set (i2 v2) (next t2 i2))) + res)) + ([f t1 t2 t3] + (let [res (empty []) + t1 (safe-seq t1) + t2 (safe-seq t2) + t3 (safe-seq t3)] + (var (i1 v1) (next t1)) + (var (i2 v2) (next t2)) + (var (i3 v3) (next t3)) + (while (and i1 i2 i3) + (when-some [tmp (f v1 v2 v3)] + (insert res tmp)) + (set (i1 v1) (next t1 i1)) + (set (i2 v2) (next t2 i2)) + (set (i3 v3) (next t3 i3))) + res)) + ([f t1 t2 t3 & tbls] + (let [step (fn step [tbls] + (if (->> tbls + (mapv #(not= (next $) nil)) + (reduce #(and $1 $2))) + (cons (mapv #(. (safe-seq $) 1) tbls) (step (mapv #(do [(unpack $ 2)]) tbls))) + (empty []))) + res (empty [])] + (each [_ v (ipairs (step (consj tbls t3 t2 t1)))] + (when-some [tmp (apply f v)] + (insert res tmp))) + res))) + +(fn* core.filter [pred tbl] + (when-let [tbl (seq tbl)] + (let [f (. tbl 1) r [(unpack tbl 2)]] + (if (pred f) + (cons f (filter pred r)) + (filter pred r))))) + +(fn kvseq [tbl] + "Transforms any table kind to key-value sequence." + (let [res (empty [])] + (each [k v (pairs tbl)] + (insert res [k v])) + res)) + + + +(fn& core.identity + "Returns its argument." + [x] + x) + +(fn* core.comp + ([] 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 (g x y z (unpack args)))))) + ([f g & fs] + (reduce comp (consj fs g f)))) + +(fn* 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)) + +(fn* 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)])))) + +(set core.not-any? + (with-meta (comp #(not $) some) + {:fnl/docstring "Test if no item in `tbl' satisfy the `pred'." + :fnl/arglist ["pred" "tbl"]})) + +(fn& 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] + (fn* + ([] (not (f))) + ([a] (not (f a))) + ([a b] (not (f a b))) + ([a b & cs] (not (apply f a b cs))))) + +(fn& core.constantly + "Returns a function that takes any number of arguments and returns `x'." + [x] + (fn [...] x)) + +(fn* 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))) + +(fn& core.reverse + "Returns table with same items as in `tbl' but in reverse order." + [tbl] + (when-some [tbl (seq tbl)] + (reduce consj (empty []) tbl))) + +(fn* core.inc "Increase number by one" [x] (+ x 1)) +(fn* core.dec "Decrease number by one" [x] (- x 1)) + +(fn* core.assoc + "Associate key `k' with value `v' in `tbl'." + ([tbl k v] + (setmetatable + (doto tbl (tset k v)) + {:cljlib/table-type :table})) + ([tbl k v & kvs] + (assert (= (% (length kvs) 2) 0) + (.. "no value supplied for key " (. kvs (length kvs)))) + (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/table-type :table}))) + +(fn& core.hash-map + "Create associative table from keys and values" + [...] + (if (> (select :# ...) 0) + (assoc {} ...) + (setmetatable {} {:cljlib/table-type :table}))) + +(fn* 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))) + +(fn* 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)) + +(fn* core.remove-method + [multifn dispatch-val] + (tset (. (getmetatable multifn) :multimethods) dispatch-val nil) + multifn) + +(fn* core.remove-all-methods + "Removes all of the methods of multimethod" + [multifn] + (let [mtable (. (getmetatable multifn) :multimethods)] + (each [k _ (pairs mtable)] + (tset mtable k nil)) + multifn)) + +(fn* core.methods + "Given a multimethod, returns a map of dispatch values -> dispatch fns" + [multifn] + (. (getmetatable multifn) :multimethods)) + +(fn* core.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." + [multifn dispatch-val] + (or (. (getmetatable multifn) :multimethods dispatch-val) + (. (getmetatable multifn) :multimethods :default))) + +(fn* core.add + ([] 0) + ([a] a) + ([a b] (+ a b)) + ([a b c] (+ a b c)) + ([a b c d] (+ a b c d)) + ([a b c d & rest] (apply add (+ a b c d) rest))) + +(fn* core.sub + ([] 0) + ([a] (- a)) + ([a b] (- a b)) + ([a b c] (- a b c)) + ([a b c d] (- a b c d)) + ([a b c d & rest] (apply sub (- a b c d) rest))) + +(fn* core.mul + ([] 1) + ([a] a) + ([a b] (* a b)) + ([a b c] (* a b c)) + ([a b c d] (* a b c d)) + ([a b c d & rest] (apply mul (* a b c d) rest))) + +(fn* core.div + ([a] (/ 1 a)) + ([a b] (/ a b)) + ([a b c] (/ a b c)) + ([a b c d] (/ a b c d)) + ([a b c d & rest] (apply div (/ a b c d) rest))) + +(fn* core.le + "Returns true if nums are in monotonically non-decreasing order" + ([x] true) + ([x y] (<= x y)) + ([x y & more] + (if (<= x y) + (if (next more 1) + (le y (. more 1) (unpack more 2)) + (<= y (. more 1))) + false))) + +(fn* core.lt + "Returns true if nums are in monotonically decreasing order" + ([x] true) + ([x y] (< x y)) + ([x y & more] + (if (< x y) + (if (next more 1) + (lt y (. more 1) (unpack more 2)) + (< y (. more 1))) + false))) + +(fn* core.ge + "Returns true if nums are in monotonically non-increasing order" + ([x] true) + ([x y] (>= x y)) + ([x y & more] + (if (>= x y) + (if (next more 1) + (ge y (. more 1) (unpack more 2)) + (>= y (. more 1))) + false))) + +(fn* core.gt + "Returns true if nums are in monotonically increasing order" + ([x] true) + ([x y] (> x y)) + ([x y & more] + (if (> x y) + (if (next more 1) + (gt y (. more 1) (unpack more 2)) + (> y (. more 1))) + false))) + +(fn* core.eq + "Deep compare values." + ([x] true) + ([x y] + (if (and (= (type x) :table) (= (type y) :table)) + (and (reduce #(and $1 $2) true (mapv (fn [[k v]] (eq (. y k) v)) (kvseq x))) + (reduce #(and $1 $2) true (mapv (fn [[k v]] (eq (. x k) v)) (kvseq y)))) + (= x y))) + ([x y & xs] + (reduce #(and $1 $2) (eq x y) (mapv #(eq x $) xs)))) + +core diff --git a/core.fnl b/core.fnl deleted file mode 100644 index 1e3576b..0000000 --- a/core.fnl +++ /dev/null @@ -1,595 +0,0 @@ -(local core {}) - -(local insert table.insert) -(local unpack (or table.unpack _G.unpack)) -(require-macros :macros.fn) -(require-macros :macros.core) - -(fn* core.vector - "Constructs sequential table out of it's arguments." - [& args] - (setmetatable args {:cljlib/table-type :seq})) - -(fn* core.apply - "Apply `f' to the argument list formed by prepending intervening -arguments to `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 (empty [])] - (for [i 1 (- (length args) 1)] - (insert flat-args (. args i))) - (each [_ a (ipairs (. args (length args)))] - (insert flat-args a)) - (f a b c d (unpack flat-args))))) - -(fn fast-table-type [tbl] - (let [m (getmetatable tbl)] - (if-let [t (and m (. m :cljlib/table-type))] - t))) - -;; predicate functions -(fn& core.map? - "Check whether `tbl' is an associative table." - [tbl] - (if (= (type tbl) :table) - (if-let [t (fast-table-type tbl)] - (= t :table) - (let [(k _) (next tbl)] - (and (not= k nil) - (or (not= (type k) :number) - (not= k 1))))))) - -(fn& core.seq? - "Check whether `tbl' is an sequential table." - [tbl] - (if (= (type tbl) :table) - (if-let [t (fast-table-type tbl)] - (= t :seq) - (let [(k _) (next tbl)] - (and (not= k nil) (= (type k) :number) (= k 1)))))) - - -(fn& core.nil? - "Test if value is nil." - [x] - (= x nil)) - -(fn& core.zero? - "Test if value is zero." - [x] - (= x 0)) - -(fn& core.pos? - "Test if `x' is greater than zero." - [x] - (> x 0)) - -(fn& core.neg? - "Test if `x' is less than zero." - [x] - (< x 0)) - -(fn& core.even? - "Test if value is even." - [x] - (= (% x 2) 0)) - -(fn& core.odd? - "Test if value is odd." - [x] - (not (even? x))) - -(fn& core.string? - "Test if `x' is a string." - [x] - (= (type x) :string)) - -(fn& core.boolean? - "Test if `x' is a Boolean" - [x] - (= (type x) :boolean)) - -(fn& core.true? - "Test if `x' is `true'" - [x] - (= x true)) - -(fn& core.false? - "Test if `x' is `false'" - [x] - (= x false)) - -(fn& core.int? - "Test if `x' is a number without floating point data." - [x] - (and (= (type x) :number) - (= x (math.floor x)))) - -(fn& core.pos-int? - "Test if `x' is a positive integer." - [x] - (and (int? x) - (pos? x))) - -(fn& core.neg-int? - "Test if `x' is a negetive integer." - [x] - (and (int? x) - (neg? x))) - -(fn& core.double? - "Test if `x' is a number with floating point data." - [x] - (and (= (type x) :number) - (not= x (math.floor x)))) - -(fn& core.empty? - "Check if collection is empty." - [x] - (match (type x) - :table (= (next x) nil) - :string (= x "") - _ (error "empty?: unsupported collection"))) - -(fn& core.not-empty - "If `x' is empty, returns `nil', otherwise `x'." - [x] - (if (not (empty? x)) - x)) - -;; sequence manipulating functions - -(fn& core.seq - "Create sequential table. -Transforms original table to sequential table of key value pairs -stored as sequential tables in linear time. If `tbl' is an -associative table, returns `[[key1 value1] ... [keyN valueN]]' table. -If `tbl' is sequential table, returns its shallow copy." - [tbl] - (when-some [_ (and tbl (next tbl))] - (var assoc? false) - (let [assoc (empty []) - seq (empty [])] - (each [k v (pairs tbl)] - (if (and (not assoc?) - (not (= (type k) :number))) - (set assoc? true)) - (insert assoc [k v]) - (tset seq k v)) - (if assoc? assoc seq)))) - -(macro safe-seq [tbl] - "Create sequential table, or empty table if `seq' returned `nil'." - `(or (seq ,tbl) (empty []))) - -(fn& core.first - "Return first element of a table. Calls `seq' on its argument." - [tbl] - (when-some [tbl (seq tbl)] - (. tbl 1))) - -(fn& core.rest - "Returns table of all elements of a table but the first one. Calls - `seq' on its argument." - [tbl] - (if-some [tbl (seq tbl)] - (vector (unpack tbl 2)) - (empty []))) - -(fn& core.last - "Returns the last element of a table. Calls `seq' on its argument." - [tbl] - (when-some [tbl (seq tbl)] - (var (i v) (next tbl)) - (while i - (local (_i _v) (next tbl i)) - (if _i (set v _v)) - (set i _i)) - v)) - -(fn& core.butlast - "Returns everything but the last element of a table as a new - table. Calls `seq' on its argument." - [tbl] - (when-some [tbl (seq tbl)] - (table.remove tbl (length tbl)) - (when (not (empty? tbl)) - tbl))) - - -(fn* core.conj - "Insert `x' as a last element of indexed table `tbl'. Modifies `tbl'" - ([] (empty [])) - ([tbl] tbl) - ([tbl x] - (when-some [x x] - (let [tbl (or tbl (empty []))] - (if (map? tbl) - (tset tbl (. x 1) (. x 2)) - (insert tbl x)))) - tbl) - ([tbl x & xs] - (apply conj (conj tbl x) xs))) - -(fn* consj - "Like conj but joins at the front. Modifies `tbl'." - ([] (empty [])) - ([tbl] tbl) - ([tbl x] - (when-some [x x] - (doto tbl (insert 1 x)))) - ([tbl x & xs] - (apply consj (consj tbl x) xs))) - -(fn& core.cons - "Insert `x' to `tbl' at the front. Modifies `tbl'." - [x tbl] - (if-some [x x] - (doto (safe-seq tbl) - (insert 1 x)) - tbl)) - -(fn* core.concat - "Concatenate tables." - ([] nil) - ([x] (safe-seq x)) - ([x y] (let [to (safe-seq x) - from (safe-seq y)] - (each [_ v (ipairs from)] - (insert to v)) - to)) - ([x y & xs] - (apply concat (concat x y) xs))) - -(fn* core.reduce - "Reduce indexed table using function `f' and optional initial value `val'. - -([f table]) -([f val table]) - -`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." - ([f tbl] - (let [tbl (safe-seq tbl)] - (match (length tbl) - 0 (f) - 1 (. tbl 1) - 2 (f (. tbl 1) (. tbl 2)) - _ (let [[a b & rest] tbl] - (reduce f (f a b) rest))))) - ([f val tbl] - (let [tbl (safe-seq tbl)] - (let [[x & xs] tbl] - (if (nil? x) - val - (reduce f (f val x) xs)))))) - -(fn* core.reduce-kv - "Reduces an associative table using function `f' and initial value `val'. - -([f val table]) - -`f' should be a function of 3 arguments. Returns the result of -applying `f' to `val', the first key and the first value in coll, then -applying `f' to that result and the 2nd key and value, etc. If coll -contains no entries, returns `val' and `f' is not called. Note that -reduce-kv is supported on vectors, where the keys will be the -ordinals." [f val tbl] - (var res val) - (each [_ [k v] (pairs (safe-seq tbl))] - (set res (f res k v))) - res) - -(fn* core.mapv - "Maps function `f' over one or more tables. - -Accepts arbitrary amount of tables, calls `seq' on each of it. -Function `f' must take the same amount of parameters 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 table of results." - ([f tbl] - (local res (empty [])) - (each [_ v (ipairs (safe-seq tbl))] - (when-some [tmp (f v)] - (insert res tmp))) - res) - ([f t1 t2] - (let [res (empty []) - t1 (safe-seq t1) - t2 (safe-seq t2)] - (var (i1 v1) (next t1)) - (var (i2 v2) (next t2)) - (while (and i1 i2) - (when-some [tmp (f v1 v2)] - (insert res tmp)) - (set (i1 v1) (next t1 i1)) - (set (i2 v2) (next t2 i2))) - res)) - ([f t1 t2 t3] - (let [res (empty []) - t1 (safe-seq t1) - t2 (safe-seq t2) - t3 (safe-seq t3)] - (var (i1 v1) (next t1)) - (var (i2 v2) (next t2)) - (var (i3 v3) (next t3)) - (while (and i1 i2 i3) - (when-some [tmp (f v1 v2 v3)] - (insert res tmp)) - (set (i1 v1) (next t1 i1)) - (set (i2 v2) (next t2 i2)) - (set (i3 v3) (next t3 i3))) - res)) - ([f t1 t2 t3 & tbls] - (let [step (fn step [tbls] - (if (->> tbls - (mapv #(not= (next $) nil)) - (reduce #(and $1 $2))) - (cons (mapv #(. (safe-seq $) 1) tbls) (step (mapv #(do [(unpack $ 2)]) tbls))) - (empty []))) - res (empty [])] - (each [_ v (ipairs (step (consj tbls t3 t2 t1)))] - (when-some [tmp (apply f v)] - (insert res tmp))) - res))) - -(fn* core.filter [pred tbl] - (when-let [tbl (seq tbl)] - (let [f (. tbl 1) r [(unpack tbl 2)]] - (if (pred f) - (cons f (filter pred r)) - (filter pred r))))) - -(fn kvseq [tbl] - "Transforms any table kind to key-value sequence." - (let [res (empty [])] - (each [k v (pairs tbl)] - (insert res [k v])) - res)) - - - -(fn& core.identity - "Returns its argument." - [x] - x) - -(fn* core.comp - ([] 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 (g x y z (unpack args)))))) - ([f g & fs] - (reduce comp (consj fs g f)))) - -(fn* 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)) - -(fn* 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)])))) - -(set core.not-any? - (with-meta (comp #(not $) some) - {:fnl/docstring "Test if no item in `tbl' satisfy the `pred'." - :fnl/arglist ["pred" "tbl"]})) - -(fn& 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] - (fn* - ([] (not (f))) - ([a] (not (f a))) - ([a b] (not (f a b))) - ([a b & cs] (not (apply f a b cs))))) - -(fn& core.constantly - "Returns a function that takes any number of arguments and returns `x'." - [x] - (fn [...] x)) - -(fn* 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))) - -(fn& core.reverse - "Returns table with same items as in `tbl' but in reverse order." - [tbl] - (when-some [tbl (seq tbl)] - (reduce consj (empty []) tbl))) - -(fn* core.inc "Increase number by one" [x] (+ x 1)) -(fn* core.dec "Decrease number by one" [x] (- x 1)) - -(fn* core.assoc - "Associate key `k' with value `v' in `tbl'." - ([tbl k v] - (setmetatable - (doto tbl (tset k v)) - {:cljlib/table-type :table})) - ([tbl k v & kvs] - (assert (= (% (length kvs) 2) 0) - (.. "no value supplied for key " (. kvs (length kvs)))) - (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/table-type :table}))) - -(fn& core.hash-map - "Create associative table from keys and values" - [...] - (if (> (select :# ...) 0) - (assoc {} ...) - (setmetatable {} {:cljlib/table-type :table}))) - -(fn* 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))) - -(fn* 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)) - -(fn* core.remove-method - [multifn dispatch-val] - (tset (. (getmetatable multifn) :multimethods) dispatch-val nil) - multifn) - -(fn* core.remove-all-methods - "Removes all of the methods of multimethod" - [multifn] - (let [mtable (. (getmetatable multifn) :multimethods)] - (each [k _ (pairs mtable)] - (tset mtable k nil)) - multifn)) - -(fn* core.methods - "Given a multimethod, returns a map of dispatch values -> dispatch fns" - [multifn] - (. (getmetatable multifn) :multimethods)) - -(fn* core.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." - [multifn dispatch-val] - (or (. (getmetatable multifn) :multimethods dispatch-val) - (. (getmetatable multifn) :multimethods :default))) - -(fn* core.add - ([] 0) - ([a] a) - ([a b] (+ a b)) - ([a b c] (+ a b c)) - ([a b c d] (+ a b c d)) - ([a b c d & rest] (apply add (+ a b c d) rest))) - -(fn* core.sub - ([] 0) - ([a] (- a)) - ([a b] (- a b)) - ([a b c] (- a b c)) - ([a b c d] (- a b c d)) - ([a b c d & rest] (apply sub (- a b c d) rest))) - -(fn* core.mul - ([] 1) - ([a] a) - ([a b] (* a b)) - ([a b c] (* a b c)) - ([a b c d] (* a b c d)) - ([a b c d & rest] (apply mul (* a b c d) rest))) - -(fn* core.div - ([a] (/ 1 a)) - ([a b] (/ a b)) - ([a b c] (/ a b c)) - ([a b c d] (/ a b c d)) - ([a b c d & rest] (apply div (/ a b c d) rest))) - -(fn* core.le - "Returns true if nums are in monotonically non-decreasing order" - ([x] true) - ([x y] (<= x y)) - ([x y & more] - (if (<= x y) - (if (next more 1) - (le y (. more 1) (unpack more 2)) - (<= y (. more 1))) - false))) - -(fn* core.lt - "Returns true if nums are in monotonically decreasing order" - ([x] true) - ([x y] (< x y)) - ([x y & more] - (if (< x y) - (if (next more 1) - (lt y (. more 1) (unpack more 2)) - (< y (. more 1))) - false))) - -(fn* core.ge - "Returns true if nums are in monotonically non-increasing order" - ([x] true) - ([x y] (>= x y)) - ([x y & more] - (if (>= x y) - (if (next more 1) - (ge y (. more 1) (unpack more 2)) - (>= y (. more 1))) - false))) - -(fn* core.gt - "Returns true if nums are in monotonically increasing order" - ([x] true) - ([x y] (> x y)) - ([x y & more] - (if (> x y) - (if (next more 1) - (gt y (. more 1) (unpack more 2)) - (> y (. more 1))) - false))) - -(fn* core.eq - "Deep compare values." - ([x] true) - ([x y] - (if (and (= (type x) :table) (= (type y) :table)) - (and (reduce #(and $1 $2) true (mapv (fn [[k v]] (eq (. y k) v)) (kvseq x))) - (reduce #(and $1 $2) true (mapv (fn [[k v]] (eq (. x k) v)) (kvseq y)))) - (= x y))) - ([x y & xs] - (reduce #(and $1 $2) (eq x y) (mapv #(eq x $) xs)))) - -core diff --git a/macros/core.fnl b/macros/core.fnl deleted file mode 100644 index ad555fb..0000000 --- a/macros/core.fnl +++ /dev/null @@ -1,263 +0,0 @@ -(require-macros :macros.fn) -(local core {}) -(local unpack (or table.unpack _G.unpack)) -(local insert table.insert) -(local meta-enabled (pcall _SCOPE.specials.doc (list (sym :doc) (sym :doc)) _SCOPE _CHUNK)) - -(fn multisym->sym [s] - (if (multi-sym? s) - (values (sym (string.gsub (tostring s) ".*[.]" "")) true) - (values s false))) - -(fn check-bindings [bindings] - (and (assert-compile (sequence? bindings) "expected binding table" []) - (assert-compile (= (length bindings) 2) "expected exactly two forms in binding vector." bindings))) - -(fn* core.if-let - ([bindings then] - (if-let bindings then nil)) - ([bindings then else] - (check-bindings bindings) - (let [[form test] bindings] - `(let [tmp# ,test] - (if tmp# - (let [,form tmp#] - ,then) - ,else))))) - -(fn* core.when-let - [bindings & body] - (check-bindings bindings) - (let [[form test] bindings] - `(let [tmp# ,test] - (if tmp# - (let [,form tmp#] - ,(unpack body)))))) - -(fn* core.if-some - ([bindings then] - (if-some bindings then nil)) - ([bindings then else] - (check-bindings bindings) - (let [[form test] bindings] - `(let [tmp# ,test] - (if (= tmp# nil) - ,else - (let [,form tmp#] - ,then)))))) - -(fn* core.when-some - [bindings & body] - (check-bindings bindings) - (let [[form test] bindings] - `(let [tmp# ,test] - (if (= tmp# nil) - nil - (let [,form tmp#] - ,(unpack body)))))) - - -(fn table-type [tbl] - (if (sequence? tbl) :seq - (table? tbl) :table - :else)) - -(fn table-type-fn [] - `(fn [tbl#] - (let [t# (type tbl#)] - (if (= t# :table) - (let [meta# (getmetatable tbl#) - table-type# (and meta# (. meta# :cljlib/table-type))] - (if table-type# table-type# - (let [(k# _#) (next tbl#)] - (if (and (= (type k#) :number) (= k# 1)) :seq - (= k# nil) :empty - :table)))) - :else)))) - -(fn seq-fn [] - `(fn [tbl#] - (var assoc# false) - (let [res# [] - insert# table.insert] - (each [k# v# (pairs tbl#)] - (if (and (not assoc#) - (not (= (type k#) :number))) - (set assoc# true)) - (insert# res# [k# v#])) - (if assoc# res# tbl#)))) - -(fn& core.empty [tbl] - (let [table-type (table-type tbl)] - (if (= table-type :seq) `(setmetatable {} {:cljlib/table-type :seq}) - (= table-type :table) `(setmetatable {} {:cljlib/table-type :table}) - `(setmetatable {} {:cljlib/table-type (,(table-type-fn) ,tbl)})))) - -(fn& core.into [to from] - (let [to-type (table-type to) - from-type (table-type from)] - (if (and (= to-type :seq) (= from-type :seq)) - `(let [to# ,to - insert# table.insert] - (each [_# v# (ipairs ,from)] - (insert# to# v#)) - to#) - (= to-type :seq) - `(let [to# ,to - seq# ,(seq-fn) - insert# table.insert] - (each [_# v# (ipairs (seq# ,from))] - (insert# to# v#)) - to#) - (and (= to-type :table) (= from-type :seq)) - `(let [to# ,to] - (each [_# [k# v#] (ipairs ,from)] - (tset to# k# v#)) - to#) - (and (= to-type :table) (= from-type :table)) - `(let [to# ,to - from# ,from] - (each [k# v# (pairs from#)] - (tset to# k# v#)) - to#) - (= to-type :table) - `(let [to# ,to - from# ,from] - (match (,(table-type-fn) from#) - :seq (each [_# [k# v#] (ipairs from#)] - (tset to# k# v#)) - :table (each [k# v# (pairs from#)] - (tset to# k# v#)) - :else (error "expected table as second argument")) - to#) - `(let [to# ,to - from# ,from - insert# table.insert - table-type# ,(table-type-fn) - seq# ,(seq-fn)] - (match (table-type# to#) - :seq (each [_# v# (ipairs (seq# from#))] - (insert# to# v#)) - :table (match (table-type# from#) - :seq (each [_# [k# v#] (ipairs from#)] - (tset to# k# v#)) - :table (each [k# v# (pairs from#)] - (tset to# k# v#)) - :else (error "expected table as second argument")) - ;; If we could not deduce type, it means that - ;; we've got empty table. We use will default - ;; to sequential table, because it will never - ;; break when converting into - :empty (each [_# v# (ipairs (seq# from#))] - (insert# to# v#)) - :else (error "expected table as first argument")) - to#)))) - -(fn first [tbl] - (. tbl 1)) - -(fn rest [tbl] - [(unpack tbl 2)]) - -(fn string? [x] - (= (type x) :string)) - -(fn& core.when-meta [...] - (when meta-enabled `(do ,...))) - -(fn* core.with-meta [val meta] - (if (not meta-enabled) val - `(let [val# ,val - (res# fennel#) (pcall require :fennel)] - (if res# - (each [k# v# (pairs ,meta)] - (fennel#.metadata:set val# k# v#))) - val#))) - -(fn* core.meta [v] - (when-meta - `(let [(res# fennel#) (pcall require :fennel)] - (if res# (. fennel#.metadata ,v))))) - -(fn eq-fn [] - `(fn eq# [a# b#] - (if (and (= (type a#) :table) (= (type b#) :table)) - (do (var [res# count-a# count-b#] [true 0 0]) - (each [k# v# (pairs a#)] - (set res# (eq# v# (. b# k#))) - (set count-a# (+ count-a# 1)) - (when (not res#) (lua :break))) - (when res# - (each [_# _# (pairs b#)] - (set count-b# (+ count-b# 1))) - (set res# (and res# (= count-a# count-b#)))) - res#) - (= a# b#)))) - -(fn* core.defmulti - [name & opts] - (let [docstring (if (string? (first opts)) (first opts)) - opts (if docstring (rest opts) opts) - dispatch-fn (first opts)] - (if (in-scope? name) - nil - `(local ,name - (let [multimethods# {}] - (setmetatable - ,(with-meta {} {:fnl/docstring docstring}) - {:__call - (fn [_# ...] - ,docstring - (let [dispatch-value# (,dispatch-fn ...) - (res# view#) (pcall require :fennelview)] - ((or (. multimethods# dispatch-value#) - (. multimethods# :default) - (error (.. "No method in multimethod '" - ,(tostring name) - "' for dispatch value: " - ((if res# view# tostring) dispatch-value#)) - 2)) ...))) - :multimethods (setmetatable multimethods# - {:__index - (fn [tbl# key#] - (let [eq# ,(eq-fn)] - (var res# nil) - (each [k# v# (pairs tbl#)] - (when (eq# k# key#) - (set res# v#) - (lua :break))) - res#))})})))))) - -(fn* core.defmethod - [multifn dispatch-val & fn-tail] - `(let [multifn# ,multifn] - (tset (. (getmetatable multifn#) :multimethods) - ,dispatch-val - (fn ,(unpack fn-tail))) - multifn#)) - -(fn* core.def - ([name expr] (def {} name expr)) - ([attr-map name expr] - (let [attr-map (if (table? attr-map) attr-map - (string? attr-map) {attr-map true} - (error "def: expected keyword or literal table as first argument" 2)) - (s multi) (multisym->sym name) - docstring (or (. attr-map :doc) - (. attr-map :fnl/docstring)) - f (if (. attr-map :dynamic) 'var 'local)] - (if multi - `(,f ,s (do (,f ,s ,expr) - (set ,name ,s) - ,(with-meta s {:fnl/docstring docstring}))) - `(,f ,name ,(with-meta expr {:fnl/docstring docstring})))))) - -(fn* core.defonce - ([name expr] - (defonce {} name expr)) - ([attr-map name expr] - (if (in-scope? name) - nil - (def attr-map name expr)))) - -core diff --git a/macros/fn.fnl b/macros/fn.fnl deleted file mode 100644 index 9e01a19..0000000 --- a/macros/fn.fnl +++ /dev/null @@ -1,281 +0,0 @@ -(local unpack (or table.unpack _G.unpack)) -(local insert table.insert) -(local concat table.concat) -(local sort table.sort) -(local gsub string.gsub) -(local meta-enabled (pcall _SCOPE.specials.doc (list (sym :doc) (sym :doc)) _SCOPE _CHUNK)) - -(fn with-meta [val meta] - (if (not meta-enabled) val - `(let [val# ,val - (res# fennel#) (pcall require :fennel)] - (if res# - (each [k# v# (pairs ,meta)] - (fennel#.metadata:set val# k# v#))) - val#))) - -(fn gen-arglist-doc [args] - (if (list? (. args 1)) - (let [arglist [] - open (if (> (length args) 1) "\n [" "") - close (if (= open "") "" "]")] - (each [i v (ipairs args)] - (insert - arglist - (.. open (concat (gen-arglist-doc v) " ") close))) - arglist) - - (sequence? (. args 1)) - (let [arglist []] - (each [_ v (ipairs (. args 1))] - (insert arglist (tostring v))) - arglist))) - -(fn multisym->sym [s] - (if (multi-sym? s) - (values (sym (gsub (tostring s) ".*[.]" "")) true) - (values s false))) - -(fn string? [x] - (= (type x) "string")) - -(fn has-amp? [args] - ;; Check if arglist has `&' and return its position of `false'. - ;; Performs additional checks for `&' and `...' usage in arglist. - (var res false) - (each [i s (ipairs args)] - (if (= (tostring s) "&") - (if res (assert-compile false "only one `&' can be specified in arglist." args) - (set res i)) - (= (tostring s) "...") - (assert-compile false "use of `...' in `fn*' is not permitted. Use `&' if you want a vararg." args) - (and res (> i (+ res 1))) - (assert-compile false "only one `more' argument can be supplied after `&' in arglist." args))) - res) - -(fn gen-arity [[args & body]] - ;; Forms three values, representing data needed to create dispatcher: - ;; - ;; - the length of arglist; - ;; - the body of the function we generate; - ;; - position of `&' in the arglist if any. - (assert-compile (sequence? args) "fn*: expected parameters table. - -* Try adding function parameters as a list of identifiers in brackets." args) - (values (length args) - (list 'let [args ['...]] (list 'do (unpack body))) - (has-amp? args))) - -(fn contains? [tbl x] - (var res false) - (each [i v (ipairs tbl)] - (if (= v x) - (do (set res i) - (lua :break)))) - res) - -(fn grows-by-one-or-equal? [tbl] - (let [t []] - (each [_ v (ipairs tbl)] (insert t v)) - (sort t) - (var prev nil) - (each [_ cur (ipairs t)] - (if prev - (when (and (not= (+ prev 1) cur) - (not= prev cur)) - (lua "return false"))) - (set prev cur)) - prev)) - -(fn arity-dispatcher [len fixed body& name] - ;; Forms an `if' expression with all fixed arities first, then `&' - ;; arity, if present, and default error message as last arity. - ;; - ;; `len' is a symbol, that represents the length of the current argument - ;; list, and is computed at runtime. - ;; - ;; `fixed' is a table of arities with fixed amount of arguments. - ;; These are put in this `if' as: `(= len fixed-len)', where - ;; `fixed-len' is the length of current arity arglist, computed with - ;; `gen-arity'. - ;; - ;; `body&' stores size of fixed part of arglist, that is, everything - ;; up until `&', and the body itself. When `body&' provided, the - ;; `(>= len more-len)' is added to the resulting `if' expression. - ;; - ;; Lastly the catchall branch is added to `if' expression, which - ;; ensures that only valid amount of arguments were passed to - ;; function, which are defined by previous branches. - (let [bodies '(if) - lengths []] - (var max nil) - (each [fixed-len body (pairs (doto fixed))] - (when (or (not max) (> fixed-len max)) - (set max fixed-len)) - (insert lengths fixed-len) - (insert bodies (list '= len fixed-len)) - (insert bodies body)) - (when body& - (let [[more-len body arity] body&] - (assert-compile (not (and max (<= more-len max))) "fn*: arity with `&' must have more arguments than maximum arity without `&'. - -* Try adding more arguments before `&'" arity) - (insert lengths (- more-len 1)) - (insert bodies (list '>= len (- more-len 1))) - (insert bodies body))) - (if (not (and (grows-by-one-or-equal? lengths) - (contains? lengths 0))) - (insert bodies (list 'error - (.. "wrong argument amount" - (if name (.. " for " name) "")) 2))) - bodies)) - -(fn single-arity-body [args fname] - ;; Produces arglist and body for single-arity function. - ;; For more info check `gen-arity' documentation. - (let [[args & body] args - (arity body amp) (gen-arity [args (unpack body)])] - `(let [len# (select :# ...)] - ,(arity-dispatcher - 'len# - (if amp {} {arity body}) - (if amp [amp body]) - fname)))) - -(fn multi-arity-body [args fname] - ;; Produces arglist and all body forms for multi-arity function. - ;; For more info check `gen-arity' documentation. - (let [bodies {} ;; bodies of fixed arity - bodies& []] ;; bodies where arglist contains `&' - (each [_ arity (ipairs args)] - (let [(n body amp) (gen-arity arity)] - (if amp - (insert bodies& [amp body arity]) - (tset bodies n body)))) - (assert-compile (<= (length bodies&) 1) - "fn* must have only one arity with `&':" - (. bodies& (length bodies&))) - `(let [len# (select :# ...)] - ,(arity-dispatcher - 'len# - bodies - (if (not= (next bodies&) nil) - (. bodies& 1)) - fname)))) - -(fn fn* [name doc? ...] - "Create (anonymous) function of fixed arity. -Supports multiple arities by defining bodies as lists: - -Named function of fixed arity 2: -(fn* f [a b] (+ a b)) - -Function of fixed arities 1 and 2: -(fn* ([x] x) - ([x y] (+ x y))) - -Named function of 2 arities, one of which accepts 0 arguments, and the -other one or more arguments: -(fn* f - ([] nil) - ([x & xs] - (print x) - (f (unpack xs)))) - -Note, that this function is recursive, and calls itself with less and -less amount of arguments until there's no arguments, and the -zero-arity body is called. - -Named functions accept additional documentation string before the -argument list: - -(fn* cube - \"raise `x' to power of 3\" - [x] - (^ x 3)) - -(fn* greet - \"greet a `person', optionally specifying default `greeting'.\" - ([person] (print (.. \"Hello, \" person \"!\"))) - ([greeting person] (print (.. greeting \", \" person \"!\")))) - -Argument lists follow the same destruction rules as in `let'. -Variadic arguments with `...' are not supported. - -If function name contains namespace part, defines local variable -without namespace part, then creates function with this name, sets -this function to the namespace, and returns it. This roughly means, -that instead of writing this: - -(local namespace {}) -(fn f [x] - (if (> x 0) (f (- x 1)))) -(set namespace.f f) -(fn g [x] (f (* x 100))) -(set namespace.g g) - -It is possible to write: - -(local namespace {}) -(fn* namespace.f [x] - (if (> x 0) (f (- x 1)))) -(fn* namespace.g [x] (f (* x 100))) - -Note that it is still possible to call `f' and `g' in current scope -without namespace part. `Namespace' will hold both functions as `f' -and `g' respectively." - (assert-compile (not (string? name)) "fn* expects symbol, vector, or list as first argument" name) - (let [docstring (if (string? doc?) doc? nil) - (name-wo-namespace namespaced?) (multisym->sym name) - fname (if (sym? name-wo-namespace) (tostring name-wo-namespace)) - args (if (sym? name-wo-namespace) - (if (string? doc?) [...] [doc? ...]) - [name-wo-namespace doc? ...]) - arglist-doc (gen-arglist-doc args) - [x] args - - body (if (sequence? x) (single-arity-body args fname) - (list? x) (multi-arity-body args fname) - (assert-compile false "fn*: expected parameters table. - -* Try adding function parameters as a list of identifiers in brackets." x))] - (if (sym? name-wo-namespace) - (if namespaced? - `(local ,name-wo-namespace - (do - (fn ,name-wo-namespace [...] ,docstring ,body) - (set ,name ,name-wo-namespace) - ,(with-meta name-wo-namespace `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) - `(local ,name ,(with-meta `(fn ,name [...] ,docstring ,body) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) - (with-meta `(fn [...] ,docstring ,body) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring})))) - -(fn fn& [name doc? args ...] - "Create (anonymous) function. -Works the same as plain `fn' except supports automatic declaration of -namespaced functions. See `fn*' for more info." - (assert-compile (not (string? name)) "fn* expects symbol, vector, or list as first argument" name) - (let [docstring (if (string? doc?) doc? nil) - (name-wo-namespace namespaced?) (multisym->sym name) - arg-list (if (sym? name-wo-namespace) - (if (string? doc?) args doc?) - name-wo-namespace) - arglist-doc (gen-arglist-doc arg-list) - body (if (sym? name) - (if (string? doc?) - [doc? ...] - [args ...]) - [doc? args ...])] - (if (sym? name-wo-namespace) - (if namespaced? - `(local ,name-wo-namespace - (do - (fn ,name-wo-namespace ,arg-list ,(unpack body)) - (set ,name ,name-wo-namespace) - ,(with-meta name-wo-namespace `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) - `(local ,name ,(with-meta `(fn ,name ,arg-list ,(unpack body)) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring}))) - (with-meta `(fn ,arg-list ,(unpack body)) `{:fnl/arglist ,arglist-doc :fnl/docstring ,docstring})))) - -{: fn* : fn&} - -;; LocalWords: arglist fn runtime arities arity multi destructuring -;; LocalWords: docstring Variadic LocalWords diff --git a/test/core.fnl b/test/core.fnl index 2a5e38f..6c705b6 100644 --- a/test/core.fnl +++ b/test/core.fnl @@ -1,5 +1,4 @@ -(require-macros :macros.fn) -(require-macros :macros.core) +(require-macros :cljlib-macros) (require-macros :test.test) (local @@ -63,7 +62,7 @@ : ge : lt : gt} - (require :core)) + (require :cljlib)) (deftest equality (testing "comparing basetypes" diff --git a/test/fn.fnl b/test/fn.fnl index f89581f..ec4e835 100644 --- a/test/fn.fnl +++ b/test/fn.fnl @@ -1,6 +1,5 @@ (require-macros :test.test) -(require-macros :macros.core) -(require-macros :macros.fn) +(require-macros :cljlib-macros) (deftest fn* (testing "fn* meta" diff --git a/test/macros.fnl b/test/macros.fnl index 4d4ac57..1876126 100644 --- a/test/macros.fnl +++ b/test/macros.fnl @@ -1,7 +1,5 @@ (require-macros :test.test) -(local {: identity} (require :core)) - -(require-macros :macros.core) +(require-macros :cljlib-macros) (deftest into (testing "into" @@ -74,7 +72,7 @@ (assert-eq (defmulti x (fn [x] (+ x 1))) nil)) (testing "defmulti defalut" - (defmulti fac identity) + (defmulti fac (fn [x] x)) (defmethod fac 0 [_] 1) (defmethod fac :default [x] (* x (fac (- x 1)))) (assert-eq (fac 42) 7538058755741581312)) diff --git a/test/test.fnl b/test/test.fnl index 211f330..6d04338 100644 --- a/test/test.fnl +++ b/test/test.fnl @@ -1,4 +1,4 @@ -(require-macros :macros.fn) +(require-macros :cljlib-macros) (fn eq-fn [] `(fn eq# [a# b#] -- cgit v1.2.3