[ruby/prism] Autoload newlines and comment visitors

Having the @newline instance variable in every node adds up, and
since it is so rarely used, we only want to add it when necessary.

Moving this into an autoloaded file and moving the instance variable
out of the default initializers reduces allocated memory because the
nodes are now smaller and some fit into the compact list. On my
machine, I'm seeing about an 8% drop.

https://github.com/ruby/prism/commit/eea92c07d2
This commit is contained in:
Kevin Newton 2024-05-13 11:26:39 -04:00 committed by git
parent 02c8e6583b
commit e6340258f8
6 changed files with 118 additions and 58 deletions

View File

@ -71,8 +71,6 @@ require_relative "prism/polyfill/byteindex"
require_relative "prism/node" require_relative "prism/node"
require_relative "prism/node_ext" require_relative "prism/node_ext"
require_relative "prism/parse_result" require_relative "prism/parse_result"
require_relative "prism/parse_result/comments"
require_relative "prism/parse_result/newlines"
# This is a Ruby implementation of the prism parser. If we're running on CRuby # This is a Ruby implementation of the prism parser. If we're running on CRuby
# and we haven't explicitly set the PRISM_FFI_BACKEND environment variable, then # and we haven't explicitly set the PRISM_FFI_BACKEND environment variable, then

View File

@ -574,6 +574,12 @@ module Prism
# This is a result specific to the `parse` and `parse_file` methods. # This is a result specific to the `parse` and `parse_file` methods.
class ParseResult < Result class ParseResult < Result
autoload :Comments, "prism/parse_result/comments"
autoload :Newlines, "prism/parse_result/newlines"
private_constant :Comments
private_constant :Newlines
# The syntax tree that was parsed from the source code. # The syntax tree that was parsed from the source code.
attr_reader :value attr_reader :value
@ -587,6 +593,17 @@ module Prism
def deconstruct_keys(keys) def deconstruct_keys(keys)
super.merge!(value: value) super.merge!(value: value)
end end
# Attach the list of comments to their respective locations in the tree.
def attach_comments!
Comments.new(self).attach! # steep:ignore
end
# Walk the tree and mark nodes that are on a new line, loosely emulating
# the behavior of CRuby's `:line` tracepoint event.
def mark_newlines!
value.accept(Newlines.new(source.offsets.size)) # steep:ignore
end
end end
# This is a result specific to the `lex` and `lex_file` methods. # This is a result specific to the `lex` and `lex_file` methods.

View File

@ -183,12 +183,5 @@ module Prism
[preceding, NodeTarget.new(node), following] [preceding, NodeTarget.new(node), following]
end end
end end
private_constant :Comments
# Attach the list of comments to their respective locations in the tree.
def attach_comments!
Comments.new(self).attach! # steep:ignore
end
end end
end end

View File

@ -17,21 +17,26 @@ module Prism
# Note that the logic in this file should be kept in sync with the Java # Note that the logic in this file should be kept in sync with the Java
# MarkNewlinesVisitor, since that visitor is responsible for marking the # MarkNewlinesVisitor, since that visitor is responsible for marking the
# newlines for JRuby/TruffleRuby. # newlines for JRuby/TruffleRuby.
#
# This file is autoloaded only when `mark_newlines!` is called, so the
# re-opening of the various nodes in this file will only be performed in
# that case. We do that to avoid storing the extra `@newline` instance
# variable on every node if we don't need it.
class Newlines < Visitor class Newlines < Visitor
# Create a new Newlines visitor with the given newline offsets. # Create a new Newlines visitor with the given newline offsets.
def initialize(newline_marked) def initialize(lines)
@newline_marked = newline_marked @lines = Array.new(1 + lines, false)
end end
# Permit block/lambda nodes to mark newlines within themselves. # Permit block/lambda nodes to mark newlines within themselves.
def visit_block_node(node) def visit_block_node(node)
old_newline_marked = @newline_marked old_lines = @lines
@newline_marked = Array.new(old_newline_marked.size, false) @lines = Array.new(old_lines.size, false)
begin begin
super(node) super(node)
ensure ensure
@newline_marked = old_newline_marked @lines = old_lines
end end
end end
@ -39,7 +44,7 @@ module Prism
# Mark if/unless nodes as newlines. # Mark if/unless nodes as newlines.
def visit_if_node(node) def visit_if_node(node)
node.set_newline_flag(@newline_marked) node.newline!(@lines)
super(node) super(node)
end end
@ -48,17 +53,101 @@ module Prism
# Permit statements lists to mark newlines within themselves. # Permit statements lists to mark newlines within themselves.
def visit_statements_node(node) def visit_statements_node(node)
node.body.each do |child| node.body.each do |child|
child.set_newline_flag(@newline_marked) child.newline!(@lines)
end end
super(node) super(node)
end end
end end
end
private_constant :Newlines class Node
def newline? # :nodoc:
@newline ? true : false
end
# Walk the tree and mark nodes that are on a new line. def newline!(lines) # :nodoc:
def mark_newlines! line = location.start_line
value.accept(Newlines.new(Array.new(1 + source.offsets.size, false))) # steep:ignore unless lines[line]
lines[line] = true
@newline = true
end
end
end
class BeginNode < Node
def newline!(lines) # :nodoc:
# Never mark BeginNode with a newline flag, mark children instead.
end
end
class ParenthesesNode < Node
def newline!(lines) # :nodoc:
# Never mark ParenthesesNode with a newline flag, mark children instead.
end
end
class IfNode < Node
def newline!(lines) # :nodoc:
predicate.newline!(lines)
end
end
class UnlessNode < Node
def newline!(lines) # :nodoc:
predicate.newline!(lines)
end
end
class UntilNode < Node
def newline!(lines) # :nodoc:
predicate.newline!(lines)
end
end
class WhileNode < Node
def newline!(lines) # :nodoc:
predicate.newline!(lines)
end
end
class RescueModifierNode < Node
def newline!(lines) # :nodoc:
expression.newline!(lines)
end
end
class InterpolatedMatchLastLineNode < Node
def newline!(lines) # :nodoc:
first = parts.first
first.newline!(lines) if first
end
end
class InterpolatedRegularExpressionNode < Node
def newline!(lines) # :nodoc:
first = parts.first
first.newline!(lines) if first
end
end
class InterpolatedStringNode < Node
def newline!(lines) # :nodoc:
first = parts.first
first.newline!(lines) if first
end
end
class InterpolatedSymbolNode < Node
def newline!(lines) # :nodoc:
first = parts.first
first.newline!(lines) if first
end
end
class InterpolatedXStringNode < Node
def newline!(lines) # :nodoc:
first = parts.first
first.newline!(lines) if first
end end
end end
end end

