|
|
- ;;; parseedn.el --- Clojure/EDN parser -*- lexical-binding: t; -*-
-
- ;; Copyright (C) 2017-2018 Arne Brasseur
-
- ;; Author: Arne Brasseur <arne@arnebrasseur.net>
- ;; Keywords: lisp clojure edn parser
- ;; Package-Version: 20190331.1058
- ;; Package-Requires: ((emacs "25") (a "0.1.0alpha4") (parseclj "0.1.0"))
- ;; Version: 0.1.0
-
- ;; This file is not part of GNU Emacs.
-
- ;; This file 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.
-
- ;; This file 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:
-
- ;; parseedn is an Emacs Lisp library for parsing EDN (Clojure) data.
- ;; It uses parseclj's shift-reduce parser internally.
-
- ;; EDN and Emacs Lisp have some important differences that make
- ;; translation from one to the other not transparent (think
- ;; representing an EDN map into Elisp, or being able to differentiate
- ;; between false and nil in Elisp). Because of this, parseedn takes
- ;; certain decisions when parsing and transforming EDN data into Elisp
- ;; data types. For more information please refer to parseclj's design
- ;; documentation.
-
- ;;; Code:
-
- ;; The EDN spec is not clear about whether \u0123 and \o012 are supported in
- ;; strings. They are described as character literals, but not as string escape
- ;; codes. In practice all implementations support them (mostly with broken
- ;; surrogate pair support), so we do the same. Sorry, emoji 🙁.
- ;;
- ;; Note that this is kind of broken, we don't correctly detect if \u or \o forms
- ;; don't have the right forms.
-
- (require 'a)
- (require 'cl-lib)
- (require 'parseclj-parser)
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Reader
-
- (defvar parseedn-default-tag-readers
- (a-list 'inst (lambda (s)
- (cl-list* 'edn-inst (date-to-time s)))
- 'uuid (lambda (s)
- (list 'edn-uuid s)))
- "Default reader functions for handling tagged literals in EDN.
- These are the ones defined in the EDN spec, #inst and #uuid. It
- is not recommended you change this variable, as this globally
- changes the behavior of the EDN reader. Instead pass your own
- handlers as an optional argument to the reader functions.")
-
- (defun parseedn-reduce-leaf (stack token _options)
- "Put in the STACK an elisp value representing TOKEN.
-
- OPTIONS is an association list. See `parseclj-parse' for more information
- on available options."
- (if (member (parseclj-lex-token-type token) (list :whitespace :comment))
- stack
- (cons (parseclj-lex--leaf-token-value token) stack)))
-
- (defun parseedn-reduce-branch (stack opening-token children options)
- "Reduce STACK with an sequence containing a collection of other elisp values.
- Ignores discard tokens.
-
- OPENING-TOKEN is a lex token representing an opening paren, bracket or
- brace.
- CHILDREN is a collection elisp values to be reduced into an elisp
- sequence.
- OPTIONS is an association list. See `parseclj-parse' for more information
- on available options."
- (let ((tag-readers (a-merge parseedn-default-tag-readers (a-get options :tag-readers)))
- (token-type (parseclj-lex-token-type opening-token)))
- (if (eq token-type :discard)
- stack
- (cons
- (cl-case token-type
- (:root children)
- (:lparen children)
- (:lbracket (apply #'vector children))
- (:set (list 'edn-set children))
- (:lbrace (let* ((kvs (seq-partition children 2))
- (hash-map (make-hash-table :test 'equal :size (length kvs))))
- (seq-do (lambda (pair)
- (puthash (car pair) (cadr pair) hash-map))
- kvs)
- hash-map))
- (:tag (let* ((tag (intern (substring (a-get opening-token :form) 1)))
- (reader (a-get tag-readers tag :missing)))
- (when (eq :missing reader)
- (user-error "No reader for tag #%S in %S" tag (a-keys tag-readers)))
- (funcall reader (car children)))))
- stack))))
-
- (defun parseedn-read (&optional tag-readers)
- "Read content from current buffer and parse it as EDN source.
- Returns an Emacs Lisp value.
-
- TAG-READERS is an optional association list where keys are symbols
- identifying *tags*, and values are tag handler functions that receive one
- argument: *the tagged element*, and specify how to interpret it."
- (parseclj-parser #'parseedn-reduce-leaf
- #'parseedn-reduce-branch
- (a-list :tag-readers tag-readers)))
-
- (defun parseedn-read-str (s &optional tag-readers)
- "Parse string S as EDN.
- Returns an Emacs Lisp value.
-
- TAG-READERS is an optional association list. For more information, see
- `parseedn-read'."
- (with-temp-buffer
- (insert s)
- (goto-char 1)
- (car (parseedn-read tag-readers))))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Printer
-
- (defun parseedn-print-seq (coll)
- "Insert sequence COLL as EDN into the current buffer."
- (parseedn-print (elt coll 0))
- (let ((next (seq-drop coll 1)))
- (when (not (seq-empty-p next))
- (insert " ")
- (parseedn-print-seq next))))
-
- (defun parseedn-print-kvs (map)
- "Insert hash table MAP as an EDN map into the current buffer."
- (let ((keys (a-keys map)))
- (parseedn-print (car keys))
- (insert " ")
- (parseedn-print (a-get map (car keys)))
- (let ((next (cdr keys)))
- (when (not (seq-empty-p next))
- (insert ", ")
- (parseedn-print-kvs next)))))
-
- (defun parseedn-print (datum)
- "Insert DATUM as EDN into the current buffer.
- DATUM can be any Emacs Lisp value."
- (cond
- ((or (null datum) (numberp datum))
- (prin1 datum (current-buffer)))
-
- ((stringp datum)
- (insert "\"")
- (seq-doseq (char datum)
- (insert (cl-case char
- (?\t "\\t")
- (?\f "\\f")
- (?\" "\\\"")
- (?\r "\\r")
- (?\n"foo\t" "\\n")
- (?\\ "\\\\")
- (t (char-to-string char)))))
- (insert "\""))
-
- ((eq t datum)
- (insert "true"))
-
- ((symbolp datum)
- (insert (symbol-name datum)))
-
- ((vectorp datum) (insert "[") (parseedn-print-seq datum) (insert "]"))
-
- ((consp datum)
- (cond
- ((eq 'edn-set (car datum))
- (insert "#{") (parseedn-print-seq (cadr datum)) (insert "}"))
- (t (insert "(") (parseedn-print-seq datum) (insert ")"))))
-
- ((hash-table-p datum)
- (insert "{") (parseedn-print-kvs datum) (insert "}"))))
-
- (defun parseedn-print-str (datum)
- "Return a string containing DATUM as EDN.
- DATUM can be any Emacs Lisp value."
- (with-temp-buffer
- (parseedn-print datum)
- (buffer-substring-no-properties (point-min) (point-max))))
-
- (provide 'parseedn)
-
- ;;; parseedn.el ends here
|