view lisp/json.el @ 94414:d86cb59eea9f

2008-04-27 Carsten Dominik <dominik@science.uva.nl> * org/org.el (org-html-level-start): Always have id's in HTML (org-export-as-html): Use `org-link-protocols' to retrieve the export form of the link. (org-add-link-type): Final parameter renamed from PUBLISH. Better documentation of how it is to be used. Avoid double entries for the same link type. (org-add-link-props): New function. (org-modules-loaded): New variable. (org-load-modules-maybe, org-set-modules): New function. (org-modules): New option. (org-mode, org-cycle, orgstruct-mode, org-run-like-in-org-mode) (orgtbl-mode, org-store-link, org-insert-link-global) (org-open-at-point): Call `org-load-modules-maybe'. (org-search-view): Add more text properties. (org-agenda-schedule, org-agenda-deadline): Allow also in search-type agendas. (org-search-view): Order of arguments has been changed. Interpret prefix-arg as TODO-ONLY. (org-agenda, org-run-agenda-series, org-agenda-manipulate-query): Take new argument order of `org-search-view' into account. (org-todo-only): New variable. (org-search-syntax-table): New variable and function. (org-search-view): Do the search with the special syntax table. (define-obsolete-function-alias): Make work with XEmacs. (org-add-planning-info): Use old date as default when modifying an existing deadline or scheduled item. (org-agenda-compute-time-span): Make argument N optional. (org-agenda-format-date-aligned): Require `cal-iso'. (org-agenda-list): Include week into into agenda heading, don't list it at each date (only on Mondays). (org-read-date-analyze): Define local variable `iso-date'. (org-agenda-format-date-aligned): Remove dependency on `calendar-time-from-absolute'. (org-remember-apply-template, org-go-to-remember-target): Interpret filenames relative to `org-directory'. (org-complete): Silently fail when trying to complete keywords that don't have a default value. (org-get-current-options): Added a #+DATE: option. (org-additional-option-like-keywords): Removed "DATE:" from the list of additional keywords. (org-export-as-html): Removed (current-time) as unnecessary second argument of `format-time-string'. (org-clock-find-position): Handle special case at end of buffer. (org-agenda-day-view): New argument DAY-OF-YEAR, pass it on to `org-agenda-change-time-span'. (org-agenda-week-view): New argument ISO-WEEK, pass it on to `org-agenda-change-time-span'. (org-agenda-month-view): New argument MONTH, pass it on to `org-agenda-change-time-span'. (org-agenda-year-view): New argument YEAR, pass it on to `org-agenda-change-time-span'. (org-agenda-change-time-span): New optional argument N, pass it on to `org-agenda-compute-time-span'. (org-agenda-compute-time-span): New argument N, interpret it by changing the starting day. (org-small-year-to-year): New function. (org-scheduled-past-days): Respect `org-scheduled-past-days'. (org-auto-repeat-maybe): Make sure that repeating dates are pushed into the future, and that the shift is at least one interval, never 0. (org-update-checkbox-count): Fix bug with checkbox counting. (org-add-note): New command. (org-add-log-setup): Renamed from `org-add-log-maybe'. (org-log-note-headings): New entry for plain notes (i.e. notes not related to state changes or clocking). (org-get-org-file): Check for availability of `remember-data-file'. (org-cached-entry-get): Allow a regexp value for `org-use-property-inheritance'. (org-use-property-inheritance): Allow regexp value. Fix bug in customization type. (org-use-tag-inheritance): Allow a list and a regexp value for this variable. (org-scan-tags, org-get-tags-at): Implement selective tag inheritance. (org-entry-get): Respect value `selective' for the INHERIT argument. (org-tag-inherit-p, org-property-inherit-p): New functions. (org-agenda-format-date-aligned): Allow 10 characters for weekday, to acomodate German locale. (org-add-archive-files): New function. (org-agenda-files): New argument `ext', to get archive files as well. (org-tbl-menu): Protect the use of variables that are only available when org-table.el gets loaded. (org-read-agenda-file-list): Error if `org-agenda-files' is a single directory. (org-open-file): Allow a batch process to trigger waiting after executing a system command. (org-store-link): Link to headline when there is not target and no region in an org-mode buffer when creating a link. (org-link-types-re): New variable. (org-make-link-regexps): Compute `org-link-types-re'. (org-make-link-description-function): New option. (org-agenda-date, org-agenda-date-weekend): New faces. (org-archive-sibling-heading): New option. (org-archive-to-archive-sibling): New function. (org-iswitchb): New command. (org-buffer-list): New function. (org-agenda-columns): Also try the #+COLUMNS line in the buffer associated with the entry at point (or with the first entry in the agenda view). (org-modules): Add entry for org-bibtex.el. (org-completion-fallback-command): Moved into `org-completion' group. (org-clock-heading-function): Moved to `org-progress' group. (org-auto-repeat-maybe): Make sure that a note can be enforces if `org-log-repeat' is `note'. (org-modules): Allow additional symbols for external packages. (org-ctrl-c-ctrl-c): Allow for `org-clock-overlays' to be undefined. (org-clock-goto): Hide drawers after showing an entry with `org-clock-goto.' (org-shiftup, org-shiftdown, org-shiftright, org-shiftleft): Try also a clocktable block shift. (org-clocktable-try-shift): New function. (org-columns-hscoll-title): New function. (org-columns-previous-hscroll): New variable. (org-columns-full-header-line-format): New variable. (org-columns-display-here-title, org-columns-remove-overlays): Install `org-columns-hscoll-title' in post-command-hook. * org/org.el: Split into many small files. * org/org-agenda.el: New file, split off from org.el. * org/org-archive.el: New file, split off from org.el. * org/org-bbdb.el: New file. * org/org-bibtex.el: New file, split off from org.el. * org/org-clock.el: New file, split off from org.el. * org/org-colview.el: New file, split off from org.el. * org/org-compat.el: New file, split off from org.el. * org/org-exp.el: New file, split off from org.el. * org/org-faces.el: New file, split off from org.el. * org/org-gnus.el: New file, split off from org.el. * org/org-info.el: New file, split off from org.el. * org/org-infojs.el: New file. * org/org-irc.el: New file. * org/org-macs.el: New file, split off from org.el. * org/org-mew.el: New file. * org/org-mhe.el: New file, split off from org.el. * org/org-publish.el: New file, split off from org.el. * org/org-remember.el: New file, split off from org.el. * org/org-rmail.el: New file, split off from org.el. * org/org-table.el: New file, split off from org.el. * org/org-vm.el: New file, split off from org.el. * org/org-wl.el: New file, split off from org.el. 2008-04-27 Jason Riedy <jason@acm.org> * lisp/org-table.el (orgtbl-to-generic): Add a :remove-nil-lines parameter that supresses lines that evaluate to NIL. (orgtbl-get-fmt): New inline function for picking apart formats that may be lists. (orgtbl-apply-fmt): New inline function for applying formats that may be functions. (orgtbl-eval-str): New inline function for strings that may be functions. (orgtbl-format-line, orgtbl-to-generic): Use and document. (orgtbl-to-latex, orgtbl-to-texinfo): Document. (*orgtbl-llfmt*, *orgtbl-llstart*) (*orgtbl-llend*): Dynamic variables for last-line formatting. (orgtbl-format-section): Shift formatting to support detecting the last line and formatting it specially. (orgtbl-to-generic): Document :ll* formats. Set to the non-ll formats unless overridden. (orgtbl-to-latex): Suggest using :llend to suppress the final \\. (*orgtbl-table*, *orgtbl-rtn*): Dynamically bound variables to hold the input collection of lines and output formatted text. (*orgtbl-hline*, *orgtbl-sep*, *orgtbl-fmt*, *orgtbl-efmt*, (*orgtbl-lfmt*, *orgtbl-lstart*, *orgtbl-lend*): Dynamically bound format parameters. (orgtbl-format-line): New function encapsulating formatting for a single line. (orgtbl-format-section): Similar for each section. Rebinding the dynamic vars customizes the formatting for each section. (orgtbl-to-generic): Use orgtbl-format-line and orgtbl-format-section. (org-get-param): Now unused, so delete. (orgtbl-gather-send-defs): New function to gather all the SEND definitions before a table. (orgtbl-send-replace-tbl): New function to find the RECEIVE corresponding to the current name. (orgtbl-send-table): Use the previous two functions and implement multiple destinations for each table. * doc/org.texi (A LaTeX example): Note that fmt may be a one-argument function, and efmt may be a two-argument function. (Radio tables): Document multiple destinations. 2008-04-27 Carsten Dominik <dominik@science.uva.nl> * org/org-agenda.el (org-add-to-diary-list): New function. (org-prefix-has-effort): New variable. (org-sort-agenda-noeffort-is-high): New option. (org-agenda-columns-show-summaries) (org-agenda-columns-compute-summary-properties): New options. (org-format-agenda-item): Compute the duration of the item. (org-agenda-weekend-days): New variable. (org-agenda-list, org-timeline): Use the proper faces for dates in the agenda and timeline buffers. (org-agenda-archive-to-archive-sibling): New command. (org-agenda-start-with-clockreport-mode): New option. (org-agenda-clockreport-parameter-plist): New option. (org-agenda-clocktable-mode): New variable. (org-agenda-deadline-leaders): Allow a function value for the deadline leader. (org-agenda-get-deadlines): Deal with new function value. * lisp/org-clock.el (org-clock): New customization group. (org-clock-into-drawer, org-clock-out-when-done) (org-clock-in-switch-to-state, org-clock-heading-function): Moved into the new group. (org-clock-out-remove-zero-time-clocks): New option. (org-clock-out): Use `org-clock-out-remove-zero-time-clocks'. (org-dblock-write:clocktable): Allow a Lisp form for the scope parameter. (org-dblock-write:clocktable): Fixed bug with total time calculation. (org-dblock-write:clocktable): Request the unrestricted list of files. (org-get-clocktable): New function. (org-dblock-write:clocktable): Make sure :tstart and :tend can not only be strings but also integers (an absolute day number) and lists (m d y). * org/org-colview.el (org-columns-next-allowed-value) (org-columns-edit-value): Limit the effort for updatig in the agenda to recomputing a single file. (org-columns-compute): Only write property value if it has changed. This avoids raising the buffer-change-flag unnecessarily. (org-agenda-colview-summarize) (org-agenda-colview-compute): New functions. (org-agenda-columns): Call `org-agenda-colview-summarize'. * org/org-exp.el (org-export-run-in-background): New option. (org-export-icalendar): Allow a batch process to trigger waiting after executing a system command. (org-export-preprocess-string): Renamed-from `org-cleaned-string-for-export'. (org-export-html-style): Made target class look like normal text. (org-export-as-html): Make use of the better proprocessing in `org-cleaned-string-for-export'. (org-cleaned-string-for-export): Better treatment of heuristic targets, many more internal links will now work in HTML export. (org-get-current-options): Incorporate LINK_UP, LINK_HOME, and INFOJS. (org-export-inbuffer-options-extra): New variable. (org-export-options-filters): New hook. (org-infile-export-plist): Find also the settings keywords in `org-export-inbuffer-options-extra'. (org-infile-export-plist): Allow multiple #+OPTIONS lines and multiple #+INFOJS_OPT lines. (org-export-html-handle-js-options): New function. (org-export-html-infojs-setup): New option. (org-export-as-html): Call `org-export-html-handle-js-options'. Add autoload to all entry points. (org-skip-comments): Function removed. * org/org-table.el (org-table-make-reference): Extra parenthesis around single fields, to make sure that algebraic formulas get correctly interpreted by calc. (org-table-current-column): No longer interactive. * org/org-export-latex.el (org-export-latex-preprocess): Renamed from `org-export-latex-cleaned-string'. 2008-04-27 Bastien Guerry <bzg@altern.org> * org/org-publish.el (org-publish-get-base-files-1): New function. (org-publish-get-base-files): Use it. (org-publish-temp-files): New variable. Don't require 'dired-aux anymore. (org-publish-initial-buffer): New variable. (org-publish-org-to, org-publish): Use it. (org-publish-get-base-files-1): Bug fix: get the proper list of files when recursing thru a directory. (org-publish-get-base-files): Use the :exclude property to skip both files and directories.
author Carsten Dominik <dominik@science.uva.nl>
date Sun, 27 Apr 2008 18:33:39 +0000
parents e670b615aad9
children ee5932bf781d
line wrap: on
line source

