diff options
| author | Andrey Listopadov <andreyorst@gmail.com> | 2021-07-13 05:42:37 +0000 |
|---|---|---|
| committer | Andrey Listopadov <andreyorst@gmail.com> | 2021-07-13 05:42:37 +0000 |
| commit | e8351c4a47ea0472151b3f5a5ac06a70f86f0d02 (patch) | |
| tree | ca1b8b870eeee9d1ffe13cf2cd65d6da0c17e2fd /init-macros.fnl | |
| parent | e1bafba9201e818d7aa3a51d8dd30654c55c7ff3 (diff) | |
| parent | f9fb932e6c1f5b916162fb5a50907a1fada646f3 (diff) | |
Merge branch 'loop-macro' into 'master'
FEATURE: Added Clojure-like loop macro!
See merge request andreyorst/fennel-cljlib!12
Diffstat (limited to 'init-macros.fnl')
| -rw-r--r-- | init-macros.fnl | 75 |
1 files changed, 74 insertions, 1 deletions
diff --git a/init-macros.fnl b/init-macros.fnl index 410eca5..6d8f6ab 100644 --- a/init-macros.fnl +++ b/init-macros.fnl @@ -1195,6 +1195,78 @@ Always run some side effect action: "}) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; loop ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(fn loop [args ...] + "Recursive loop macro. + +Similar to `let`, but binds a special `recur` call that will reassign the values +of the bindings and restart the loop. + +The first argument is a binding table with alternating symbols (or destructure +forms), and the values to bind to them. + +For example: + +```fennel + (loop [[first & rest] [1 2 3 4 5] + i 0] + (if (= nil first) + i + (recur rest (+ 1 i)))) +``` + +This would destructure the first table argument, with the first value inside it +being assigned to `first` and the remainder of the table being assigned to +`rest`. `i` simply gets bound to 0. + +The body of the form executes for every item in the table, calling `recur` each +time with the table lacking its head element (thus consuming one element per +iteration), and with `i` being called with one value greater than the previous. + +When the loop terminates (When the user doesn't call `recur`) it will return the +number of elements in the passed in table. (In this case, 5)" + (let [recur (sym :recur) + keys [] + gensyms [] + bindings []] + (each [i v (ipairs args)] + (when (= 0 (% i 2)) + (let [key (. args (- i 1)) + gs (gensym i)] + ;; Converts a form like + ;; (loop [[first & rest] (expression)] + ;; ...) + ;; + ;; to code like: + ;; (let [sym1# (expression) ; bindings table + ;; [first & rest] sym1#] + ;; ((fn recur [[first & rest]] ; keys table + ;; ...) + ;; sym1#)) ; gensyms table, but unpacked + ;; + ;; That way it only evaluates once, and so destructuring + ;; doesn't stomp us. + + ;; [sym1# sym2# etc...], for the function application below + (table.insert gensyms gs) + + ;; let bindings + (table.insert bindings gs) ;; sym1# + (table.insert bindings v) ;; (expression) + (table.insert bindings key) ;; [first & rest] + (table.insert bindings gs) ;; sym1# + + ;; The gensyms we use for function application + (table.insert keys key)))) + `(let ,bindings + ((fn ,recur ,keys + ,...) + ,(table.unpack gensyms))))) + +(attach-meta loop {:fnl/arglist [:binding-vec :body*]}) + + (setmetatable {: fn* : try @@ -1210,7 +1282,8 @@ Always run some side effect action: : defmulti : defmethod : def - : defonce} + : defonce + : loop} {:__index {:_DOC_ORDER [:fn* :try |