prism 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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