Source

medea / medea.scm

(module medea

(read-json
 json-parsers
 write-json
 json-unparsers
 json->string)

(import chicken (except scheme string member exp))
(use srfi-14 (except utf8 string) srfi-69 lazy-seq
     (except comparse as-string)
     (prefix utf8-srfi-14 utf8-)
     (only srfi-1 cons* find remove)
     (only data-structures compose constantly identity alist-ref)
     (only utf8-srfi-13 string-concatenate string-pad)
     (only vector-lib vector-for-each)
     (only ports with-output-to-port with-output-to-string))

(define json-parsers
  (make-parameter `((string . ,identity)
                    (number . ,string->number)
                    (member . ,(lambda (name value)
                                 (cons (string->symbol name) value)))
                    (object . ,identity)
                    (array  . ,list->vector))))

(define (parser-ref name)
  (alist-ref name (json-parsers)))

(define (handle-number x)
  (result ((parser-ref 'number) x)))

(define (handle-array args)
  (result ((parser-ref 'array) args)))

(define (handle-member name value)
  (result ((parser-ref 'member ) name value)))

(define (handle-object args)
  (result ((parser-ref 'object) args)))

(define (handle-string string)
  (result ((parser-ref 'string) string)))

(define (handle-unicode u)
  (result (integer->char (string->number u 16))))

(define (handle-escape e)
  (result
   (case e
     ((#\\) #\\)
     ((#\/) #\/)
     ((#\") #\")
     ((#\b) #\backspace)
     ((#\f) #\page)
     ((#\n) #\newline)
     ((#\r) #\return)
     ((#\t) #\tab))))

(define handle-true (constantly (result #t)))
(define handle-false (constantly (result #f)))
(define handle-null (constantly (result 'null)))

(define (as-string parser)
  (sequence* ((parts parser))
    (result (apply conc (remove boolean? parts)))))

(define char-set:json-escaped
  (string->char-set "\"\\/bfnrt"))

(define char-set:json-unescaped
  (utf8-char-set-union
   (utf8-ucs-range->char-set #x20 #x22)
   (utf8-ucs-range->char-set #x23 #x5C)
   (utf8-ucs-range->char-set #x5D #x110000)))

(define char-set:json-char
  (utf8-char-set-union
   utf8-char-set:iso-control
   (utf8-char-set #\" #\\)))

(define (json-char? c)
  (utf8-char-set-contains? char-set:json-char c))

(define consume-trailing-whitespace?
  (make-parameter #f))

(include "grammar.scm")

(define (read-json #!optional (input (current-input-port))
                   #!key (memoize? #t) (consume-trailing-whitespace #t))
  (parameterize ((consume-trailing-whitespace? consume-trailing-whitespace))
    (parse document
           (cond ((input-port? input)
                  (input-port->lazy-seq input read-char))
                 ((string? input)
                  (string->list input))
                 (else input))
           memoize: memoize?)))

(define (unparse-string s)
  (let ((chars (string->list s)))
    (display #\")
    (for-each
     (lambda (c)
       (cond ((utf8-char-set-contains? char-set:json-unescaped c)
              (display c))
             ((char-set-contains? char-set:json-escaped c)
              (display "\\")
              (display c))
             (else
              (display "\\u")
              (display (string-pad (number->string (char->integer c) 16) 4 #\0)))))
     chars)
    (display #\")))

(define (for-each/delimiter proc list delimiter)
  (let ((size (if (vector? list)
                  (vector-length list)
                  (length list)))
        (count 0))
    ((if (vector? list) vector-for-each for-each)
     (lambda (d #!optional (ad d))
       (proc ad)
       (set! count (add1 count))
       (unless (= count size)
         (display #\,)))
     list)))

(define json-unparsers
  (make-parameter
   (list (cons list?
               (lambda (object)
                 (display #\{)
                 (for-each/delimiter
                  (lambda (member)
                    (unparse-string (symbol->string (car member)))
                    (display #\:)
                    (write-json (cdr member)))
                  object
                  #\,)
                 (display #\})))

         (cons vector?
               (lambda (array)
                 (display #\[)
                 (for-each/delimiter write-json array #\,)
                 (display #\])))

         (cons string? unparse-string)
         (cons number? write)
         (cons (cut eq? <> #t) (lambda (o) (display "true")))
         (cons (cut eq? <> #f) (lambda (o) (display "false")))
         (cons (cut eq? <> 'null) (lambda (o) (display "null")))
         (cons (constantly #t)
               (lambda datum
                 (error 'write-json "don't know how to write datum as JSON" datum))))))

(define (write-json object #!optional (port (current-output-port)))
  (with-output-to-port port
    (lambda ()
      ((cdr (find (lambda (unparser)
                    ((car unparser) object))
                  (json-unparsers)))
       object))))

(define (json->string object)
  (with-output-to-string (lambda () (write-json object))))

)