Bring in code from a web essay for parsing data structures
This commit is contained in:
parent
b8df072250
commit
612b479c3a
1 changed files with 134 additions and 0 deletions
134
elisp/query-tree.el
Normal file
134
elisp/query-tree.el
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
;;; query-tree.el --- query interface to data structures. -*- lexical-binding: t; -*-
|
||||||
|
;;
|
||||||
|
;; Copyright © 2023 Howard X. Abrams
|
||||||
|
;;
|
||||||
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||||
|
;; Maintainer: Howard X. Abrams <howard.abrams@gmail.com>
|
||||||
|
;; Created: April 23, 2023
|
||||||
|
;;
|
||||||
|
;; This file is not part of GNU Emacs.
|
||||||
|
;;
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; A Lisp-focused approach to extracting values from a complex data
|
||||||
|
;; structure, similar to how jq works with JSON objects.
|
||||||
|
;;
|
||||||
|
;; For instance, given a _parsed_ JSON object (a collection of
|
||||||
|
;; hash-tables and vectors), like (stored in variable data):
|
||||||
|
;;
|
||||||
|
;; { "a": 1,
|
||||||
|
;; "b": {
|
||||||
|
;; "x": 10,
|
||||||
|
;; "y": [100, 101, 102, 103],
|
||||||
|
;; "z": 30
|
||||||
|
;; },
|
||||||
|
;; "c": 3
|
||||||
|
;; }
|
||||||
|
;;
|
||||||
|
;; We should be able to call: (query-tree data 'a 'b 2)
|
||||||
|
;; Should return 102.
|
||||||
|
;;
|
||||||
|
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||||||
|
;; ~/website/Technical/Emacs/query-data-structures.org
|
||||||
|
;; And tangle the file to recreate this one.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defun query-tree (data-structure &rest query)
|
||||||
|
"Return value from DATA-STRUCTURE based on elements of QUERY."
|
||||||
|
(query-tree-parse query data-structure))
|
||||||
|
|
||||||
|
(defun query-tree-parse (query tree)
|
||||||
|
"Return value of QUERY from TREE data structure."
|
||||||
|
(if (null query)
|
||||||
|
tree
|
||||||
|
(query-tree-parse (cdr query)
|
||||||
|
(query-tree--parse-node (car query) tree))))
|
||||||
|
|
||||||
|
(defun query-tree--parse-node (node tree)
|
||||||
|
"Return results of TREE data structure of symbol, NODE.
|
||||||
|
|
||||||
|
If TREE is a hash-table, then NODE would be the key, either a
|
||||||
|
string or a symbol. Return value.
|
||||||
|
|
||||||
|
If TREE is a sequence and NODE is number, Return element indexed
|
||||||
|
by NODE.
|
||||||
|
|
||||||
|
If TREE is a Property List, return value of NODE as a key. This
|
||||||
|
should work with association lists as well."
|
||||||
|
(cond
|
||||||
|
((null tree) nil)
|
||||||
|
((hash-table-p tree) (or (gethash node tree)
|
||||||
|
(gethash (symbol-name node) tree)))
|
||||||
|
((and (numberp node) (seqp tree)) (seq-elt tree node))
|
||||||
|
((and (eq node :first) (seqp tree)) (car node))
|
||||||
|
((and (eq node :last) (seqp tree)) (last node))
|
||||||
|
|
||||||
|
((assoc node tree) (alist-get node tree))
|
||||||
|
((plistp tree) (plist-get tree node))
|
||||||
|
(t (error "query-tree: Looking for '%s' in %s" node tree))))
|
||||||
|
|
||||||
|
(ert-deftest query-tree-walk-tree-test ()
|
||||||
|
;; PLIST:
|
||||||
|
(let ((data '(:a 1 :b 2 :c 3)))
|
||||||
|
(should (eq (query-tree--parse-node :b data) 2)))
|
||||||
|
;; ALIST:
|
||||||
|
(let ((data '((pine . cones) (oak . acorns) (maple . seeds))))
|
||||||
|
(should (eq (query-tree--parse-node 'oak data) 'acorns)))
|
||||||
|
;; LIST, aka a sequence
|
||||||
|
(let ((data '(a b c d e f)))
|
||||||
|
(should (eq (query-tree--parse-node 3 data) 'd)))
|
||||||
|
;; VECTOR:
|
||||||
|
(let ((data [a b c d e]))
|
||||||
|
(should (eq (query-tree--parse-node 2 data) 'c)))
|
||||||
|
;; HASH TABLE with strings:
|
||||||
|
(let ((h (make-hash-table :test 'equal)))
|
||||||
|
(puthash "a" 1 h)
|
||||||
|
(puthash "b" 2 h)
|
||||||
|
(puthash "c" 3 h)
|
||||||
|
(should (eq (query-tree--parse-node "b" h) 2))
|
||||||
|
(should (eq (query-tree--parse-node 'b h) 2)))
|
||||||
|
;; HASH TABLE with symbols:
|
||||||
|
(let ((h (make-hash-table)))
|
||||||
|
(puthash 'a 1 h)
|
||||||
|
(puthash 'b 2 h)
|
||||||
|
(puthash 'c 3 h)
|
||||||
|
(should (eq (query-tree--parse-node 'b h) 2))))
|
||||||
|
|
||||||
|
(ert-deftest query-tree-parse-tree-test ()
|
||||||
|
(should (null (query-tree-parse nil nil)))
|
||||||
|
(let ((data :final))
|
||||||
|
(should (eq (query-tree-parse nil data) :final)))
|
||||||
|
(let ((data [ a b c d e f ]))
|
||||||
|
(should (eq (query-tree-parse '(3) data) 'd)))
|
||||||
|
(let ((data (json-parse-string
|
||||||
|
"{ \"a\": 1,
|
||||||
|
\"b\": {
|
||||||
|
\"x\": 10,
|
||||||
|
\"y\": [100, 101, 102, 103],
|
||||||
|
\"z\": 30
|
||||||
|
},
|
||||||
|
\"c\": 3
|
||||||
|
}")))
|
||||||
|
(should (hash-table-p data))
|
||||||
|
(should (= (query-tree-parse '("a") data) 1))
|
||||||
|
(should (= (query-tree-parse '("b" "x") data) 10))
|
||||||
|
(should (= (query-tree-parse '("b" "y" 2) data) 102))
|
||||||
|
(should (= (query-tree-parse '(a) data) 1))
|
||||||
|
(should (= (query-tree-parse '(b x) data) 10))
|
||||||
|
(should (= (query-tree-parse '(b y 2) data) 102))))
|
||||||
|
|
||||||
|
(ert-deftest query-tree-test ()
|
||||||
|
(let ((data (json-parse-string
|
||||||
|
"{ \"a\": 1,
|
||||||
|
\"b\": {
|
||||||
|
\"x\": 10,
|
||||||
|
\"y\": [100, 101, 102, 103],
|
||||||
|
\"z\": 30
|
||||||
|
},
|
||||||
|
\"c\": 3
|
||||||
|
}")))
|
||||||
|
(should (= (query-tree data 'b 'y 2) 102))))
|
||||||
|
|
||||||
|
(provide 'query-tree)
|
||||||
|
;;; query-tree.el ends here
|
Loading…
Reference in a new issue