;;; 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))))

(provide 'query-tree)
;;; query-tree.el ends here