Browse code
Add new features !
Showing 8 changed files
- clos-helpers.lisp
- counter.lisp
- fwoar.lisputils.asd
- fwoar.lisputils.lisp
- hash-functions.lisp
- multiple-values.lisp
- package.lisp
- vector-utils.lisp
0 | 7 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,55 @@ |
1 |
+(defpackage :fwoar.counter |
|
2 |
+ (:use :cl)) |
|
3 |
+ |
|
4 |
+(in-package :fwoar.counter) |
|
5 |
+ |
|
6 |
+(defclass counter () |
|
7 |
+ ((%counts :type hash-table :accessor item-counts) |
|
8 |
+ (%test :initarg :test :initform 'eql :accessor counter-test) |
|
9 |
+ (%key :initarg :key :initform 'identity :accessor counter-key))) |
|
10 |
+ |
|
11 |
+(defmethod initialize-instance :after ((counter counter) &rest initargs &key test) |
|
12 |
+ (declare (ignorable initargs)) |
|
13 |
+ (setf (item-counts counter) (make-hash-table :test test))) |
|
14 |
+ |
|
15 |
+(defgeneric count-sequence (sequence &key test key) |
|
16 |
+ (:documentation "take a sequence, count it using test to compare elements and key to extract values from them")) |
|
17 |
+ |
|
18 |
+(defgeneric update-counts (counter sequence) |
|
19 |
+ (:documentation "given a sequence, update a counter")) |
|
20 |
+ |
|
21 |
+(defgeneric extract-count (counter item) |
|
22 |
+ (:documentation "Given a counter and an item, return the number of times that item has been counted.")) |
|
23 |
+ |
|
24 |
+(defgeneric format-counts (counter stream) |
|
25 |
+ (:documentation "Given a counter and a stream, format the counter's counts to that stream")) |
|
26 |
+ |
|
27 |
+(defmethod extract-count ((counter counter) item) |
|
28 |
+ (gethash (funcall (counter-key counter) |
|
29 |
+ item) |
|
30 |
+ (item-counts counter) |
|
31 |
+ 0)) |
|
32 |
+ |
|
33 |
+(defmethod count-sequence ((sequence string) &key (test 'eql) (key 'identity)) |
|
34 |
+ (let ((result (make-instance 'counter :test test :key key))) |
|
35 |
+ (loop for c across sequence |
|
36 |
+ do (incf (gethash (funcall key c) |
|
37 |
+ (item-counts result) |
|
38 |
+ 0)) |
|
39 |
+ finally (return result)))) |
|
40 |
+ |
|
41 |
+(defmethod format-counts ((counter counter) (stream stream)) |
|
42 |
+ (let ((result '())) |
|
43 |
+ (maphash (lambda (key val) |
|
44 |
+ (push (list key val) |
|
45 |
+ result)) |
|
46 |
+ (item-counts counter)) |
|
47 |
+ (format t "~:{~s: ~2d~%~}" |
|
48 |
+ (stable-sort result #'< :key #'cadr)))) |
|
49 |
+ |
|
50 |
+(defmethod update-counts ((counter counter) sequence) |
|
51 |
+ (with-accessors ((item-counts item-counts) (test counter-test) (key counter-key)) counter |
|
52 |
+ (maphash (lambda (key value) |
|
53 |
+ (incf (gethash key item-counts 0) |
|
54 |
+ value)) |
|
55 |
+ (item-counts (count-sequence sequence :test test :key key))))) |
... | ... |
@@ -6,6 +6,16 @@ |
6 | 6 |
:license "MIT" |
7 | 7 |
:serial t |
8 | 8 |
:components ((:file "package") |
9 |
- (:file "fwoar.lisputils")) |
|
10 |
- :depends-on (#:anaphora #:alexandria #:iterate #:plump)) |
|
9 |
+ (:file "fwoar.lisputils") |
|
10 |
+ (:file "hash-functions") |
|
11 |
+ (:file "multiple-values") |
|
12 |
+ (:file "clos-helpers") |
|
13 |
+ (:file "vector-utils")) |
|
14 |
+ :depends-on (#:anaphora |
|
15 |
+ #:alexandria |
|
16 |
+ #:serapeum |
|
17 |
+ #:cl-containers |
|
18 |
+ #:iterate |
|
19 |
+ #:plump |
|
20 |
+ #:positional-lambda)) |
|
11 | 21 |
|
... | ... |
@@ -48,55 +48,96 @@ |
48 | 48 |
,@body)) |
49 | 49 |
|
50 | 50 |
(eval-when (:compile-toplevel :load-toplevel :execute) |
51 |
+ (defun map-cons (cb cons) |
|
52 |
+ (cond |
|
53 |
+ ((null cons) '()) |
|
54 |
+ ((consp (cdr cons)) (cons (funcall cb (car cons)) |
|
55 |
+ (map-cons cb (cdr cons)))) |
|
56 |
+ (t (list (funcall cb (car cons)) |
|
57 |
+ (funcall cb (cdr cons)))))) |
|
58 |
+ |
|
59 |
+ (defun generate-declarations-for (sym ignored ignorable) |
|
60 |
+ (let ((ignores (list)) |
|
61 |
+ (ignorables (list))) |
|
62 |
+ (map-cons (op (cond ((member _1 ignorable) |
|
63 |
+ (push _1 ignorables)) |
|
64 |
+ ((member _1 ignored) |
|
65 |
+ (push _1 ignores)))) |
|
66 |
+ (ensure-cons sym)) |
|
67 |
+ (if (or ignores ignorables) |
|
68 |
+ `((declare |
|
69 |
+ ,@(when ignores |
|
70 |
+ `((ignore ,@ignores))) |
|
71 |
+ ,@(when ignorables |
|
72 |
+ `((ignorable ,@ignorables))))) |
|
73 |
+ '()))) |
|
74 |
+ |
|
75 |
+ (defun find-ignored-vars (body) |
|
76 |
+ (let ((possible-declarations (car body)) |
|
77 |
+ (ignored-vars nil) |
|
78 |
+ (ignorable-vars nil)) |
|
79 |
+ (if (and (eq (car possible-declarations) 'declare) |
|
80 |
+ (consp (cadr possible-declarations))) |
|
81 |
+ (let* ((declarations (cdr possible-declarations))) |
|
82 |
+ (setf ignored-vars (cdr (assoc 'ignore declarations)) |
|
83 |
+ ignorable-vars (cdr (assoc 'ignorable declarations)) |
|
84 |
+ body (cdr body)))) |
|
85 |
+ (values ignored-vars |
|
86 |
+ ignorable-vars |
|
87 |
+ body))) |
|
88 |
+ |
|
51 | 89 |
(defun ensure-mapping (list) |
52 | 90 |
"Take a list and make sure that it's parseable as a let-style binding. |
53 | 91 |
Very handy for certain sorts of macros." |
54 | 92 |
(let ((symbols->mappings (lambda-cond (x) |
55 |
- ((symbolp x) `(,x ,x)) |
|
56 |
- ((null (cdr x)) `(,#1=(car x) ,#1#)) |
|
57 |
- (t x)))) |
|
93 |
+ ((symbolp x) `(,x ,x)) |
|
94 |
+ ((null (cdr x)) `(,#1=(car x) ,#1#)) |
|
95 |
+ (t x)))) |
|
58 | 96 |
(mapcar symbols->mappings list))) |
59 | 97 |
|
60 | 98 |
|
61 |
- (defun rollup-list (list &optional body) |
|
62 |
- (labels ((helper (list &optional accum start) |
|
63 |
- (tagbody |
|
64 |
- start |
|
65 |
- (cond |
|
66 |
- ((endp list) (return-from rollup-list accum)) |
|
67 |
- (t (psetf accum (cond |
|
68 |
- ((null accum) (car list)) |
|
69 |
- (start `(,@(car list) ,@accum)) |
|
70 |
- (t `(,@(car list) ,accum))) |
|
71 |
- list (cdr list) |
|
72 |
- start nil) |
|
73 |
- ;; NOTE: REMEMBER! This call to #'GO is the "tail call" |
|
74 |
- (go start)))))) |
|
75 |
- (helper (reverse list) body t)))) |
|
99 |
+ (defun rollup-list (list &optional body) |
|
100 |
+ (labels ((helper (list &optional accum start) |
|
101 |
+ (tagbody |
|
102 |
+ start |
|
103 |
+ (cond |
|
104 |
+ ((endp list) (return-from rollup-list accum)) |
|
105 |
+ (t (psetf accum (cond |
|
106 |
+ ((null accum) (car list)) |
|
107 |
+ (start `(,@(car list) ,@accum)) |
|
108 |
+ (t `(,@(car list) ,accum))) |
|
109 |
+ list (cdr list) |
|
110 |
+ start nil) |
|
111 |
+ ;; NOTE: REMEMBER! This call to #'GO is the "tail call" |
|
112 |
+ (go start)))))) |
|
113 |
+ (helper (reverse list) body t)))) |
|
76 | 114 |
|
77 | 115 |
(defmacro m-lambda (sym &rest args) |
78 | 116 |
(let ((arglist (loop for x in args |
79 |
- unless (member x (list '&optional '&key '&rest)) |
|
80 |
- collect (ctypecase x |
|
81 |
- (cons (car x)) |
|
82 |
- ((or symbol keyword string) x))))) |
|
117 |
+ unless (member x (list '&optional '&key '&rest)) |
|
118 |
+ collect (ctypecase x |
|
119 |
+ (cons (car x)) |
|
120 |
+ ((or symbol keyword string) x))))) |
|
83 | 121 |
`(lambda (,@args) |
84 | 122 |
(,sym ,@arglist)))) |
85 | 123 |
|
86 | 124 |
(defmacro destructuring-lambda ((&rest args) &body body) |
87 | 125 |
"A lambda whose arguments can be lambda-lists to be destructured" |
88 |
- (let* ((args-syms (mapcar (alambda (gensym "arg")) |
|
89 |
- args)) |
|
90 |
- (args (mapcar #'list args args-syms)) |
|
91 |
- (destructuring-expressions |
|
92 |
- (rollup-list |
|
93 |
- (loop for (arg arg-sym) in args |
|
94 |
- collect (if (consp arg) |
|
95 |
- `(destructuring-bind ,arg ,arg-sym) |
|
96 |
- `(let ((,arg ,arg-sym))))) |
|
97 |
- body))) |
|
98 |
- `(lambda ,args-syms |
|
99 |
- ,destructuring-expressions))) |
|
126 |
+ (multiple-value-bind (ignored ignorable body) (find-ignored-vars body) |
|
127 |
+ (let* ((args-syms (mapcar (lambda (_) (declare (ignore _)) (gensym "arg")) |
|
128 |
+ args)) |
|
129 |
+ (args (mapcar #'list args args-syms)) |
|
130 |
+ (destructuring-expressions |
|
131 |
+ (rollup-list |
|
132 |
+ (loop for (arg arg-sym) in args |
|
133 |
+ collect (if (consp arg) |
|
134 |
+ `(destructuring-bind ,arg ,arg-sym |
|
135 |
+ ,@(generate-declarations-for arg ignored ignorable)) |
|
136 |
+ `(let ((,arg ,arg-sym)) |
|
137 |
+ ,@(generate-declarations-for arg ignored ignorable)))) |
|
138 |
+ body))) |
|
139 |
+ `(lambda ,args-syms |
|
140 |
+ ,destructuring-expressions)))) |
|
100 | 141 |
|
101 | 142 |
|
102 | 143 |
;;; CASES::: |
... | ... |
@@ -190,9 +231,37 @@ |
190 | 231 |
"Make setf a bit nicer to use with paredit" |
191 | 232 |
(list* 'setf (apply #'append body))) |
192 | 233 |
|
234 |
+(defmacro prog2-let (first-form (&rest result-binding) &body body) |
|
235 |
+ "Execute a form, make a bunch of bindings and retern the bound values via prog1 after executing body" |
|
236 |
+ `(progn ,first-form |
|
237 |
+ (let (,@result-binding) |
|
238 |
+ (prog1 (list ,@(mapcar #'car result-binding)) |
|
239 |
+ ,@body)))) |
|
240 |
+ |
|
193 | 241 |
(defmacro prog1-let ((&rest result-binding) &body body) |
242 |
+ "Bind a bunch of symbols to values and return them via prog1" |
|
194 | 243 |
`(let (,@result-binding) |
195 | 244 |
(prog1 (list ,@(mapcar #'car result-binding)) |
196 | 245 |
,@body))) |
197 | 246 |
|
247 |
+(defmacro if-let* ((&rest bindings) &body (then-form &optional else-form)) |
|
248 |
+ "Like if-let, but sets bindings sequentially. Doesn't short-circuit." |
|
249 |
+ `(let* ,bindings |
|
250 |
+ (if (and ,@(mapcar #'car bindings)) |
|
251 |
+ ,then-form |
|
252 |
+ ,else-form))) |
|
253 |
+ |
|
254 |
+(defmacro with ((var val) &body body) |
|
255 |
+ "A stripped down let for binding a single name" |
|
256 |
+ `(let ((,var ,val)) |
|
257 |
+ ,@body)) |
|
258 |
+ |
|
259 |
+(define-modify-macro aconsf (key datum) |
|
260 |
+ (lambda (alist key datum) |
|
261 |
+ (acons key datum alist))) |
|
262 |
+ |
|
263 |
+;(defun ensure-list (val) |
|
264 |
+; (typecase val |
|
265 |
+; (list val) |
|
266 |
+; (t (list val)))) |
|
198 | 267 |
|
0 | 7 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,31 @@ |
1 |
+(in-package :fwoar.lisputils) |
|
2 |
+ |
|
3 |
+;; (multiple-value-call #'local-time:encode-timestamp |
|
4 |
+;; (limit-values 7 |
|
5 |
+;; (substitute-values 0 0 0 1 |
|
6 |
+;; (local-time:decode-timestamp |
|
7 |
+;; (local-time:now))))) |
|
8 |
+ |
|
9 |
+(defmacro skip-values (n form) |
|
10 |
+ (let* ((gensyms (loop repeat n collect (gensym "V"))) |
|
11 |
+ (ignore-sym (gensym)) |
|
12 |
+ (value-limiter `(lambda (,@gensyms &rest ,ignore-sym) |
|
13 |
+ (declare (ignore ,@gensyms)) |
|
14 |
+ (values-list ,ignore-sym)))) |
|
15 |
+ `(multiple-value-call ,value-limiter ,form))) |
|
16 |
+ |
|
17 |
+(defmacro limit-values (n form) |
|
18 |
+ (let* ((gensyms (loop repeat n collect (gensym "V"))) |
|
19 |
+ (ignore-sym (gensym)) |
|
20 |
+ (value-limiter `(lambda (,@gensyms &rest ,ignore-sym) |
|
21 |
+ (declare (ignore ,ignore-sym)) |
|
22 |
+ (values ,@gensyms)))) |
|
23 |
+ `(multiple-value-call ,value-limiter ,form))) |
|
24 |
+ |
|
25 |
+(defmacro substitute-values (&rest forms) |
|
26 |
+ (let* ((call (car (last forms))) |
|
27 |
+ (values (butlast forms)) |
|
28 |
+ (num-values (length values))) |
|
29 |
+ `(multiple-value-call #'values |
|
30 |
+ ,@values |
|
31 |
+ (skip-values ,num-values ,call)))) |
... | ... |
@@ -3,6 +3,7 @@ |
3 | 3 |
(defpackage #:fwoar.lisputils |
4 | 4 |
(:use #:cl #:alexandria #:iterate) |
5 | 5 |
(:nicknames #:fw.lu) |
6 |
+ (:import-from #:serapeum #:op) |
|
6 | 7 |
(:export #:lambda-if #:lambda-cond #:alambda #:rollup-list |
7 | 8 |
#:ensure-mapping #:alist-string-hash-table #:make-pairs |
8 | 9 |
#:copy-slots #:transform-alist #:%json-pair-transform |
... | ... |
@@ -10,5 +11,8 @@ |
10 | 11 |
#:transform-result #:slots-to-pairs #:normalize-html |
11 | 12 |
#:destructuring-lambda #:let-each #:let-first #:let-second |
12 | 13 |
#:neither #:neither-null #:m-lambda #:sets #:defparameters |
13 |
- #:setfs #:prog1-let)) |
|
14 |
+ #:setfs #:prog1-let #:if-let* #:with #:aconsf #:ensure-list #:pick |
|
15 |
+ #:vector-destructuring-bind #:with-accessors* |
|
16 |
+ #:skip-values #:limit-values #:substitute-values |
|
17 |
+ #:op)) |
|
14 | 18 |
|
15 | 19 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,42 @@ |
1 |
+(in-package #:fwoar.lisputils) |
|
2 |
+ |
|
3 |
+(defmacro vector-destructuring-bind ((&rest symbols) vector &body body) |
|
4 |
+ (let ((mappings (loop for symbol in symbols |
|
5 |
+ for num from 0 |
|
6 |
+ collect (list num symbol)))) |
|
7 |
+ (once-only (vector) |
|
8 |
+ `(symbol-macrolet ,(mapcar (destructuring-lambda ((num symbol)) |
|
9 |
+ `(,symbol (aref ,vector ,num))) |
|
10 |
+ mappings) |
|
11 |
+ ,@body)))) |
|
12 |
+ |
|
13 |
+(defun v-first (vector) |
|
14 |
+ (elt vector 0)) |
|
15 |
+ |
|
16 |
+(defun index-table (table &key (test 'eql) (key 'v-first)) |
|
17 |
+ (declare (optimize (speed 0) (debug 3))) |
|
18 |
+ (check-type table (array * (* *))) |
|
19 |
+ (let ((table-index (cl-containers:make-container 'cl-containers:simple-associative-container :test test))) |
|
20 |
+ (loop for row-num from 0 to (1- (array-dimension table 0)) |
|
21 |
+ for current-row = (make-array (array-dimension table 1) |
|
22 |
+ :displaced-to table |
|
23 |
+ :displaced-index-offset (apply #'array-row-major-index table (list row-num 0))) |
|
24 |
+ do (setf (cl-containers:item-at table-index (funcall key current-row)) row-num)) |
|
25 |
+ table-index)) |
|
26 |
+ |
|
27 |
+(defun join-tables (table1 table2 &key (test 'eql) (key1 'v-first) (key2 'v-first)) |
|
28 |
+ (declare (optimize (speed 0) (debug 3))) |
|
29 |
+ (check-type table1 (array * (* *))) |
|
30 |
+ (check-type table2 (array * (* *))) |
|
31 |
+ (let ((table2-index (index-table table2 :test test :key key2))) |
|
32 |
+ (loop for row-num from 0 to (1- (array-dimension table1 0)) |
|
33 |
+ for current-row = (make-array (array-dimension table1 1) |
|
34 |
+ :displaced-to table1 |
|
35 |
+ :displaced-index-offset (apply #'array-row-major-index table1 (list row-num 0))) |
|
36 |
+ for dest-index = (cl-containers:item-at table2-index (funcall key1 current-row)) |
|
37 |
+ when dest-index |
|
38 |
+ collect (let* ((to-row (make-array (array-dimension table2 1) |
|
39 |
+ :displaced-to table2 |
|
40 |
+ :displaced-index-offset (apply #'array-row-major-index table2 (list dest-index 0))))) |
|
41 |
+ (concatenate 'vector current-row to-row))))) |
|
42 |
+ |