[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/parseedn ef0f8772bf 01/32: Initial commit
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/parseedn ef0f8772bf 01/32: Initial commit |
Date: |
Tue, 28 Dec 2021 14:04:40 -0500 (EST) |
branch: elpa/parseedn
commit ef0f8772bf5b05bf420b447b74c3cbe3c0b31338
Author: Daniel Barreto <daniel.barreto.n@gmail.com>
Commit: Daniel Barreto <daniel.barreto.n@gmail.com>
Initial commit
---
.gitignore | 2 +
.travis.yml | 22 +++
Cask | 11 ++
README.md | 65 ++++++++
benchmark/speed-comparison.el | 26 +++
parseedn.el | 191 ++++++++++++++++++++++
test/parseclj-test-data.el | 345 ++++++++++++++++++++++++++++++++++++++++
test/parseedn-el-parity-test.el | 286 +++++++++++++++++++++++++++++++++
test/parseedn-test.el | 77 +++++++++
9 files changed, 1025 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..433da842b5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.cask
+*.elc
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000..2c544d182d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,22 @@
+language: emacs
+cache: apt
+env:
+ - EVM_EMACS=emacs-25.1-travis
+ - EVM_EMACS=emacs-25.2-travis
+ - EVM_EMACS=emacs-25.3-travis
+ - EVM_EMACS=emacs-26-pretest-travis
+ - EVM_EMACS=emacs-git-snapshot-travis
+matrix:
+ fast_finish: true
+ allow_failures:
+ - env: EVM_EMACS=emacs-26-pretest-travis
+ - env: EVM_EMACS=emacs-git-snapshot-travis
+before_install:
+ - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw >
travis.sh && source ./travis.sh
+ - evm install $EVM_EMACS --use --skip
+install:
+ - cask install
+script:
+ - export ECUKES_EMACS=${EMACS:-$(which emacs)}
+ - $ECUKES_EMACS --version
+ - cask exec ert-runner
diff --git a/Cask b/Cask
new file mode 100644
index 0000000000..33c29274a8
--- /dev/null
+++ b/Cask
@@ -0,0 +1,11 @@
+(source gnu)
+(source melpa)
+
+(package-file "parseedn.el")
+
+(development
+ (depends-on "a")
+ (depends-on "parseclj"
+ :git "https://github.com/clojure-emacs/parseclj"
+ :ref "13059d8529f8352497f5ceaf671476d7b8968dac")
+ (depends-on "ert-runner"))
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..fc4dcc169b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,65 @@
+[![Build
Status](https://travis-ci.org/clojure-emacs/parseedn.svg?branch=master)](https://travis-ci.org/clojure-emacs/parseedn)
+
+# EDN parser for Emacs Lisp
+
+`parseedn` is an Emacs Lisp library for parsing [EDN
+data](https://github.com/edn-format/edn). It uses the
+[`parseclj`](https://github.com/clojure-emacs/parseclj)'s shift-reduce parser
+internally.
+
+EDN and Emacs Lisp share 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`
+DESIGN.md](https://github.com/clojure-emacs/parseclj/blob/master/DESIGN.md)
+document.
+
+Lastly, `parseedn` is in **alpha** stage, so its API is subject to change.
+
+## Installation
+
+Currently `parseend` is not part of [MELPA](http://melpa.milkbox.net/), so the
+best way to install it is by getting your own copy of `parseedn`, *and* its
+`parseclj` dependency, and putting it somewhere in your Emacs
+[`load-path`](https://www.emacswiki.org/emacs/LoadPath).
+
+You can just copy-paste this code into your Emacs init file:
+
+```emacs-lisp
+(add-to-list 'load-path "/path/to/your/copy/of/parseclj/")
+(add-to-list 'load-path "/path/to/your/copy/of/parseedn/")
+```
+
+## Usage
+
+- `parseedn-read`
+
+ Read content from the current buffer as EDN and transforms it into an Emacs
+ Lisp value.
+
+- `parseedn-read-str` str
+
+ Read STR as EDN and transfroms it into an Emacs Lisp value.
+
+- `parseedn-print` datum
+
+ Inserts DATUM as EDN Into the current buffer. DATUM can be any Emacs Lisp
+ value.
+
+- `parseedn-print-str` datum
+
+ Returns a string containing DATUM as EDN. DATUM can be any Emacs Lisp
+ value.
+
+## Prior art
+
+[edn.el](https://github.com/expez/edn.el) is an EDN-to-elisp parser based on
the
+PEG parser generator library.
+
+## License
+
+© 2017-2018 Arne Brasseur
+
+Distributed under the terms of the GNU General Public License 3.0 or later. See
+[LICENSE](LICENSE).
diff --git a/benchmark/speed-comparison.el b/benchmark/speed-comparison.el
new file mode 100644
index 0000000000..12682112d2
--- /dev/null
+++ b/benchmark/speed-comparison.el
@@ -0,0 +1,26 @@
+;; takes a file containing edn file names, one per line
+;;
+;; locate *.edn > edn.list
+;;
+;; results end up as edn in *edn-parse-time-results*
+(with-current-buffer (find-file-noselect "edn.list")
+ (goto-char 1)
+ (while (and (< (point) (point-max)))
+ (end-of-line)
+ (let* ((fn (buffer-substring-no-properties (line-beginning-position)
(point)))
+ (buff (find-file-noselect fn))
+ (edn-time 0)
+ (clj-time 0))
+ ;;(message fn)
+ (with-current-buffer buff
+ (let ((start (time-to-seconds (current-time))))
+ (parseedn-read)
+ (setq clj-time (+ clj-time (- (time-to-seconds (current-time))
start))))
+ (goto-char 1)
+ (let ((start (time-to-seconds (current-time))))
+ (edn-read)
+ (setq edn-time (+ edn-time (- (time-to-seconds (current-time))
start)))))
+ (kill-buffer buff)
+ (when (< (point) (point-max)) (right-char))
+ (with-current-buffer "*edn-parse-time-results*"
+ (insert "{:file \"" fn "\", :edn-time " (number-to-string edn-time) ",
:clj-time " (number-to-string clj-time) "}\n")))))
diff --git a/parseedn.el b/parseedn.el
new file mode 100644
index 0000000000..70c257ed78
--- /dev/null
+++ b/parseedn.el
@@ -0,0 +1,191 @@
+;;; parseedn.el --- Clojure/EDN parser -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017-2018 Arne Brasseur
+
+;; Author: Arne Brasseur <arne@arnebrasseur.net>
+;; Keywords: lisp
+;; 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:
+
+;; The EDN <-> Elisp reader and printer
+
+;;; Code:
+
+;; The EDN spec is not clear about wether \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 '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
diff --git a/test/parseclj-test-data.el b/test/parseclj-test-data.el
new file mode 100644
index 0000000000..1b739de399
--- /dev/null
+++ b/test/parseclj-test-data.el
@@ -0,0 +1,345 @@
+;;; parseclj-test-data.el --- Clojure/EDN parser - test data
+
+;; Copyright (C) 2017-2018 Arne Brasseur
+
+;; Author: Arne Brasseur <arne@arnebrasseur.net>
+
+;; 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:
+
+;; Test data for reader / parser / printer / unparser
+
+;;; Code:
+
+(setq parseclj-test-data
+ (a-list
+
+ "simple-list"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source "(1 2 3)"
+ :edn '((1 2 3))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :number)
+ (:position . 2)
+ (:form . "1")
+ (:value . 1))
+ ((:node-type . :number)
+ (:position . 4)
+ (:form . "2")
+ (:value . 2))
+ ((:node-type . :number)
+ (:position . 6)
+ (:form . "3")
+ (:value . 3)))))))))
+
+
+ "empty-list"
+ (a-list
+ :source "()"
+ :edn '(())
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . nil))))))
+
+ "size-1"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source "(1)"
+ :edn '((1))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :number)
+ (:position . 2)
+ (:form . "1")
+ (:value . 1)))))))))
+
+ "leafs"
+ (a-list
+ :source "(nil true false hello-world)"
+ :edn '((nil t nil hello-world))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :nil)
+ (:position . 2)
+ (:form . "nil")
+ (:value . nil))
+ ((:node-type . :true)
+ (:position . 6)
+ (:form . "true")
+ (:value . t))
+ ((:node-type . :false)
+ (:position . 11)
+ (:form . "false")
+ (:value . nil))
+ ((:node-type . :symbol)
+ (:position . 17)
+ (:form . "hello-world")
+ (:value . hello-world)))))))))
+
+ "qualified-symbol"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source "clojure.string/join"
+ :edn '(clojure.string/join)
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :symbol)
+ (:position . 1)
+ (:form . "clojure.string/join")
+ (:value . clojure.string/join))))))
+
+ "nested-lists"
+ (a-list
+ :source "((.9 abc (true) (hello)))"
+ :edn '(((0.9 abc (t) (hello))))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 2)
+ (:children ((:node-type . :number)
+ (:position . 3)
+ (:form . ".9")
+ (:value . 0.9))
+ ((:node-type . :symbol)
+ (:position . 6)
+ (:form . "abc")
+ (:value . abc))
+ ((:node-type . :list)
+ (:position . 10)
+ (:children
((:node-type . :true)
+
(:position . 11)
+ (:form .
"true")
+ (:value .
t))))
+ ((:node-type . :list)
+ (:position . 17)
+ (:children
((:node-type . :symbol)
+
(:position . 18)
+ (:form .
"hello")
+ (:value .
hello)))))))))))))
+
+ "strings-1"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source "\"abc hello \\t\\\"x\""
+ :edn '("abc hello \t\"x")
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :string)
+ (:position . 1)
+ (:form . "\"abc hello \\t\\\"x\"")
+ (:value . "abc hello \t\"x"))))))
+
+ "strings-2"
+ (a-list
+ :source "(\"---\\f---\\\"-'\\'-\\\\-\\r\\n\")"
+ :edn '(("---\f---\"-''-\\-\r\n"))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :string)
+ (:position . 2)
+ (:form .
"\"---\\f---\\\"-'\\'-\\\\-\\r\\n\"")
+ (:value .
"---\f---\"-''-\\-\r\n")))))))))
+
+ "chars-1"
+ (a-list
+ :source "(\\newline \\return \\space \\tab \\a \\b \\c \\u0078 \\o171)"
+ :edn '((?\n ?\r ?\ ?\t ?a ?b ?c ?x ?y))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :character)
(:position . 2) (:form . "\\newline") (:value . ?\n))
+ ((:node-type . :character)
(:position . 11) (:form . "\\return") (:value . ?\r))
+ ((:node-type . :character)
(:position . 19) (:form . "\\space") (:value . 32))
+ ((:node-type . :character)
(:position . 26) (:form . "\\tab") (:value . ?\t))
+ ((:node-type . :character)
(:position . 31) (:form . "\\a") (:value . ?a))
+ ((:node-type . :character)
(:position . 34) (:form . "\\b") (:value . ?b))
+ ((:node-type . :character)
(:position . 37) (:form . "\\c") (:value . ?c))
+ ((:node-type . :character)
(:position . 40) (:form . "\\u0078") (:value . ?x))
+ ((:node-type . :character)
(:position . 47) (:form . "\\o171") (:value . ?y)))))))))
+
+ "chars-2"
+ (a-list
+ :source "\"\\u0078 \\o171\""
+ :edn '("x y")
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :string)
+ (:position . 1)
+ (:form . "\"\\u0078 \\o171\"")
+ (:value . "x y"))))))
+
+ "keywords"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source ":foo-bar"
+ :edn '(:foo-bar)
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :keyword)
+ (:position . 1)
+ (:form . ":foo-bar")
+ (:value . :foo-bar))))))
+
+ "vector"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source "[123]"
+ :edn '([123])
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :vector)
+ (:position . 1)
+ (:children . (((:node-type . :number)
+ (:position . 2)
+ (:form . "123")
+ (:value . 123)))))))))
+
+ "map"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source "{:count 123}"
+ :edn (list (a-hash-table :count 123))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :map)
+ (:position . 1)
+ (:children . (((:node-type . :keyword)
+ (:position . 2)
+ (:form . ":count")
+ (:value . :count))
+ ((:node-type . :number)
+ (:position . 9)
+ (:form . "123")
+ (:value . 123)))))))))
+
+ "set"
+ (a-list
+ :tags '(:edn-roundtrip)
+ :source "#{:x}"
+ :edn '((edn-set (:x)))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :set)
+ (:position . 1)
+ (:children . (((:node-type . :keyword)
+ (:position . 3)
+ (:form . ":x")
+ (:value . :x)))))))))
+
+ "discard"
+ (a-list
+ :source "(10 #_11 12 #_#_ 13 14)"
+ :edn '((10 12))
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :number)
+ (:position . 2)
+ (:form . "10")
+ (:value . 10))
+ ((:node-type . :number)
+ (:position . 10)
+ (:form . "12")
+ (:value . 12)))))))))
+
+
+ "tag-1"
+ (a-list
+ :source "#foo/bar [1]"
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :tag)
+ (:position . 1)
+ (:tag . foo/bar)
+ (:children . (((:node-type . :vector)
+ (:position . 10)
+ (:children . (((:node-type .
:number)
+ (:position . 11)
+ (:form . "1")
+ (:value .
1))))))))))))
+
+ "tag-2"
+ (a-list
+ :source "(fn #param :param-name 1)"
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children . (((:node-type . :list)
+ (:position . 1)
+ (:children . (((:node-type . :symbol)
+ (:position . 2)
+ (:form . "fn")
+ (:value . fn))
+ ((:node-type . :tag)
+ (:position . 5)
+ (:tag . param)
+ (:children . (((:node-type .
:keyword)
+ (:position . 12)
+ (:form .
":param-name")
+ (:value .
:param-name)))))
+ ((:node-type . :number)
+ (:position . 24)
+ (:form . "1")
+ (:value . 1)))))))))
+
+ "nested-tags"
+ (a-list
+ :source "[#lazy-error #error {:cause \"Divide by zero\"}]"
+ :ast '((:node-type . :root)
+ (:position . 1)
+ (:children ((:node-type . :vector)
+ (:position . 1)
+ (:children ((:node-type . :tag)
+ (:position . 2)
+ (:tag . lazy-error)
+ (:children ((:node-type . :tag)
+ (:position . 14)
+ (:tag . error)
+ (:children ((:node-type .
:map)
+ (:position . 21)
+ (:children
((:node-type . :keyword)
+
(:position . 22)
+
(:form . ":cause")
+
(:value . :cause))
+
((:node-type . :string)
+
(:position . 29)
+
(:form . "\"Divide by zero\"")
+
(:value . "Divide by zero")))))))))))))
+
+ "booleans"
+ (a-list
+ :source "[nil true false]"
+ :edn '([nil t nil]))))
+
+;;; parseclj-test-data.el ends here
diff --git a/test/parseedn-el-parity-test.el b/test/parseedn-el-parity-test.el
new file mode 100644
index 0000000000..c58641a4f3
--- /dev/null
+++ b/test/parseedn-el-parity-test.el
@@ -0,0 +1,286 @@
+;;; edn-el-parity.el --- Tests from edn.el
+
+;; Author: Lars Andersen <expez@expez.com>, Arne Brasseur
<arne@arnebrasseur.net>
+
+;; Copyright (C) 2015 Lars Andersen
+
+;; 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:
+
+;; These tests are copied verbatim from the edn.el source, and adapted to use
+;; our API. This way we assure that parseclj can act as a drop-in replacement
+;; for edn.el.
+
+;;; Code:
+
+(require 'ert)
+(require 'parseclj)
+(eval-when-compile (require 'subr-x)) ;; for things like hash-table-keys
+
+(ert-deftest whitespace ()
+ (should (null (parseedn-read-str "")))
+ (should (null (parseedn-read-str " ")))
+ (should (null (parseedn-read-str " ")))
+ (should (null (parseedn-read-str " ")))
+ (should (null (parseedn-read-str " ")))
+ (should (null (parseedn-read-str ",")))
+ (should (null (parseedn-read-str ",,,,")))
+ (should (null (parseedn-read-str " , ,\n")))
+ (should (null (parseedn-read-str "\n ,, ")))
+ (should (equal [a b c d] (parseedn-read-str "[a ,,,,,, b,,,,,c ,d]"))))
+
+(ert-deftest symbols ()
+ :tags '(edn symbol)
+ (should (equal 'foo (parseedn-read-str "foo")))
+ (should (equal 'foo\. (parseedn-read-str "foo.")))
+ (should (equal '%foo\. (parseedn-read-str "%foo.")))
+ (should (equal 'foo/bar (parseedn-read-str "foo/bar")))
+ (equal 'some\#sort\#of\#symbol (parseedn-read-str "some#sort#of#symbol"))
+ (equal 'truefalse (parseedn-read-str "truefalse"))
+ (equal 'true. (parseedn-read-str "true."))
+ (equal '/ (parseedn-read-str "/"))
+ (should (equal '.true (parseedn-read-str ".true")))
+ (should (equal 'some:sort:of:symbol (parseedn-read-str
"some:sort:of:symbol")))
+ (equal 'foo-bar (parseedn-read-str "foo-bar"))
+ (should (equal '+some-symbol (parseedn-read-str "+some-symbol")))
+ (should (equal '-symbol (parseedn-read-str "-symbol"))))
+
+(ert-deftest booleans ()
+ :tags '(edn boolean)
+ (should (equal t (parseedn-read-str "true")))
+ (should (equal nil (parseedn-read-str "false "))))
+
+(ert-deftest characters ()
+ :tags '(edn characters)
+ (should (equal 97 (parseedn-read-str "\\a")))
+ (should (equal 960 (parseedn-read-str "\\u03C0")))
+ ;;(should (equal 'newline (parseedn-read-str "\\newline")))
+ )
+
+(ert-deftest elision ()
+ :tags '(edn elision)
+ (should-not (parseedn-read-str "#_foo"))
+ (should-not (parseedn-read-str "#_ 123"))
+ (should-not (parseedn-read-str "#_:foo"))
+ (should-not (parseedn-read-str "#_ \\a"))
+ (should-not (parseedn-read-str "#_
+\"foo\""))
+ (should-not (parseedn-read-str "#_ (1 2 3)"))
+ (should (equal '(1 3) (parseedn-read-str "(1 #_ 2 3)")))
+ (should (equal '[1 2 3 4] (parseedn-read-str "[1 2 #_[4 5 6] 3 4]")))
+ (should (map-equal (make-seeded-hash-table :foo :bar)
+ (parseedn-read-str "{:foo #_elided :bar}")))
+ (should (equal '(edn-set (1 2 3 4))
+ (parseedn-read-str "#{1 2 #_[1 2 3] 3 #_ (1 2) 4}")))
+ (should (equal [a d] (parseedn-read-str "[a #_ ;we are discarding what comes
next
+ c d]"))))
+
+(ert-deftest string ()
+ :tags '(edn string)
+ (should (equal "this is a string" (parseedn-read-str "\"this is a
string\"")))
+ (should (equal "this has an escaped \"quote in it"
+ (parseedn-read-str "\"this has an escaped \\\"quote in
it\"")))
+ (should (equal "foo\tbar" (parseedn-read-str "\"foo\\tbar\"")))
+ (should (equal "foo\nbar" (parseedn-read-str "\"foo\\nbar\"")))
+ (should (equal "this is a string \\ that has an escaped backslash"
+ (parseedn-read-str "\"this is a string \\\\ that has an
escaped backslash\"")))
+ (should (equal "[" (parseedn-read-str "\"[\""))))
+
+(ert-deftest keywords ()
+ :tags '(edn keywords)
+ (should (equal :namespace\.of\.some\.length/keyword-name
+ (parseedn-read-str ":namespace.of.some.length/keyword-name")))
+ (should (equal :\#/\# (parseedn-read-str ":#/#")))
+ (should (equal :\#/:a (parseedn-read-str ":#/:a")))
+ (should (equal :\#foo (parseedn-read-str ":#foo"))))
+
+(ert-deftest integers ()
+ :tags '(edn integers)
+ (should (= 0 (parseedn-read-str "0")))
+ (should (= 0 (parseedn-read-str "+0")))
+ (should (= 0 (parseedn-read-str "-0")))
+ (should (= 100 (parseedn-read-str "100")))
+ (should (= -100 (parseedn-read-str "-100"))))
+
+(ert-deftest floats ()
+ :tags '(edn floats)
+ (should (= 12.32 (parseedn-read-str "12.32")))
+ (should (= -12.32 (parseedn-read-str "-12.32")))
+ (should (= 9923.23 (parseedn-read-str "+9923.23")))
+ (should (= 4.5e+044 (parseedn-read-str "45e+43")))
+ (should (= -4.5e-042 (parseedn-read-str "-45e-43")))
+ (should (= 4.5e+044 (parseedn-read-str "45E+43"))))
+
+(ert-deftest lists ()
+ :tags '(edn lists)
+ (should-not (parseedn-read-str "()"))
+ (should (equal '(1 2 3) (parseedn-read-str "( 1 2 3)")))
+ (should (equal '(12.1 ?a foo :bar) (parseedn-read-str "(12.1 \\a foo
:bar)")))
+ (should (equal '((:foo bar :bar 12)) (parseedn-read-str "( (:foo bar :bar
12))")))
+ (should (equal
+ '(defproject com\.thortech/data\.edn "0.1.0-SNAPSHOT")
+ (parseedn-read-str "(defproject com.thortech/data.edn
\"0.1.0-SNAPSHOT\")"))))
+
+(ert-deftest vectors ()
+ :tags '(edn vectors)
+ (should (equal [] (parseedn-read-str "[]")))
+ (should (equal [] (parseedn-read-str "[ ]")))
+ (should (equal '[1 2 3] (parseedn-read-str "[ 1 2 3 ]")))
+ (should (equal '[12.1 ?a foo :bar] (parseedn-read-str "[ 12.1 \\a foo
:bar]")))
+ (should (equal '[[:foo bar :bar 12]] (parseedn-read-str "[[:foo bar :bar
12]]")))
+ (should (equal '[( :foo bar :bar 12 ) "foo"]
+ (parseedn-read-str "[(:foo bar :bar 12) \"foo\"]")))
+ (should (equal '[/ \. * ! _ \? $ % & = - +]
+ (parseedn-read-str "[/ . * ! _ ? $ % & = - +]")))
+ (should (equal
+ ;;[99 newline return space tab]
+ [99 10 13 32 9]
+ (parseedn-read-str "[\\c \\newline \\return \\space \\tab]"))))
+
+(defun map-equal (m1 m2)
+ (and (and (hash-table-p m1) (hash-table-p m2))
+ (eq (hash-table-test m1) (hash-table-test m2))
+ (= (hash-table-count m1) (hash-table-count m2))
+ (equal (hash-table-keys m1) (hash-table-keys m2))
+ (equal (hash-table-values m1) (hash-table-values m2))))
+
+(defun make-seeded-hash-table (&rest keys-and-values)
+ (let ((m (make-hash-table :test #'equal)))
+ (while keys-and-values
+ (puthash (pop keys-and-values) (pop keys-and-values) m))
+ m))
+
+(ert-deftest maps ()
+ :tags '(edn maps)
+ (should (hash-table-p (parseedn-read-str "{ }")))
+ (should (hash-table-p (parseedn-read-str "{}")))
+ (should (map-equal (make-seeded-hash-table :foo :bar :baz :qux)
+ (parseedn-read-str "{ :foo :bar :baz :qux}")))
+ (should (map-equal (make-seeded-hash-table 1 "123" 'vector [1 2 3])
+ (parseedn-read-str "{ 1 \"123\" vector [1 2 3]}")))
+ (should (map-equal (make-seeded-hash-table [1 2 3] "some numbers")
+ (parseedn-read-str "{[1 2 3] \"some numbers\"}"))))
+
+(ert-deftest sets ()
+ :tags '(edn sets)
+ (should (eq 'edn-set (car (parseedn-read-str "#{}"))))
+ (should (eq 'edn-set (car (parseedn-read-str "#{ }"))))
+ (should (equal '(edn-set (1 2 3)) (parseedn-read-str "#{1 2 3}")))
+ (should (equal '(edn-set (1 [1 2 3] 3)) (parseedn-read-str "#{1 [1 2 3]
3}"))))
+
+(ert-deftest comment ()
+ :tags '(edn comments)
+ (should-not (parseedn-read-str ";nada"))
+ (should (equal 1 (parseedn-read-str ";; comment
+1")))
+ (should (equal [1 2 3] (parseedn-read-str "[1 2 ;comment to eol
+3]")))
+ (should (equal '[valid more items] (parseedn-read-str "[valid;touching
trailing comment
+ more items]")))
+ (should (equal [valid vector more vector items] (parseedn-read-str "[valid
vector
+ ;;comment in vector
+ more vector items]"))))
+
+(defun test-val-passed-to-handler (val)
+ (should (listp val))
+ (should (= (length val) 2))
+ (should (= 1 (car val)))
+ 1)
+
+(setq parseedn-test-extra-handlers
+ (a-list
+ 'my/type #'test-val-passed-to-handler
+ 'my/other-type (lambda (val) 2)))
+
+(ert-deftest tags ()
+ :tags '(edn tags)
+ (should-error (parseedn-read-str "#my/type value"
parseedn-test-extra-handlers))
+ (should (= 1 (parseedn-read-str "#my/type (1 2)"
parseedn-test-extra-handlers)))
+ (should (= 2 (parseedn-read-str "#my/other-type {:foo :bar}"
parseedn-test-extra-handlers)))
+ (should-error (parseedn-read-str "#myapp/Person {:first \"Fred\" :last
\"Mertz\"}")))
+
+(ert-deftest roundtrip ()
+ :tags '(edn roundtrip)
+ (let ((data [1 2 3 :foo (4 5) qux "quux"]))
+ (should (equal data (parseedn-read-str (parseedn-print-str data))))
+ (should (map-equal (make-seeded-hash-table :foo :bar)
+ (parseedn-read-str (parseedn-print-str
(make-seeded-hash-table :foo :bar)))))
+ (should (equal '(edn-set (1 2 3 [3 1.11]))
+ (parseedn-read-str (parseedn-print-str '(edn-set (1 2 3 [3
1.11]))))))))
+
+(ert-deftest inst ()
+ :tags '(edn inst)
+ (let* ((inst-str "#inst \"1985-04-12T23:20:50.52Z\"")
+ (inst (parseedn-read-str inst-str))
+ (time (date-to-time "1985-04-12T23:20:50.52Z")))
+ (should (eq 'edn-inst (car inst)))
+ (should (equal time (cdr inst)))))
+
+(ert-deftest uuid ()
+ :tags '(edn uuid)
+ (let* ((str "f81d4fae-7dec-11d0-a765-00a0c91e6bf6")
+ (uuid (parseedn-read-str (concat "#uuid \"" str "\""))))
+ (should (eq 'edn-uuid (car uuid)))))
+
+;; (ert-deftest invalid-edn ()
+;; (should-error (parseedn-read-str "///"))
+;; (should-error (parseedn-read-str "~cat"))
+;; (should-error (parseedn-read-str "foo/bar/baz/qux/quux"))
+;; (should-error (parseedn-read-str "#foo/"))
+;; (should-error (parseedn-read-str "foo/"))
+;; (should-error (parseedn-read-str ":foo/"))
+;; (should-error (parseedn-read-str "#/foo"))
+;; (should-error (parseedn-read-str "/symbol"))
+;; (should-error (parseedn-read-str ":/foo"))
+;; (should-error (parseedn-read-str "+5symbol"))
+;; (should-error (parseedn-read-str ".\\newline"))
+;; (should-error (parseedn-read-str "0cat"))
+;; (should-error (parseedn-read-str "-4cats"))
+;; (should-error (parseedn-read-str ".9"))
+;; (should-error (parseedn-read-str ":keyword/with/too/many/slashes"))
+;; (should-error (parseedn-read-str ":a.b.c/"))
+;; (should-error (parseedn-read-str "\\itstoolong"))
+;; (should-error (parseedn-read-str ":#/:"))
+;; (should-error (parseedn-read-str "/foo//"))
+;; (should-error (parseedn-read-str "///foo"))
+;; (should-error (parseedn-read-str ":{}"))
+;; (should-error (parseedn-read-str "//"))
+;; (should-error (parseedn-read-str "##"))
+;; (should-error (parseedn-read-str "::"))
+;; (should-error (parseedn-read-str "::a"))
+;; (should-error (parseedn-read-str ".5symbol"))
+;; (should-error (parseedn-read-str "{ \"foo\""))
+;; (should-error (parseedn-read-str "{ \"foo\" :bar"))
+;; (should-error (parseedn-read-str "{"))
+;; (should-error (parseedn-read-str ":{"))
+;; (should-error (parseedn-read-str "{{"))
+;; (should-error (parseedn-read-str "}"))
+;; (should-error (parseedn-read-str ":}"))
+;; (should-error (parseedn-read-str "}}"))
+;; (should-error (parseedn-read-str "#:foo"))
+;; (should-error (parseedn-read-str "\\newline."))
+;; (should-error (parseedn-read-str "\\newline0.1"))
+;; (should-error (parseedn-read-str "^"))
+;; (should-error (parseedn-read-str ":^"))
+;; (should-error (parseedn-read-str "_:^"))
+;; (should-error (parseedn-read-str "#{{[}}"))
+;; (should-error (parseedn-read-str "[}"))
+;; (should-error (parseedn-read-str "@cat")))
+
+;;; edn-el-parity-test.el ends here
diff --git a/test/parseedn-test.el b/test/parseedn-test.el
new file mode 100644
index 0000000000..eb4cfb41fa
--- /dev/null
+++ b/test/parseedn-test.el
@@ -0,0 +1,77 @@
+;;; parseedn-test.el --- Unit tests for EDN reading/printing
+
+;; Copyright (C) 2017-2018 Arne Brasseur
+
+;; Author: Arne Brasseur <arne@arnebrasseur.net>
+
+;; 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
+
+;; Unit tests for EDN reading/printing
+
+;;; Code
+
+(require 'ert)
+(require 'parseclj)
+
+(load "test/parseclj-test-data.el")
+
+(ert-deftest parseedn-print-test ()
+ (should (equal (parseedn-print-str nil) "nil"))
+ (should (equal (parseedn-print-str 100) "100"))
+ (should (equal (parseedn-print-str 1.2) "1.2"))
+ (should (equal (parseedn-print-str [1 2 3]) "[1 2 3]"))
+ (should (equal (parseedn-print-str t) "true")))
+
+(ert-deftest parseedn-read-test ()
+ (should (equal (parseedn-read-str "true") t)))
+
+(defmacro define-parseedn-read-tests ()
+ `(progn
+ ,@(mapcar
+ (lambda (pair)
+ (let ((name (car pair))
+ (data (cdr pair)))
+ (if (and (a-get data :edn) (a-get data :source))
+ (let ((test-name (intern (concat "parseedn-read:" name))))
+ `(ert-deftest ,test-name ()
+ :tags '(parseedn)
+ (with-temp-buffer
+ (insert ,(a-get data :source))
+ (goto-char 1)
+ (should (a-equal (parseedn-read) ',(a-get data
:edn)))))))))
+ parseclj-test-data)))
+
+(defmacro define-parseedn-roundtrip-tests ()
+ `(progn
+ ,@(mapcar
+ (lambda (pair)
+ (let ((name (car pair))
+ (data (cdr pair)))
+ (if (and (a-get data :edn) (a-get data :source) (member
:edn-roundtrip (a-get data :tags)))
+ (let ((test-name (intern (concat "parseedn-rountrip:" name))))
+ `(ert-deftest ,test-name ()
+ :tags '(parseedn-rountrip)
+ (should (equal (parseedn-print-str (car ',(a-get data
:edn))) ,(a-get data :source))))))))
+ parseclj-test-data)))
+
+(define-parseedn-read-tests)
+(define-parseedn-roundtrip-tests)
+
+;;; parseedn-test.el
- [nongnu] elpa/parseedn 5470d4ed67 21/32: Fix the inst parsing test for the case where TZ=UTC, (continued)
- [nongnu] elpa/parseedn 5470d4ed67 21/32: Fix the inst parsing test for the case where TZ=UTC, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn acbea6bb1e 03/32: Fix a typo, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn ddf824bc1d 08/32: Update the installation instructions, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 4efa23a851 15/32: oops! fix test., ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 1a3640d298 29/32: Replace `cl-case` with `cond`, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn b00eb42a1c 28/32: Update the required Emacs version, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn adf57f36f4 11/32: Fix printing of hash-maps with multiple entries, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 17c4b2f658 05/32: Fix a couple of typos, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 342359abd1 10/32: Merge pull request #2 from clojure-emacs/fix-build-question-mark, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 7b9ca20b39 22/32: Merge pull request #7 from clojure-emacs/print-uuid-inst, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn ef0f8772bf 01/32: Initial commit,
ELPA Syncer <=
- [nongnu] elpa/parseedn 92bf875962 17/32: Merge pull request #5 from clojure-emacs/support-dotted-pairs, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn e5ba280d1f 32/32: Correctly bump versions, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn f42ff98833 13/32: Merge pull request #1 from ak-coram/fix-hash-map-printing, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 8f0582da3f 06/32: Improve the commentary front matter, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 0ffab01927 12/32: Add test to cover the printing of hash-maps, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn 3b1bea1425 27/32: Drop use of map-merge alist, for Emacs 25/26, ELPA Syncer, 2021/12/28
- [nongnu] elpa/parseedn fe8e30a770 31/32: Update CHANGELOG, ELPA Syncer, 2021/12/28