Skip to content

Commit 5b5e6e5

Browse files
committed
core: Add node property names validation to tree traversal functions
1 parent b3355cc commit 5b5e6e5

File tree

3 files changed

+58
-40
lines changed

3 files changed

+58
-40
lines changed

core/src/cursor.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,12 @@ fn _iter_next_node(iterator: &mut DepthFirstIterator, props: Vector, output: Vec
340340

341341
/// Return CURSOR's current node, if PROPS is nil.
342342
///
343-
/// If PROPS is a vector of keywords, this function returns a vector containing the
344-
/// corresponding node properties instead of the node itself. If OUTPUT is also a
345-
/// vector, this function overwrites its contents instead of creating a new vector.
343+
/// If PROPS is a vector of property names, this function returns a vector
344+
/// containing the node's corresponding properties instead of the node itself. If
345+
/// OUTPUT is also a vector, this function overwrites its contents instead of
346+
/// creating a new vector.
347+
///
348+
/// See `tsc-valid-node-props' for the list of available properties.
346349
#[defun]
347350
fn _current_node<'e>(cursor: &RCursor, props: Option<Vector<'e>>, output: Option<Vector<'e>>, env: &'e Env) -> Result<Value<'e>> {
348351
macro_rules! sugar {

core/tsc.el

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -260,20 +260,14 @@ QUERY. Otherwise, a newly created query-cursor is used."
260260

261261
;;; Traversal.
262262

263-
(defconst tsc-valid-node-props '(field
264-
type
265-
named-p
266-
extra-p
267-
error-p
268-
missing-p
269-
has-error-p
270-
start-byte
271-
start-point
272-
end-byte
273-
end-point
274-
range
275-
byte-range
276-
depth)
263+
(defconst tsc-valid-node-props
264+
'(:type
265+
:field ;node's field name within the parent node
266+
:depth ;node's depth, relative to the iterator's start
267+
:named-p :extra-p :error-p :missing-p :has-error-p
268+
:start-byte :end-byte
269+
:start-point :end-point
270+
:range :byte-range)
277271
"Node properties that the traversal functions can return.
278272
279273
When dealing with a large number of nodes, working with node objects creates a
@@ -285,14 +279,23 @@ values.
285279
This wouldn't be necessary if the runtime supported stack-allocated objects.
286280
e.g. automatically through escape analysis. How about porting ELisp to GraalVM?")
287281

282+
(defun tsc--check-node-props (props)
283+
"Validate that PROPS are valid node properties."
284+
(when props
285+
(when-let ((invalid-props (seq-filter (lambda (kw)
286+
(not (memq kw tsc-valid-node-props)))
287+
props)))
288+
(error "Invalid node properties %s" invalid-props))))
289+
288290
(defun tsc-traverse-mapc (func tree-or-node &optional props)
289291
"Call FUNC for each node of TREE-OR-NODE.
290292
The traversal is depth-first pre-order.
291293
292-
If the optional arg PROPS is a vector of keywords, FUNC is called with a vector
293-
containing the corresponding node properties, instead of the node itself. For
294-
efficiency, this vector is reused across invocations of FUNC. *DO NOT* keep a
295-
reference to it. It's recommended to use `pcase-let' to extract the properties.
294+
If the optional arg PROPS is a vector of property names, FUNC is called with a
295+
vector containing the node's corresponding properties, instead of the node
296+
itself. For efficiency, this vector is reused across invocations of FUNC. *DO
297+
NOT KEEP* a reference to it. It's recommended to use `pcase-let' to extract the
298+
properties. See `tsc-valid-node-props' for the list of available properties.
296299
297300
For example, to crudely render a syntax tree:
298301
@@ -305,16 +308,18 @@ For example, to crudely render a syntax tree:
305308
tree
306309
[:type :depth :named-p])
307310
"
311+
(tsc--check-node-props props)
308312
(tsc--traverse-mapc func tree-or-node props))
309313

310314
(defun tsc-traverse-iter (tree-or-node &optional props)
311315
"Return an iterator that traverse TREE-OR-NODE.
312316
The traversal is depth-first pre-order.
313317
314-
If the optional arg PROPS is a vector of keywords, the iterator yields a vector
315-
containing corresponding node properties, instead of the node itself. For
316-
efficiency, this vector is reused across iterations. *DO NOT* keep a reference
317-
to it. It's recommended to use `pcase-let' to extract the properties.
318+
If the optional arg PROPS is a vector of property names, the iterator yields a
319+
vector containing the node's corresponding properties, instead of the node
320+
itself. For efficiency, this vector is reused across iterations. *DO NOT KEEP* a
321+
reference to it. It's recommended to use `pcase-let' to extract the properties.
322+
See `tsc-valid-node-props' for the list of available properties.
318323
319324
For example, to crudely render a syntax tree:
320325
@@ -325,6 +330,7 @@ For example, to crudely render a syntax tree:
325330
(insert (make-string depth \\? ) ;indentation
326331
(format \"%S\" type) \"\\n\"))))
327332
"
333+
(tsc--check-node-props props)
328334
(let ((iter (tsc--iter tree-or-node))
329335
(output (when props
330336
(make-vector (length props) nil))))
@@ -340,7 +346,10 @@ For example, to crudely render a syntax tree:
340346
"Evaluate BODY with VARS bound to properties of each node in TREE-OR-NODE.
341347
The traversal is depth-first pre-order.
342348
343-
VARS must be a vector of symbols. For example, to crudely render a syntax tree:
349+
VARS must be a vector of symbols. See `tsc-valid-node-props' for the list of
350+
available properties. (In VARS, they must be symbols, not keywords.)
351+
352+
For example, to crudely render a syntax tree:
344353
345354
(tsc-traverse-do ([type depth named-p] tree)
346355
(when named-p ;AST
@@ -351,17 +360,13 @@ VARS must be a vector of symbols. For example, to crudely render a syntax tree:
351360
(debug ((vectorp form) body)))
352361
(unless (vectorp vars)
353362
(error "Var bindings must be a vector"))
354-
(let* ((invalid-props (seq-filter (lambda (symbol)
355-
(not (memq symbol tsc-valid-node-props)))
356-
vars))
357-
(_ (when invalid-props
358-
(error "Invalid bindings %s" invalid-props)))
359-
(iter (gensym "iter"))
360-
(output (gensym "output"))
361-
(props (cl-map 'vector
362-
(lambda (symbol)
363-
(intern (format ":%s" symbol)))
364-
vars)))
363+
(let ((props (cl-map 'vector
364+
(lambda (symbol)
365+
(intern (format ":%s" symbol)))
366+
vars))
367+
(iter (make-symbol "iter"))
368+
(output (make-symbol "output")))
369+
(tsc--check-node-props props)
365370
`(let ((,iter (tsc--iter ,tree-or-node))
366371
(,output ,(when props
367372
(make-vector (length props) nil))))

doc/emacs-tree-sitter.org

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,17 @@ Tree-walking functions enable efficient traversal of the syntax tree.
628628
*** Traversing All Descendant Nodes
629629
These functions are high-level APIs that allow traversing the syntax tree in depth-first pre-order.
630630

631-
When dealing with a large number of nodes, working with node objects creates a huge pressure on the garbage collector. For better performance, it's advisable to extract and work with individual node properties.
631+
When dealing with a large number of nodes, working with node objects creates a huge pressure on the garbage collector. For better performance, it's advisable to extract and work with individual node properties. The constant ~tsc-valid-node-props~ holds the list of all available property names.
632+
# TODO: Make :exports results work with ox-hugo.
633+
#+begin_src emacs-lisp
634+
'(:type
635+
:field ;node's field name within the parent node
636+
:depth ;node's depth, relative to the iterator's start
637+
:named-p :extra-p :error-p :missing-p :has-error-p
638+
:start-byte :end-byte
639+
:start-point :end-point
640+
:range :byte-range)
641+
#+end_src
632642

633643
- ~tsc-traverse-do~ /~(vars tree-or-node) body~/ :: Evaluate ~body~ with ~vars~ bound to corresponding properties of each traversed node.
634644
#+begin_src emacs-lisp
@@ -637,7 +647,7 @@ When dealing with a large number of nodes, working with node objects creates a h
637647
(insert (make-string depth \? ) ;indentation
638648
(format "%S" type) "\n")))
639649
#+end_src
640-
- ~tsc-traverse-mapc~ /~func tree-or-node [props]~/ :: Call ~func~ for each traversed node, passing the node as the argument. If the vector of keywords ~props~ is specified, ~func~ receives a vector containing the node's properties instead. *Do not keep a reference to the vector*, as it is reused across invocations. Use ~pcase-let~ to extract the properties.
650+
- ~tsc-traverse-mapc~ /~func tree-or-node [props]~/ :: Call ~func~ for each traversed node, passing the node as the argument. If ~props~ is a vector of property names, ~func~ receives a vector containing the node's properties instead. *Do not keep a reference to the vector*, as it is reused across invocations. Use ~pcase-let~ to extract the properties.
641651
#+begin_src emacs-lisp
642652
(tsc-traverse-mapc
643653
(lambda (props)
@@ -648,7 +658,7 @@ When dealing with a large number of nodes, working with node objects creates a h
648658
tree
649659
[:type :depth :named-p])
650660
#+end_src
651-
- ~tsc-traverse-iter~ /~tree-or-node [props]~/ :: Create an iterator that yields traversed nodes. If the vector of keywords ~props~ is specified, the iterator yields a vector containing the node's properties instead. *Do not keep a reference to the vector*, as it is reused across iterations. Use ~pcase-let~ to extract the properties.
661+
- ~tsc-traverse-iter~ /~tree-or-node [props]~/ :: Create an iterator that yields traversed nodes. If ~props~ is a vector of property names, the iterator yields a vector containing the node's properties instead. *Do not keep a reference to the vector*, as it is reused across iterations. Use ~pcase-let~ to extract the properties.
652662
#+begin_src emacs-lisp
653663
(iter-do (props (tsc-traverse-iter
654664
tree [:type :depth :named-p]))

0 commit comments

Comments
 (0)