2024-09-13 21:59:25 +00:00
|
|
|
(import
|
|
|
|
(scheme)
|
2024-09-29 21:49:37 +00:00
|
|
|
(only (scheme base) make-parameter)
|
2024-09-13 21:59:25 +00:00
|
|
|
(chicken format)
|
|
|
|
(chicken port)
|
2024-09-29 21:49:37 +00:00
|
|
|
(only (chicken string) reverse-list->string)
|
2024-09-13 21:59:25 +00:00
|
|
|
(srfi-34) ;;Exception Handling
|
|
|
|
(srfi-35) ;;Exception Types
|
|
|
|
(srfi-158) ;;Generators
|
|
|
|
)
|
|
|
|
|
|
|
|
(define-condition-type &json-error &error
|
|
|
|
json-error?
|
|
|
|
(json-error-reason json-error-reason)
|
|
|
|
(json-invalid-token json-invalid-token))
|
|
|
|
|
|
|
|
(define json-nesting-depth-limit (make-parameter +inf.0)) ;; the maximum nesting depth of JSON that can be read.
|
|
|
|
(define json-number-of-character-limit (make-parameter +inf.0)) ;; the maximum length of JSON input that can be read.
|
|
|
|
|
|
|
|
(define (json-null? obj) (eq? obj 'null))
|
|
|
|
|
|
|
|
(define (is-array-start? c)
|
|
|
|
(char=? #\[ c))
|
|
|
|
|
|
|
|
(define (is-array-end? c)
|
|
|
|
(char=? #\] c))
|
|
|
|
|
|
|
|
(define (is-object-start? c)
|
|
|
|
(char=? #\{ c))
|
|
|
|
|
|
|
|
(define (is-object-end? c)
|
|
|
|
(char=? #\} c))
|
|
|
|
|
|
|
|
(define (is-number-start? c)
|
|
|
|
(or (char-numeric? c)
|
|
|
|
(char=? #\+ c)
|
|
|
|
(char=? #\- c)))
|
|
|
|
|
|
|
|
(define (is-string-start? c)
|
|
|
|
(char=? #\" c))
|
|
|
|
|
|
|
|
(define (is-null-start? c)
|
|
|
|
(char=? #\n c))
|
|
|
|
|
|
|
|
(define (is-bool-start? c)
|
|
|
|
(or (char=? #\t c)
|
|
|
|
(char=? #\f c)))
|
|
|
|
|
|
|
|
(define (is-whitespace? c)
|
|
|
|
(or (char-whitespace? c)
|
|
|
|
(char=? #\, c)
|
|
|
|
(char=? #\: c)))
|
|
|
|
|
|
|
|
(define (is-delimiter? x)
|
|
|
|
(or (eof-object? x)
|
|
|
|
(is-whitespace? x)
|
|
|
|
(is-array-start? x)
|
|
|
|
(is-array-end? x)
|
|
|
|
(is-object-start? x)
|
|
|
|
(is-object-end? x)))
|
|
|
|
|
|
|
|
(define (determine-reader-proc peek-char)
|
|
|
|
(cond
|
|
|
|
((is-array-start? peek-char) read-array-start)
|
|
|
|
((is-array-end? peek-char) read-array-end)
|
|
|
|
((is-object-start? peek-char) read-object-start)
|
|
|
|
((is-object-end? peek-char) read-object-end)
|
|
|
|
((is-null-start? peek-char) read-null-sym)
|
|
|
|
((is-bool-start? peek-char) read-boolean)
|
|
|
|
((is-number-start? peek-char) read-number)
|
|
|
|
((is-string-start? peek-char) read-string)
|
|
|
|
((is-whitespace? peek-char) read-whitespace)
|
|
|
|
(else (raise (make-condition &json-error 'json-error-reason "Invalid token" 'json-invalid-token peek-char)))))
|
|
|
|
|
|
|
|
(define (json-generator #!optional (port-or-generator (current-input-port)))
|
|
|
|
(let* ((input-generator (if (procedure? port-or-generator)
|
|
|
|
port-or-generator
|
|
|
|
(lambda () (read-char port-or-generator))))
|
|
|
|
(nesting-limit (json-nesting-depth-limit))
|
|
|
|
(character-limit (json-number-of-character-limit)))
|
|
|
|
(make-coroutine-generator
|
|
|
|
(lambda (yield)
|
|
|
|
(let loop ((next-char (input-generator))
|
|
|
|
(json-nesting-depth #f)
|
|
|
|
(json-number-of-characters 0))
|
|
|
|
(cond
|
|
|
|
((> (or json-nesting-depth 0) nesting-limit)
|
|
|
|
(raise (make-condition &json-error
|
|
|
|
'json-error-reason "Nesting depth exceeded"
|
|
|
|
'json-invalid-token next-char)))
|
|
|
|
((> json-number-of-characters character-limit)
|
|
|
|
(raise (make-condition &json-error
|
|
|
|
'json-error-reason "Character limit exceeded"
|
|
|
|
'json-invalid-token next-char)))
|
|
|
|
((and (eof-object? next-char)
|
|
|
|
(< 0 json-nesting-depth))
|
|
|
|
(raise (make-condition &json-error
|
|
|
|
'json-error-reason "Unfinished JSON expression"
|
|
|
|
'json-invalid-token next-char)))
|
|
|
|
((or (eof-object? next-char)
|
|
|
|
(eq? 0 json-nesting-depth))
|
|
|
|
#!eof)
|
|
|
|
(else
|
|
|
|
(let-values (((token next-char* new-charcount nesting-delta)
|
|
|
|
((determine-reader-proc next-char)
|
|
|
|
json-number-of-characters next-char input-generator)))
|
2024-09-14 12:53:27 +00:00
|
|
|
(unless (null? token)
|
2024-09-13 21:59:25 +00:00
|
|
|
(yield token))
|
|
|
|
(loop next-char* (+ (or json-nesting-depth 0) nesting-delta) new-charcount)))))))))
|
|
|
|
|
|
|
|
(define (read-whitespace charcount next-char input-proc)
|
|
|
|
(values '() (input-proc) (+ charcount 1) 0))
|
|
|
|
|
|
|
|
(define (read-array-start charcount next-char input-proc)
|
|
|
|
(values 'array-start (input-proc) (+ charcount 1) +1))
|
|
|
|
|
|
|
|
(define (read-array-end charcount next-char input-proc)
|
|
|
|
(values 'array-end (input-proc) (+ charcount 1) -1))
|
|
|
|
|
|
|
|
(define (read-object-start charcount next-char input-proc)
|
|
|
|
(values 'object-start (input-proc) (+ charcount 1) +1))
|
|
|
|
|
|
|
|
(define (read-object-end charcount next-char input-proc)
|
|
|
|
(values 'object-end (input-proc) (+ charcount 1) -1))
|
|
|
|
|
|
|
|
(define (read-null-sym charcount next-char input-proc)
|
|
|
|
(if (not (is-delimiter? next-char))
|
|
|
|
(read-null-sym (+ charcount 1) (input-proc) input-proc)
|
|
|
|
(values 'null next-char charcount 0)))
|
|
|
|
|
|
|
|
(define (read-boolean charcount next-char input-proc #!optional (accu '()))
|
|
|
|
(set! accu (cons next-char accu))
|
|
|
|
(let ((accu-str (reverse-list->string accu)))
|
|
|
|
(cond
|
|
|
|
((string=? "true" accu-str) (values #t (input-proc) (+ charcount 1) 0))
|
|
|
|
((string=? "false" accu-str) (values #f (input-proc) (+ charcount 1) 0))
|
|
|
|
(else (let ((next-char* (input-proc)))
|
|
|
|
(if (is-delimiter? next-char*)
|
|
|
|
(values accu next-char* charcount 0) ;; TODO: Throw error instead
|
|
|
|
(read-boolean (+ charcount 1) next-char* input-proc accu)))))))
|
|
|
|
|
|
|
|
(define (read-number charcount next-char input-proc #!optional (accu '()))
|
|
|
|
(set! accu (cons next-char accu))
|
|
|
|
(let ((next-char* (input-proc)))
|
|
|
|
(if (is-delimiter? next-char*)
|
|
|
|
(values (string->number (reverse-list->string accu))
|
|
|
|
next-char* (+ charcount 1) 0)
|
|
|
|
(read-number (+ charcount 1) next-char* input-proc accu))))
|
|
|
|
|
2024-09-14 12:53:27 +00:00
|
|
|
(define (translate-escape char input-proc)
|
|
|
|
(case char
|
|
|
|
((#\") #\")
|
|
|
|
((#\') #\')
|
|
|
|
((#\\) #\\)
|
|
|
|
((#\n) #\newline)
|
|
|
|
((#\t) #\tab)
|
|
|
|
((#\u) (read-unicode-escape input-proc))
|
|
|
|
((#\x) (read-hex-escape input-proc))
|
|
|
|
((#\O) #\null)
|
|
|
|
((#\r) #\return)
|
|
|
|
((#\|) #\|)
|
|
|
|
((#\v) #\vtab)
|
|
|
|
((#\a) #\alarm)
|
|
|
|
((#\b) #\backspace)))
|
|
|
|
|
|
|
|
(define (read-hex-escape input-proc)
|
|
|
|
(let ((pos1 (input-proc))
|
|
|
|
(pos2 (input-proc)))
|
|
|
|
(integer->char
|
|
|
|
(string->number (list->string (list pos1 pos2)) 16))))
|
|
|
|
|
|
|
|
(define (read-unicode-escape input-proc)
|
|
|
|
(let ((pos1 (input-proc))
|
|
|
|
(pos2 (input-proc))
|
|
|
|
(pos3 (input-proc))
|
|
|
|
(pos4 (input-proc)))
|
|
|
|
(integer->char
|
|
|
|
(string->number (list->string (list pos1 pos2 pos3 pos4)) 16))))
|
|
|
|
|
2024-09-13 21:59:25 +00:00
|
|
|
(define (read-string charcount next-char input-proc #!optional (beginning? #t) (accu '()) (esc? #f))
|
|
|
|
(cond
|
|
|
|
(beginning?
|
|
|
|
(read-string (+ charcount 1)
|
|
|
|
(input-proc)
|
|
|
|
input-proc
|
|
|
|
#f '() #f))
|
|
|
|
((and (not esc?) (char=? next-char #\"))
|
|
|
|
(values (reverse-list->string accu)
|
|
|
|
(input-proc) (+ charcount 1) 0))
|
2024-09-14 12:53:27 +00:00
|
|
|
((and (not esc?) (char=? next-char #\\))
|
|
|
|
(read-string (+ charcount 1) (input-proc) input-proc #f accu #t))
|
|
|
|
(else (let ((current-char (if esc?
|
|
|
|
(translate-escape next-char input-proc)
|
|
|
|
next-char)))
|
|
|
|
(read-string (+ charcount 1)
|
|
|
|
(input-proc) input-proc
|
|
|
|
#f
|
|
|
|
(cons current-char accu)
|
|
|
|
#f)))))
|
2024-09-13 21:59:25 +00:00
|
|
|
|
|
|
|
(define-record json-foldstate mode cache accumulator)
|
|
|
|
|
|
|
|
(define (json-proc obj foldstate)
|
|
|
|
(if (json-foldstate? foldstate)
|
|
|
|
(case (json-foldstate-mode foldstate)
|
|
|
|
((%array) (begin
|
|
|
|
(json-foldstate-accumulator-set!
|
|
|
|
foldstate
|
|
|
|
(cons obj (json-foldstate-accumulator foldstate)))
|
|
|
|
foldstate))
|
|
|
|
((%object) (begin
|
2024-09-14 12:53:27 +00:00
|
|
|
(if (null? (json-foldstate-cache foldstate))
|
2024-09-13 21:59:25 +00:00
|
|
|
(begin
|
|
|
|
(json-foldstate-cache-set! foldstate obj))
|
|
|
|
(begin
|
|
|
|
(json-foldstate-accumulator-set!
|
|
|
|
foldstate
|
|
|
|
(cons (cons (json-foldstate-cache foldstate) obj)
|
|
|
|
(json-foldstate-accumulator foldstate)))
|
|
|
|
(json-foldstate-cache-set! foldstate '())))
|
|
|
|
foldstate)))
|
|
|
|
obj))
|
|
|
|
|
|
|
|
(define (object-start seed)
|
|
|
|
(make-json-foldstate '%object '() '()))
|
|
|
|
|
|
|
|
(define (object-end seed)
|
|
|
|
(reverse (json-foldstate-accumulator seed)))
|
|
|
|
|
|
|
|
(define (array-start seed)
|
|
|
|
(make-json-foldstate '%array '() '()))
|
|
|
|
|
|
|
|
(define (array-end seed)
|
|
|
|
(list->vector (reverse (json-foldstate-accumulator seed))))
|
|
|
|
|
|
|
|
(define (json-fold proc array-start array-end object-start object-end seed #!optional (port-or-generator (current-input-port)))
|
|
|
|
(let ((generator (json-generator port-or-generator)))
|
|
|
|
(let recurse ((seed seed)
|
|
|
|
(jump #f))
|
|
|
|
(generator-fold
|
|
|
|
(lambda (token seed)
|
|
|
|
(case token
|
|
|
|
((array-start) (proc
|
|
|
|
(call-with-current-continuation
|
|
|
|
(lambda (jump)
|
|
|
|
(recurse (array-start seed) jump)))
|
|
|
|
seed))
|
|
|
|
((array-end) (if jump
|
|
|
|
(jump (array-end seed))
|
|
|
|
(array-end seed)))
|
|
|
|
((object-start) (proc
|
|
|
|
(call-with-current-continuation
|
|
|
|
(lambda (jump)
|
|
|
|
(recurse (object-start seed) jump)))
|
|
|
|
seed))
|
|
|
|
((object-end) (if jump
|
|
|
|
(jump (object-end seed))
|
|
|
|
(object-end seed)))
|
|
|
|
(else (proc token seed))))
|
|
|
|
seed generator))))
|
|
|
|
|
|
|
|
(define (json-read #!optional (port-or-generator (current-input-port)))
|
|
|
|
(json-fold json-proc array-start array-end object-start object-end '() port-or-generator))
|
|
|
|
|
|
|
|
(define json-lines-read json-read)
|
|
|
|
|
|
|
|
(define json-sequence-read json-read)
|
|
|
|
|
|
|
|
(define (accumulate-boolean accumulator bool)
|
|
|
|
(if bool (accumulator 'true) (accumulator 'false)))
|
|
|
|
|
|
|
|
(define (accumulate-null accumulator)
|
|
|
|
(accumulator 'null))
|
|
|
|
|
|
|
|
(define (accumulate-number accumulator num)
|
|
|
|
(accumulator num))
|
|
|
|
|
|
|
|
(define (accumulate-string accumulator str)
|
|
|
|
(accumulator str))
|
|
|
|
|
|
|
|
(define (accumulate-vector accumulator vec)
|
|
|
|
(accumulator #\[)
|
|
|
|
(let ((max-index (- (vector-length vec) 1)))
|
|
|
|
(let loop ((index 0))
|
|
|
|
(accumulate-dispatch accumulator
|
|
|
|
(vector-ref vec index))
|
|
|
|
(if (< index max-index)
|
|
|
|
(begin (accumulator #\,) (accumulator #\space)
|
|
|
|
(loop (+ index 1))))))
|
|
|
|
(accumulator #\]))
|
|
|
|
|
|
|
|
(define (accumulate-alist accumulator alist)
|
|
|
|
(accumulator #\{)
|
|
|
|
(let loop ((alist alist))
|
|
|
|
(let ((kv-pair (car alist)))
|
|
|
|
(if (not (pair? kv-pair))
|
|
|
|
(raise (make-condition &json-error
|
|
|
|
'json-error-reason "Unbalanced alist"
|
|
|
|
'json-invalid-token kv-pair)))
|
|
|
|
(accumulate-dispatch accumulator
|
|
|
|
(symbol->string (car kv-pair)))
|
|
|
|
(accumulator #\:) (accumulator #\space)
|
|
|
|
(accumulate-dispatch accumulator (cdr kv-pair))
|
|
|
|
(if (not (eq? '() (cdr alist)))
|
|
|
|
(begin
|
|
|
|
(accumulator #\,) (accumulator #\space)
|
|
|
|
(loop (cdr alist))))))
|
|
|
|
(accumulator #\}))
|
|
|
|
|
|
|
|
(define (accumulate-dispatch accumulator obj)
|
|
|
|
(cond
|
|
|
|
((number? obj) (accumulate-number accumulator obj))
|
|
|
|
((string? obj) (accumulate-string accumulator obj))
|
|
|
|
((boolean? obj) (accumulate-boolean accumulator obj))
|
|
|
|
((eq? 'null obj) (accumulate-null accumulator))
|
|
|
|
((vector? obj) (accumulate-vector accumulator obj))
|
|
|
|
((list? obj) (accumulate-alist accumulator obj))))
|
|
|
|
|
|
|
|
(define (json-accumulator #!optional (port-or-accumulator (current-output-port)))
|
|
|
|
(let ((accumulator (if (procedure? port-or-accumulator)
|
|
|
|
port-or-accumulator
|
|
|
|
(lambda (txt)
|
|
|
|
(if (char? txt)
|
|
|
|
(display txt port-or-accumulator)
|
|
|
|
(write txt port-or-accumulator)))))
|
|
|
|
(leading-space? #f))
|
|
|
|
(lambda (obj)
|
|
|
|
(if leading-space? (accumulator #\space) (set! leading-space? #t))
|
|
|
|
(accumulate-dispatch accumulator obj))))
|
|
|
|
|
|
|
|
(define (json-write obj #!optional (port-or-accumulator (current-output-port)))
|
|
|
|
(let ((black-hole (make-output-port (lambda (poor-soul) #t) (lambda () #t))))
|
|
|
|
((json-accumulator black-hole) obj))
|
|
|
|
((json-accumulator port-or-accumulator) obj))
|