]> git.eshelyaron.com Git - emacs.git/commitdiff
New node traversal functions
authorYuan Fu <casouri@gmail.com>
Fri, 13 May 2022 20:47:41 +0000 (13:47 -0700)
committerYuan Fu <casouri@gmail.com>
Fri, 13 May 2022 21:37:24 +0000 (14:37 -0700)
* lisp/treesit.el (treesit-traverse-parent): New alias.
(treesit-traverse-depth-first, treesit--traverse-breadth-first-1,
treesit-traverse-breadth-first, treesit-next-sibling-or-up,
treesit-traverse-forward-depth-first): New functions.
* test/src/treesit-tests.el (treesit-node-supplemental): Add reminders
for tests.

lisp/treesit.el
test/src/treesit-tests.el

index dbbe0e409a86399516daceae3e19b6f515534b8a..0fe3a8ed244dd877e73517766bf3943e8de4fc9e 100644 (file)
@@ -227,6 +227,113 @@ one argument, the parent node."
             node (treesit-node-parent node)))
     last))
 
+(defalias 'treesit-traverse-parent #'treesit-parent-until)
+
+(defun treesit-traverse-depth-first (node pred &optional step)
+  "Traverse the subtree of NODE depth-first.
+
+Traverse starting from NODE (i.e., NODE is passed to PRED).  For
+each node traversed, call PRED with the node, stop and return the
+node if PRED returns non-nil.  If STEP >= 0 or nil, go forward,
+if STEP < 0, go backward.  If no node satisfies PRED, return
+nil."
+  (if (funcall pred node)
+      node
+    (cl-loop for child in (if (or (null step) (>= step 0))
+                              (treesit-node-children node)
+                            (nreverse (treesit-node-children node)))
+             if (treesit-traverse-depth-first child pred step)
+             return child)))
+
+(defun treesit--traverse-breadth-first-1 (pred step queue tail)
+  "The work horse for `treesit-traverse-breadth-first'.
+PRED and STEP are the same as in
+`treesit-traverse-breadth-first'.  This function simply runes BFS
+on QUEUE: pops an element from QUEUE, append children to QUEUE,
+process the element, and next iteration.  TAIL is the pointer to
+the last cons in QUEUE, used for appending elements."
+  (cl-loop while queue
+           if (funcall pred (car queue)) return (car queue)
+           else do
+           (let ((children (if (or (null step) (>= step 0))
+                               (treesit-node-children (car queue))
+                             (nreverse (treesit-node-children (car queue))))))
+             ;; Append children to the end.
+             (setcdr tail children)
+             (setq tail (last tail))
+             ;; Pop the head off.
+             (setq queue (cdr queue)))
+           finally return nil))
+
+(defun treesit-traverse-breadth-first (node pred &optional step)
+  "Traverse the subtree of NODE breadth-first.
+
+Traverse starting from NODE (i.e., NODE is passed to PRED).  For
+each node traversed, call PRED with the node, stop and return the
+node if PRED returns non-nil.  If STEP >= 0 or nil, go forward,
+if STEP < 0, go backward.  If no node satisfies PRED, return
+nil."
+  ;; Traverse with a queue.
+  (let* ((queue (list node))
+         (tail (last queue)))
+    (treesit--traverse-breadth-first-1 pred step queue tail)))
+
+(defun treesit-next-sibling-or-up (node step)
+  "Return the next sibling of NODE.
+
+If there is no next sibling of NODE but NODE has a parent, return
+the parent.  If there is no parent, return nil.  If STEP >= 0 or
+nil, return the next sibling, if STEP < 0, return the previous
+one.
+
+Return either ('sibling node) or ('parent node)."
+  ;; First deplete siblings.
+  (if-let ((sibling (if (or (null step) (>= step 0))
+                        (treesit-node-next-sibling node)
+                      (treesit-node-prev-sibling node))))
+      (list 'sibling sibling)
+    ;; When siblings depleted, go up one level.
+    (when (treesit-node-parent node)
+      (list 'parent (treesit-node-parent node)))))
+
+(defun treesit-traverse-forward-depth-first (node pred &optional step)
+  "Traverse the whole tree forward from NODE depth-first.
+
+Traverse starting from NODE (i.e., NODE is passed to PRED).  For
+each node traversed, call PRED with the node, stop and return the
+node if PRED returns non-nil.  If STEP >= 0 or nil, go forward,
+if STEP < 0, go backward.  If no node satisfies PRED, return
+nil.
+
+Traversing forward depth-first means, for a tree like the below
+where NODE is marked 1, traverse as numbered:
+
+                16
+                |
+       3--------4-----------8
+       |        |           |
+  o--o-+--1  5--+--6    9---+-----12
+  |  |    |        |    |         |
+  o  o    2        7  +-+-+    +--+--+
+                      |   |    |  |  |
+                      10  11   13 14 15"
+  ;; First try NODE's subtree.
+  (or (treesit-traverse-depth-first node pred step)
+      ;; If no match, try the next node: next sibling, or parent if no
+      ;; next sibling exists.
+      (catch 'match
+        (let ((next (list nil node)))
+          ;; If NEXT is parent, call PRED on it and keep going.
+          (while (and (setq next (treesit-next-sibling-or-up
+                                  (cadr next) step))
+                      (eq (car next) 'parent))
+            (when (funcall pred (cadr next))
+              (throw 'match (cadr next))))
+          (when next
+            ;; If NEXT is non-nil, it must be ('sibling node).
+            (treesit-traverse-forward-depth-first
+             (cadr next) pred step))))))
+
 (defun treesit-node-children (node &optional named)
   "Return a list of NODE's children.
 If NAMED is non-nil, collect named child only."
index c995542a2ae1f573f57d1659676451ed3a0f6725..429e12088f78ad548cb145348b40d77106a0e0e8 100644 (file)
     ;; `treesit-parent-while'
     ;; `treesit-node-children'
     ;; `treesit-node-field-name'
+    ;; `treesit-next-sibling-or-up'
+    ;; `treesit-traverse-depth-first'
+    ;; `treesit-traverse-breadth-first'
+    ;; `treesit-traverse-forward-depth-first'
     ))
 
 ;; TODO