File size: 22,404 Bytes
43203b4 | 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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 | ;;;; -*- Mode: LISP; Syntax: ANSI-COMMON-LISP; Base: 10; Package: JSON -*-
(in-package :json)
;;; Custom variables
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *custom-vars* nil)
(defmacro with-shadowed-custom-vars (&body body)
`(let ,(loop for (var) in *custom-vars*
collect `(,var (if (boundp ',var) ,var)))
,@body))
(defmacro set-custom-vars (&rest key-args)
`(setq
,@(loop for (supplied-key value) on key-args by #'cddr
append (loop for (var . var-key) in *custom-vars*
thereis (if (eql var-key supplied-key)
(list var value))))))
)
(defmacro define-custom-var ((key name) &rest other-args)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(progn (pushnew '(,name . ,key) *custom-vars* :test #'equal)
(defvar ,name ,@other-args))))
;;; Token reader
(define-condition json-syntax-error (simple-error stream-error)
((stream-file-position :reader stream-error-stream-file-position
:initarg :stream-file-position))
(:report
(lambda (condition stream)
(format stream "~? [in ~S~@[ at position ~D~]]"
(simple-condition-format-control condition)
(simple-condition-format-arguments condition)
(stream-error-stream condition)
(stream-error-stream-file-position condition)))))
(defun json-syntax-error (stream format-control &rest format-arguments)
(error 'json-syntax-error
:stream stream
:stream-file-position (file-position stream)
:format-control format-control
:format-arguments format-arguments))
(defun read-json-token (stream)
(let ((c (peek-char t stream)))
(case c
((#\{ #\[ #\] #\} #\" #\: #\,)
(values :punct (read-char stream)))
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\-)
(read-json-number-token stream))
(t (if (alpha-char-p c)
(read-json-symbol-token stream)
(json-syntax-error stream "Invalid char on JSON input: `~C'"
c))))))
(defun read-json-number-token (stream)
(let ((int (make-array 32 :adjustable t :fill-pointer 0
:element-type 'character))
(frac (make-array 32 :adjustable t :fill-pointer 0
:element-type 'character))
(exp (make-array 32 :adjustable t :fill-pointer 0
:element-type 'character))
(type :integer)
c)
(flet ((safe-read-char (stream)
(handler-case (read-char stream)
(end-of-file ()
(return-from read-json-number-token
(values type (concatenate 'string int frac exp)))))))
(macrolet
((read-digits (part)
(let ((error-fmt
(format nil "Invalid JSON number: no ~(~A~) digits"
part)))
`(loop while (char<= #\0 c #\9)
with count = 0
do (vector-push-extend c ,part 32)
(setq c (safe-read-char stream))
(incf count)
finally
(if (zerop count)
(json-syntax-error stream ,error-fmt))))))
(setq c (read-char stream))
(when (char= c #\-)
(vector-push c int)
(setq c (read-char stream)))
(if (char= c #\0)
(progn
(vector-push c int)
(setq c (safe-read-char stream)))
(read-digits int))
(when (char= c #\.)
(vector-push c frac)
(setq c (read-char stream)
type :real)
(read-digits frac))
(when (char-equal c #\e)
(vector-push c exp)
(setq c (read-char stream)
type :real)
(when (or (char= c #\+) (char= c #\-))
(vector-push c exp)
(setq c (read-char stream)))
(read-digits exp))
(unread-char c stream)
(values type (concatenate 'string int frac exp))))))
(defun read-json-symbol-token (stream)
(let ((symbol (make-array 8 :adjustable t :fill-pointer 0
:element-type 'character)))
(loop for c = (read-char stream nil)
while (and c (alpha-char-p c))
do (vector-push-extend c symbol 32)
finally (if c (unread-char c stream)))
(setq symbol (coerce symbol 'string))
(if (or (string= symbol "true")
(string= symbol "false")
(string= symbol "null"))
(values :boolean symbol)
(json-syntax-error stream "Invalid JSON symbol: ~A" symbol))))
(define-condition no-char-for-code (error)
((offending-code :initarg :code :reader offending-code))
(:report (lambda (condition stream)
(format stream "No character corresponds to code #x~4,'0X."
(offending-code condition)))))
(defun read-json-string-char (stream)
(let ((esc-error-fmt "Invalid JSON character escape sequence: ~A~A"))
(case (peek-char nil stream)
(#\" (read-char stream)
nil) ; End of string
(#\\ (read-char stream)
(let ((c (read-char stream)))
(case c
((#\" #\\ #\/) c)
(#\r #\Return)
(#\n #\Linefeed)
(#\t #\Tab)
(#\b #\Backspace)
(#\f #\)
(#\u (let ((hex (make-string 4)))
(dotimes (i 4)
(setf (aref hex i) (read-char stream)))
(let ((c (handler-case (parse-integer hex :radix 16)
(parse-error ()
(json-syntax-error stream esc-error-fmt
"\\u" hex)))))
(if (< c 256)
(code-char c)
(restart-case (error 'no-char-for-code :code c)
(substitute-char (char)
:report "Substitute another char."
:interactive
(lambda ()
(format *query-io* "Char: ")
(list (read-char *query-io*)))
char)
(pass-code ()
:report "Pass the code to char handler."
c))))))
(t (json-syntax-error stream esc-error-fmt "\\" c)))))
(t (read-char stream)))))
;;; The decoder base
(define-custom-var (:integer *integer-handler*))
(define-custom-var (:real *real-handler*))
(define-custom-var (:boolean *boolean-handler*))
(define-custom-var (:beginning-of-string *beginning-of-string-handler*))
(define-custom-var (:string-char *string-char-handler*))
(define-custom-var (:end-of-string *end-of-string-handler*))
(define-custom-var (:beginning-of-array *beginning-of-array-handler*))
(define-custom-var (:array-element *array-element-handler*))
(define-custom-var (:end-of-array *end-of-array-handler*))
(define-custom-var (:beginning-of-object *beginning-of-object-handler*))
(define-custom-var (:object-key *object-key-handler*))
(define-custom-var (:object-value *object-value-handler*))
(define-custom-var (:end-of-object *end-of-object-handler*))
(defun decode-json (stream &optional parent-handler-state)
(multiple-value-bind (type token) (read-json-token stream)
(dispatch-on-token type token parent-handler-state stream)))
(defun dispatch-on-token (type token parent-handler-state stream)
(ecase type
(:punct
(case token
(#\" (decode-json-string stream parent-handler-state))
(#\[ (decode-json-array stream parent-handler-state))
(#\{ (decode-json-object stream parent-handler-state))
(t (json-syntax-error stream
"Token out of place on JSON input: `~C'"
token))))
(:integer
(funcall *integer-handler* parent-handler-state token))
(:real
(funcall *real-handler* parent-handler-state token))
(:boolean
(funcall *boolean-handler* parent-handler-state token))))
(defun decode-json-array (stream &optional parent-handler-state)
(let ((handler-state
(funcall *beginning-of-array-handler* parent-handler-state)))
(multiple-value-bind (type token) (read-json-token stream)
(if (and (eql type :punct) (char= token #\]))
(return-from decode-json-array
(funcall *end-of-array-handler* handler-state))
(setq handler-state
(funcall *array-element-handler* handler-state
(dispatch-on-token type token handler-state
stream)))))
(loop
(multiple-value-bind (type token) (read-json-token stream)
(if (eql type :punct)
(case token
(#\] (return-from decode-json-array
(funcall *end-of-array-handler* handler-state)))
(#\, (setq token nil))))
(if token
(json-syntax-error
stream
"Token out of place in array on JSON input: `~A'"
token)))
(setq handler-state
(funcall *array-element-handler* handler-state
(decode-json stream handler-state))))))
(defun decode-json-object (stream &optional parent-handler-state)
(loop with handler-state =
(funcall *beginning-of-object-handler* parent-handler-state)
with key = nil
for first-time-p = t then nil
do (multiple-value-bind (type token) (read-json-token stream)
(if (eql type :punct)
(case token
(#\}
(if first-time-p
(return-from decode-json-object
(funcall *end-of-object-handler* handler-state))))
(#\"
(setq key (decode-json-string stream handler-state t)))))
(if key
(setq handler-state
(funcall *object-key-handler* handler-state key))
(json-syntax-error
stream
"Expected a key string in object on JSON input ~
but found `~A'"
token)))
(multiple-value-bind (type token) (read-json-token stream)
(unless (and (eql type :punct) (char= token #\:))
(json-syntax-error
stream
"Expected a `:' separator in object on JSON input ~
but found `~A'"
token)))
(setq handler-state
(funcall *object-value-handler* handler-state
(decode-json stream handler-state)))
(multiple-value-bind (type token) (read-json-token stream)
(if (eql type :punct)
(case token
(#\} (return-from decode-json-object
(funcall *end-of-object-handler* handler-state)))
(#\, (setq key nil))))
(if key
(json-syntax-error
stream
"Expected a `,' separator or `}' in object on JSON input ~
but found `~A'"
token)))))
(defun decode-json-string (stream &optional parent-handler-state
as-object-key)
(loop with handler-state = (funcall *beginning-of-string-handler*
parent-handler-state as-object-key)
for c = (read-json-string-char stream)
while c
do (setq handler-state
(funcall *string-char-handler* handler-state c))
finally (return
(funcall *end-of-string-handler* handler-state))))
;;; Name translation
(defun camel-case-split (string)
(let ((length (length string)))
(macrolet ((shift-part (e new-cat &optional subst-cat)
`(prog1 (if b (cons ,(or subst-cat 'cat)
(subseq string b ,e)))
(setq b ,e cat ,new-cat))))
(loop for i from 0 to length
with cat = nil and b = nil
if (= i length)
if (shift-part i nil) collect it end
else if (let ((c (aref string i)))
(cond
((upper-case-p c)
(case cat
((:upper-1 :upper) (setq cat :upper) nil)
(t (shift-part i :upper-1))))
((lower-case-p c)
(case cat
(:upper-1 (setq cat :mixed) nil)
(:upper (let ((subst-cat
(if (> (- i b) 2) :upper :upper-1)))
(shift-part (1- i) :mixed subst-cat)))
((:numeric :punct nil) (shift-part i :lower))))
((digit-char-p c)
(if (not (eql cat :numeric))
(shift-part i :numeric)))
(t (shift-part i :punct))))
collect it))))
(defun camel-case-transform-all-caps (parts
&optional cat-before from-numeric)
(if (endp parts)
(cond (from-numeric (throw 'all-caps nil))
((eql cat-before :punct) nil)
(t '("+")))
(destructuring-bind ((cat . part) . rest) parts
(case cat
((:lower :mixed) (throw 'all-caps nil))
(:punct
(let ((transformed (if (string= part "_") "-" part)))
(if (or from-numeric (eql cat-before :punct))
(cons transformed (camel-case-transform-all-caps rest cat))
(let ((transformed-rest
(catch 'all-caps
(camel-case-transform-all-caps rest cat))))
(if transformed-rest
(cons transformed transformed-rest)
(list* "+"
(if (string= part "_") "--" part)
(camel-case-transform rest cat)))))))
((:upper :upper1)
(cons part (camel-case-transform-all-caps rest cat nil)))
(t (cons part (camel-case-transform-all-caps
rest cat from-numeric)))))))
(defun camel-case-transform (parts &optional (cat-before :punct))
(if (endp parts)
'("")
(destructuring-bind ((cat . part) . rest) parts
(case cat
(:upper
(if (eql cat-before :punct)
(let ((transformed-rest
(catch 'all-caps
(camel-case-transform-all-caps rest cat))))
(if transformed-rest
(list* "+" part transformed-rest)
(list* "+" part "+" (camel-case-transform rest cat))))
(list* "-+" part "+" (camel-case-transform rest cat))))
(:upper-1
(case cat-before
(:punct
(let ((transformed-rest
(catch 'all-caps
(camel-case-transform-all-caps rest cat))))
(if transformed-rest
(list* "+" part transformed-rest)
(list* "*" part (camel-case-transform rest cat)))))
(:numeric (list* "-*" part (camel-case-transform rest cat)))
(t (list* "-" part (camel-case-transform rest cat)))))
(:numeric
(case cat-before
(:punct
(let ((transformed-rest
(catch 'all-caps
(camel-case-transform-all-caps rest cat t))))
(if transformed-rest
(list* "+" part transformed-rest)
(cons part (camel-case-transform rest cat)))))
(t (list* "-" part (camel-case-transform rest cat)))))
(:mixed
(list* (case cat-before (:punct "*") (:numeric "-*") (t "-"))
(string-upcase part)
(camel-case-transform rest cat)))
(:lower
(list* (if (eql cat-before :punct) "" "-")
(string-upcase part)
(camel-case-transform rest cat)))
(:punct
(cons (if (string= part "_") "--" part)
(camel-case-transform rest cat)))))))
(defun camel-case-to-lisp (string)
(apply #'concatenate 'string
(camel-case-transform (camel-case-split string))))
(define-custom-var (:symbols-package *json-symbols-package*)
(find-package 'keyword))
(defun json-intern (string)
(intern string *json-symbols-package*))
;;; The list semantics
(define-custom-var (:array-type *json-array-type*) 'vector)
(defun parse-number (parent-handler-state token)
(declare (ignore parent-handler-state))
;; We can be reasonably sure that nothing but well-formed (both in
;; JSON and Lisp sense) number literals gets to this point.
(read-from-string token))
(defun json-boolean-to-lisp (parent-handler-state token)
(declare (ignore parent-handler-state))
(string= token "true"))
(defun make-accumulator (parent-handler-state)
(declare (ignore parent-handler-state))
(let ((head (cons nil nil)))
(cons head head)))
(defun accumulator-add (accumulator element)
(destructuring-bind (head . last) accumulator
(cons head (setf (cdr last) (cons element nil)))))
(defun accumulator-add-key (accumulator key)
(destructuring-bind (head . last) accumulator
(cons head
(let ((key (json-intern (camel-case-to-lisp key))))
(setf (cdr last) (cons (cons key nil) nil))))))
(defun accumulator-add-value (accumulator value)
(destructuring-bind (head . last) accumulator
(declare (ignore head))
(setf (cdar last) value)
accumulator))
(defun accumulator-get-sequence (accumulator)
(coerce (cdar accumulator) *json-array-type*))
(defun accumulator-get (accumulator)
(cdar accumulator))
(defun make-vector-accumulator (parent-accumulator &optional as-object-key)
(declare (ignore parent-accumulator as-object-key))
(make-array 32 :adjustable t :fill-pointer 0))
(defun vector-accumulator-add (accumulator element)
(vector-push-extend element accumulator (fill-pointer accumulator))
accumulator)
(defun vector-accumulator-get-sequence (accumulator)
(coerce accumulator *json-array-type*))
(defun vector-accumulator-get-string (accumulator)
(coerce accumulator 'string))
(defun set-decoder-simple-list-semantics ()
(set-custom-vars
:integer #'parse-number
:real #'parse-number
:boolean #'json-boolean-to-lisp
:beginning-of-array #'make-accumulator
:array-element #'accumulator-add
:end-of-array #'accumulator-get-sequence
:beginning-of-object #'make-accumulator
:object-key #'accumulator-add-key
:object-value #'accumulator-add-value
:end-of-object #'accumulator-get
:beginning-of-string #'make-vector-accumulator
:string-char #'vector-accumulator-add
:end-of-string #'vector-accumulator-get-string))
(defmacro with-decoder-simple-list-semantics (&body body)
`(with-shadowed-custom-vars
(set-decoder-simple-list-semantics)
,@body))
;;; The CLOS semantics
(defvar *prototype-prototype*
(make-instance 'prototype
:lisp-class 'prototype
:lisp-package :json))
(defun make-accumulator-with-prototype (parent-handler-state)
(if (and (consp parent-handler-state)
(consp (car parent-handler-state))
(eql (caar parent-handler-state) t))
(let ((head (cons *prototype-prototype* nil)))
(cons head head))
(make-accumulator parent-handler-state)))
(defun accumulator-add-key-or-set-prototype (accumulator key)
(destructuring-bind (head . last) accumulator
(let ((key (camel-case-to-lisp key)))
(if (and (not (car head))
*prototype-name*
(string= key (symbol-name *prototype-name*)))
(progn (setf (car head) t)
accumulator)
(cons head
(setf (cdr last) (cons (cons key nil) nil)))))))
(defun accumulator-add-value-or-set-prototype (accumulator value)
(destructuring-bind (head . last) accumulator
(declare (ignore last))
(if (and (consp head) (eql (car head) t))
(progn
(assert (typep value 'prototype) (value)
"Invalid prototype: ~S. Want to substitute something else?"
value)
(setf (car head) value)
accumulator)
(accumulator-add-value accumulator value))))
(defun accumulator-get-object (accumulator)
(destructuring-bind (head . last) accumulator
(declare (ignore last))
(flet ((as-symbol (value)
(if (stringp value)
(json-intern (camel-case-to-lisp value))
value))
(intern-keys (bindings)
(loop for (key . value) in bindings
collect (cons (json-intern key) value))))
(if (typep (car head) 'prototype)
(with-slots (lisp-class lisp-superclasses lisp-package)
(car head)
(let* ((*json-symbols-package*
(or (find-package (as-symbol lisp-package))
*json-symbols-package*))
(class (as-symbol lisp-class))
(superclasses (mapcar #'as-symbol lisp-superclasses)))
(make-object (intern-keys (cdr head)) class
:superclasses superclasses)))
(make-object (intern-keys (cdr head)) nil)))))
(defun set-decoder-simple-clos-semantics ()
(set-custom-vars
:integer #'parse-number
:real #'parse-number
:boolean #'json-boolean-to-lisp
:beginning-of-array #'make-vector-accumulator
:array-element #'vector-accumulator-add
:end-of-array #'vector-accumulator-get-sequence
:beginning-of-object #'make-accumulator-with-prototype
:object-key #'accumulator-add-key-or-set-prototype
:object-value #'accumulator-add-value-or-set-prototype
:end-of-object #'accumulator-get-object
:beginning-of-string #'make-vector-accumulator
:string-char #'vector-accumulator-add
:end-of-string #'vector-accumulator-get-string))
(defmacro with-decoder-simple-clos-semantics (&body body)
`(with-shadowed-custom-vars
(set-decoder-simple-clos-semantics)
,@body))
|