;;; json.el --- JavaScript Object Notation parser / generator

;; Copyright (C) 2006, 2007, 2008 Free Software Foundation, Inc.

;; Author: Edward O'Connor <ted@oconnor.cx>
;; Version: 1.2
;; Keywords: convenience

;; This file is part of GNU Emacs.

;; GNU Emacs 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, or (at your option)
;; any later version.

;; GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; This is a library for parsing and generating JSON (JavaScript Object
;; Notation).

;; Learn all about JSON here: <URL:http://json.org/>.

;; The user-serviceable entry points for the parser are the functions
;; `json-read' and `json-read-from-string'. The encoder has a single
;; entry point, `json-encode'.

;; Since there are several natural representations of key-value pair
;; mappings in elisp (alist, plist, hash-table), `json-read' allows you
;; to specify which you'd prefer (see `json-object-type' and
;; `json-array-type').

;; Similarly, since `false' and `null' are distinct in JSON, you can
;; distinguish them by binding `json-false' and `json-null' as desired.

;;; History:

;; 2006-03-11 - Initial version.
;; 2006-03-13 - Added JSON generation in addition to parsing. Various
;;              other cleanups, bugfixes, and improvements.
;; 2006-12-29 - XEmacs support, from Aidan Kehoe <kehoea@parhasard.net>.
;; 2008-02-21 - Installed in GNU Emacs.

