;;; (json parser) --- Guile JSON implementation. ;; Copyright (C) 2013-2018 Aleix Conchillo Flaque ;; ;; This file is part of guile-json. ;; ;; guile-json is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3 of the License, or ;; (at your option) any later version. ;; ;; guile-json is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with guile-json. If not, see https://www.gnu.org/licenses/. ;;; Commentary: ;; JSON module for Guile ;;; Code: (define-module (gnucash json parser) #:use-module (ice-9 rdelim) #:use-module (rnrs bytevectors) #:use-module (srfi srfi-9) #:export (json->scm json-string->scm json-parser? json-parser-port)) ;; ;; Parser record and read helpers ;; (define-record-type json-parser (make-json-parser port) json-parser? (port json-parser-port)) (define (parser-peek-char parser) (peek-char (json-parser-port parser))) (define (parser-read-char parser) (read-char (json-parser-port parser))) (define (parser-read-delimited parser delim handle-delim) (let ((port (json-parser-port parser))) (read-delimited delim port handle-delim))) ;; ;; Number parsing helpers ;; ;; Read + or -. . If something different is found, return empty string. (define (read-sign parser) (let loop ((c (parser-peek-char parser)) (s "")) (case c ((#\+ #\-) (let ((ch (parser-read-char parser))) (string-append s (string ch)))) (else s)))) ;; Read digits [0..9]. If something different is found, return empty ;; string. (define (read-digits parser) (let loop ((c (parser-peek-char parser)) (s "")) (case c ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) (let ((ch (parser-read-char parser))) (loop (parser-peek-char parser) (string-append s (string ch))))) (else s)))) (define (read-exp-part parser) (let ((c (parser-peek-char parser)) (s "")) (cond ((eof-object? c) s) (else (case c ;; Stop parsing if whitespace found. ((#\ht #\vt #\lf #\cr #\sp) s) ;; We might be in an array or object, so stop here too. ((#\, #\] #\}) s) ;; We might have the exponential part ((#\e #\E) (let ((ch (parser-read-char parser)) ; current char (sign (read-sign parser)) (digits (read-digits parser))) ;; If we don't have sign or digits, we have an invalid ;; number. (if (not (and (string-null? sign) (string-null? digits))) (string-append s (string ch) sign digits) #f))) ;; If we have a character different than e or E, we have an ;; invalid number. (else #f)))))) (define (read-real-part parser) (let ((c (parser-peek-char parser)) (s "")) (cond ((eof-object? c) s) (else (case c ;; Stop parsing if whitespace found. ((#\ht #\vt #\lf #\cr #\sp) s) ;; We might be in an array or object, so stop here too. ((#\, #\] #\}) s) ;; If we read . we might have a real number ((#\.) (let ((ch (parser-read-char parser)) (digits (read-digits parser))) ;; If we have digits, try to read the exponential part, ;; otherwise we have an invalid number. (cond ((not (string-null? digits)) (let ((exp (read-exp-part parser))) (cond (exp (string-append s (string ch) digits exp)) (else #f)))) (else #f)))) ;; If we have a character different than . we might continue ;; processing. (else #f)))))) (define (read-number parser) (let loop ((c (parser-peek-char parser)) (s "")) (cond ((eof-object? c) s) (else (case c ;; Stop parsing if whitespace found. ((#\ht #\vt #\lf #\cr #\sp) s) ;; We might be in an array or object, so stop here too. ((#\, #\] #\}) s) ((#\-) (let ((ch (parser-read-char parser))) (loop (parser-peek-char parser) (string-append s (string ch))))) ((#\0) (let ((ch (parser-read-char parser))) (string-append s (string ch) (or (read-real-part parser) (throw 'json-invalid parser))))) ((#\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) (let ((ch (parser-read-char parser))) (string-append s (string ch) (read-digits parser) (or (read-real-part parser) (read-exp-part parser) (throw 'json-invalid parser))))) (else (throw 'json-invalid parser))))))) ;; ;; Object parsing helpers ;; (define (read-pair parser) ;; Read string key (let ((key (json-read-string parser))) (let loop ((c (parser-peek-char parser))) (case c ;; Skip whitespaces ((#\ht #\vt #\lf #\cr #\sp) (parser-read-char parser) (loop (parser-peek-char parser))) ;; Skip colon and read value ((#\:) (parser-read-char parser) (cons key (json-read parser))) ;; invalid object (else (throw 'json-invalid parser)))))) (define (read-object parser) (let loop ((c (parser-peek-char parser)) (pairs '())) (case c ;; Skip whitespaces ((#\ht #\vt #\lf #\cr #\sp) (parser-read-char parser) (loop (parser-peek-char parser) pairs)) ;; end of object ((#\}) (parser-read-char parser) pairs) ;; Read one pair and continue ((#\") (let ((pair (read-pair parser))) (loop (parser-peek-char parser) (cons pair pairs)))) ;; Skip comma and read more pairs ((#\,) (parser-read-char parser) (loop (parser-peek-char parser) pairs)) ;; invalid object (else (throw 'json-invalid parser))))) ;; ;; Array parsing helpers ;; (define (read-array parser) (let loop ((c (parser-peek-char parser)) (values '())) (case c ;; Skip whitespace and comma ((#\ht #\vt #\lf #\cr #\sp #\,) (parser-read-char parser) (loop (parser-peek-char parser) values)) ;; end of array ((#\]) (parser-read-char parser) (list->vector values)) ;; this can be any json object (else (let ((value (json-read parser))) (loop (parser-peek-char parser) (append values (list value)))))))) ;; ;; String parsing helpers ;; (define (expect parser expected) (let ((ch (parser-read-char parser))) (if (not (char=? ch expected)) (throw 'json-invalid parser) ch))) (define (expect-string parser expected) (list->string (map (lambda (ch) (expect parser ch)) (string->list expected)))) (define (read-hex-digit parser) (let ((c (parser-read-char parser))) (case c ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\A #\B #\C #\D #\E #\F #\a #\b #\c #\d #\e #\f) c) (else (throw 'json-invalid parser))))) (define (read-control-char parser) (let ((c (parser-read-char parser))) (case c ((#\" #\\ #\/) (string c)) ((#\b) (string #\bs)) ((#\f) (string #\ff)) ((#\n) (string #\lf)) ((#\r) (string #\cr)) ((#\t) (string #\ht)) ((#\u) (let* ((utf1 (string (read-hex-digit parser) (read-hex-digit parser))) (utf2 (string (read-hex-digit parser) (read-hex-digit parser))) (vu8 (list (string->number utf1 16) (string->number utf2 16))) (utf (u8-list->bytevector vu8))) (utf16->string utf))) (else #f)))) (define (read-string parser) ;; Read characters until \ or " are found. (let loop ((result "") (current (parser-read-delimited parser "\\\"" 'split))) (case (cdr current) ((#\") (string-append result (car current))) ((#\\) (let ((ch (read-control-char parser))) (if ch (loop (string-append result (car current) ch) (parser-read-delimited parser "\\\"" 'split)) (throw 'json-invalid parser )))) (else (throw 'json-invalid parser))))) ;; ;; Main parser functions ;; (define-syntax json-read-delimited (syntax-rules () ((json-read-delimited parser delim read-func) (let loop ((c (parser-read-char parser))) (case c ;; skip whitespace ((#\ht #\vt #\lf #\cr #\sp) (loop (parser-peek-char parser))) ;; read contents ((delim) (read-func parser)) (else (throw 'json-invalid parser))))))) (define (json-read-true parser) (expect-string parser "true") #t) (define (json-read-false parser) (expect-string parser "false") #f) (define (json-read-null parser) (expect-string parser "null") #nil) (define (json-read-object parser) (json-read-delimited parser #\{ read-object)) (define (json-read-array parser) (json-read-delimited parser #\[ read-array)) (define (json-read-string parser) (json-read-delimited parser #\" read-string)) (define (json-read-number parser) (string->number (read-number parser))) (define (json-read parser) (let loop ((c (parser-peek-char parser))) (cond ;;If we reach the end we might have an incomplete document ((eof-object? c) (throw 'json-invalid parser)) (else (case c ;; skip whitespaces ((#\ht #\vt #\lf #\cr #\sp) (parser-read-char parser) (loop (parser-peek-char parser))) ;; read json values ((#\t) (json-read-true parser)) ((#\f) (json-read-false parser)) ((#\n) (json-read-null parser)) ((#\{) (json-read-object parser)) ((#\[) (json-read-array parser)) ((#\") (json-read-string parser)) ;; anything else should be a number (else (json-read-number parser))))))) ;; ;; Public procedures ;; (define* (json->scm #:optional (port (current-input-port))) "Parse a JSON document into native. Takes one optional argument, @var{port}, which defaults to the current input port from where the JSON document is read." (json-read (make-json-parser port))) (define* (json-string->scm str) "Parse a JSON document into native. Takes a string argument, @var{str}, that contains the JSON document." (call-with-input-string str (lambda (p) (json->scm p)))) ;;; (json parser) ends here