1
1
# frozen_string_literal: true
2
2
3
3
module YARP
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
- "#<YARP::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
- "#<YARP::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
- "#<YARP::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
- "#<YARP::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
-
267
- # This represents a node in the tree.
268
- class Node
269
- attr_reader :location
270
-
271
- def newline?
272
- @newline ? true : false
273
- end
274
-
275
- def set_newline_flag ( newline_marked )
276
- line = location . start_line
277
- unless newline_marked [ line ]
278
- newline_marked [ line ] = true
279
- @newline = true
280
- end
281
- end
282
-
283
- # Slice the location of the node from the source.
284
- def slice
285
- location . slice
286
- end
287
-
288
- def pretty_print ( q )
289
- q . seplist ( inspect . chomp . each_line , -> { q . breakable } ) do |line |
290
- q . text ( line . chomp )
291
- end
292
- q . current_group . break
293
- end
294
- end
295
-
296
4
# There are many files in YARP that are templated to handle every node type,
297
5
# which means the files can end up being quite large. We autoload them to make
298
6
# our require speed faster since consuming libraries are unlikely to use all
@@ -340,6 +48,8 @@ def self.load(source, serialized)
340
48
end
341
49
342
50
require_relative "yarp/node"
51
+ require_relative "yarp/node_ext"
52
+ require_relative "yarp/parse_result"
343
53
require_relative "yarp/parse_result/comments"
344
54
require_relative "yarp/parse_result/newlines"
345
55
@@ -352,59 +62,3 @@ def self.load(source, serialized)
352
62
else
353
63
require_relative "yarp/ffi"
354
64
end
355
-
356
- # Reopening the YARP module after yarp/node is required so that constant
357
- # reflection APIs will find the constants defined in the node file before these.
358
- # This block is meant to contain extra APIs we define on YARP nodes that aren't
359
- # templated and are meant as convenience methods.
360
- module YARP
361
- class FloatNode < Node
362
- # Returns the value of the node as a Ruby Float.
363
- def value
364
- Float ( slice )
365
- end
366
- end
367
-
368
- class ImaginaryNode < Node
369
- # Returns the value of the node as a Ruby Complex.
370
- def value
371
- Complex ( 0 , numeric . value )
372
- end
373
- end
374
-
375
- class IntegerNode < Node
376
- # Returns the value of the node as a Ruby Integer.
377
- def value
378
- Integer ( slice )
379
- end
380
- end
381
-
382
- class InterpolatedRegularExpressionNode < Node
383
- # Returns a numeric value that represents the flags that were used to create
384
- # the regular expression.
385
- def options
386
- o = flags & ( RegularExpressionFlags ::IGNORE_CASE | RegularExpressionFlags ::EXTENDED | RegularExpressionFlags ::MULTI_LINE )
387
- o |= Regexp ::FIXEDENCODING if flags . anybits? ( RegularExpressionFlags ::EUC_JP | RegularExpressionFlags ::WINDOWS_31J | RegularExpressionFlags ::UTF_8 )
388
- o |= Regexp ::NOENCODING if flags . anybits? ( RegularExpressionFlags ::ASCII_8BIT )
389
- o
390
- end
391
- end
392
-
393
- class RationalNode < Node
394
- # Returns the value of the node as a Ruby Rational.
395
- def value
396
- Rational ( slice . chomp ( "r" ) )
397
- end
398
- end
399
-
400
- class RegularExpressionNode < Node
401
- # Returns a numeric value that represents the flags that were used to create
402
- # the regular expression.
403
- def options
404
- o = flags & ( RegularExpressionFlags ::IGNORE_CASE | RegularExpressionFlags ::EXTENDED | RegularExpressionFlags ::MULTI_LINE )
405
- o |= Regexp ::FIXEDENCODING if flags . anybits? ( RegularExpressionFlags ::EUC_JP | RegularExpressionFlags ::WINDOWS_31J | RegularExpressionFlags ::UTF_8 )
406
- o |= Regexp ::NOENCODING if flags . anybits? ( RegularExpressionFlags ::ASCII_8BIT )
407
- o
408
- end
409
- end
410
- end
0 commit comments