;;; Code:

(eval-when-compile (require 'cl))

;; Compatibility code

(defalias 'json-encode-char0 'encode-char)
(defalias 'json-decode-char0 'decode-char)


;; Parameters

(defvar json-object-type 'alist
  "Type to convert JSON objects to.
Must be one of `alist', `plist', or `hash-table'. Consider let-binding
this around your call to `json-read' instead of `setq'ing it.")

(defvar json-array-type 'vector
  "Type to convert JSON arrays to.
Must be one of `vector' or `list'. Consider let-binding this around
your call to `json-read' instead of `setq'ing it.")

(defvar json-key-type nil
  "Type to convert JSON keys to.
Must be one of `string', `symbol', `keyword', or nil.

If nil, `json-read' will guess the type based on the value of
`json-object-type':

    If `json-object-type' is:   nil will be interpreted as:
      `hash-table'                `string'
      `alist'                     `symbol'
      `plist'                     `keyword'

Note that values other than `string' might behave strangely for
Sufficiently Weird keys. Consider let-binding this around your call to
`json-read' instead of `setq'ing it.")

(defvar json-false :json-false
  "Value to use when reading JSON `false'.
If this has the same value as `json-null', you might not be able to tell
the difference between `false' and `null'. Consider let-binding this
around your call to `json-read' instead of `setq'ing it.")

(defvar json-null nil
  "Value to use when reading JSON `null'.
If this has the same value as `json-false', you might not be able to
tell the difference between `false' and `null'. Consider let-binding
this around your call to `json-read' instead of `setq'ing it.")



;;; Utilities

(defun json-join (strings separator)
  "Join STRINGS with SEPARATOR."
  (mapconcat 'identity strings separator))

(defun json-alist-p (list)
  "Non-null iff LIST is an alist."
  (or (null list)
      (and (consp (car list))
           (json-alist-p (cdr list)))))

(defun json-plist-p (list)
  "Non-null iff LIST is a plist."
  (or (null list)
      (and (keywordp (car list))
           (consp (cdr list))
           (json-plist-p (cddr list)))))

;; Reader utilities

(defsubst json-advance (&optional n)
  "Skip past the following N characters."
  (unless n (setq n 1))
  (let ((goal (+ (point) n)))
    (goto-char goal)
    (when (< (point) goal)
      (signal 'end-of-file nil))))

(defsubst json-peek ()
  "Return the character at point."
  (let ((char (char-after (point))))
    (or char :json-eof)))

(defsubst json-pop ()
  "Advance past the character at point, returning it."
  (let ((char (json-peek)))
    (if (eq char :json-eof)
        (signal 'end-of-file nil)
      (json-advance)
      char)))

(defun json-skip-whitespace ()
  "Skip past the whitespace at point."
  (while (looking-at "[\t\r\n\f\b ]")
    (goto-char (match-end 0))))



;; Error conditions

(put 'json-error 'error-message "Unknown JSON error")
(put 'json-error 'error-conditions '(json-error error))

(put 'json-readtable-error 'error-message "JSON readtable error")
(put 'json-readtable-error 'error-conditions
     '(json-readtable-error json-error error))

(put 'json-unknown-keyword 'error-message "Unrecognized keyword")
(put 'json-unknown-keyword 'error-conditions
     '(json-unknown-keyword json-error error))

(put 'json-number-format 'error-message "Invalid number format")
(put 'json-number-format 'error-conditions
     '(json-number-format json-error error))

(put 'json-string-escape 'error-message "Bad unicode escape")
(put 'json-string-escape 'error-conditions
     '(json-string-escape json-error error))

(put 'json-string-format 'error-message "Bad string format")
(put 'json-string-format 'error-conditions
     '(json-string-format json-error error))

(put 'json-object-format 'error-message "Bad JSON object")
(put 'json-object-format 'error-conditions
     '(json-object-format json-error error))



;;; Keywords

(defvar json-keywords '("true" "false" "null")
  "List of JSON keywords.")

;; Keyword parsing

(defun json-read-keyword (keyword)
  "Read a JSON keyword at point.
KEYWORD is the keyword expected."
  (unless (member keyword json-keywords)
    (signal 'json-unknown-keyword (list keyword)))
  (mapc (lambda (char)
          (unless (char-equal char (json-peek))
            (signal 'json-unknown-keyword
                    (list (save-excursion
                            (backward-word 1)
                            (thing-at-point 'word)))))
          (json-advance))
        keyword)
  (unless (looking-at "\\(\\s-\\|[],}]\\|$\\)")
    (signal 'json-unknown-keyword
            (list (save-excursion
                    (backward-word 1)
                    (thing-at-point 'word)))))
  (cond ((string-equal keyword "true") t)
        ((string-equal keyword "false") json-false)
        ((string-equal keyword "null") json-null)))

;; Keyword encoding

(defun json-encode-keyword (keyword)
  "Encode KEYWORD as a JSON value."
  (cond ((eq keyword t)          "true")
        ((eq keyword json-false) "false")
        ((eq keyword json-null)  "null")))

;;; Numbers

;; Number parsing

(defun json-read-number ()
  "Read the JSON number following point.
N.B.: Only numbers which can fit in Emacs Lisp's native number
representation will be parsed correctly."
  (if (char-equal (json-peek) ?-)
      (progn
        (json-advance)
        (- 0 (json-read-number)))
    (if (looking-at "[0-9]+\\([.][0-9]+\\)?\\([eE][+-]?[0-9]+\\)?")
        (progn
          (goto-char (match-end 0))
          (string-to-number (match-string 0)))
      (signal 'json-number-format (list (point))))))

;; Number encoding

(defun json-encode-number (number)
  "Return a JSON representation of NUMBER."
  (format "%s" number))

;;; Strings

(defvar json-special-chars
  '((?\" . ?\")
    (?\\ . ?\\)
    (?/ . ?/)
    (?b . ?\b)
    (?f . ?\f)
    (?n . ?\n)
    (?r . ?\r)
    (?t . ?\t))
  "Characters which are escaped in JSON, with their elisp counterparts.")

;; String parsing

(defun json-read-escaped-char ()
  "Read the JSON string escaped character at point."
  ;; Skip over the '\'
  (json-advance)
  (let* ((char (json-pop))
         (special (assq char json-special-chars)))
    (cond
     (special (cdr special))
     ((not (eq char ?u)) char)
     ((looking-at "[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]")
      (let ((hex (match-string 0)))
        (json-advance 4)
        (json-decode-char0 'ucs (string-to-number hex 16))))
     (t
      (signal 'json-string-escape (list (point)))))))

(defun json-read-string ()
  "Read the JSON string at point."
  (unless (char-equal (json-peek) ?\")
    (signal 'json-string-format (list "doesn't start with '\"'!")))
  ;; Skip over the '"'
  (json-advance)
  (let ((characters '())
        (char (json-peek)))
    (while (not (char-equal char ?\"))
      (push (if (char-equal char ?\\)
                (json-read-escaped-char)
              (json-pop))
            characters)
      (setq char (json-peek)))
    ;; Skip over the '"'
    (json-advance)
    (if characters
        (apply 'string (nreverse characters))
      "")))

;; String encoding

(defun json-encode-char (char)
  "Encode CHAR as a JSON string."
  (setq char (json-encode-char0 char 'ucs))
  (let ((control-char (car (rassoc char json-special-chars))))
    (cond
     ;; Special JSON character (\n, \r, etc.)
     (control-char
      (format "\\%c" control-char))
     ;; ASCIIish printable character
     ((and (> char 31) (< char 161))
      (format "%c" char))
     ;; Fallback: UCS code point in \uNNNN form
     (t
      (format "\\u%04x" char)))))

(defun json-encode-string (string)
  "Return a JSON representation of STRING."
  (format "\"%s\"" (mapconcat 'json-encode-char string "")))

;;; JSON Objects

(defun json-new-object ()
  "Create a new Elisp object corresponding to a JSON object.
Please see the documentation of `json-object-type'."
  (cond ((eq json-object-type 'hash-table)
         (make-hash-table :test 'equal))
        (t
         (list))))

(defun json-add-to-object (object key value)
  "Add a new KEY -> VALUE association to OBJECT.
Returns the updated object, which you should save, e.g.:
    (setq obj (json-add-to-object obj \"foo\" \"bar\"))
Please see the documentation of `json-object-type' and `json-key-type'."
  (let ((json-key-type
         (if (eq json-key-type nil)
             (cdr (assq json-object-type '((hash-table . string)
                                           (alist . symbol)
                                           (plist . keyword))))
           json-key-type)))
    (setq key
          (cond ((eq json-key-type 'string)
                 key)
                ((eq json-key-type 'symbol)
                 (intern key))
                ((eq json-key-type 'keyword)
                 (intern (concat ":" key)))))
    (cond ((eq json-object-type 'hash-table)
           (puthash key value object)
           object)
          ((eq json-object-type 'alist)
           (cons (cons key value) object))
          ((eq json-object-type 'plist)
           (cons key (cons value object))))))

;; JSON object parsing

(defun json-read-object ()
  "Read the JSON object at point."
  ;; Skip over the "{"
  (json-advance)
  (json-skip-whitespace)
  ;; read key/value pairs until "}"
  (let ((elements (json-new-object))
        key value)
    (while (not (char-equal (json-peek) ?}))
      (json-skip-whitespace)
      (setq key (json-read-string))
      (json-skip-whitespace)
      (if (char-equal (json-peek) ?:)
          (json-advance)
        (signal 'json-object-format (list ":" (json-peek))))
      (setq value (json-read))
      (setq elements (json-add-to-object elements key value))
      (json-skip-whitespace)
      (unless (char-equal (json-peek) ?})
        (if (char-equal (json-peek) ?,)
            (json-advance)
          (signal 'json-object-format (list "," (json-peek))))))
    ;; Skip over the "}"
    (json-advance)
    elements))

;; Hash table encoding

(defun json-encode-hash-table (hash-table)
  "Return a JSON representation of HASH-TABLE."
  (format "{%s}"
          (json-join
           (let (r)
             (maphash
              (lambda (k v)
                (push (format "%s:%s"
                              (json-encode k)
                              (json-encode v))
                      r))
              hash-table)
             r)
           ", ")))

;; List encoding (including alists and plists)

(defun json-encode-alist (alist)
  "Return a JSON representation of ALIST."
  (format "{%s}"
          (json-join (mapcar (lambda (cons)
                               (format "%s:%s"
                                       (json-encode (car cons))
                                       (json-encode (cdr cons))))
                             alist)
                     ", ")))

(defun json-encode-plist (plist)
  "Return a JSON representation of PLIST."
  (let (result)
    (while plist
      (push (concat (json-encode (car plist))
                    ":"
                    (json-encode (cadr plist)))
            result)
      (setq plist (cddr plist)))
    (concat "{" (json-join (nreverse result) ", ") "}")))

(defun json-encode-list (list)
  "Return a JSON representation of LIST.
Tries to DWIM: simple lists become JSON arrays, while alists and plists
become JSON objects."
  (cond ((null list)         "null")
        ((json-alist-p list) (json-encode-alist list))
        ((json-plist-p list) (json-encode-plist list))
        ((listp list)        (json-encode-array list))
        (t
         (signal 'json-error (list list)))))

;;; Arrays

;; Array parsing

(defun json-read-array ()
  "Read the JSON array at point."
  ;; Skip over the "["
  (json-advance)
  (json-skip-whitespace)
  ;; read values until "]"
  (let (elements)
    (while (not (char-equal (json-peek) ?\]))
      (push (json-read) elements)
      (json-skip-whitespace)
      (unless (char-equal (json-peek) ?\])
        (if (char-equal (json-peek) ?,)
            (json-advance)
          (signal 'json-error (list 'bleah)))))
    ;; Skip over the "]"
    (json-advance)
    (apply json-array-type (nreverse elements))))

;; Array encoding

(defun json-encode-array (array)
  "Return a JSON representation of ARRAY."
  (concat "[" (mapconcat 'json-encode array ", ") "]"))



;;; JSON reader.

(defvar json-readtable
  (let ((table
         '((?t json-read-keyword "true")
           (?f json-read-keyword "false")
           (?n json-read-keyword "null")
           (?{ json-read-object)
           (?\[ json-read-array)
           (?\" json-read-string))))
    (mapc (lambda (char)
            (push (list char 'json-read-number) table))
          '(?- ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))
    table)
  "Readtable for JSON reader.")

(defun json-read ()
  "Parse and return the JSON object following point.
Advances point just past JSON object."
  (json-skip-whitespace)
  (let ((char (json-peek)))
    (if (not (eq char :json-eof))
        (let ((record (cdr (assq char json-readtable))))
          (if (functionp (car record))
              (apply (car record) (cdr record))
            (signal 'json-readtable-error record)))
      (signal 'end-of-file nil))))

;; Syntactic sugar for the reader

(defun json-read-from-string (string)
  "Read the JSON object contained in STRING and return it."
  (with-temp-buffer
    (insert string)
    (goto-char (point-min))
    (json-read)))

(defun json-read-file (file)
  "Read the first JSON object contained in FILE and return it."
  (with-temp-buffer
    (insert-file-contents file)
    (goto-char (point-min))
    (json-read)))



;;; JSON encoder

(defun json-encode (object)
  "Return a JSON representation of OBJECT as a string."
  (cond ((memq object (list t json-null json-false))
         (json-encode-keyword object))
        ((stringp object)      (json-encode-string object))
        ((keywordp object)     (json-encode-string
                                (substring (symbol-name object) 1)))
        ((symbolp object)      (json-encode-string
                                (symbol-name object)))
        ((numberp object)      (json-encode-number object))
        ((arrayp object)       (json-encode-array object))
        ((hash-table-p object) (json-encode-hash-table object))
        ((listp object)        (json-encode-list object))
        (t                     (signal 'json-error (list object)))))

(provide 'json)

;; arch-tag: 15f6e4c8-b831-4172-8749-bbc680c50ea1
;;; json.el ends here