View File

@ -116,13 +116,8 @@ module Prism
# Compose a header for the given node. # Compose a header for the given node.
def inspect_node(name, node) def inspect_node(name, node)
result = +"@ #{name} ("
location = node.location location = node.location
result << "location: (#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column})" "@ #{name} (location: (#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}))\n"
result << ", newline: true" if node.newline?
result << ")\n"
end end
# Compose a string representing the given inner location field. # Compose a string representing the given inner location field.

View File

@ -28,18 +28,6 @@ module Prism
location.is_a?(Location) ? location.end_offset : ((location >> 32) + (location & 0xFFFFFFFF)) location.is_a?(Location) ? location.end_offset : ((location >> 32) + (location & 0xFFFFFFFF))
end end
def newline? # :nodoc:
@newline ? true : false
end
def set_newline_flag(newline_marked) # :nodoc:
line = location.start_line
unless newline_marked[line]
newline_marked[line] = true
@newline = true
end
end
# Returns all of the lines of the source code associated with this node. # Returns all of the lines of the source code associated with this node.
def source_lines def source_lines
location.source_lines location.source_lines
@ -181,7 +169,6 @@ module Prism
# def initialize: (<%= (node.fields.map { |field| "#{field.rbs_class} #{field.name}" } + ["Location location"]).join(", ") %>) -> void # def initialize: (<%= (node.fields.map { |field| "#{field.rbs_class} #{field.name}" } + ["Location location"]).join(", ") %>) -> void
def initialize(source, <%= (node.fields.map(&:name) + ["location"]).join(", ") %>) def initialize(source, <%= (node.fields.map(&:name) + ["location"]).join(", ") %>)
@source = source @source = source
@newline = false
@location = location @location = location
<%- node.fields.each do |field| -%> <%- node.fields.each do |field| -%>
<%- if Prism::Template::CHECK_FIELD_KIND && field.respond_to?(:check_field_kind) -%> <%- if Prism::Template::CHECK_FIELD_KIND && field.respond_to?(:check_field_kind) -%>
@ -195,25 +182,6 @@ module Prism
def accept(visitor) def accept(visitor)
visitor.visit_<%= node.human %>(self) visitor.visit_<%= node.human %>(self)
end end
<%- if node.newline == false -%>
def set_newline_flag(newline_marked) # :nodoc:
# Never mark <%= node.name %> with a newline flag, mark children instead
end
<%- elsif node.newline.is_a?(String) -%>
def set_newline_flag(newline_marked) # :nodoc:
<%- field = node.fields.find { |f| f.name == node.newline } or raise node.newline -%>
<%- case field -%>
<%- when Prism::Template::NodeField -%>
<%= field.name %>.set_newline_flag(newline_marked)
<%- when Prism::Template::NodeListField -%>
first = <%= field.name %>.first
first.set_newline_flag(newline_marked) if first
<%- else raise field.class.name -%>
<%- end -%>
end
<%- end -%>
# def child_nodes: () -> Array[nil | Node] # def child_nodes: () -> Array[nil | Node]
def child_nodes def child_nodes