emacs-elpa-diffs
[Top][All Lists]
Advanced

[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
+
+&copy; 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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]