-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a couple of variations of traverse
.
#1291
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly small documentation-related comments.
src/codegen/traverse.lisp
Outdated
(apply *traverse* initial-node initial-args))))) | ||
|
||
(defun identity-hook (thunk node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I suggest renaming to identity-traverse-hook
src/codegen/traverse.lisp
Outdated
"Default traversal hook function, which doesn't do any extra operation." | ||
(declare (ignore node)) (funcall thunk)) | ||
|
||
(defvar *traverse-hook* #'identity-hook) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add documentation, but be explicit that traverse-with-hook
should be used.
src/codegen/traverse.lisp
Outdated
(let ((*traverse-hook* hook)) | ||
(apply #'traverse node action-list args))) | ||
|
||
;; Used in traverse-with-parent-ndoe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: typo
src/codegen/traverse.lisp
Outdated
(let ((*traverse-parent-node-parent* *traverse-parent-node-current*) | ||
(*traverse-parent-node-current* node)) | ||
(funcall thunk)))) | ||
(let ((*traverse-hook* #'track-parent-node-hook)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
traverse-with-hook can be used here, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. That was the original intention but somehow I forgot, I guess.
fd99228
to
393b36f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is a small typo, but otherwise lgtm
src/codegen/traverse.lisp
Outdated
|
||
(defvar *traverse-hook* #'identity-traverse-hook | ||
"A private dynamic variable used by `traverse-with-hook`. This is rebound | ||
dyncamically during running `traverse-with-hook`.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: typo
`traverse-with-parent-thunk`: In an optimizing pass, you sometimes neeed to know the parent of the node you're looking at. With this variation of `traverse`, the action functions receives a thunk after the node argument, which returns the parent node when called inside the function. An example is `print-node-traversal-order`. `traverse-with-hook`: This is more general mechanism to intercept calling action procedures. `traverse-with-parent-hook` is implemented on top of this. Traverse hook is a function that is called whenever `traverse` recurse into subnodes. It allows to perform operations during traversal, anostic to how the node walker handle recursion for each node class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty cool idea and it's nice that you were able to do it quite concisely!
There were a few minor typos, but I am mostly curious to see if there is a better example where the extra hook is actually needed.
The `traverse` function is equivalent to this with passing | ||
`identity-traverse-hook` as the hook. | ||
|
||
One of the use cases is to record parent node of the node being traversed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the use cases is to record parent node of the node being traversed. | |
One of the use cases is to record the parent node of the node being traversed. |
(defun traverse-with-parent-thunk (node action-list &rest args) | ||
"Like 'traverse', but actions are called with a thunk in the first argument, | ||
which returns the node's parent node. | ||
This captures one of the typical use case of `traverse-with-hook`." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This captures one of the typical use case of `traverse-with-hook`." | |
This captures one typical use case of `traverse-with-hook`." |
The `traverse` function is equivalent to this with passing | ||
`identity-traverse-hook` as the hook. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this is not quite true. Calling traverse
does not revert *traverse-hook*
to be identity-traverse-hook
if it has already been redefined at the point where the traverse
is called, while calling this function with identity-traverse-hook
will make sure that the hook is reverted to identity-traverse-hook
for the duration of the (sub-)traversal.
I guess this could be fixed by renaming the old traverse
to something else, and making a new traverse
which actually does pass identity-traverse-hook
as a hook to this function. (Maybe the newer traverse
and traverse-with-hook
can be one function, just with the hook defaulting to identity-traverse-hook
?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I didn't think the case that traverse
is called during other traverse
-family functions, but it is certainly possible.
Is there already a case that a function called from an action recursively calls traverse
, thus can be affected with this commit?
Otherwise, I'd just fix this docstring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the inliner definitely needs other sub-traversals of the code to be inlined, in order to rename type variables to fresh ones, as well as to perform AST variable substitution.
(defun print-node-parent (node) | ||
"Print visiting node and its parent, using `traverse-with-parent-thunk`." | ||
(declare (type node node) | ||
(values node &optional)) | ||
(let ((counter 0)) | ||
(traverse-with-parent-thunk | ||
node | ||
(list | ||
(action (:before node node parent-thunk) | ||
(format t "PRE: ~v@{| ~}~A ~A~%" counter | ||
(class-name (class-of node)) | ||
(class-name (class-of (funcall parent-thunk)))) | ||
(incf counter) | ||
(values)) | ||
(action (:after node node parent-thunk) | ||
(decf counter) | ||
(format t "POST: ~v@{| ~}~A ~A~%" counter | ||
(class-name (class-of node)) | ||
(class-name (class-of (funcall parent-thunk)))) | ||
(values)))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, identical behavior can be achieved without traversing with the extra hook:
(not actually a code suggestion, just formatting it as one for easy comparison)
(defun print-node-parent (node) | |
"Print visiting node and its parent, using `traverse-with-parent-thunk`." | |
(declare (type node node) | |
(values node &optional)) | |
(let ((counter 0)) | |
(traverse-with-parent-thunk | |
node | |
(list | |
(action (:before node node parent-thunk) | |
(format t "PRE: ~v@{| ~}~A ~A~%" counter | |
(class-name (class-of node)) | |
(class-name (class-of (funcall parent-thunk)))) | |
(incf counter) | |
(values)) | |
(action (:after node node parent-thunk) | |
(decf counter) | |
(format t "POST: ~v@{| ~}~A ~A~%" counter | |
(class-name (class-of node)) | |
(class-name (class-of (funcall parent-thunk)))) | |
(values)))))) | |
(defun print-node-parent (node) | |
"Print visiting node and its parent, using `traverse-with-parent-thunk`." | |
(declare (type node node) | |
(values node &optional)) | |
(let ((traversal-stack nil)) | |
(traverse | |
node | |
(list | |
(action (:before node node) | |
(format t "PRE: ~v@{| ~}~A ~A~%" (length traversal-stack) | |
(class-name (class-of node)) | |
(class-name (class-of (car traversal-stack)))) | |
(push node traversal-stack) | |
(values)) | |
(action (:after node node) | |
(pop traversal-stack) | |
(format t "POST: ~v@{| ~}~A ~A~%" (length traversal-stack) | |
(class-name (class-of node)) | |
(class-name (class-of (car traversal-stack)))) | |
(values)))))) |
Can you think of any other examples, where some functionality can only be achieved conveniently with traverse-with-hook
, but not with the original traverse
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're completely right. Merely obtaining parent can be achieved by this.
What I really need is a convenient API to access to the parent node, so we may avoid employing *traverse-hook*
hack. Let me think on it.
@amorphedstar The original motivation is to get parent node during traversal. We didn't want a big surgery to I don't see other applications immediately, but what that might be needed for optimizations can be:
|
I looked at #1291 (comment) and actually what I wanted could be achievable without |
@amorphedstar Need some input. I like your approach of keeping I'm trying to write
But I need to use
Any ideas? |
Found a workaround. Will submit another PR that supersedes this. |
Superseded by #1300. Closing. |
These are particularly useful in the optimizing path where traverser wants to have a broader view than a single subtree.
traverse-with-parent-thunk
: In an optimizing pass, you sometimes neeed to know the parent of the node you're looking at. With this variation oftraverse
, the action functions receives a thunk after the node argument, which returns the parent node when called inside the function. An example isprint-node-traversal-order
.traverse-with-hook
: This is more general mechanism to intercept calling action procedures.traverse-with-parent-hook
is implemented on top of this. Traverse hook is a function that is called whenevertraverse
recurse into subnodes. It allows to perform operations during traversal, anostic to how the node walker handle recursion for each node class.