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