[bknr-cvs] hans changed trunk/projects/quickhoney/src/
BKNR Commits
bknr at bknr.net
Tue Nov 11 13:27:30 UTC 2008
Revision: 4033
Author: hans
URL: http://bknr.net/trac/changeset/4033
add simple json parser
A trunk/projects/quickhoney/src/json-parser.lisp
U trunk/projects/quickhoney/src/json.lisp
U trunk/projects/quickhoney/src/packages.lisp
Added: trunk/projects/quickhoney/src/json-parser.lisp
===================================================================
--- trunk/projects/quickhoney/src/json-parser.lisp (rev 0)
+++ trunk/projects/quickhoney/src/json-parser.lisp 2008-11-11 13:27:30 UTC (rev 4033)
@@ -0,0 +1,145 @@
+(in-package :json-parser)
+
+(defconstant +default-string-length+ 20
+ "Default length of strings that are created while reading json input.")
+
+(defvar *parse-object-key-fn* #'identity
+ "Function to call to convert a key string in a JSON array to a key in the CL hash produced.")
+
+(defun make-adjustable-string ()
+ "Return an adjustable empty string, usable as a buffer for parsing strings and numbers."
+ (make-array +default-string-length+
+ :adjustable t :fill-pointer 0 :element-type 'character))
+
+(defun parse-number (input)
+ ;; would be
+ ;; (cl-ppcre:scan-to-strings "^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+|)(?:[eE][-+]?[0-9]+|)" buffer)
+ ;; but we want to operate on streams
+ (let ((buffer (make-adjustable-string)))
+ (loop
+ while (position (peek-char nil input nil) ".0123456789+-Ee")
+ do (vector-push-extend (read-char input) buffer))
+ (values (read-from-string buffer))))
+
+(defun parse-string (input)
+ (let ((output (make-adjustable-string)))
+ (labels ((outc (c)
+ (vector-push-extend c output))
+ (next ()
+ (read-char input))
+ (peek ()
+ (peek-char nil input)))
+ (next)
+ (loop
+ (cond
+ ((eql (peek) #\")
+ (next)
+ (return-from parse-string output))
+ ((eql (peek) #\\)
+ (next)
+ (ecase (next)
+ (#\" (outc #\"))
+ (#\\ (outc #\\))
+ (#\/ (outc #\/))
+ (#\b (outc #\Backspace))
+ (#\f (outc #\Page))
+ (#\n (outc #\Newline))
+ (#\r (outc #\Return))
+ (#\t (outc #\Tab))
+ (#\u (outc (code-char (let ((buffer (make-string 4)))
+ (read-sequence buffer input)
+ (parse-integer buffer :radix 16)))))))
+ (t
+ (outc (next))))))))
+
+(defun whitespace-p (char)
+ (member char '(#\Space #\Newline #\Tab #\Linefeed)))
+
+(defun skip-whitespace (input)
+ (loop
+ while (and (listen input)
+ (whitespace-p (peek-char nil input)))
+ do (read-char input)))
+
+(defun peek-char-skipping-whitespace (input &optional (eof-error-p t))
+ (skip-whitespace input)
+ (peek-char nil input eof-error-p))
+
+(defun parse-constant (input)
+ (destructuring-bind (expected-string return-value)
+ (find (peek-char nil input nil)
+ '(("true" t)
+ ("false" nil)
+ ("null" nil))
+ :key (lambda (entry) (aref (car entry) 0))
+ :test #'eql)
+ (loop
+ for char across expected-string
+ unless (eql (read-char input nil) char)
+ do (error "invalid constant"))
+ return-value))
+
+(define-condition cannot-convert-key (error)
+ ((key-string :initarg :key-string
+ :reader key-string))
+ (:report (lambda (c stream)
+ (format stream "cannot convert key ~S used in JSON object to hash table key"
+ (key-string c)))))
+
+(defun parse-object (input)
+ (let ((return-value (make-hash-table :test #'equal)))
+ (read-char input)
+ (loop
+ (when (eql (peek-char-skipping-whitespace input)
+ #\})
+ (return))
+ (skip-whitespace input)
+ (setf (gethash (prog1
+ (let ((key-string (parse-string input)))
+ (or (funcall *parse-object-key-fn* key-string)
+ (error 'cannot-convert-key :key-string key-string)))
+ (skip-whitespace input)
+ (unless (eql #\: (read-char input))
+ (error 'expected-colon))
+ (skip-whitespace input))
+ return-value)
+ (parse input))
+ (ecase (peek-char-skipping-whitespace input)
+ (#\, (read-char input))
+ (#\} nil)))
+ (read-char input)
+ return-value))
+
+(defconstant +initial-array-size+ 20
+ "Initial size of JSON arrays read, they will grow as needed.")
+
+(defun parse-array (input)
+ (let ((return-value (make-array +initial-array-size+ :adjustable t :fill-pointer 0)))
+ (read-char input)
+ (loop
+ (when (eql (peek-char-skipping-whitespace input)
+ #\])
+ (return))
+ (vector-push-extend (parse input) return-value)
+ (ecase (peek-char-skipping-whitespace input)
+ (#\, (read-char input))
+ (#\] nil)))
+ (read-char input)
+ return-value))
+
+(defgeneric parse (input)
+ (:method ((input stream))
+ (ecase (peek-char-skipping-whitespace input)
+ (#\"
+ (parse-string input))
+ ((#\- #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
+ (parse-number input))
+ (#\{
+ (parse-object input))
+ (#\[
+ (parse-array input))
+ ((#\t #\f #\n)
+ (parse-constant input))))
+ (:method ((input string))
+ (parse (make-string-input-stream input))))
+
Modified: trunk/projects/quickhoney/src/json.lisp
===================================================================
--- trunk/projects/quickhoney/src/json.lisp 2008-11-10 10:07:00 UTC (rev 4032)
+++ trunk/projects/quickhoney/src/json.lisp 2008-11-11 13:27:30 UTC (rev 4033)
@@ -65,3 +65,4 @@
(with-json-output-to-string ()
(with-json-object ()
, at body))))
+
Modified: trunk/projects/quickhoney/src/packages.lisp
===================================================================
--- trunk/projects/quickhoney/src/packages.lisp 2008-11-10 10:07:00 UTC (rev 4032)
+++ trunk/projects/quickhoney/src/packages.lisp 2008-11-11 13:27:30 UTC (rev 4033)
@@ -87,4 +87,9 @@
(defpackage :twitter
(:use :cl :bknr.datastore)
- (:export #:update-status))
\ No newline at end of file
+ (:export #:update-status))
+
+(defpackage :json-parser
+ (:use :cl)
+ (:export #:parse
+ #:*parse-object-key-fn*))
\ No newline at end of file
More information about the Bknr-cvs
mailing list