blob: 1370ca76364231cbcd148aa7c50dc9fe06c4a7fe (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
module Prism
# The dispatcher class fires events for nodes that are found while walking an
# AST to all registered listeners. It's useful for performing different types
# of analysis on the AST while only having to walk the tree once.
#
# To use the dispatcher, you would first instantiate it and register listeners
# for the events you're interested in:
#
# class OctalListener
# def on_integer_node_enter(node)
# if node.octal? && !node.slice.start_with?("0o")
# warn("Octal integers should be written with the 0o prefix")
# end
# end
# end
#
# dispatcher = Dispatcher.new
# dispatcher.register(listener, :on_integer_node_enter)
#
# Then, you can walk any number of trees and dispatch events to the listeners:
#
# result = Prism.parse("001 + 002 + 003")
# dispatcher.dispatch(result.value)
#
# Optionally, you can also use `#dispatch_once` to dispatch enter and leave
# events for a single node without recursing further down the tree. This can
# be useful in circumstances where you want to reuse the listeners you already
# have registers but want to stop walking the tree at a certain point.
#
# integer = result.value.statements.body.first.receiver.receiver
# dispatcher.dispatch_once(integer)
#
class Dispatcher < Visitor
# attr_reader listeners: Hash[Symbol, Array[Listener]]
attr_reader :listeners
# Initialize a new dispatcher.
def initialize
@listeners = {}
end
# Register a listener for one or more events.
#
# def register: (Listener, *Symbol) -> void
def register(listener, *events)
events.each { |event| (listeners[event] ||= []) << listener }
end
# Walks `root` dispatching events to all registered listeners.
#
# def dispatch: (Node) -> void
alias dispatch visit
# Dispatches a single event for `node` to all registered listeners.
#
# def dispatch_once: (Node) -> void
def dispatch_once(node)
node.accept(DispatchOnce.new(listeners))
end
<%- nodes.each do |node| -%>
# Dispatch enter and leave events for <%= node.name %> nodes and continue
# walking the tree.
def visit_<%= node.human %>(node)
listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) }
super
listeners[:on_<%= node.human %>_leave]&.each { |listener| listener.on_<%= node.human %>_leave(node) }
end
<%- end -%>
class DispatchOnce < Visitor # :nodoc:
attr_reader :listeners
def initialize(listeners)
@listeners = listeners
end
<%- nodes.each do |node| -%>
# Dispatch enter and leave events for <%= node.name %> nodes.
def visit_<%= node.human %>(node)
listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) }
listeners[:on_<%= node.human %>_leave]&.each { |listener| listener.on_<%= node.human %>_leave(node) }
end
<%- end -%>
end
private_constant :DispatchOnce
end
end
|