summaryrefslogtreecommitdiff
path: root/prism/templates/lib/prism/node.rb.erb
diff options
context:
space:
mode:
Diffstat (limited to 'prism/templates/lib/prism/node.rb.erb')
-rw-r--r--prism/templates/lib/prism/node.rb.erb414
1 files changed, 414 insertions, 0 deletions
diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb
new file mode 100644
index 0000000000..f033cdea9b
--- /dev/null
+++ b/prism/templates/lib/prism/node.rb.erb
@@ -0,0 +1,414 @@
+module Prism
+ # This represents a node in the tree. It is the parent class of all of the
+ # various node types.
+ class Node
+ # A pointer to the source that this node was created from.
+ attr_reader :source
+ private :source
+
+ # A unique identifier for this node. This is used in a very specific
+ # use case where you want to keep around a reference to a node without
+ # having to keep around the syntax tree in memory. This unique identifier
+ # will be consistent across multiple parses of the same source code.
+ attr_reader :node_id
+
+ # A Location instance that represents the location of this node in the
+ # source.
+ def location
+ location = @location
+ return location if location.is_a?(Location)
+ @location = Location.new(source, location >> 32, location & 0xFFFFFFFF)
+ end
+
+ # The start offset of the node in the source. This method is effectively a
+ # delegate method to the location object.
+ def start_offset
+ location = @location
+ location.is_a?(Location) ? location.start_offset : location >> 32
+ end
+
+ # The end offset of the node in the source. This method is effectively a
+ # delegate method to the location object.
+ def end_offset
+ location = @location
+ location.is_a?(Location) ? location.end_offset : ((location >> 32) + (location & 0xFFFFFFFF))
+ end
+
+ # Returns all of the lines of the source code associated with this node.
+ def source_lines
+ location.source_lines
+ end
+
+ # An alias for source_lines, used to mimic the API from
+ # RubyVM::AbstractSyntaxTree to make it easier to migrate.
+ alias script_lines source_lines
+
+ # Slice the location of the node from the source.
+ def slice
+ location.slice
+ end
+
+ # Slice the location of the node from the source, starting at the beginning
+ # of the line that the location starts on, ending at the end of the line
+ # that the location ends on.
+ def slice_lines
+ location.slice_lines
+ end
+
+ # An bitset of flags for this node. There are certain flags that are common
+ # for all nodes, and then some nodes have specific flags.
+ attr_reader :flags
+ protected :flags
+
+ # Returns true if the node has the newline flag set.
+ def newline?
+ flags.anybits?(NodeFlags::NEWLINE)
+ end
+
+ # Returns true if the node has the static literal flag set.
+ def static_literal?
+ flags.anybits?(NodeFlags::STATIC_LITERAL)
+ end
+
+ # Similar to inspect, but respects the current level of indentation given by
+ # the pretty print object.
+ def pretty_print(q)
+ q.seplist(inspect.chomp.each_line, -> { q.breakable }) do |line|
+ q.text(line.chomp)
+ end
+ q.current_group.break
+ end
+
+ # Convert this node into a graphviz dot graph string.
+ def to_dot
+ # @type self: node
+ DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
+ end
+
+ # Returns a list of nodes that are descendants of this node that contain the
+ # given line and column. This is useful for locating a node that is selected
+ # based on the line and column of the source code.
+ #
+ # Important to note is that the column given to this method should be in
+ # bytes, as opposed to characters or code units.
+ def tunnel(line, column)
+ queue = [self] #: Array[Prism::node]
+ result = []
+
+ while (node = queue.shift)
+ result << node
+
+ node.compact_child_nodes.each do |child_node|
+ child_location = child_node.location
+
+ start_line = child_location.start_line
+ end_line = child_location.end_line
+
+ if start_line == end_line
+ if line == start_line && column >= child_location.start_column && column < child_location.end_column
+ queue << child_node
+ break
+ end
+ elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column)
+ queue << child_node
+ break
+ elsif line > start_line && line < end_line
+ queue << child_node
+ break
+ end
+ end
+ end
+
+ result
+ end
+
+ # Returns the first node that matches the given block when visited in a
+ # depth-first search. This is useful for finding a node that matches a
+ # particular condition.
+ #
+ # node.breadth_first_search { |node| node.node_id == node_id }
+ #
+ def breadth_first_search(&block)
+ queue = [self] #: Array[Prism::node]
+
+ while (node = queue.shift)
+ return node if yield node
+ queue.concat(node.compact_child_nodes)
+ end
+
+ nil
+ end
+
+ # Returns a list of the fields that exist for this node class. Fields
+ # describe the structure of the node. This kind of reflection is useful for
+ # things like recursively visiting each node _and_ field in the tree.
+ def self.fields
+ # This method should only be called on subclasses of Node, not Node
+ # itself.
+ raise NoMethodError, "undefined method `fields' for #{inspect}" if self == Node
+
+ Reflection.fields_for(self)
+ end
+
+ # --------------------------------------------------------------------------
+ # :section: Node interface
+ # These methods are effectively abstract methods that must be implemented by
+ # the various subclasses of Node. They are here to make it easier to work
+ # with typecheckers.
+ # --------------------------------------------------------------------------
+
+ # Accepts a visitor and calls back into the specialized visit function.
+ def accept(visitor)
+ raise NoMethodError, "undefined method `accept' for #{inspect}"
+ end
+
+ # Returns an array of child nodes, including `nil`s in the place of optional
+ # nodes that were not present.
+ def child_nodes
+ raise NoMethodError, "undefined method `child_nodes' for #{inspect}"
+ end
+
+ alias deconstruct child_nodes
+
+ # Returns an array of child nodes, excluding any `nil`s in the place of
+ # optional nodes that were not present.
+ def compact_child_nodes
+ raise NoMethodError, "undefined method `compact_child_nodes' for #{inspect}"
+ end
+
+ # Returns an array of child nodes and locations that could potentially have
+ # comments attached to them.
+ def comment_targets
+ raise NoMethodError, "undefined method `comment_targets' for #{inspect}"
+ end
+
+ # Returns a string representation of the node.
+ def inspect
+ raise NoMethodError, "undefined method `inspect' for #{inspect}"
+ end
+
+ # Sometimes you want to check an instance of a node against a list of
+ # classes to see what kind of behavior to perform. Usually this is done by
+ # calling `[cls1, cls2].include?(node.class)` or putting the node into a
+ # case statement and doing `case node; when cls1; when cls2; end`. Both of
+ # these approaches are relatively slow because of the constant lookups,
+ # method calls, and/or array allocations.
+ #
+ # Instead, you can call #type, which will return to you a symbol that you
+ # can use for comparison. This is faster than the other approaches because
+ # it uses a single integer comparison, but also because if you're on CRuby
+ # you can take advantage of the fact that case statements with all symbol
+ # keys will use a jump table.
+ def type
+ raise NoMethodError, "undefined method `type' for #{inspect}"
+ end
+
+ # Similar to #type, this method returns a symbol that you can use for
+ # splitting on the type of the node without having to do a long === chain.
+ # Note that like #type, it will still be slower than using == for a single
+ # class, but should be faster in a case statement or an array comparison.
+ def self.type
+ raise NoMethodError, "undefined method `type' for #{inspect}"
+ end
+ end
+ <%- nodes.each do |node| -%>
+
+ <%- node.each_comment_line do |line| -%>
+ #<%= line %>
+ <%- end -%>
+ class <%= node.name -%> < Node
+ # Initialize a new <%= node.name %> node.
+ def initialize(<%= ["source", "node_id", "location", "flags", *node.fields.map(&:name)].join(", ") %>)
+ @source = source
+ @node_id = node_id
+ @location = location
+ @flags = flags
+ <%- node.fields.each do |field| -%>
+ <%- if Prism::Template::CHECK_FIELD_KIND && field.respond_to?(:check_field_kind) -%>
+ raise "<%= node.name %>#<%= field.name %> was of unexpected type:\n#{<%= field.name %>.inspect}" unless <%= field.check_field_kind %>
+ <%- end -%>
+ @<%= field.name %> = <%= field.name %>
+ <%- end -%>
+ end
+
+ # def accept: (Visitor visitor) -> void
+ def accept(visitor)
+ visitor.visit_<%= node.human %>(self)
+ end
+
+ # def child_nodes: () -> Array[nil | Node]
+ def child_nodes
+ [<%= node.fields.map { |field|
+ case field
+ when Prism::Template::NodeField, Prism::Template::OptionalNodeField then field.name
+ when Prism::Template::NodeListField then "*#{field.name}"
+ end
+ }.compact.join(", ") %>]
+ end
+
+ # def compact_child_nodes: () -> Array[Node]
+ def compact_child_nodes
+ <%- if node.fields.any? { |field| field.is_a?(Prism::Template::OptionalNodeField) } -%>
+ compact = [] #: Array[Prism::node]
+ <%- node.fields.each do |field| -%>
+ <%- case field -%>
+ <%- when Prism::Template::NodeField -%>
+ compact << <%= field.name %>
+ <%- when Prism::Template::OptionalNodeField -%>
+ compact << <%= field.name %> if <%= field.name %>
+ <%- when Prism::Template::NodeListField -%>
+ compact.concat(<%= field.name %>)
+ <%- end -%>
+ <%- end -%>
+ compact
+ <%- else -%>
+ [<%= node.fields.map { |field|
+ case field
+ when Prism::Template::NodeField then field.name
+ when Prism::Template::NodeListField then "*#{field.name}"
+ end
+ }.compact.join(", ") %>]
+ <%- end -%>
+ end
+
+ # def comment_targets: () -> Array[Node | Location]
+ def comment_targets
+ [<%= node.fields.map { |field|
+ case field
+ when Prism::Template::NodeField, Prism::Template::LocationField then field.name
+ when Prism::Template::OptionalNodeField, Prism::Template::NodeListField, Prism::Template::OptionalLocationField then "*#{field.name}"
+ end
+ }.compact.join(", ") %>] #: Array[Prism::node | Location]
+ end
+
+ # def copy: (<%= (["?node_id: Integer", "?location: Location", "?flags: Integer"] + node.fields.map { |field| "?#{field.name}: #{field.rbs_class}" }).join(", ") %>) -> <%= node.name %>
+ def copy(<%= (["node_id", "location", "flags"] + node.fields.map(&:name)).map { |field| "#{field}: self.#{field}" }.join(", ") %>)
+ <%= node.name %>.new(<%= ["source", "node_id", "location", "flags", *node.fields.map(&:name)].join(", ") %>)
+ end
+
+ # def deconstruct: () -> Array[nil | Node]
+ alias deconstruct child_nodes
+
+ # def deconstruct_keys: (Array[Symbol] keys) -> { <%= (["node_id: Integer", "location: Location"] + node.fields.map { |field| "#{field.name}: #{field.rbs_class}" }).join(", ") %> }
+ def deconstruct_keys(keys)
+ { <%= (["node_id: node_id", "location: location"] + node.fields.map { |field| "#{field.name}: #{field.name}" }).join(", ") %> }
+ end
+ <%- if (node_flags = node.flags) -%>
+ <%- node_flags.values.each do |value| -%>
+
+ # def <%= value.name.downcase %>?: () -> bool
+ def <%= value.name.downcase %>?
+ flags.anybits?(<%= node_flags.name %>::<%= value.name %>)
+ end
+ <%- end -%>
+ <%- end -%>
+ <%- node.fields.each do |field| -%>
+
+ <%- if field.comment.nil? -%>
+ # attr_reader <%= field.name %>: <%= field.rbs_class %>
+ <%- else -%>
+ <%- field.each_comment_line do |line| -%>
+ #<%= line %>
+ <%- end -%>
+ <%- end -%>
+ <%- case field -%>
+ <%- when Prism::Template::LocationField -%>
+ def <%= field.name %>
+ location = @<%= field.name %>
+ return location if location.is_a?(Location)
+ @<%= field.name %> = Location.new(source, location >> 32, location & 0xFFFFFFFF)
+ end
+ <%- when Prism::Template::OptionalLocationField -%>
+ def <%= field.name %>
+ location = @<%= field.name %>
+ case location
+ when nil
+ nil
+ when Location
+ location
+ else
+ @<%= field.name %> = Location.new(source, location >> 32, location & 0xFFFFFFFF)
+ end
+ end
+ <%- else -%>
+ attr_reader :<%= field.name %>
+ <%- end -%>
+ <%- end -%>
+ <%- node.fields.each do |field| -%>
+ <%- case field -%>
+ <%- when Prism::Template::LocationField -%>
+ <%- raise unless field.name.end_with?("_loc") -%>
+ <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%>
+
+ # def <%= field.name.delete_suffix("_loc") %>: () -> String
+ def <%= field.name.delete_suffix("_loc") %>
+ <%= field.name %>.slice
+ end
+ <%- when Prism::Template::OptionalLocationField -%>
+ <%- raise unless field.name.end_with?("_loc") -%>
+ <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%>
+
+ # def <%= field.name.delete_suffix("_loc") %>: () -> String?
+ def <%= field.name.delete_suffix("_loc") %>
+ <%= field.name %>&.slice
+ end
+ <%- end -%>
+ <%- end -%>
+
+ # def inspect -> String
+ def inspect
+ InspectVisitor.compose(self)
+ end
+
+ # Return a symbol representation of this node type. See `Node#type`.
+ def type
+ :<%= node.human %>
+ end
+
+ # Return a symbol representation of this node type. See `Node::type`.
+ def self.type
+ :<%= node.human %>
+ end
+
+ # Implements case-equality for the node. This is effectively == but without
+ # comparing the value of locations. Locations are checked only for presence.
+ def ===(other)
+ other.is_a?(<%= node.name %>)<%= " &&" if (fields = [*node.flags, *node.fields]).any? %>
+ <%- fields.each_with_index do |field, index| -%>
+ <%- if field.is_a?(Prism::Template::LocationField) || field.is_a?(Prism::Template::OptionalLocationField) -%>
+ (<%= field.name %>.nil? == other.<%= field.name %>.nil?)<%= " &&" if index != fields.length - 1 %>
+ <%- elsif field.is_a?(Prism::Template::NodeListField) || field.is_a?(Prism::Template::ConstantListField) -%>
+ (<%= field.name %>.length == other.<%= field.name %>.length) &&
+ <%= field.name %>.zip(other.<%= field.name %>).all? { |left, right| left === right }<%= " &&" if index != fields.length - 1 %>
+ <%- elsif field.is_a?(Prism::Template::Flags) -%>
+ (flags === other.flags)<%= " &&" if index != fields.length - 1 %>
+ <%- else -%>
+ (<%= field.name %> === other.<%= field.name %>)<%= " &&" if index != fields.length - 1 %>
+ <%- end -%>
+ <%- end -%>
+ end
+ end
+ <%- end -%>
+ <%- flags.each do |flag| -%>
+
+ # <%= flag.comment %>
+ module <%= flag.name %>
+ <%- flag.values.each_with_index do |value, index| -%>
+ # <%= value.comment %>
+ <%= value.name %> = 1 << <%= index + Prism::Template::COMMON_FLAGS_COUNT %>
+<%= "\n" if value != flag.values.last -%>
+ <%- end -%>
+ end
+ <%- end -%>
+
+ # The flags that are common to all nodes.
+ module NodeFlags
+ # A flag to indicate that the node is a candidate to emit a :line event
+ # through tracepoint when compiled.
+ NEWLINE = 1
+
+ # A flag to indicate that the value that the node represents is a value that
+ # can be determined at parse-time.
+ STATIC_LITERAL = 2
+ end
+end