From 612b479c3a861b1b6a0e03b98bba9aa1bc091247 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Wed, 14 Jun 2023 17:36:30 -0700 Subject: [PATCH] Bring in code from a web essay for parsing data structures --- elisp/query-tree.el | 134 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 elisp/query-tree.el diff --git a/elisp/query-tree.el b/elisp/query-tree.el new file mode 100644 index 0000000..9c4f572 --- /dev/null +++ b/elisp/query-tree.el @@ -0,0 +1,134 @@ +;;; query-tree.el --- query interface to data structures. -*- lexical-binding: t; -*- +;; +;; Copyright © 2023 Howard X. Abrams +;; +;; Author: Howard X. Abrams +;; Maintainer: Howard X. Abrams +;; 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