Browse code
Merge pull request #11 from jasonmelbye/master
Additional parser functionality, new emitter interface
Fernando Borretti authored on 24/12/2016 23:59:34Showing 8 changed files
- cl-yaml-test.asd
- src/emitter.lisp
- src/parser.lisp
- src/scalar.lisp
- src/yaml.lisp
- t/cl-yaml.lisp
- t/emitter2.lisp
- t/round-trip.lisp
... | ... |
@@ -1,9 +1,52 @@ |
1 | 1 |
(in-package :cl-user) |
2 | 2 |
(defpackage yaml.emitter |
3 | 3 |
(:use :cl) |
4 |
- (:export :encode |
|
4 |
+ (:import-from :cffi |
|
5 |
+ :foreign-free |
|
6 |
+ :null-pointer) |
|
7 |
+ (:import-from :libyaml.emitter |
|
8 |
+ :allocate-emitter |
|
9 |
+ :emitter-initialize |
|
10 |
+ :emitter-delete |
|
11 |
+ :set-output |
|
12 |
+ :stream-start-event-initialize |
|
13 |
+ :stream-end-event-initialize |
|
14 |
+ :document-start-event-initialize |
|
15 |
+ :document-end-event-initialize |
|
16 |
+ :scalar-event-initialize |
|
17 |
+ :sequence-start-event-initialize |
|
18 |
+ :sequence-end-event-initialize |
|
19 |
+ :mapping-start-event-initialize |
|
20 |
+ :mapping-end-event-initialize) |
|
21 |
+ (:import-from :libyaml.event |
|
22 |
+ :allocate-event |
|
23 |
+ :event-delete) |
|
24 |
+ (:import-from :libyaml.write-handler |
|
25 |
+ :*write-handler-callback* |
|
26 |
+ :*write-handler-stream*) |
|
27 |
+ (:export ;; Original interface |
|
5 | 28 |
:emit |
6 |
- :emit-to-string) |
|
29 |
+ :emit-to-string |
|
30 |
+ :encode |
|
31 |
+ ;; libyaml based interface |
|
32 |
+ :stream-start-event |
|
33 |
+ :stream-end-event |
|
34 |
+ :document-start-event |
|
35 |
+ :document-end-event |
|
36 |
+ :scalar-event |
|
37 |
+ :sequence-start-event |
|
38 |
+ :sequence-end-event |
|
39 |
+ :mapping-start-event |
|
40 |
+ :mapping-end-event |
|
41 |
+ :emit-stream |
|
42 |
+ :emit-document |
|
43 |
+ :emit-sequence |
|
44 |
+ :emit-mapping |
|
45 |
+ :emit-scalar |
|
46 |
+ :emit-object |
|
47 |
+ :print-scalar |
|
48 |
+ :with-emitter-to-stream |
|
49 |
+ :with-emitter-to-string) |
|
7 | 50 |
(:documentation "The YAML emitter.")) |
8 | 51 |
(in-package :yaml.emitter) |
9 | 52 |
|
... | ... |
@@ -67,4 +110,196 @@ |
67 | 110 |
(defun emit-to-string (value) |
68 | 111 |
"Emit a value to string." |
69 | 112 |
(with-output-to-string (stream) |
70 |
- (emit value stream))) |
|
113 |
+(emit value stream))) |
|
114 |
+ |
|
115 |
+;;; Wrappers around cl-libyaml event interface with defaults and keyword args |
|
116 |
+ |
|
117 |
+(defun stream-start-event (event &key (encoding :utf8-encoding)) |
|
118 |
+ (stream-start-event-initialize event encoding)) |
|
119 |
+ |
|
120 |
+(defun stream-end-event (event) |
|
121 |
+ (stream-end-event-initialize event)) |
|
122 |
+ |
|
123 |
+(defun document-start-event (event &key (version-directive (null-pointer)) |
|
124 |
+ (tag-directive-start (null-pointer)) |
|
125 |
+ (tag-directive-end (null-pointer)) |
|
126 |
+ (implicit nil)) |
|
127 |
+ (document-start-event-initialize event version-directive |
|
128 |
+ tag-directive-start |
|
129 |
+ tag-directive-end |
|
130 |
+ implicit)) |
|
131 |
+ |
|
132 |
+(defun document-end-event (event &key (implicit nil)) |
|
133 |
+ (document-end-event-initialize event implicit)) |
|
134 |
+ |
|
135 |
+(defun sequence-start-event (event &key (anchor (null-pointer)) |
|
136 |
+ (tag (null-pointer)) |
|
137 |
+ (implicit nil) |
|
138 |
+ (style :any-sequence-style)) |
|
139 |
+ (sequence-start-event-initialize event anchor tag implicit style)) |
|
140 |
+ |
|
141 |
+(defun sequence-end-event (event) |
|
142 |
+ (sequence-end-event-initialize event)) |
|
143 |
+ |
|
144 |
+(defun mapping-start-event (event &key (anchor (null-pointer)) |
|
145 |
+ (tag (null-pointer)) |
|
146 |
+ (implicit nil) |
|
147 |
+ (style :any-mapping-style)) |
|
148 |
+ (mapping-start-event-initialize event anchor tag implicit style)) |
|
149 |
+ |
|
150 |
+(defun mapping-end-event (event) |
|
151 |
+ (mapping-end-event-initialize event)) |
|
152 |
+ |
|
153 |
+(defun scalar-event (event value length &key (anchor (null-pointer)) |
|
154 |
+ (tag (null-pointer)) |
|
155 |
+ (plain-implicit t) |
|
156 |
+ (quoted-implicit t) |
|
157 |
+ (style :plain-scalar-style)) |
|
158 |
+ (scalar-event-initialize event anchor tag value length |
|
159 |
+ plain-implicit quoted-implicit style)) |
|
160 |
+ |
|
161 |
+;;; Emitter macros and output functions |
|
162 |
+ |
|
163 |
+;;; When passing a foreign emitter object, it is also paired with a |
|
164 |
+;;; foreign event object. |
|
165 |
+ |
|
166 |
+(defun foreign-emitter (emitter) (car emitter)) |
|
167 |
+ |
|
168 |
+(defun foreign-event (emitter) (cdr emitter)) |
|
169 |
+ |
|
170 |
+(defmacro with-emitter-to-stream ((emitter-var output-stream) &rest body) |
|
171 |
+ (let ((foreign-emitter (gensym "EMITTER")) |
|
172 |
+ (foreign-event (gensym "EVENT"))) |
|
173 |
+ `(let* ((,foreign-emitter (allocate-emitter)) |
|
174 |
+ (,foreign-event (allocate-event)) |
|
175 |
+ (,emitter-var (cons ,foreign-emitter ,foreign-event)) |
|
176 |
+ (*write-handler-stream* ,output-stream)) |
|
177 |
+ (unwind-protect |
|
178 |
+ (progn |
|
179 |
+ (emitter-initialize ,foreign-emitter) |
|
180 |
+ (set-output ,foreign-emitter *write-handler-callback* (null-pointer)) |
|
181 |
+ ,@body) |
|
182 |
+ (libyaml.event:event-delete ,foreign-event) |
|
183 |
+ (libyaml.emitter:emitter-delete ,foreign-emitter) |
|
184 |
+ (foreign-free ,foreign-event) |
|
185 |
+ (foreign-free ,foreign-emitter))))) |
|
186 |
+ |
|
187 |
+(defmacro with-emitter-to-string ((emitter-var) &rest body) |
|
188 |
+ (let ((str (gensym "STR"))) |
|
189 |
+ `(with-output-to-string (,str) |
|
190 |
+ (with-emitter-to-stream (,emitter-var ,str) |
|
191 |
+ ,@body)))) |
|
192 |
+ |
|
193 |
+(defmacro emit-stream ((emitter &key (encoding :utf8-encoding)) &body body) |
|
194 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
195 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
196 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
197 |
+ `(let* ((,emitter-value ,emitter) |
|
198 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
199 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
200 |
+ (stream-start-event ,foreign-event :encoding ,encoding) |
|
201 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
202 |
+ ,@body |
|
203 |
+ (stream-end-event ,foreign-event) |
|
204 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
205 |
+ |
|
206 |
+(defmacro emit-document ((emitter &rest rest |
|
207 |
+ &key version-directive |
|
208 |
+ tag-directive-start |
|
209 |
+ tag-directive-end |
|
210 |
+ (implicit nil)) &body body) |
|
211 |
+ (declare (ignorable version-directive tag-directive-start |
|
212 |
+ tag-directive-end implicit)) |
|
213 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
214 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
215 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
216 |
+ `(let* ((,emitter-value ,emitter) |
|
217 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
218 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
219 |
+ (apply #'document-start-event ,foreign-event (list ,@rest)) |
|
220 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
221 |
+ ,@body |
|
222 |
+ (document-end-event ,foreign-event :implicit ,implicit) |
|
223 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
224 |
+ |
|
225 |
+(defmacro emit-mapping ((emitter &rest rest &key anchor tag implicit style) |
|
226 |
+ &body body) |
|
227 |
+ (declare (ignorable anchor tag implicit style)) |
|
228 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
229 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
230 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
231 |
+ `(let* ((,emitter-value ,emitter) |
|
232 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
233 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
234 |
+ (apply #'mapping-start-event ,foreign-event (list ,@rest)) |
|
235 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
236 |
+ ,@body |
|
237 |
+ (mapping-end-event ,foreign-event) |
|
238 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
239 |
+ |
|
240 |
+(defmacro emit-sequence ((emitter &rest rest &key anchor tag implicit style) |
|
241 |
+ &body body) |
|
242 |
+ (declare (ignorable anchor tag implicit style)) |
|
243 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
244 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
245 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
246 |
+ `(let* ((,emitter-value ,emitter) |
|
247 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
248 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
249 |
+ (apply #'sequence-start-event ,foreign-event (list ,@rest)) |
|
250 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
251 |
+ ,@body |
|
252 |
+ (sequence-end-event ,foreign-event) |
|
253 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
254 |
+ |
|
255 |
+(defun emit-scalar (emitter value &rest rest &key anchor tag |
|
256 |
+ plain-implicit |
|
257 |
+ quoted-implicit |
|
258 |
+ style) |
|
259 |
+ (declare (ignorable anchor tag plain-implicit quoted-implicit style)) |
|
260 |
+ (let ((printed-value (print-scalar value))) |
|
261 |
+ (apply #'scalar-event (foreign-event emitter) |
|
262 |
+ printed-value (length printed-value) rest) |
|
263 |
+ (libyaml.emitter:emit (foreign-emitter emitter) (foreign-event emitter)))) |
|
264 |
+ |
|
265 |
+(defgeneric print-scalar (scalar) |
|
266 |
+ (:documentation "Convert a scalar object into its printed representation")) |
|
267 |
+ |
|
268 |
+(defmethod print-scalar ((scalar (eql 't))) |
|
269 |
+ "true") |
|
270 |
+ |
|
271 |
+(defmethod print-scalar ((scalar (eql 'nil))) |
|
272 |
+ "false") |
|
273 |
+ |
|
274 |
+(defmethod print-scalar ((scalar symbol)) |
|
275 |
+ (symbol-name scalar)) |
|
276 |
+ |
|
277 |
+(defmethod print-scalar ((scalar string)) |
|
278 |
+ scalar) |
|
279 |
+ |
|
280 |
+(defmethod print-scalar ((scalar integer)) |
|
281 |
+ (princ-to-string scalar)) |
|
282 |
+ |
|
283 |
+(defmethod print-scalar ((scalar single-float)) |
|
284 |
+ (let ((*read-default-float-format* 'single-float)) |
|
285 |
+ (princ-to-string scalar))) |
|
286 |
+ |
|
287 |
+(defmethod print-scalar ((scalar double-float)) |
|
288 |
+ (let ((*read-default-float-format* 'double-float)) |
|
289 |
+ (princ-to-string scalar))) |
|
290 |
+ |
|
291 |
+(defgeneric emit-object (emitter obj) |
|
292 |
+ (:documentation "Emit YAML representation of obj")) |
|
293 |
+ |
|
294 |
+(defmethod emit-object (emitter (obj symbol)) |
|
295 |
+ (emit-scalar emitter obj)) |
|
296 |
+ |
|
297 |
+(defmethod emit-object (emitter (obj string)) |
|
298 |
+ (emit-scalar emitter obj)) |
|
299 |
+ |
|
300 |
+(defmethod emit-object (emitter (obj integer)) |
|
301 |
+ (emit-scalar emitter obj)) |
|
302 |
+ |
|
303 |
+(defmethod emit-object (emitter (obj float)) |
|
304 |
+ (emit-scalar emitter obj)) |
|
305 |
+ |
... | ... |
@@ -6,10 +6,44 @@ |
6 | 6 |
(:import-from :libyaml.macros |
7 | 7 |
:with-parser |
8 | 8 |
:with-event) |
9 |
- (:export :parse-string) |
|
9 |
+ (:export :parse-string |
|
10 |
+ :register-scalar-converter |
|
11 |
+ :register-sequence-converter |
|
12 |
+ :register-mapping-converter) |
|
10 | 13 |
(:documentation "The YAML parser.")) |
11 | 14 |
(in-package :yaml.parser) |
12 | 15 |
|
16 |
+(defvar +scalar-converters+ (make-hash-table :test #'equalp)) |
|
17 |
+(defvar +sequence-converters+ (make-hash-table :test #'equalp)) |
|
18 |
+(defvar +mapping-converters+ (make-hash-table :test #'equalp)) |
|
19 |
+ |
|
20 |
+(defun scalar-converter (tag) |
|
21 |
+ (gethash tag +scalar-converters+)) |
|
22 |
+ |
|
23 |
+(defun convert-scalar (string tag &optional (style :plain-scalar-stype)) |
|
24 |
+ (let ((converter (scalar-converter tag))) |
|
25 |
+ (if converter |
|
26 |
+ (funcall converter string) |
|
27 |
+ (yaml.scalar:parse-scalar string style)))) |
|
28 |
+ |
|
29 |
+(defun sequence-converter (tag) |
|
30 |
+ (gethash tag +sequence-converters+)) |
|
31 |
+ |
|
32 |
+(defun convert-sequence (list tag) |
|
33 |
+ (let ((converter (sequence-converter tag))) |
|
34 |
+ (if converter |
|
35 |
+ (funcall converter list) |
|
36 |
+ list))) |
|
37 |
+ |
|
38 |
+(defun mapping-converter (tag) |
|
39 |
+ (gethash tag +mapping-converters+)) |
|
40 |
+ |
|
41 |
+(defun convert-mapping (hashtable tag) |
|
42 |
+ (let ((converter (mapping-converter tag))) |
|
43 |
+ (if converter |
|
44 |
+ (funcall converter hashtable) |
|
45 |
+ hashtable))) |
|
46 |
+ |
|
13 | 47 |
;;; The parser |
14 | 48 |
|
15 | 49 |
(defun signal-reader-error (parser) |
... | ... |
@@ -94,31 +128,32 @@ |
94 | 128 |
t) |
95 | 129 |
|# |
96 | 130 |
;; Scalar |
97 |
- ((:scalar-event &key anchor tag value) |
|
98 |
- (declare (ignore anchor tag)) |
|
131 |
+ ((:scalar-event &key anchor tag value length plain-implicit quoted-implicit style) |
|
132 |
+ (declare (ignore anchor length plain-implicit quoted-implicit)) |
|
99 | 133 |
(setf (first contexts) |
100 | 134 |
(append (first contexts) |
101 |
- (list (yaml.scalar:parse-scalar value))))) |
|
135 |
+ (list (convert-scalar value tag style))))) |
|
102 | 136 |
;; Sequence start event |
103 |
- ((:sequence-start-event &key anchor tag) |
|
104 |
- (declare (ignore anchor tag)) |
|
105 |
- (push (list) contexts)) |
|
137 |
+ ((:sequence-start-event &key anchor tag implicit style) |
|
138 |
+ (declare (ignore anchor implicit style)) |
|
139 |
+ (push (list tag) contexts)) |
|
106 | 140 |
;; Mapping start event |
107 |
- ((:mapping-start-event &key anchor tag) |
|
108 |
- (declare (ignore anchor tag)) |
|
109 |
- (push (list) contexts)) |
|
141 |
+ ((:mapping-start-event &key anchor tag implicit style) |
|
142 |
+ (declare (ignore anchor implicit style)) |
|
143 |
+ (push (list tag) contexts)) |
|
110 | 144 |
;; End events |
111 | 145 |
((:sequence-end-event) |
112 |
- (let ((con (pop contexts))) |
|
146 |
+ (destructuring-bind (tag &rest seq) (pop contexts) |
|
113 | 147 |
(setf (first contexts) |
114 | 148 |
(append (first contexts) |
115 |
- (list con))))) |
|
149 |
+ (list (convert-sequence seq tag)))))) |
|
116 | 150 |
((:mapping-end-event) |
117 |
- (let ((con (pop contexts))) |
|
151 |
+ (destructuring-bind (tag &rest plist) (pop contexts) |
|
118 | 152 |
(setf (first contexts) |
119 | 153 |
(append (first contexts) |
120 |
- (list |
|
121 |
- (alexandria:plist-hash-table con :test #'equal)))))) |
|
154 |
+ (list (convert-mapping |
|
155 |
+ (alexandria:plist-hash-table plist :test #'equalp) |
|
156 |
+ tag)))))) |
|
122 | 157 |
;; Do nothing |
123 | 158 |
((t &rest rest) |
124 | 159 |
(declare (ignore rest))))) |
... | ... |
@@ -126,5 +161,14 @@ |
126 | 161 |
|
127 | 162 |
;;; The public interface |
128 | 163 |
|
164 |
+(defun register-scalar-converter (tag converter) |
|
165 |
+ (setf (gethash tag +scalar-converters+) converter)) |
|
166 |
+ |
|
167 |
+(defun register-sequence-converter (tag converter) |
|
168 |
+ (setf (gethash tag +sequence-converters+) converter)) |
|
169 |
+ |
|
170 |
+(defun register-mapping-converter (tag converter) |
|
171 |
+ (setf (gethash tag +mapping-converters+) converter)) |
|
172 |
+ |
|
129 | 173 |
(defun parse-string (yaml-string) |
130 | 174 |
(parse-tokens (parse-yaml yaml-string))) |
... | ... |
@@ -15,6 +15,9 @@ |
15 | 15 |
|
16 | 16 |
;;; Regular expressions or lists of names |
17 | 17 |
|
18 |
+(defparameter +quoted-scalar-styles+ |
|
19 |
+ (list :single-quoted-scalar-style :double-quoted-scalar-style)) |
|
20 |
+ |
|
18 | 21 |
(defparameter +null-names+ |
19 | 22 |
(list "null" "Null" "NULL" "~")) |
20 | 23 |
|
... | ... |
@@ -48,9 +51,12 @@ |
48 | 51 |
|
49 | 52 |
;;; The actual parser |
50 | 53 |
|
51 |
-(defun parse-scalar (string) |
|
54 |
+(defun parse-scalar (string &optional (style :plain-scalar-style)) |
|
52 | 55 |
"Parse a YAML scalar string into a Lisp scalar value." |
53 | 56 |
(cond |
57 |
+ ;; Quoted string |
|
58 |
+ ((member style +quoted-scalar-styles+) |
|
59 |
+ string) |
|
54 | 60 |
;; Null |
55 | 61 |
((member string +null-names+ :test #'equal) |
56 | 62 |
+null+) |
... | ... |
@@ -2,9 +2,21 @@ |
2 | 2 |
(defpackage cl-yaml |
3 | 3 |
(:use :cl) |
4 | 4 |
(:nicknames :yaml) |
5 |
+ (:import-from :yaml.parser |
|
6 |
+ :register-scalar-converter |
|
7 |
+ :register-sequence-converter |
|
8 |
+ :register-mapping-converter) |
|
9 |
+ (:import-from :yaml.emitter |
|
10 |
+ :emit-object |
|
11 |
+ :print-scalar) |
|
5 | 12 |
(:export :parse |
6 | 13 |
:emit |
7 |
- :emit-to-string) |
|
14 |
+ :emit-to-string |
|
15 |
+ :register-scalar-converter |
|
16 |
+ :register-sequence-converter |
|
17 |
+ :register-mapping-converter |
|
18 |
+ :emit-object |
|
19 |
+ :print-scalar) |
|
8 | 20 |
(:documentation "The main YAML interface.")) |
9 | 21 |
(in-package :yaml) |
10 | 22 |
|
... | ... |
@@ -10,5 +10,7 @@ |
10 | 10 |
(run! 'cl-yaml-test.scalar:scalar) |
11 | 11 |
(run! 'cl-yaml-test.parser:parser) |
12 | 12 |
(run! 'cl-yaml-test.emitter:emitter) |
13 |
+ (run! 'cl-yaml-test.emitter2:emitter2) |
|
14 |
+ (run! 'cl-yaml-test.round-trip:round-trip) |
|
13 | 15 |
(run! 'cl-yaml-test.spec:spec) |
14 | 16 |
(run! 'cl-yaml-test.bench:bench)) |
15 | 17 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,79 @@ |
1 |
+(in-package :cl-user) |
|
2 |
+(defpackage cl-yaml-test.emitter2 |
|
3 |
+ (:use :cl :fiveam) |
|
4 |
+ (:export :emitter2) |
|
5 |
+ (:documentation "Emitter tests - libyaml-based emitter.")) |
|
6 |
+(in-package :cl-yaml-test.emitter2) |
|
7 |
+ |
|
8 |
+(defmacro define-test-cases ((name) &rest pairs) |
|
9 |
+ `(test ,name |
|
10 |
+ ,@(loop for (form string) in pairs collecting |
|
11 |
+ `(is (equal (yaml.emitter:with-emitter-to-string (emitter) |
|
12 |
+ (yaml.emitter:emit-stream (emitter) |
|
13 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
14 |
+ (yaml.emitter:emit-scalar emitter ,form)))) |
|
15 |
+ ,(format nil "~a~%...~%" string)))))) ;; Document end marker |
|
16 |
+ ;; is libyaml behavior |
|
17 |
+ |
|
18 |
+(defun test-emit-sequence (sequence style) |
|
19 |
+ (yaml.emitter:with-emitter-to-string (emitter) |
|
20 |
+ (yaml.emitter:emit-stream (emitter) |
|
21 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
22 |
+ (yaml.emitter:emit-sequence (emitter :style style) |
|
23 |
+ (mapcar (lambda (element) (yaml.emitter:emit-scalar emitter element)) |
|
24 |
+ sequence)))))) |
|
25 |
+ |
|
26 |
+(defun test-emit-mapping (mapping style) |
|
27 |
+ (yaml.emitter:with-emitter-to-string (emitter) |
|
28 |
+ (yaml.emitter:emit-stream (emitter) |
|
29 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
30 |
+ (yaml.emitter:emit-mapping (emitter :style style) |
|
31 |
+ (mapcar (lambda (pair) |
|
32 |
+ (yaml.emitter:emit-scalar emitter (car pair)) |
|
33 |
+ (yaml.emitter:emit-scalar emitter (cdr pair))) |
|
34 |
+ mapping)))))) |
|
35 |
+ |
|
36 |
+(def-suite emitter2 |
|
37 |
+ :description "YAML libyaml-based emitter tests.") |
|
38 |
+(in-suite emitter2) |
|
39 |
+ |
|
40 |
+(define-test-cases (boolean) |
|
41 |
+ (t "true") |
|
42 |
+ (nil "false")) |
|
43 |
+ |
|
44 |
+(define-test-cases (integers) |
|
45 |
+ (1 "1") |
|
46 |
+ (123 "123") |
|
47 |
+ (+123 "123") |
|
48 |
+ (-123 "-123")) |
|
49 |
+ |
|
50 |
+(define-test-cases (floats) |
|
51 |
+ (1.23 "1.23") |
|
52 |
+ (6.62607e-34 "6.62607e-34")) |
|
53 |
+ |
|
54 |
+(test flow-sequence |
|
55 |
+ (is (equal (test-emit-sequence '(1 "a" 3.14f0 3.14d0) :flow-sequence-style) |
|
56 |
+ "[1, a, 3.14, 3.14] |
|
57 |
+"))) |
|
58 |
+ |
|
59 |
+(test block-sequence |
|
60 |
+ (is (equal (test-emit-sequence '(1 "a" 3.14f0 3.14d0) :block-sequence-style) |
|
61 |
+ "- 1 |
|
62 |
+- a |
|
63 |
+- 3.14 |
|
64 |
+- 3.14 |
|
65 |
+"))) |
|
66 |
+ |
|
67 |
+(test flow-mapping |
|
68 |
+ (is (equal (test-emit-mapping '(("integer" . 1) ("string" . "test") ("bool" . nil)) |
|
69 |
+ :flow-mapping-style) |
|
70 |
+ "{integer: 1, string: test, bool: false} |
|
71 |
+"))) |
|
72 |
+ |
|
73 |
+(test block-mapping |
|
74 |
+ (is (equal (test-emit-mapping '(("integer" . 1) ("string" . "test") ("bool" . nil)) |
|
75 |
+ :block-mapping-style) |
|
76 |
+ "integer: 1 |
|
77 |
+string: test |
|
78 |
+bool: false |
|
79 |
+"))) |
0 | 80 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,165 @@ |
1 |
+(in-package :cl-user) |
|
2 |
+(defpackage cl-yaml-test.round-trip |
|
3 |
+ (:use :cl :fiveam) |
|
4 |
+ (:export :round-trip) |
|
5 |
+ (:documentation "Round-trip Emitter/Parser tests.")) |
|
6 |
+(in-package :cl-yaml-test.round-trip) |
|
7 |
+ |
|
8 |
+(def-suite round-trip |
|
9 |
+ :description "YAML emitter/parser tests.") |
|
10 |
+(in-suite round-trip) |
|
11 |
+ |
|
12 |
+(defclass invoice () |
|
13 |
+ ((number :initarg :number :accessor invoice-number) |
|
14 |
+ (date :initarg :date :accessor invoice-date) |
|
15 |
+ (bill-to :initarg :bill-to :accessor invoice-bill-to) |
|
16 |
+ ;; skip ship-to until aliases and anchors are more developed) |
|
17 |
+ (product :initarg :product :accessor invoice-product) |
|
18 |
+ (tax :initarg :tax :accessor invoice-tax) |
|
19 |
+ (total :initarg :total :accessor invoice-total) |
|
20 |
+ (comments :initarg :comments :accessor invoice-comments))) |
|
21 |
+ |
|
22 |
+(defclass address () |
|
23 |
+ ((lines :initarg :lines :accessor address-lines) |
|
24 |
+ (city :initarg :city :accessor address-city) |
|
25 |
+ (state :initarg :state :accessor address-state) |
|
26 |
+ (postal :initarg :postal :accessor address-postal))) |
|
27 |
+ |
|
28 |
+(defclass product-order () |
|
29 |
+ ((sku :initarg :sku :accessor product-order-sku) |
|
30 |
+ (quantity :initarg :quantity :accessor product-order-quantity) |
|
31 |
+ (description :initarg :description :accessor product-order-description) |
|
32 |
+ (price :initarg :price :accessor product-order-price))) |
|
33 |
+ |
|
34 |
+(defun make-test-invoice () |
|
35 |
+ (make-instance 'invoice |
|
36 |
+ :number 34843 |
|
37 |
+ :date "2001-01-23" |
|
38 |
+ :bill-to (list "Chris" "Dumars" |
|
39 |
+ (make-instance 'address |
|
40 |
+ :lines "458 Walkman Dr. |
|
41 |
+Suite #292" |
|
42 |
+ :city "Royal Oak" |
|
43 |
+ :state "MI" |
|
44 |
+ :postal "48046")) |
|
45 |
+ :product (list (make-instance 'product-order |
|
46 |
+ :sku "BL394D" |
|
47 |
+ :quantity 4 |
|
48 |
+ :description "Basketball" |
|
49 |
+ :price 450.00) |
|
50 |
+ (make-instance 'product-order |
|
51 |
+ :sku "BL4438H" |
|
52 |
+ :quantity 1 |
|
53 |
+ :description "Super Hoop" |
|
54 |
+ :price 2392.00)) |
|
55 |
+ :tax 251.42 |
|
56 |
+ :total 4443.52 |
|
57 |
+ :comments "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.")) |
|
58 |
+ |
|
59 |
+;;; Methods to emit CLOS objects |
|
60 |
+ |
|
61 |
+(defmethod yaml.emitter:emit-object (emitter (obj address)) |
|
62 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style) |
|
63 |
+ (yaml.emitter:emit-scalar emitter "lines") |
|
64 |
+ (yaml.emitter:emit-scalar emitter (address-lines obj) |
|
65 |
+ :style :literal-scalar-style) |
|
66 |
+ (yaml.emitter:emit-scalar emitter "city") |
|
67 |
+ (yaml.emitter:emit-scalar emitter (address-city obj)) |
|
68 |
+ (yaml.emitter:emit-scalar emitter "state") |
|
69 |
+ (yaml.emitter:emit-scalar emitter (address-state obj)) |
|
70 |
+ (yaml.emitter:emit-scalar emitter "postal") |
|
71 |
+ (yaml.emitter:emit-scalar emitter (address-postal obj)))) |
|
72 |
+ |
|
73 |
+(defmethod yaml.emitter:emit-object (emitter (obj product-order)) |
|
74 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style) |
|
75 |
+ (yaml.emitter:emit-scalar emitter "sku") |
|
76 |
+ (yaml.emitter:emit-scalar emitter (product-order-sku obj)) |
|
77 |
+ (yaml.emitter:emit-scalar emitter "quantity") |
|
78 |
+ (yaml.emitter:emit-scalar emitter (product-order-quantity obj)) |
|
79 |
+ (yaml.emitter:emit-scalar emitter "description") |
|
80 |
+ (yaml.emitter:emit-scalar emitter (product-order-description obj)) |
|
81 |
+ (yaml.emitter:emit-scalar emitter "price") |
|
82 |
+ (yaml.emitter:emit-scalar emitter (product-order-price obj)))) |
|
83 |
+ |
|
84 |
+(defmethod yaml.emitter:emit-object (emitter (obj invoice)) |
|
85 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style |
|
86 |
+ :tag "clarkevans.com,2002:invoice") |
|
87 |
+ (yaml.emitter:emit-scalar emitter "invoice") |
|
88 |
+ (yaml.emitter:emit-scalar emitter (invoice-number obj)) |
|
89 |
+ (yaml.emitter:emit-scalar emitter "date") |
|
90 |
+ (yaml.emitter:emit-scalar emitter (invoice-date obj)) |
|
91 |
+ (yaml.emitter:emit-scalar emitter "bill-to") |
|
92 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style) |
|
93 |
+ (yaml.emitter:emit-scalar emitter "given") |
|
94 |
+ (yaml.emitter:emit-scalar emitter (first (invoice-bill-to obj))) |
|
95 |
+ (yaml.emitter:emit-scalar emitter "family") |
|
96 |
+ (yaml.emitter:emit-scalar emitter (second (invoice-bill-to obj))) |
|
97 |
+ (yaml.emitter:emit-scalar emitter "address") |
|
98 |
+ (yaml.emitter:emit-object emitter (third (invoice-bill-to obj)))) |
|
99 |
+ (yaml.emitter:emit-scalar emitter "product") |
|
100 |
+ (yaml.emitter:emit-sequence (emitter :style :block-sequence-style) |
|
101 |
+ (dolist (product (invoice-product obj)) |
|
102 |
+ (yaml.emitter:emit-object emitter product))) |
|
103 |
+ (yaml.emitter:emit-scalar emitter "tax") |
|
104 |
+ (yaml.emitter:emit-scalar emitter (invoice-tax obj)) |
|
105 |
+ (yaml.emitter:emit-scalar emitter "total") |
|
106 |
+ (yaml.emitter:emit-scalar emitter (invoice-total obj)) |
|
107 |
+ (yaml.emitter:emit-scalar emitter "comments") |
|
108 |
+ (yaml.emitter:emit-scalar emitter (invoice-comments obj) |
|
109 |
+ :plain-implicit t |
|
110 |
+ :quoted-implicit t |
|
111 |
+ :style :plain-scalar-style))) |
|
112 |
+ |
|
113 |
+;;; Functions to construct objects from YAML |
|
114 |
+ |
|
115 |
+(defun construct-address (mapping) |
|
116 |
+ (make-instance 'address |
|
117 |
+ :lines (gethash "lines" mapping) |
|
118 |
+ :city (gethash "city" mapping) |
|
119 |
+ :state (gethash "state" mapping) |
|
120 |
+ :postal (gethash "postal" mapping))) |
|
121 |
+ |
|
122 |
+(defun construct-product-order (mapping) |
|
123 |
+ (make-instance 'product-order |
|
124 |
+ :sku (gethash "sku" mapping) |
|
125 |
+ :quantity (gethash "quantity" mapping) |
|
126 |
+ :description (gethash "description" mapping) |
|
127 |
+ :price (gethash "price" mapping))) |
|
128 |
+ |
|
129 |
+(defun construct-invoice (mapping) |
|
130 |
+ (let ((bill-to-mapping (gethash "bill-to" mapping))) |
|
131 |
+ (make-instance 'invoice |
|
132 |
+ :number (gethash "invoice" mapping) |
|
133 |
+ :date (gethash "date" mapping) |
|
134 |
+ :bill-to (list (gethash "given" bill-to-mapping) |
|
135 |
+ (gethash "family" bill-to-mapping) |
|
136 |
+ (construct-address (gethash "address" bill-to-mapping))) |
|
137 |
+ :product (mapcar #'construct-product-order |
|
138 |
+ (gethash "product" mapping)) |
|
139 |
+ :tax (gethash "tax" mapping) |
|
140 |
+ :total (gethash "total" mapping) |
|
141 |
+ :comments (gethash "comments" mapping)))) |
|
142 |
+ |
|
143 |
+;;; Register a construction function with a YAML tag |
|
144 |
+ |
|
145 |
+(eval-when (:load-toplevel) |
|
146 |
+ (yaml.parser:register-mapping-converter "clarkevans.com,2002:invoice" |
|
147 |
+ #'construct-invoice)) |
|
148 |
+ |
|
149 |
+;;; Helper |
|
150 |
+ |
|
151 |
+(defun test-emit (obj) |
|
152 |
+ (yaml.emitter:with-emitter-to-string (emitter) |
|
153 |
+ (yaml.emitter:emit-stream (emitter) |
|
154 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
155 |
+ (yaml.emitter:emit-object emitter obj))))) |
|
156 |
+ |
|
157 |
+;;; Round-trip test |
|
158 |
+ |
|
159 |
+(test invoice |
|
160 |
+ (let* ((invoice-a (make-test-invoice)) |
|
161 |
+ (yaml-a (test-emit invoice-a)) |
|
162 |
+ (invoice-b (yaml:parse yaml-a)) |
|
163 |
+ (yaml-b (test-emit invoice-b))) |
|
164 |
+ (is (string= yaml-a yaml-b)))) |
|
165 |
+ |