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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/parseedn c94ac06b53 1/3: Default data reader function and


From: ELPA Syncer
Subject: [nongnu] elpa/parseedn c94ac06b53 1/3: Default data reader function and tagged literals
Date: Mon, 7 Feb 2022 08:58:46 -0500 (EST)

branch: elpa/parseedn
commit c94ac06b5311df39090cc3af2081c421f8016b0c
Author: r0man <roman@burningswell.com>
Commit: r0man <roman@burningswell.com>

    Default data reader function and tagged literals
    
    Sometime it is not possible to know in advance which tagged readers
    need to be registered when reading EDN data.
    
    For example, I would like to read Splunk log lines that have been
    produced by many different systems, and I do not know in advance which
    data readers those system have defined.
    
    However, it's still useful to read this data and maybe only use some
    of the data, or pass the data on to other systems. Clojure 1.7 added
    support for this via so called default data readers.
    
    This PR implements a way to provide a default data reader function. It
    is modeled after Clojure's implementation which is described here [1].
    
    [1] https://insideclojure.org/2018/06/21/tagged-literal/
---
 parseedn.el                | 32 ++++++++++++++++++++++++++++----
 test/parseedn-test-data.el |  9 +++++++++
 test/parseedn-test.el      | 19 ++++++++++++++++++-
 3 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/parseedn.el b/parseedn.el
index a1cecbf99c..239f56a60c 100644
--- a/parseedn.el
+++ b/parseedn.el
@@ -65,6 +65,23 @@ 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-tagged-literal (tag form)
+  "Construct a data representation of a tagged literal from TAG and FORM."
+  (list 'edn-tagged-literal tag form))
+
+(defvar parseedn-default-data-reader-fn nil
+  "The default tagged literal reader function.
+
+When no data reader is found for a tag and
+`parseedn-default-data-reader-fn' is non-nil, it will be called
+with two arguments, the tag and the value.  If
+`parseedn-default-data-reader-fn' is nil (the default), an
+exception will be thrown for the unknown tag.
+
+The default data reader can also be provided via the tagged
+reader options registered under the :default keyword when calling
+the reader functions.")
+
 (defun parseedn-reduce-leaf (stack token _options)
   "Put in the STACK an elisp value representing TOKEN.
 
@@ -101,10 +118,14 @@ on available options."
                                            kvs)
                                    hash-map))
         ((eq :tag token-type) (let* ((tag (intern (substring (alist-get :form 
opening-token) 1)))
-                                     (reader (alist-get tag tag-readers 
:missing)))
-                                (when (eq :missing reader)
-                                  (user-error "No reader for tag #%S in %S" 
tag (map-keys tag-readers)))
-                                (funcall reader (car children)))))
+                                     (reader (alist-get tag tag-readers))
+                                     (default-reader (alist-get :default 
tag-readers parseedn-default-data-reader-fn)))
+                                (cond
+                                 ((functionp reader)
+                                  (funcall reader (car children)))
+                                 ((functionp default-reader)
+                                  (funcall default-reader tag (car children)))
+                                 (t (user-error "No reader for tag #%S in %S" 
tag (map-keys tag-readers)))))))
        stack))))
 
 (defun parseedn-read (&optional tag-readers)
@@ -236,6 +257,9 @@ DATUM can be any Emacs Lisp value."
       (insert "#uuid ") (parseedn-print-seq (cdr datum)))
      ((eq 'edn-inst (car datum))
       (insert "#inst ") (parseedn-print-inst (cdr datum)))
+     ((eq 'edn-tagged-literal (car datum))
+      (insert "#" (symbol-name (cadr datum)) " ")
+      (parseedn-print (caddr datum)))
      (t (insert "(") (parseedn-print-seq datum) (insert ")"))))
 
    (t (error "Don't know how to print: %s" datum))))
