awwx.ws

fromjson0

A JSON parser

http://awwx.ws/fromjson0.arc:

(= json-true  (match-literal "true"  t))
(= json-false (match-literal "false" nil))
(= json-null  (match-literal "null"  nil))

(= json-number-char
  (match [find _ ".-+eE1234567890"]))

(= json-number
  (with-result cs (many1 json-number-char)
    (coerce (string cs) 'num)))

(def hexdigit (c)
  (and (isa c 'char)
       (or (<= #\a c #\f) (<= #\A c #\F) (<= #\0 c #\9))))

(= fourhex
  (must "four hex digits required after \\u"  
    (with-seq (h1 (match hexdigit)
               h2 (match hexdigit)
               h3 (match hexdigit)
               h4 (match hexdigit))
      (coerce (int (coerce (list h1 h2 h3 h4) 'string) 16) 'char))))

(def json-backslash-char (c)
  (case c
    #\" #\"
    #\\ #\\
    #\/ #\/
    #\b #\backspace
    #\f #\page
    #\n #\newline
    #\r #\return
    #\t #\tab
    (err "invalid backslash char" c)))

(= json-backslash-escape
  (seq2 (match-is #\\)
        (alt (seq2 (match-is #\u)
                   fourhex)
             (fn (p)
               (return cdr.p (json-backslash-char car.p))))))

(= json-string
  (on-result string
    (seq2 (match-is #\")
          (must "missing closing quote in JSON string"
                (seq1 (many (alt json-backslash-escape
                                 (match [isnt _ #\"])))
                      (match-is #\"))))))

(= json-array
  (seq2 (match-is #\[)
        (optional (cons-seq forward.json-value
                            (many (seq2 (skipwhite:match-is #\,)
                                        (must "a comma must be followed by a value"
                                              forward.json-value)))))
        (must "a JSON array must be terminated with a closing ]"
              (skipwhite:match-is #\]))))

(= json-object-kv
  (with-seq (key   skipwhite.json-string
             colon (must "a JSON object key string must be followed by a :"
                         (skipwhite:match-is #\:))
             value (must "a colon in a JSON object must be followed by a value"
                         forward.json-value))
    (list key value)))

(= json-object
   (on-result listtab
     (seq2 (match-is #\{)
           (comma-separated json-object-kv "comma must be followed by a key")
           (must "a JSON object must be terminated with a closing }"
                 (skipwhite:match-is #\})))))

(= json-value
  (skipwhite:alt json-true
                 json-false
                 json-null
                 json-number
                 json-string
                 json-array
                 json-object))

(def fromjson (s)
  (iflet (p r) (json-value (coerce s 'cons))
    (do (if p (err "Unexpected characters after JSON value" (coerce p 'string)))
        r)
    (err:string "not a JSON value: " s)))

Parses a JSON expression and converts it to an Arc value.

arc> (fromjson "[1, 2, {\"a\": 3, \"b\": 4}, true, false]")
(1 2 #hash(("a" . 3) ("b" . 4)) t nil)

Prerequisites

This hack depends on arc3.1 and parsecomb0.

Get this hack

Download fromjson0.arc and its prerequisite, parsecomb.

Or, using the hackinator:

hack ycombinator.com/arc/arc3.1.tar \
  awwx.ws/parsecomb0.arc \
  awwx.ws/fromjson0.arc

unLicense

This code is in the public domain.

Contact me

Twitter: awwx
Email: andrew.wilcox [at] gmail.com