#lang mzscheme
(require "packrat.ss")
(provide json-read
json-write
hashtable->vector
vector->hashtable)
(define (hashtable->vector ht)
(list->vector
(hash-table-map ht cons)))
(define (vector->hashtable v)
(let ((ht (make-hash-table)))
(for-each (lambda (entry) (hash-table-put! ht (car entry) (cdr entry)))
(vector->list v))
ht))
(define json-write
(let ()
(define (write-ht vec p)
(display "{" p)
(do ((need-comma #f #t)
(i 0 (+ i 1)))
((= i (vector-length vec)))
(if need-comma
(display ", " p)
(set! need-comma #t))
(let* ((entry (vector-ref vec i))
(k (car entry))
(v (cdr entry)))
(cond
((symbol? k) (write (symbol->string k) p))
((string? k) (write k p)) (else (error "Invalid JSON table key in json-write" k)))
(display ": " p)
(write-any v p)))
(display "}" p))
(define (write-array a p)
(display "[" p)
(let ((need-comma #f))
(for-each (lambda (v)
(if need-comma
(display ", " p)
(set! need-comma #t))
(write-any v p))
a))
(display "]" p))
(define (write-any x p)
(cond
((hash-table? x) (write-ht (hashtable->vector x) p))
((vector? x) (write-ht x p))
((pair? x) (write-array x p))
((symbol? x) (write (symbol->string x) p)) ((or (string? x)
(number? x)) (write x p))
((boolean? x) (display (if x "true" "false") p))
((void? x) (display "null" p))
(else (error "Invalid JSON object in json-write" x))))
(lambda (x . maybe-port)
(write-any x (if (pair? maybe-port) (car maybe-port) (current-output-port))))))
(define json-read
(let ()
(define (generator p)
(let ((ateof #f)
(pos (top-parse-position "<?>")))
(lambda ()
(if ateof
(values pos #f)
(let ((x (read-char p)))
(if (eof-object? x)
(begin
(set! ateof #t)
(values pos #f))
(let ((old-pos pos))
(set! pos (update-parse-position pos x))
(values old-pos (cons x x)))))))))
(define parser
(packrat-parser (begin
(define (white results)
(if (char-whitespace? (parse-results-token-value results))
(white (parse-results-next results))
(comment results)))
(define (skip-comment-char results)
(comment-body (parse-results-next results)))
(define (skip-to-newline results)
(if (memv (parse-results-token-value results) '(#\newline #\return))
(white results)
(skip-to-newline (parse-results-next results))))
(define (token str)
(lambda (starting-results)
(let loop ((pos 0) (results starting-results))
(if (= pos (string-length str))
(make-result str results)
(if (char=? (parse-results-token-value results) (string-ref str pos))
(loop (+ pos 1) (parse-results-next results))
(make-expected-result (parse-results-position starting-results) str))))))
(define (interpret-string-escape results k)
(let ((ch (parse-results-token-value results)))
(k (cond
((assv ch '((#\b . #\backspace)
(#\n . #\newline)
(#\f . #\page)
(#\r . #\return)
(#\t . #\tab))) => cdr) (else ch))
(parse-results-next results))))
(define (jstring-body results)
(let loop ((acc '()) (results results))
(let ((ch (parse-results-token-value results)))
(case ch
((#\\) (interpret-string-escape (parse-results-next results)
(lambda (val results)
(loop (cons val acc) results))))
((#\") (make-result (list->string (reverse acc)) results))
(else (loop (cons ch acc) (parse-results-next results)))))))
(define (jnumber-body starting-results)
(let loop ((acc '()) (results starting-results))
(let ((ch (parse-results-token-value results)))
(if (memv ch '(#\- #\+ #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\. #\e #\E))
(loop (cons ch acc) (parse-results-next results))
(let ((n (string->number (list->string (reverse acc)))))
(if n
(make-result n results)
(make-expected-result (parse-results-position starting-results) 'number)))))))
any)
(any ((white '#\{ entries <- table-entries white '#\}) (list->vector entries))
((white '#\[ entries <- array-entries white '#\]) entries)
((s <- jstring) s)
((n <- jnumber) n)
((white (token "true")) #t)
((white (token "false")) #f)
((white (token "null")) (void)))
(comment (((token "/*") b <- comment-body) b)
(((token "//") b <- skip-to-newline) b)
(() 'whitespace))
(comment-body (((token "*/") w <- white) w)
((skip-comment-char) 'skipped-comment-char))
(table-entries ((a <- table-entries-nonempty) a)
(() '()))
(table-entries-nonempty ((entry <- table-entry white '#\, entries <- table-entries-nonempty) (cons entry entries))
((entry <- table-entry) (list entry)))
(table-entry ((key <- jstring white '#\: val <- any) (cons key val)))
(array-entries ((a <- array-entries-nonempty) a)
(() '()))
(array-entries-nonempty ((entry <- any white '#\, entries <- array-entries-nonempty) (cons entry entries))
((entry <- any) (list entry)))
(jstring ((white '#\" body <- jstring-body '#\") body))
(jnumber ((white body <- jnumber-body) body))
))
(define (read-any p)
(let ((result (parser (base-generator->results (generator p)))))
(if (parse-result-successful? result)
(parse-result-semantic-value result)
(error "JSON Parse Error"
(let ((e (parse-result-error result)))
(list 'json-parse-error
(parse-position->string (parse-error-position e))
(parse-error-expected e)
(parse-error-messages e)))))))
(lambda maybe-port
(read-any (if (pair? maybe-port) (car maybe-port) (current-input-port))))))