prism 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +172 -0
  3. data/CODE_OF_CONDUCT.md +76 -0
  4. data/CONTRIBUTING.md +62 -0
  5. data/LICENSE.md +7 -0
  6. data/Makefile +84 -0
  7. data/README.md +89 -0
  8. data/config.yml +2481 -0
  9. data/docs/build_system.md +74 -0
  10. data/docs/building.md +22 -0
  11. data/docs/configuration.md +60 -0
  12. data/docs/design.md +53 -0
  13. data/docs/encoding.md +117 -0
  14. data/docs/fuzzing.md +93 -0
  15. data/docs/heredocs.md +36 -0
  16. data/docs/mapping.md +117 -0
  17. data/docs/ripper.md +36 -0
  18. data/docs/ruby_api.md +25 -0
  19. data/docs/serialization.md +181 -0
  20. data/docs/testing.md +55 -0
  21. data/ext/prism/api_node.c +4725 -0
  22. data/ext/prism/api_pack.c +256 -0
  23. data/ext/prism/extconf.rb +136 -0
  24. data/ext/prism/extension.c +626 -0
  25. data/ext/prism/extension.h +18 -0
  26. data/include/prism/ast.h +1932 -0
  27. data/include/prism/defines.h +45 -0
  28. data/include/prism/diagnostic.h +231 -0
  29. data/include/prism/enc/pm_encoding.h +95 -0
  30. data/include/prism/node.h +41 -0
  31. data/include/prism/pack.h +141 -0
  32. data/include/prism/parser.h +418 -0
  33. data/include/prism/regexp.h +19 -0
  34. data/include/prism/unescape.h +48 -0
  35. data/include/prism/util/pm_buffer.h +51 -0
  36. data/include/prism/util/pm_char.h +91 -0
  37. data/include/prism/util/pm_constant_pool.h +78 -0
  38. data/include/prism/util/pm_list.h +67 -0
  39. data/include/prism/util/pm_memchr.h +14 -0
  40. data/include/prism/util/pm_newline_list.h +61 -0
  41. data/include/prism/util/pm_state_stack.h +24 -0
  42. data/include/prism/util/pm_string.h +61 -0
  43. data/include/prism/util/pm_string_list.h +25 -0
  44. data/include/prism/util/pm_strpbrk.h +29 -0
  45. data/include/prism/version.h +4 -0
  46. data/include/prism.h +82 -0
  47. data/lib/prism/compiler.rb +465 -0
  48. data/lib/prism/debug.rb +157 -0
  49. data/lib/prism/desugar_compiler.rb +206 -0
  50. data/lib/prism/dispatcher.rb +2051 -0
  51. data/lib/prism/dsl.rb +750 -0
  52. data/lib/prism/ffi.rb +251 -0
  53. data/lib/prism/lex_compat.rb +838 -0
  54. data/lib/prism/mutation_compiler.rb +718 -0
  55. data/lib/prism/node.rb +14540 -0
  56. data/lib/prism/node_ext.rb +55 -0
  57. data/lib/prism/node_inspector.rb +68 -0
  58. data/lib/prism/pack.rb +185 -0
  59. data/lib/prism/parse_result/comments.rb +172 -0
  60. data/lib/prism/parse_result/newlines.rb +60 -0
  61. data/lib/prism/parse_result.rb +266 -0
  62. data/lib/prism/pattern.rb +239 -0
  63. data/lib/prism/ripper_compat.rb +174 -0
  64. data/lib/prism/serialize.rb +662 -0
  65. data/lib/prism/visitor.rb +470 -0
  66. data/lib/prism.rb +64 -0
  67. data/prism.gemspec +113 -0
  68. data/src/diagnostic.c +287 -0
  69. data/src/enc/pm_big5.c +52 -0
  70. data/src/enc/pm_euc_jp.c +58 -0
  71. data/src/enc/pm_gbk.c +61 -0
  72. data/src/enc/pm_shift_jis.c +56 -0
  73. data/src/enc/pm_tables.c +507 -0
  74. data/src/enc/pm_unicode.c +2324 -0
  75. data/src/enc/pm_windows_31j.c +56 -0
  76. data/src/node.c +2633 -0
  77. data/src/pack.c +493 -0
  78. data/src/prettyprint.c +2136 -0
  79. data/src/prism.c +14587 -0
  80. data/src/regexp.c +580 -0
  81. data/src/serialize.c +1899 -0
  82. data/src/token_type.c +349 -0
  83. data/src/unescape.c +637 -0
  84. data/src/util/pm_buffer.c +103 -0
  85. data/src/util/pm_char.c +272 -0
  86. data/src/util/pm_constant_pool.c +252 -0
  87. data/src/util/pm_list.c +41 -0
  88. data/src/util/pm_memchr.c +33 -0
  89. data/src/util/pm_newline_list.c +134 -0
  90. data/src/util/pm_state_stack.c +19 -0
  91. data/src/util/pm_string.c +200 -0
  92. data/src/util/pm_string_list.c +29 -0
  93. data/src/util/pm_strncasecmp.c +17 -0
  94. data/src/util/pm_strpbrk.c +66 -0
  95. metadata +138 -0
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prism
4
+ # This represents a source of Ruby code that has been parsed. It is used in
5
+ # conjunction with locations to allow them to resolve line numbers and source
6
+ # ranges.
7
+ class Source
8
+ attr_reader :source, :offsets
9
+
10
+ def initialize(source, offsets = compute_offsets(source))
11
+ @source = source
12
+ @offsets = offsets
13
+ end
14
+
15
+ def slice(offset, length)
16
+ source.byteslice(offset, length)
17
+ end
18
+
19
+ def line(value)
20
+ offsets.bsearch_index { |offset| offset > value } || offsets.length
21
+ end
22
+
23
+ def line_offset(value)
24
+ offsets[line(value) - 1]
25
+ end
26
+
27
+ def column(value)
28
+ value - offsets[line(value) - 1]
29
+ end
30
+
31
+ private
32
+
33
+ def compute_offsets(code)
34
+ offsets = [0]
35
+ code.b.scan("\n") { offsets << $~.end(0) }
36
+ offsets
37
+ end
38
+ end
39
+
40
+ # This represents a location in the source.
41
+ class Location
42
+ # A Source object that is used to determine more information from the given
43
+ # offset and length.
44
+ protected attr_reader :source
45
+
46
+ # The byte offset from the beginning of the source where this location
47
+ # starts.
48
+ attr_reader :start_offset
49
+
50
+ # The length of this location in bytes.
51
+ attr_reader :length
52
+
53
+ # The list of comments attached to this location
54
+ attr_reader :comments
55
+
56
+ def initialize(source, start_offset, length)
57
+ @source = source
58
+ @start_offset = start_offset
59
+ @length = length
60
+ @comments = []
61
+ end
62
+
63
+ # Create a new location object with the given options.
64
+ def copy(**options)
65
+ Location.new(
66
+ options.fetch(:source) { source },
67
+ options.fetch(:start_offset) { start_offset },
68
+ options.fetch(:length) { length }
69
+ )
70
+ end
71
+
72
+ # Returns a string representation of this location.
73
+ def inspect
74
+ "#<Prism::Location @start_offset=#{@start_offset} @length=#{@length} start_line=#{start_line}>"
75
+ end
76
+
77
+ # The source code that this location represents.
78
+ def slice
79
+ source.slice(start_offset, length)
80
+ end
81
+
82
+ # The byte offset from the beginning of the source where this location ends.
83
+ def end_offset
84
+ start_offset + length
85
+ end
86
+
87
+ # The line number where this location starts.
88
+ def start_line
89
+ source.line(start_offset)
90
+ end
91
+
92
+ # The content of the line where this location starts before this location.
93
+ def start_line_slice
94
+ offset = source.line_offset(start_offset)
95
+ source.slice(offset, start_offset - offset)
96
+ end
97
+
98
+ # The line number where this location ends.
99
+ def end_line
100
+ source.line(end_offset - 1)
101
+ end
102
+
103
+ # The column number in bytes where this location starts from the start of
104
+ # the line.
105
+ def start_column
106
+ source.column(start_offset)
107
+ end
108
+
109
+ # The column number in bytes where this location ends from the start of the
110
+ # line.
111
+ def end_column
112
+ source.column(end_offset)
113
+ end
114
+
115
+ def deconstruct_keys(keys)
116
+ { start_offset: start_offset, end_offset: end_offset }
117
+ end
118
+
119
+ def pretty_print(q)
120
+ q.text("(#{start_line},#{start_column})-(#{end_line},#{end_column}))")
121
+ end
122
+
123
+ def ==(other)
124
+ other.is_a?(Location) &&
125
+ other.start_offset == start_offset &&
126
+ other.end_offset == end_offset
127
+ end
128
+
129
+ # Returns a new location that stretches from this location to the given
130
+ # other location. Raises an error if this location is not before the other
131
+ # location or if they don't share the same source.
132
+ def join(other)
133
+ raise "Incompatible sources" if source != other.source
134
+ raise "Incompatible locations" if start_offset > other.start_offset
135
+
136
+ Location.new(source, start_offset, other.end_offset - start_offset)
137
+ end
138
+
139
+ def self.null
140
+ new(0, 0)
141
+ end
142
+ end
143
+
144
+ # This represents a comment that was encountered during parsing.
145
+ class Comment
146
+ TYPES = [:inline, :embdoc, :__END__]
147
+
148
+ attr_reader :type, :location
149
+
150
+ def initialize(type, location)
151
+ @type = type
152
+ @location = location
153
+ end
154
+
155
+ def deconstruct_keys(keys)
156
+ { type: type, location: location }
157
+ end
158
+
159
+ # Returns true if the comment happens on the same line as other code and false if the comment is by itself
160
+ def trailing?
161
+ type == :inline && !location.start_line_slice.strip.empty?
162
+ end
163
+
164
+ def inspect
165
+ "#<Prism::Comment @type=#{@type.inspect} @location=#{@location.inspect}>"
166
+ end
167
+ end
168
+
169
+ # This represents an error that was encountered during parsing.
170
+ class ParseError
171
+ attr_reader :message, :location
172
+
173
+ def initialize(message, location)
174
+ @message = message
175
+ @location = location
176
+ end
177
+
178
+ def deconstruct_keys(keys)
179
+ { message: message, location: location }
180
+ end
181
+
182
+ def inspect
183
+ "#<Prism::ParseError @message=#{@message.inspect} @location=#{@location.inspect}>"
184
+ end
185
+ end
186
+
187
+ # This represents a warning that was encountered during parsing.
188
+ class ParseWarning
189
+ attr_reader :message, :location
190
+
191
+ def initialize(message, location)
192
+ @message = message
193
+ @location = location
194
+ end
195
+
196
+ def deconstruct_keys(keys)
197
+ { message: message, location: location }
198
+ end
199
+
200
+ def inspect
201
+ "#<Prism::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect}>"
202
+ end
203
+ end
204
+
205
+ # This represents the result of a call to ::parse or ::parse_file. It contains
206
+ # the AST, any comments that were encounters, and any errors that were
207
+ # encountered.
208
+ class ParseResult
209
+ attr_reader :value, :comments, :errors, :warnings, :source
210
+
211
+ def initialize(value, comments, errors, warnings, source)
212
+ @value = value
213
+ @comments = comments
214
+ @errors = errors
215
+ @warnings = warnings
216
+ @source = source
217
+ end
218
+
219
+ def deconstruct_keys(keys)
220
+ { value: value, comments: comments, errors: errors, warnings: warnings }
221
+ end
222
+
223
+ def success?
224
+ errors.empty?
225
+ end
226
+
227
+ def failure?
228
+ !success?
229
+ end
230
+ end
231
+
232
+ # This represents a token from the Ruby source.
233
+ class Token
234
+ attr_reader :type, :value, :location
235
+
236
+ def initialize(type, value, location)
237
+ @type = type
238
+ @value = value
239
+ @location = location
240
+ end
241
+
242
+ def deconstruct_keys(keys)
243
+ { type: type, value: value, location: location }
244
+ end
245
+
246
+ def pretty_print(q)
247
+ q.group do
248
+ q.text(type.to_s)
249
+ self.location.pretty_print(q)
250
+ q.text("(")
251
+ q.nest(2) do
252
+ q.breakable("")
253
+ q.pp(value)
254
+ end
255
+ q.breakable("")
256
+ q.text(")")
257
+ end
258
+ end
259
+
260
+ def ==(other)
261
+ other.is_a?(Token) &&
262
+ other.type == type &&
263
+ other.value == value
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prism
4
+ # A pattern is an object that wraps a Ruby pattern matching expression. The
5
+ # expression would normally be passed to an `in` clause within a `case`
6
+ # expression or a rightward assignment expression. For example, in the
7
+ # following snippet:
8
+ #
9
+ # case node
10
+ # in ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]]
11
+ # end
12
+ #
13
+ # the pattern is the `ConstantPathNode[...]` expression.
14
+ #
15
+ # The pattern gets compiled into an object that responds to #call by running
16
+ # the #compile method. This method itself will run back through Prism to
17
+ # parse the expression into a tree, then walk the tree to generate the
18
+ # necessary callable objects. For example, if you wanted to compile the
19
+ # expression above into a callable, you would:
20
+ #
21
+ # callable = Prism::Pattern.new("ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]]").compile
22
+ # callable.call(node)
23
+ #
24
+ # The callable object returned by #compile is guaranteed to respond to #call
25
+ # with a single argument, which is the node to match against. It also is
26
+ # guaranteed to respond to #===, which means it itself can be used in a `case`
27
+ # expression, as in:
28
+ #
29
+ # case node
30
+ # when callable
31
+ # end
32
+ #
33
+ # If the query given to the initializer cannot be compiled into a valid
34
+ # matcher (either because of a syntax error or because it is using syntax we
35
+ # do not yet support) then a Prism::Pattern::CompilationError will be
36
+ # raised.
37
+ class Pattern
38
+ # Raised when the query given to a pattern is either invalid Ruby syntax or
39
+ # is using syntax that we don't yet support.
40
+ class CompilationError < StandardError
41
+ def initialize(repr)
42
+ super(<<~ERROR)
43
+ prism was unable to compile the pattern you provided into a usable
44
+ expression. It failed on to understand the node represented by:
45
+
46
+ #{repr}
47
+
48
+ Note that not all syntax supported by Ruby's pattern matching syntax
49
+ is also supported by prism's patterns. If you're using some syntax
50
+ that you believe should be supported, please open an issue on
51
+ GitHub at https://github.com/ruby/prism/issues/new.
52
+ ERROR
53
+ end
54
+ end
55
+
56
+ attr_reader :query
57
+
58
+ def initialize(query)
59
+ @query = query
60
+ @compiled = nil
61
+ end
62
+
63
+ def compile
64
+ result = Prism.parse("case nil\nin #{query}\nend")
65
+ compile_node(result.value.statements.body.last.conditions.last.pattern)
66
+ end
67
+
68
+ def scan(root)
69
+ return to_enum(__method__, root) unless block_given?
70
+
71
+ @compiled ||= compile
72
+ queue = [root]
73
+
74
+ while (node = queue.shift)
75
+ yield node if @compiled.call(node)
76
+ queue.concat(node.compact_child_nodes)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Shortcut for combining two procs into one that returns true if both return
83
+ # true.
84
+ def combine_and(left, right)
85
+ ->(other) { left.call(other) && right.call(other) }
86
+ end
87
+
88
+ # Shortcut for combining two procs into one that returns true if either
89
+ # returns true.
90
+ def combine_or(left, right)
91
+ ->(other) { left.call(other) || right.call(other) }
92
+ end
93
+
94
+ # Raise an error because the given node is not supported.
95
+ def compile_error(node)
96
+ raise CompilationError, node.inspect
97
+ end
98
+
99
+ # in [foo, bar, baz]
100
+ def compile_array_pattern_node(node)
101
+ compile_error(node) if !node.rest.nil? || node.posts.any?
102
+
103
+ constant = node.constant
104
+ compiled_constant = compile_node(constant) if constant
105
+
106
+ preprocessed = node.requireds.map { |required| compile_node(required) }
107
+
108
+ compiled_requireds = ->(other) do
109
+ deconstructed = other.deconstruct
110
+
111
+ deconstructed.length == preprocessed.length &&
112
+ preprocessed
113
+ .zip(deconstructed)
114
+ .all? { |(matcher, value)| matcher.call(value) }
115
+ end
116
+
117
+ if compiled_constant
118
+ combine_and(compiled_constant, compiled_requireds)
119
+ else
120
+ compiled_requireds
121
+ end
122
+ end
123
+
124
+ # in foo | bar
125
+ def compile_alternation_pattern_node(node)
126
+ combine_or(compile_node(node.left), compile_node(node.right))
127
+ end
128
+
129
+ # in Prism::ConstantReadNode
130
+ def compile_constant_path_node(node)
131
+ parent = node.parent
132
+
133
+ if parent.is_a?(ConstantReadNode) && parent.slice == "Prism"
134
+ compile_node(node.child)
135
+ else
136
+ compile_error(node)
137
+ end
138
+ end
139
+
140
+ # in ConstantReadNode
141
+ # in String
142
+ def compile_constant_read_node(node)
143
+ value = node.slice
144
+
145
+ if Prism.const_defined?(value, false)
146
+ clazz = Prism.const_get(value)
147
+
148
+ ->(other) { clazz === other }
149
+ elsif Object.const_defined?(value, false)
150
+ clazz = Object.const_get(value)
151
+
152
+ ->(other) { clazz === other }
153
+ else
154
+ compile_error(node)
155
+ end
156
+ end
157
+
158
+ # in InstanceVariableReadNode[name: Symbol]
159
+ # in { name: Symbol }
160
+ def compile_hash_pattern_node(node)
161
+ compile_error(node) unless node.kwrest.nil?
162
+ compiled_constant = compile_node(node.constant) if node.constant
163
+
164
+ preprocessed =
165
+ node.assocs.to_h do |assoc|
166
+ [assoc.key.unescaped.to_sym, compile_node(assoc.value)]
167
+ end
168
+
169
+ compiled_keywords = ->(other) do
170
+ deconstructed = other.deconstruct_keys(preprocessed.keys)
171
+
172
+ preprocessed.all? do |keyword, matcher|
173
+ deconstructed.key?(keyword) && matcher.call(deconstructed[keyword])
174
+ end
175
+ end
176
+
177
+ if compiled_constant
178
+ combine_and(compiled_constant, compiled_keywords)
179
+ else
180
+ compiled_keywords
181
+ end
182
+ end
183
+
184
+ # in nil
185
+ def compile_nil_node(node)
186
+ ->(attribute) { attribute.nil? }
187
+ end
188
+
189
+ # in /foo/
190
+ def compile_regular_expression_node(node)
191
+ regexp = Regexp.new(node.unescaped, node.closing[1..])
192
+
193
+ ->(attribute) { regexp === attribute }
194
+ end
195
+
196
+ # in ""
197
+ # in "foo"
198
+ def compile_string_node(node)
199
+ string = node.unescaped
200
+
201
+ ->(attribute) { string === attribute }
202
+ end
203
+
204
+ # in :+
205
+ # in :foo
206
+ def compile_symbol_node(node)
207
+ symbol = node.unescaped.to_sym
208
+
209
+ ->(attribute) { symbol === attribute }
210
+ end
211
+
212
+ # Compile any kind of node. Dispatch out to the individual compilation
213
+ # methods based on the type of node.
214
+ def compile_node(node)
215
+ case node
216
+ when AlternationPatternNode
217
+ compile_alternation_pattern_node(node)
218
+ when ArrayPatternNode
219
+ compile_array_pattern_node(node)
220
+ when ConstantPathNode
221
+ compile_constant_path_node(node)
222
+ when ConstantReadNode
223
+ compile_constant_read_node(node)
224
+ when HashPatternNode
225
+ compile_hash_pattern_node(node)
226
+ when NilNode
227
+ compile_nil_node(node)
228
+ when RegularExpressionNode
229
+ compile_regular_expression_node(node)
230
+ when StringNode
231
+ compile_string_node(node)
232
+ when SymbolNode
233
+ compile_symbol_node(node)
234
+ else
235
+ compile_error(node)
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ module Prism
6
+ # This class is meant to provide a compatibility layer between prism and
7
+ # Ripper. It functions by parsing the entire tree first and then walking it
8
+ # and executing each of the Ripper callbacks as it goes.
9
+ #
10
+ # This class is going to necessarily be slower than the native Ripper API. It
11
+ # is meant as a stopgap until developers migrate to using prism. It is also
12
+ # meant as a test harness for the prism parser.
13
+ class RipperCompat
14
+ # This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that
15
+ # returns the arrays of [type, *children].
16
+ class SexpBuilder < RipperCompat
17
+ private
18
+
19
+ Ripper::PARSER_EVENTS.each do |event|
20
+ define_method(:"on_#{event}") do |*args|
21
+ [event, *args]
22
+ end
23
+ end
24
+
25
+ Ripper::SCANNER_EVENTS.each do |event|
26
+ define_method(:"on_#{event}") do |value|
27
+ [:"@#{event}", value, [lineno, column]]
28
+ end
29
+ end
30
+ end
31
+
32
+ # This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that
33
+ # returns the same values as ::Ripper::SexpBuilder except with a couple of
34
+ # niceties that flatten linked lists into arrays.
35
+ class SexpBuilderPP < SexpBuilder
36
+ private
37
+
38
+ def _dispatch_event_new
39
+ []
40
+ end
41
+
42
+ def _dispatch_event_push(list, item)
43
+ list << item
44
+ list
45
+ end
46
+
47
+ Ripper::PARSER_EVENT_TABLE.each do |event, arity|
48
+ case event
49
+ when /_new\z/
50
+ alias_method :"on_#{event}", :_dispatch_event_new if arity == 0
51
+ when /_add\z/
52
+ alias_method :"on_#{event}", :_dispatch_event_push
53
+ end
54
+ end
55
+ end
56
+
57
+ attr_reader :source, :lineno, :column
58
+
59
+ def initialize(source)
60
+ @source = source
61
+ @result = nil
62
+ @lineno = nil
63
+ @column = nil
64
+ end
65
+
66
+ ############################################################################
67
+ # Public interface
68
+ ############################################################################
69
+
70
+ def error?
71
+ result.errors.any?
72
+ end
73
+
74
+ def parse
75
+ result.value.accept(self) unless error?
76
+ end
77
+
78
+ ############################################################################
79
+ # Visitor methods
80
+ ############################################################################
81
+
82
+ def visit(node)
83
+ node&.accept(self)
84
+ end
85
+
86
+ def visit_call_node(node)
87
+ if !node.opening_loc && node.arguments.arguments.length == 1
88
+ bounds(node.receiver.location)
89
+ left = visit(node.receiver)
90
+
91
+ bounds(node.arguments.arguments.first.location)
92
+ right = visit(node.arguments.arguments.first)
93
+
94
+ on_binary(left, source[node.message_loc.start_offset...node.message_loc.end_offset].to_sym, right)
95
+ else
96
+ raise NotImplementedError
97
+ end
98
+ end
99
+
100
+ def visit_integer_node(node)
101
+ bounds(node.location)
102
+ on_int(source[node.location.start_offset...node.location.end_offset])
103
+ end
104
+
105
+ def visit_statements_node(node)
106
+ bounds(node.location)
107
+ node.body.inject(on_stmts_new) do |stmts, stmt|
108
+ on_stmts_add(stmts, visit(stmt))
109
+ end
110
+ end
111
+
112
+ def visit_token(node)
113
+ bounds(node.location)
114
+
115
+ case node.type
116
+ when :MINUS
117
+ on_op(node.value)
118
+ when :PLUS
119
+ on_op(node.value)
120
+ else
121
+ raise NotImplementedError, "Unknown token: #{node.type}"
122
+ end
123
+ end
124
+
125
+ def visit_program_node(node)
126
+ bounds(node.location)
127
+ on_program(visit(node.statements))
128
+ end
129
+
130
+ ############################################################################
131
+ # Entrypoints for subclasses
132
+ ############################################################################
133
+
134
+ # This is a convenience method that runs the SexpBuilder subclass parser.
135
+ def self.sexp_raw(source)
136
+ SexpBuilder.new(source).parse
137
+ end
138
+
139
+ # This is a convenience method that runs the SexpBuilderPP subclass parser.
140
+ def self.sexp(source)
141
+ SexpBuilderPP.new(source).parse
142
+ end
143
+
144
+ private
145
+
146
+ # This method is responsible for updating lineno and column information
147
+ # to reflect the current node.
148
+ #
149
+ # This method could be drastically improved with some caching on the start
150
+ # of every line, but for now it's good enough.
151
+ def bounds(location)
152
+ start_offset = location.start_offset
153
+
154
+ @lineno = source[0..start_offset].count("\n") + 1
155
+ @column = start_offset - (source.rindex("\n", start_offset) || 0)
156
+ end
157
+
158
+ def result
159
+ @result ||= Prism.parse(source)
160
+ end
161
+
162
+ def _dispatch0; end
163
+ def _dispatch1(_); end
164
+ def _dispatch2(_, _); end
165
+ def _dispatch3(_, _, _); end
166
+ def _dispatch4(_, _, _, _); end
167
+ def _dispatch5(_, _, _, _, _); end
168
+ def _dispatch7(_, _, _, _, _, _, _); end
169
+
170
+ (Ripper::SCANNER_EVENT_TABLE.merge(Ripper::PARSER_EVENT_TABLE)).each do |event, arity|
171
+ alias_method :"on_#{event}", :"_dispatch#{arity}"
172
+ end
173
+ end
174
+ end