Skip to content

Instantly share code, notes, and snippets.

@trentgill
Last active March 12, 2025 05:55
Show Gist options
  • Save trentgill/d65eb7e5724fa9a25ef7524bc5a03463 to your computer and use it in GitHub Desktop.
Save trentgill/d65eb7e5724fa9a25ef7524bc5a03463 to your computer and use it in GitHub Desktop.
sequins lovingly rewritten in fennel
;;; sequins library
;; lovingly re-written in fennel
(var s-mt {}) ;to be metatable (avoid circular dependence)
(fn %1 [ix wrap]
"modulo for 1-based indices"
(-> ix (- 1) (% wrap) (+ 1)))
(fn sequins? [t]
"return true if the argument is a sequins-table"
(= s-mt (getmetatable t)))
;; modifier implementations
;; #[ix n self] -> string, number, or nil
(local flows
{:every (fn [ix n _]
(if (not= 0 (% ix n))
:skip))
:times (fn [ix n _]
(if (< n ix) :dead))
:count (fn [ix n self]
(if (< ix n)
:again
(tset self :count :ix 0)))
:select (fn [_ n self]
(if (> n 0)
(do
(tset self :select :n (- n)) ;; available for :again undo
n) ;; return the index
(< n 0)
(tset self :select :n 0)))
:step (fn [ix n self]
(let [nix (%1 (+ ix n))]
(tset self :ix nix)
nix))}) ;; return the index
(fn flow-with [key self ?revert]
"apply the next step of a flow-modifier and maybe return a value"
(let [step (if ?revert -1 1)
flow (. self key)]
(tset flow :ix (+ step flow.ix))
(let [{: ix : n} flow]
((. flows key) ix n self))))
(fn unroll-count [self]
"revert the effect of flow-with on all modifiers except count"
(each [_ v (ipairs [:every :times :select])] ;; FIXME broken for :select
(flow-with v self :revert)))
(fn unwrap [f v]
"if the value is a nested sequins, unwrap it"
(if (sequins? v)
(values (v)) ;unwrap by calling the sequins
v))
(fn realize [self]
"realize the next value from a sequins, or signal to its parent"
(case (case-try (flow-with :every self)
nil (flow-with :times self)
nil (flow-with :select self) ;; may return early with index
nil (let [again (flow-with :count self)]
(values (flow-with :step self) again)))
(where s (or (= s :skip) (= s :dead))) (values nil s)
(ix ?again) (do
(when (= ?again :again)
(unroll-count self))
(case (unwrap (. self.data ix))
(where (_ s) (= :string (type s)) (self) ;; recur to overcome skip etc
(val _) (values (self.map val) ?again))))
(fn adorn [self mods]
"add or modify a sequins objects metaparams"
(each [k v (pairs mods)]
(tset self k :n v)) ;set modifer value (number | fn | sequins)
self)
(fn new [data mods]
"create a new sequins object with optional modifiers"
(-> {:data data
:len (length data) ;memoize length for speed
;; mods
:step {:ix 0 :qix 1 :n 1} ;; TODO remove :qix
:select {:ix 0 :n 1} ;; 0 is no select, -ve is previous
:count {:ix 0 :n 1}
:every {:ix 0 :n 1}
:times {:ix 0 :n 99999999} ;; a really big number of repeats
:map #$}
(adorn mods)
(setmetatable s-mt)))
; sequins metatable attached to each instance
(set s-mt {:__call realize
:__len #(. $ :len)})
; expose public interface
; NOTE redundant is_sequins() for lua compatibility
(setmetatable {: new : adorn : sequins? :is_sequins sequins?}
{:__call (fn [_ ...] (new ...))})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment