-
Notifications
You must be signed in to change notification settings - Fork 0
/
swiss-knife.lisp
130 lines (106 loc) · 3.88 KB
/
swiss-knife.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
;;;; swiss-knife.lisp
;;;;
;;;; The most light-weighted package in basicl. Provides foundations
;;;; for creating useful macro and function utilities that are usually
;;;; serve as building blocks for higher-level features.
;;;;
;;;; Author: BreakDS <breakds@gmail.com>
(in-package #:breakds.basicl.swiss-knife)
;;; ---- List Operations
;; ensure-lisp is a function from "on Lisp" by Paul Graham
(defun ensure-list (obj)
"returns OBJ if OBJ is a list, otherwise nil."
(if (listp obj) obj (list obj)))
(defun map-n (fn n)
"mapcar on natural number list till N."
(loop for i below n
collect (funcall fn i)))
(defun group (lst n)
"Group elements of a list in N-sized sub-lists. The last group may
contain less than N elements."
(labels ((rec (rest accu)
(if (null rest)
(nreverse accu)
(if (nthcdr n rest)
(rec (nthcdr n rest)
(cons (subseq rest 0 n)
accu))
(nreverse (cons rest accu))))))
(rec lst nil)))
(defun flatten (lst)
"Flatten a list so that the result contains no sub-list."
(labels ((rec (lst accu)
(if (null lst)
accu
(let ((x (car lst)))
(rec (rest lst)
(or (and (listp x)
(rec x accu))
(cons x accu)))))))
(nreverse (rec lst nil))))
;;; ---- Macro Candies
(defmacro with-gensyms (var-list &body body)
"bind (gensym) to the variables in VAR-LIST in BODY"
`(let ,(mapcar (lambda (x) `(,x (gensym ,(mkstr x "-"))))
var-list)
,@body))
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun mkstr (&rest args)
"concatenate arguments into a string"
(with-output-to-string (s)
(dolist (a args) (princ a s)))))
(defun symb (&rest args)
"convert arguments into a symbol"
(values (intern (apply #'mkstr args))))
(defun mk-keyword (&rest args)
"concatenate arguments and make the result a keyword"
(values (intern (apply #'mkstr args) "KEYWORD")))
(defmacro aif (predicate then &optional else)
"equivalent to special form if, except that PREDICATE is evaluated
once and stored in a local variable called IT"
`(let ((it ,predicate))
(if it
,then
,else)))
(defmacro awhen (predicate &body body)
"equivalent to special form when, except that PREDICATE is evaluated
once and stored in a local variable called IT"
`(let ((it ,predicate))
(when it
,@body)))
;; one place that alambda is usually found useful is when you want to
;; define an anonymous recursive function.
(defmacro alambda (args &body body)
"equivalent to special form/function lambda, except that SELF is
used to denote the lambda itself"
`(labels ((self ,args
,@body))
#'self))
;; alet is the superstar macro from the book "LET OVER LAMBDA", where
;; Doug uses it a lot to create powerful closures. What it does is
;; define a closure with THIS pointed to the function delegating the
;; closure itself.
(defmacro alet (letargs &body body)
(with-gensyms (args)
`(let ((this) ,@letargs)
(setq this ,@(last body))
,@(butlast body)
(lambda (&rest ,args)
(apply this ,args)))))
;;; ---- Dispatching Macros (Reader Macros)
(set-dispatch-macro-character
#\# #\` (lambda (stream sub-char numarg)
(declare (ignorable sub-char))
(unless numarg (setf numarg 1))
`(lambda ,(loop for i from 1 to numarg
collect (symb 'x i))
,(funcall (get-macro-character #\`)
stream nil))))
(set-dispatch-macro-character
#\# #\f (lambda (stream sub-char numarg)
(declare (ignorable stream sub-char))
(setq numarg (or numarg 3))
(unless (<= numarg 3)
(error "bad value for #f: ~a" numarg))
`(declare (optimize (speed ,numarg)
(safety ,(- 3 numarg))))))