git.fiddlerwoaroof.com
Browse code

Initial Commit

Ed L authored on 17/03/2016 07:32:01
Showing 4 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,13 @@
1
+A simple package implementing a DSL for generating format strings.
2
+
3
+```(Common Lisp)
4
+
5
+(make-format-string '(:str)) #| ==> "~a" |#
6
+(make-format-string '((:map () :str))) #| ==> "~{~a~}" |#
7
+
8
+(define-message hello (name)
9
+  "Hello " :str)
10
+(define-message print-comma-separated (values)
11
+  (:map () :str))
12
+
13
+```
0 14
new file mode 100644
... ...
@@ -0,0 +1,11 @@
1
+;;;; format-string-builder.asd
2
+
3
+(asdf:defsystem #:format-string-builder
4
+  :description "Describe format-string-builder here"
5
+  :author "fiddlerwoaroof"
6
+  :license "MIT"
7
+  :depends-on (:alexandria)
8
+  :serial t
9
+  :components ((:file "package")
10
+               (:file "format-string-builder")))
11
+
0 12
new file mode 100644
... ...
@@ -0,0 +1,235 @@
1
+(in-package #:format-string-builder)
2
+
3
+(defvar *format-command-stream*)
4
+
5
+(defclass format-string-command ()
6
+  ((at-p :initarg :at-p :accessor at-p :initform nil)
7
+   (colon-p :initarg :colon-p :accessor colon-p :initform nil)))
8
+
9
+(defclass simple-format-string-command (format-string-command)
10
+  ((format-char :initarg :format-char :accessor format-char)
11
+   (modifiers :initarg :modifiers :accessor modifiers :initform nil)))
12
+
13
+(defclass compound-format-string-command (format-string-command)
14
+  ((start-char :initarg :start-char :accessor start-char)
15
+   (contents :initarg :contents :accessor contents :initform nil)
16
+   (end-char :initarg :end-char :accessor end-char)
17
+   (modifiers :initarg :modifiers :accessor modifiers :initform nil)))
18
+
19
+(defmethod print-object ((obj compound-format-string-command) s)
20
+  (declare (optimize (debug 3)))
21
+  (print-unreadable-object (obj s :type t :identity t)
22
+    (format s "~:@{\"~:[~;:~]~:[~;@~]~a...~a\" modifiers: ~s~}"
23
+            (mapcar (lambda (x) (funcall x obj))
24
+                    '(colon-p at-p start-char end-char modifiers)))))
25
+
26
+(defgeneric print-format-modifiers (command stream)
27
+  (:method ((command format-string-command) s)
28
+   ; TODO: format here causes inconsistent behavior :p
29
+     (mapl (lambda (x)
30
+             (princ (car x) s)
31
+             (when (cdr x) (princ #\, s)))
32
+           (modifiers command))))
33
+
34
+(defgeneric print-format-representation (command stream)
35
+  (:method ((commands list) s)
36
+   (dolist (command commands)
37
+     (print-format-representation command s)))
38
+  (:method ((literal character) s)
39
+   (princ literal s))
40
+  (:method ((literal string) s)
41
+   (princ literal s))
42
+
43
+  (:method :around ((command format-string-command) s)
44
+   (princ #\~ s)
45
+   (call-next-method))
46
+  (:method :before ((command format-string-command) s)
47
+   (when (colon-p command)
48
+     (princ #\: s))
49
+   (when (at-p command)
50
+     (princ #\@ s)))
51
+
52
+  (:method :before ((command simple-format-string-command) s)
53
+   (print-format-modifiers command s))
54
+  (:method ((command simple-format-string-command) s)
55
+   (princ (format-char command) s))
56
+
57
+  (:method :before ((command compound-format-string-command) s)
58
+   (print-format-modifiers command s))
59
+  (:method ((command compound-format-string-command) s)
60
+   (princ (start-char command) s)
61
+   (print-format-representation (contents command) s)
62
+   (princ #\~ s)
63
+   (princ (end-char command) s))
64
+
65
+  (:documentation "Prints the appropriate control sequence to the stream passed in
66
+                   The :before method will print the Tilde."))
67
+
68
+; TODO: should this be a generic function?
69
+(defun convert-modifier (modifier)
70
+  (flet ((validate-modifier (modifier)
71
+           (when (member (elt (string modifier) 0) '(#\: #\@ #\,))
72
+             (error "Invalid modifier: ~s" modifier))
73
+           modifier))
74
+    (typecase modifier
75
+      (character  (concatenate 'string "'" (string (validate-modifier modifier))))
76
+      (string (validate-modifier modifier))
77
+      (null "")
78
+      (t modifier))))
79
+
80
+(#-dev defvar
81
+ #+dev defparameter *format-string-registry* (make-hash-table))
82
+
83
+(defun generate-function-defs ()
84
+  (loop for name being the hash-keys in *format-string-registry* using (hash-value spec)
85
+        collect `(,name (&key colon-p at-p)
86
+                        (make-instance ,@spec :colon-p colon-p :at-p at-p))))
87
+
88
+(defgeneric dispatch-command (command args)
89
+  (:method ((command simple-format-string-command) args)
90
+   (setf (modifiers command) (mapcar #'convert-modifier args))
91
+   command)
92
+  (:method ((command compound-format-string-command) args)
93
+   (destructuring-bind ((&rest modifiers) &rest contents) args
94
+     (setf (modifiers command) (mapcar #'convert-modifier modifiers))
95
+     (setf (contents command) (mapcar #'form-step contents))
96
+     command)))
97
+
98
+(defun form-step (form)
99
+  (flet ((dispatch-keyword (keyword &optional args)
100
+           (if-let ((spec (gethash keyword *format-string-registry*)))
101
+             (let ((result (apply #'make-instance spec)))
102
+               (when args
103
+                 (dispatch-command result args))
104
+               result))))
105
+    (etypecase form
106
+      (list (cond ((null form) nil)
107
+                  ((keywordp (car form)) (dispatch-keyword (car form) (cdr form)))))
108
+      ((or character string) form)
109
+      (keyword (dispatch-keyword form)))))
110
+
111
+(defun make-format-string (forms)
112
+  (with-output-to-string (*format-stream*)
113
+    (print-format-representation
114
+      (mapcar #'form-step (macroexpand forms))
115
+      *format-stream*)))
116
+
117
+(defun %define-format-char (name format-char closing-char &key at-p colon-p)
118
+  ; TODO: implement closing-char when we implement the block constructs.
119
+  (declare (ignore closing-char))
120
+  (setf (gethash (intern (string name) :keyword)
121
+                 *format-string-registry*)
122
+        `(simple-format-string-command :format-char ,format-char
123
+                                       :at-p ,at-p
124
+                                       :colon-p ,colon-p)))
125
+
126
+(defun define-simple-format-char (name format-char &key at-p colon-p)
127
+  (%define-format-char name format-char nil :at-p at-p :colon-p colon-p))
128
+
129
+(defun define-compound-format-char (name start-char end-char &key at-p colon-p)
130
+  (setf (gethash (intern (string name) :keyword)
131
+                 *format-string-registry*)
132
+        `(compound-format-string-command :start-char ,start-char
133
+                                         :end-char ,end-char
134
+                                         :at-p ,at-p
135
+                                         :colon-p ,colon-p)))
136
+
137
+; ('compound-format-string-command :start-char #\{ :end-char #\})
138
+
139
+(defmacro define-compound-format-chars (&body specs)
140
+  (list*
141
+    'progn
142
+    (loop for spec in specs
143
+        collect `(define-compound-format-char ,@spec))))
144
+
145
+(defmacro define-simple-format-chars (&body specs)
146
+  (list*
147
+    'progn
148
+    (loop for spec in specs
149
+        collect `(define-simple-format-char ,@spec))))
150
+
151
+(defmacro define-format-chars (&body body)
152
+  `(macrolet ((:simple (name (char) &key at-p colon-p)
153
+                `(define-simple-format-char ,name ,char
154
+                                            :at-p ,at-p :colon-p ,colon-p))
155
+              (:simples (&rest specs)
156
+                (list* 'progn
157
+                       (mapcar (lambda (spec)
158
+                                 `(:simple ,@spec))
159
+                               specs)))
160
+              (:compound (name (start-char end-char) &key at-p colon-p)
161
+                `(define-compound-format-char ,name ,start-char ,end-char
162
+                                              :at-p ,at-p :colon-p ,colon-p))
163
+              (:compounds (&rest specs)
164
+                (list* 'progn
165
+                       (mapcar (lambda (spec)
166
+                                 `(:compound ,@spec))
167
+                               specs))))
168
+     ,@body))
169
+
170
+(defmacro &format (stream format-spec &rest args)
171
+  `(format ,stream
172
+           (make-format-string ',format-spec)
173
+           ,@args))
174
+
175
+(defmacro define-message (name (&rest args) &body spec)
176
+  (with-gensyms (stream)
177
+    `(defun ,name (,stream ,@args)
178
+       (&format ,stream ,spec ,@args))))
179
+
180
+(define-format-chars
181
+
182
+  ; Iteration characters.
183
+  (:compounds
184
+    (:map (#\{ #\}))
185
+    (:rest (#\{ #\}) :at-p t)
186
+
187
+    (:ap (#\{ #\}) :colon-p t)
188
+    (:apply (#\{ #\}) :colon-p t)
189
+
190
+    (:aprest (#\{ #\}) :at-p t :colon-p t)
191
+    (:apply-rest (#\{ #\}) :at-p t :colon-p t))
192
+
193
+  ; Alignment characters.
194
+  (:compounds
195
+    (:spread (#\< #\>))
196
+
197
+    (:ljust (#\< #\>) :at-p t)
198
+    (:left (#\< #\>) :at-p t)
199
+
200
+    (:rjust (#\< #\>) :colon-p t)
201
+    (:right (#\< #\>) :colon-p t)
202
+
203
+    (:cjust (#\< #\>) :at-p t :colon-p t)
204
+    (:center (#\< #\>) :at-p t :colon-p t))
205
+
206
+  ; Case printing characters.
207
+  (:compounds
208
+    (:lowercase (#\( #\)))
209
+    (:uppercase (#\( #\)) :at-p t :colon-p t)
210
+    (:titlecase (#\( #\)) :colon-p t)
211
+    (:initialcap (#\( #\)) :at-p t))
212
+
213
+  (:compounds
214
+    (:own-line (#\& #\%)))
215
+
216
+  (:simples
217
+    (:str (#\a))
218
+    (:repr (#\s))
219
+    (:float (#\f))
220
+    (:dec (#\d))
221
+    (:decimal (#\d))
222
+    (:hex (#\x))
223
+    (:oct (#\o))
224
+    (:currency (#\$))
225
+    (:exit (#\^))
226
+    (:go-to (#\*))
227
+    (:end-section (#\;))
228
+    (:ensure-line (#\&))
229
+    (:new-line (#\%))))
230
+
231
+(define-message hello (name)
232
+  "Hello " :str)
233
+
234
+(define-message print-comma-separated (values)
235
+  (:map () :str :exit ", "))
0 236
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+;;;; package.lisp
2
+
3
+(defpackage #:format-string-builder
4
+  (:use #:cl #:alexandria)
5
+  (:export #:make-format-string
6
+           #:define-message))
7
+