diff --git a/test/parseedn-test-data.el b/test/parseedn-test-data.el
index 84da3c89f8..74b791574c 100644
--- a/test/parseedn-test-data.el
+++ b/test/parseedn-test-data.el
@@ -277,7 +277,10 @@
 
        "tag-1"
        (a-list
+        :tags '(:edn-roundtrip)
+        :tag-readers '((:default . parseedn-tagged-literal))
         :source "#foo/bar [1]"
+        :edn '((edn-tagged-literal foo/bar [1]))
         :ast '((:node-type . :root)
                (:position . 1)
                (:children . (((:node-type . :tag)
@@ -292,7 +295,10 @@
 
        "tag-2"
        (a-list
+        :tags '(:edn-roundtrip)
+        :tag-readers '((:default . parseedn-tagged-literal))
         :source "(fn #param :param-name 1)"
+        :edn '((fn (edn-tagged-literal param :param-name) 1))
         :ast '((:node-type . :root)
                (:position . 1)
                (:children . (((:node-type . :list)
@@ -315,6 +321,9 @@
 
        "nested-tags"
        (a-list
+        :tags '(:edn-roundtrip)
+        :tag-readers '((:default . parseedn-tagged-literal))
+        :edn (list (vector `(edn-tagged-literal lazy-error (edn-tagged-literal 
error ,(a-hash-table :cause "Divide by zero")))))
         :source "[#lazy-error #error {:cause \"Divide by zero\"}]"
         :ast '((:node-type . :root)
                (:position . 1)
diff --git a/test/parseedn-test.el b/test/parseedn-test.el
index 0c060b9924..49a3a66b2c 100644
--- a/test/parseedn-test.el
+++ b/test/parseedn-test.el
@@ -43,6 +43,8 @@
   (should (equal (parseedn-print-str '((a . 1) (b . ((c . 3))))) "{a 1, b {c 
3}}"))
   (should (equal (parseedn-print-str '(:a 1 :b 2)) "{:a 1, :b 2}"))
   (should (equal (parseedn-print-str '(:a 1 :b (:c 3))) "{:a 1, :b {:c 3}}"))
+  (should (equal (parseedn-print-str '(edn-tagged-literal unknown "data")) 
"#unknown \"data\""))
+  (should (equal (parseedn-print-str '(edn-tagged-literal unknown 
(edn-tagged-literal unknown "data"))) "#unknown #unknown \"data\""))
   (should (listp (member (parseedn-print-str
                           (let ((ht (make-hash-table)))
                             (puthash :a 1 ht)
@@ -59,6 +61,20 @@
 (ert-deftest parseedn-read-test ()
   (should (equal (parseedn-read-str "true") t)))
 
+(ert-deftest parseedn-tagged-literal-test ()
+  (let ((data "#unknown \"data\"")
+        (expected '(edn-tagged-literal unknown "data")))
+    ;; Default reader can be passed as a function
+    (should (equal expected (parseedn-read-str data `((:default . 
,#'parseedn-tagged-literal)))))
+    ;; Default reader can be passed as a symbol
+    (should (equal expected (parseedn-read-str data '((:default . 
parseedn-tagged-literal)))))
+    ;; Default reader can be bound to a function
+    (let ((parseedn-default-data-reader-fn #'parseedn-tagged-literal))
+      (should (equal expected (parseedn-read-str data))))
+    ;; Default reader can be bound to a symbol
+    (let ((parseedn-default-data-reader-fn 'parseedn-tagged-literal))
+      (should (equal expected (parseedn-read-str data))))))
+
 (defmacro define-parseedn-read-tests ()
   `(progn
      ,@(mapcar
@@ -72,7 +88,8 @@
                      (with-temp-buffer
                        (insert ,(a-get data :source))
                        (goto-char 1)
-                       (should (a-equal (parseedn-read) ',(a-get data 
:edn)))))))))
+                       (should (a-equal (parseedn-read ',(a-get data 
:tag-readers))
+                                        ',(a-get data :edn)))))))))
         parseedn-test-data)))
 
 (defmacro define-parseedn-roundtrip-tests ()



reply via email to

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