prism 0.29.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -1
  3. data/CONTRIBUTING.md +0 -4
  4. data/Makefile +1 -1
  5. data/README.md +4 -0
  6. data/config.yml +920 -148
  7. data/docs/build_system.md +8 -11
  8. data/docs/fuzzing.md +1 -1
  9. data/docs/parsing_rules.md +4 -1
  10. data/docs/relocation.md +34 -0
  11. data/docs/ripper_translation.md +22 -0
  12. data/docs/serialization.md +3 -0
  13. data/ext/prism/api_node.c +2863 -2079
  14. data/ext/prism/extconf.rb +14 -37
  15. data/ext/prism/extension.c +241 -391
  16. data/ext/prism/extension.h +2 -2
  17. data/include/prism/ast.h +2156 -453
  18. data/include/prism/defines.h +58 -7
  19. data/include/prism/diagnostic.h +24 -6
  20. data/include/prism/node.h +0 -21
  21. data/include/prism/options.h +94 -3
  22. data/include/prism/parser.h +82 -40
  23. data/include/prism/regexp.h +18 -8
  24. data/include/prism/static_literals.h +3 -2
  25. data/include/prism/util/pm_char.h +1 -2
  26. data/include/prism/util/pm_constant_pool.h +0 -8
  27. data/include/prism/util/pm_integer.h +22 -15
  28. data/include/prism/util/pm_newline_list.h +11 -0
  29. data/include/prism/util/pm_string.h +28 -12
  30. data/include/prism/version.h +3 -3
  31. data/include/prism.h +47 -11
  32. data/lib/prism/compiler.rb +3 -0
  33. data/lib/prism/desugar_compiler.rb +111 -74
  34. data/lib/prism/dispatcher.rb +16 -1
  35. data/lib/prism/dot_visitor.rb +55 -34
  36. data/lib/prism/dsl.rb +660 -468
  37. data/lib/prism/ffi.rb +113 -8
  38. data/lib/prism/inspect_visitor.rb +296 -64
  39. data/lib/prism/lex_compat.rb +1 -1
  40. data/lib/prism/mutation_compiler.rb +11 -6
  41. data/lib/prism/node.rb +4262 -5023
  42. data/lib/prism/node_ext.rb +91 -14
  43. data/lib/prism/parse_result/comments.rb +0 -7
  44. data/lib/prism/parse_result/errors.rb +65 -0
  45. data/lib/prism/parse_result/newlines.rb +101 -11
  46. data/lib/prism/parse_result.rb +183 -6
  47. data/lib/prism/reflection.rb +12 -10
  48. data/lib/prism/relocation.rb +504 -0
  49. data/lib/prism/serialize.rb +496 -609
  50. data/lib/prism/string_query.rb +30 -0
  51. data/lib/prism/translation/parser/compiler.rb +185 -155
  52. data/lib/prism/translation/parser/lexer.rb +26 -4
  53. data/lib/prism/translation/parser.rb +9 -4
  54. data/lib/prism/translation/ripper.rb +23 -25
  55. data/lib/prism/translation/ruby_parser.rb +86 -17
  56. data/lib/prism/visitor.rb +3 -0
  57. data/lib/prism.rb +6 -8
  58. data/prism.gemspec +9 -5
  59. data/rbi/prism/dsl.rbi +521 -0
  60. data/rbi/prism/node.rbi +1115 -1120
  61. data/rbi/prism/parse_result.rbi +29 -0
  62. data/rbi/prism/string_query.rbi +12 -0
  63. data/rbi/prism/visitor.rbi +3 -0
  64. data/rbi/prism.rbi +36 -30
  65. data/sig/prism/dsl.rbs +190 -303
  66. data/sig/prism/mutation_compiler.rbs +1 -0
  67. data/sig/prism/node.rbs +678 -632
  68. data/sig/prism/parse_result.rbs +22 -0
  69. data/sig/prism/relocation.rbs +185 -0
  70. data/sig/prism/string_query.rbs +11 -0
  71. data/sig/prism/visitor.rbs +1 -0
  72. data/sig/prism.rbs +103 -64
  73. data/src/diagnostic.c +64 -28
  74. data/src/node.c +502 -1739
  75. data/src/options.c +76 -27
  76. data/src/prettyprint.c +188 -112
  77. data/src/prism.c +3376 -2293
  78. data/src/regexp.c +208 -71
  79. data/src/serialize.c +182 -50
  80. data/src/static_literals.c +64 -85
  81. data/src/token_type.c +4 -4
  82. data/src/util/pm_char.c +1 -1
  83. data/src/util/pm_constant_pool.c +0 -8
  84. data/src/util/pm_integer.c +53 -25
  85. data/src/util/pm_newline_list.c +29 -0
  86. data/src/util/pm_string.c +131 -80
  87. data/src/util/pm_strpbrk.c +32 -6
  88. metadata +11 -7
  89. data/include/prism/util/pm_string_list.h +0 -44
  90. data/lib/prism/debug.rb +0 -249
  91. data/lib/prism/translation/parser/rubocop.rb +0 -73
  92. data/src/util/pm_string_list.c +0 -28
@@ -112,7 +112,7 @@ module Prism
112
112
  when :and_node
113
113
  [NodeField.new(:left), NodeField.new(:right), LocationField.new(:operator_loc)]
114
114
  when :arguments_node
115
- [FlagsField.new(:flags, [:contains_keywords?, :contains_keyword_splat?]), NodeListField.new(:arguments)]
115
+ [FlagsField.new(:flags, [:contains_forwarding?, :contains_keywords?, :contains_keyword_splat?, :contains_splat?, :contains_multiple_splats?]), NodeListField.new(:arguments)]
116
116
  when :array_node
117
117
  [FlagsField.new(:flags, [:contains_splat?]), NodeListField.new(:elements), OptionalLocationField.new(:opening_loc), OptionalLocationField.new(:closing_loc)]
118
118
  when :array_pattern_node
@@ -150,9 +150,9 @@ module Prism
150
150
  when :capture_pattern_node
151
151
  [NodeField.new(:value), NodeField.new(:target), LocationField.new(:operator_loc)]
152
152
  when :case_match_node
153
- [OptionalNodeField.new(:predicate), NodeListField.new(:conditions), OptionalNodeField.new(:consequent), LocationField.new(:case_keyword_loc), LocationField.new(:end_keyword_loc)]
153
+ [OptionalNodeField.new(:predicate), NodeListField.new(:conditions), OptionalNodeField.new(:else_clause), LocationField.new(:case_keyword_loc), LocationField.new(:end_keyword_loc)]
154
154
  when :case_node
155
- [OptionalNodeField.new(:predicate), NodeListField.new(:conditions), OptionalNodeField.new(:consequent), LocationField.new(:case_keyword_loc), LocationField.new(:end_keyword_loc)]
155
+ [OptionalNodeField.new(:predicate), NodeListField.new(:conditions), OptionalNodeField.new(:else_clause), LocationField.new(:case_keyword_loc), LocationField.new(:end_keyword_loc)]
156
156
  when :class_node
157
157
  [ConstantListField.new(:locals), LocationField.new(:class_keyword_loc), NodeField.new(:constant_path), OptionalLocationField.new(:inheritance_operator_loc), OptionalNodeField.new(:superclass), OptionalNodeField.new(:body), LocationField.new(:end_keyword_loc), ConstantField.new(:name)]
158
158
  when :class_variable_and_write_node
@@ -236,7 +236,7 @@ module Prism
236
236
  when :hash_pattern_node
237
237
  [OptionalNodeField.new(:constant), NodeListField.new(:elements), OptionalNodeField.new(:rest), OptionalLocationField.new(:opening_loc), OptionalLocationField.new(:closing_loc)]
238
238
  when :if_node
239
- [OptionalLocationField.new(:if_keyword_loc), NodeField.new(:predicate), OptionalLocationField.new(:then_keyword_loc), OptionalNodeField.new(:statements), OptionalNodeField.new(:consequent), OptionalLocationField.new(:end_keyword_loc)]
239
+ [OptionalLocationField.new(:if_keyword_loc), NodeField.new(:predicate), OptionalLocationField.new(:then_keyword_loc), OptionalNodeField.new(:statements), OptionalNodeField.new(:subsequent), OptionalLocationField.new(:end_keyword_loc)]
240
240
  when :imaginary_node
241
241
  [NodeField.new(:numeric)]
242
242
  when :implicit_node
@@ -277,6 +277,8 @@ module Prism
277
277
  [OptionalLocationField.new(:opening_loc), NodeListField.new(:parts), OptionalLocationField.new(:closing_loc)]
278
278
  when :interpolated_x_string_node
279
279
  [LocationField.new(:opening_loc), NodeListField.new(:parts), LocationField.new(:closing_loc)]
280
+ when :it_local_variable_read_node
281
+ []
280
282
  when :it_parameters_node
281
283
  []
282
284
  when :keyword_hash_node
@@ -346,7 +348,7 @@ module Prism
346
348
  when :range_node
347
349
  [FlagsField.new(:flags, [:exclude_end?]), OptionalNodeField.new(:left), OptionalNodeField.new(:right), LocationField.new(:operator_loc)]
348
350
  when :rational_node
349
- [NodeField.new(:numeric)]
351
+ [FlagsField.new(:flags, [:binary?, :decimal?, :octal?, :hexadecimal?]), IntegerField.new(:numerator), IntegerField.new(:denominator)]
350
352
  when :redo_node
351
353
  []
352
354
  when :regular_expression_node
@@ -358,13 +360,13 @@ module Prism
358
360
  when :rescue_modifier_node
359
361
  [NodeField.new(:expression), LocationField.new(:keyword_loc), NodeField.new(:rescue_expression)]
360
362
  when :rescue_node
361
- [LocationField.new(:keyword_loc), NodeListField.new(:exceptions), OptionalLocationField.new(:operator_loc), OptionalNodeField.new(:reference), OptionalNodeField.new(:statements), OptionalNodeField.new(:consequent)]
363
+ [LocationField.new(:keyword_loc), NodeListField.new(:exceptions), OptionalLocationField.new(:operator_loc), OptionalNodeField.new(:reference), OptionalNodeField.new(:statements), OptionalNodeField.new(:subsequent)]
362
364
  when :rest_parameter_node
363
365
  [FlagsField.new(:flags, [:repeated_parameter?]), OptionalConstantField.new(:name), OptionalLocationField.new(:name_loc), LocationField.new(:operator_loc)]
364
366
  when :retry_node
365
367
  []
366
368
  when :return_node
367
- [FlagsField.new(:flags, [:redundant?]), LocationField.new(:keyword_loc), OptionalNodeField.new(:arguments)]
369
+ [LocationField.new(:keyword_loc), OptionalNodeField.new(:arguments)]
368
370
  when :self_node
369
371
  []
370
372
  when :shareable_constant_node
@@ -392,13 +394,13 @@ module Prism
392
394
  when :undef_node
393
395
  [NodeListField.new(:names), LocationField.new(:keyword_loc)]
394
396
  when :unless_node
395
- [LocationField.new(:keyword_loc), NodeField.new(:predicate), OptionalLocationField.new(:then_keyword_loc), OptionalNodeField.new(:statements), OptionalNodeField.new(:consequent), OptionalLocationField.new(:end_keyword_loc)]
397
+ [LocationField.new(:keyword_loc), NodeField.new(:predicate), OptionalLocationField.new(:then_keyword_loc), OptionalNodeField.new(:statements), OptionalNodeField.new(:else_clause), OptionalLocationField.new(:end_keyword_loc)]
396
398
  when :until_node
397
- [FlagsField.new(:flags, [:begin_modifier?]), LocationField.new(:keyword_loc), OptionalLocationField.new(:closing_loc), NodeField.new(:predicate), OptionalNodeField.new(:statements)]
399
+ [FlagsField.new(:flags, [:begin_modifier?]), LocationField.new(:keyword_loc), OptionalLocationField.new(:do_keyword_loc), OptionalLocationField.new(:closing_loc), NodeField.new(:predicate), OptionalNodeField.new(:statements)]
398
400
  when :when_node
399
401
  [LocationField.new(:keyword_loc), NodeListField.new(:conditions), OptionalLocationField.new(:then_keyword_loc), OptionalNodeField.new(:statements)]
400
402
  when :while_node
401
- [FlagsField.new(:flags, [:begin_modifier?]), LocationField.new(:keyword_loc), OptionalLocationField.new(:closing_loc), NodeField.new(:predicate), OptionalNodeField.new(:statements)]
403
+ [FlagsField.new(:flags, [:begin_modifier?]), LocationField.new(:keyword_loc), OptionalLocationField.new(:do_keyword_loc), OptionalLocationField.new(:closing_loc), NodeField.new(:predicate), OptionalNodeField.new(:statements)]
402
404
  when :x_string_node
403
405
  [FlagsField.new(:flags, [:forced_utf8_encoding?, :forced_binary_encoding?]), LocationField.new(:opening_loc), LocationField.new(:content_loc), LocationField.new(:closing_loc), StringField.new(:unescaped)]
404
406
  when :yield_node
@@ -0,0 +1,504 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prism
4
+ # Prism parses deterministically for the same input. This provides a nice
5
+ # property that is exposed through the #node_id API on nodes. Effectively this
6
+ # means that for the same input, these values will remain consistent every
7
+ # time the source is parsed. This means we can reparse the source same with a
8
+ # #node_id value and find the exact same node again.
9
+ #
10
+ # The Relocation module provides an API around this property. It allows you to
11
+ # "save" nodes and locations using a minimal amount of memory (just the
12
+ # node_id and a field identifier) and then reify them later.
13
+ module Relocation
14
+ # An entry in a repository that will lazily reify its values when they are
15
+ # first accessed.
16
+ class Entry
17
+ # Raised if a value that could potentially be on an entry is missing
18
+ # because it was either not configured on the repository or it has not yet
19
+ # been fetched.
20
+ class MissingValueError < StandardError
21
+ end
22
+
23
+ # Initialize a new entry with the given repository.
24
+ def initialize(repository)
25
+ @repository = repository
26
+ @values = nil
27
+ end
28
+
29
+ # Fetch the filepath of the value.
30
+ def filepath
31
+ fetch_value(:filepath)
32
+ end
33
+
34
+ # Fetch the start line of the value.
35
+ def start_line
36
+ fetch_value(:start_line)
37
+ end
38
+
39
+ # Fetch the end line of the value.
40
+ def end_line
41
+ fetch_value(:end_line)
42
+ end
43
+
44
+ # Fetch the start byte offset of the value.
45
+ def start_offset
46
+ fetch_value(:start_offset)
47
+ end
48
+
49
+ # Fetch the end byte offset of the value.
50
+ def end_offset
51
+ fetch_value(:end_offset)
52
+ end
53
+
54
+ # Fetch the start character offset of the value.
55
+ def start_character_offset
56
+ fetch_value(:start_character_offset)
57
+ end
58
+
59
+ # Fetch the end character offset of the value.
60
+ def end_character_offset
61
+ fetch_value(:end_character_offset)
62
+ end
63
+
64
+ # Fetch the start code units offset of the value, for the encoding that
65
+ # was configured on the repository.
66
+ def start_code_units_offset
67
+ fetch_value(:start_code_units_offset)
68
+ end
69
+
70
+ # Fetch the end code units offset of the value, for the encoding that was
71
+ # configured on the repository.
72
+ def end_code_units_offset
73
+ fetch_value(:end_code_units_offset)
74
+ end
75
+
76
+ # Fetch the start byte column of the value.
77
+ def start_column
78
+ fetch_value(:start_column)
79
+ end
80
+
81
+ # Fetch the end byte column of the value.
82
+ def end_column
83
+ fetch_value(:end_column)
84
+ end
85
+
86
+ # Fetch the start character column of the value.
87
+ def start_character_column
88
+ fetch_value(:start_character_column)
89
+ end
90
+
91
+ # Fetch the end character column of the value.
92
+ def end_character_column
93
+ fetch_value(:end_character_column)
94
+ end
95
+
96
+ # Fetch the start code units column of the value, for the encoding that
97
+ # was configured on the repository.
98
+ def start_code_units_column
99
+ fetch_value(:start_code_units_column)
100
+ end
101
+
102
+ # Fetch the end code units column of the value, for the encoding that was
103
+ # configured on the repository.
104
+ def end_code_units_column
105
+ fetch_value(:end_code_units_column)
106
+ end
107
+
108
+ # Fetch the leading comments of the value.
109
+ def leading_comments
110
+ fetch_value(:leading_comments)
111
+ end
112
+
113
+ # Fetch the trailing comments of the value.
114
+ def trailing_comments
115
+ fetch_value(:trailing_comments)
116
+ end
117
+
118
+ # Fetch the leading and trailing comments of the value.
119
+ def comments
120
+ leading_comments.concat(trailing_comments)
121
+ end
122
+
123
+ # Reify the values on this entry with the given values. This is an
124
+ # internal-only API that is called from the repository when it is time to
125
+ # reify the values.
126
+ def reify!(values) # :nodoc:
127
+ @repository = nil
128
+ @values = values
129
+ end
130
+
131
+ private
132
+
133
+ # Fetch a value from the entry, raising an error if it is missing.
134
+ def fetch_value(name)
135
+ values.fetch(name) do
136
+ raise MissingValueError, "No value for #{name}, make sure the " \
137
+ "repository has been properly configured"
138
+ end
139
+ end
140
+
141
+ # Return the values from the repository, reifying them if necessary.
142
+ def values
143
+ @values || (@repository.reify!; @values)
144
+ end
145
+ end
146
+
147
+ # Represents the source of a repository that will be reparsed.
148
+ class Source
149
+ # The value that will need to be reparsed.
150
+ attr_reader :value
151
+
152
+ # Initialize the source with the given value.
153
+ def initialize(value)
154
+ @value = value
155
+ end
156
+
157
+ # Reparse the value and return the parse result.
158
+ def result
159
+ raise NotImplementedError, "Subclasses must implement #result"
160
+ end
161
+
162
+ # Create a code units cache for the given encoding.
163
+ def code_units_cache(encoding)
164
+ result.code_units_cache(encoding)
165
+ end
166
+ end
167
+
168
+ # A source that is represented by a file path.
169
+ class SourceFilepath < Source
170
+ # Reparse the file and return the parse result.
171
+ def result
172
+ Prism.parse_file(value)
173
+ end
174
+ end
175
+
176
+ # A source that is represented by a string.
177
+ class SourceString < Source
178
+ # Reparse the string and return the parse result.
179
+ def result
180
+ Prism.parse(value)
181
+ end
182
+ end
183
+
184
+ # A field that represents the file path.
185
+ class FilepathField
186
+ # The file path that this field represents.
187
+ attr_reader :value
188
+
189
+ # Initialize a new field with the given file path.
190
+ def initialize(value)
191
+ @value = value
192
+ end
193
+
194
+ # Fetch the file path.
195
+ def fields(_value)
196
+ { filepath: value }
197
+ end
198
+ end
199
+
200
+ # A field representing the start and end lines.
201
+ class LinesField
202
+ # Fetches the start and end line of a value.
203
+ def fields(value)
204
+ { start_line: value.start_line, end_line: value.end_line }
205
+ end
206
+ end
207
+
208
+ # A field representing the start and end byte offsets.
209
+ class OffsetsField
210
+ # Fetches the start and end byte offset of a value.
211
+ def fields(value)
212
+ { start_offset: value.start_offset, end_offset: value.end_offset }
213
+ end
214
+ end
215
+
216
+ # A field representing the start and end character offsets.
217
+ class CharacterOffsetsField
218
+ # Fetches the start and end character offset of a value.
219
+ def fields(value)
220
+ {
221
+ start_character_offset: value.start_character_offset,
222
+ end_character_offset: value.end_character_offset
223
+ }
224
+ end
225
+ end
226
+
227
+ # A field representing the start and end code unit offsets.
228
+ class CodeUnitOffsetsField
229
+ # A pointer to the repository object that is used for lazily creating a
230
+ # code units cache.
231
+ attr_reader :repository
232
+
233
+ # The associated encoding for the code units.
234
+ attr_reader :encoding
235
+
236
+ # Initialize a new field with the associated repository and encoding.
237
+ def initialize(repository, encoding)
238
+ @repository = repository
239
+ @encoding = encoding
240
+ @cache = nil
241
+ end
242
+
243
+ # Fetches the start and end code units offset of a value for a particular
244
+ # encoding.
245
+ def fields(value)
246
+ {
247
+ start_code_units_offset: value.cached_start_code_units_offset(cache),
248
+ end_code_units_offset: value.cached_end_code_units_offset(cache)
249
+ }
250
+ end
251
+
252
+ private
253
+
254
+ # Lazily create a code units cache for the associated encoding.
255
+ def cache
256
+ @cache ||= repository.code_units_cache(encoding)
257
+ end
258
+ end
259
+
260
+ # A field representing the start and end byte columns.
261
+ class ColumnsField
262
+ # Fetches the start and end byte column of a value.
263
+ def fields(value)
264
+ { start_column: value.start_column, end_column: value.end_column }
265
+ end
266
+ end
267
+
268
+ # A field representing the start and end character columns.
269
+ class CharacterColumnsField
270
+ # Fetches the start and end character column of a value.
271
+ def fields(value)
272
+ {
273
+ start_character_column: value.start_character_column,
274
+ end_character_column: value.end_character_column
275
+ }
276
+ end
277
+ end
278
+
279
+ # A field representing the start and end code unit columns for a specific
280
+ # encoding.
281
+ class CodeUnitColumnsField
282
+ # The repository object that is used for lazily creating a code units
283
+ # cache.
284
+ attr_reader :repository
285
+
286
+ # The associated encoding for the code units.
287
+ attr_reader :encoding
288
+
289
+ # Initialize a new field with the associated repository and encoding.
290
+ def initialize(repository, encoding)
291
+ @repository = repository
292
+ @encoding = encoding
293
+ @cache = nil
294
+ end
295
+
296
+ # Fetches the start and end code units column of a value for a particular
297
+ # encoding.
298
+ def fields(value)
299
+ {
300
+ start_code_units_column: value.cached_start_code_units_column(cache),
301
+ end_code_units_column: value.cached_end_code_units_column(cache)
302
+ }
303
+ end
304
+
305
+ private
306
+
307
+ # Lazily create a code units cache for the associated encoding.
308
+ def cache
309
+ @cache ||= repository.code_units_cache(encoding)
310
+ end
311
+ end
312
+
313
+ # An abstract field used as the parent class of the two comments fields.
314
+ class CommentsField
315
+ # An object that represents a slice of a comment.
316
+ class Comment
317
+ # The slice of the comment.
318
+ attr_reader :slice
319
+
320
+ # Initialize a new comment with the given slice.
321
+ def initialize(slice)
322
+ @slice = slice
323
+ end
324
+ end
325
+
326
+ private
327
+
328
+ # Create comment objects from the given values.
329
+ def comments(values)
330
+ values.map { |value| Comment.new(value.slice) }
331
+ end
332
+ end
333
+
334
+ # A field representing the leading comments.
335
+ class LeadingCommentsField < CommentsField
336
+ # Fetches the leading comments of a value.
337
+ def fields(value)
338
+ { leading_comments: comments(value.leading_comments) }
339
+ end
340
+ end
341
+
342
+ # A field representing the trailing comments.
343
+ class TrailingCommentsField < CommentsField
344
+ # Fetches the trailing comments of a value.
345
+ def fields(value)
346
+ { trailing_comments: comments(value.trailing_comments) }
347
+ end
348
+ end
349
+
350
+ # A repository is a configured collection of fields and a set of entries
351
+ # that knows how to reparse a source and reify the values.
352
+ class Repository
353
+ # Raised when multiple fields of the same type are configured on the same
354
+ # repository.
355
+ class ConfigurationError < StandardError
356
+ end
357
+
358
+ # The source associated with this repository. This will be either a
359
+ # SourceFilepath (the most common use case) or a SourceString.
360
+ attr_reader :source
361
+
362
+ # The fields that have been configured on this repository.
363
+ attr_reader :fields
364
+
365
+ # The entries that have been saved on this repository.
366
+ attr_reader :entries
367
+
368
+ # Initialize a new repository with the given source.
369
+ def initialize(source)
370
+ @source = source
371
+ @fields = {}
372
+ @entries = Hash.new { |hash, node_id| hash[node_id] = {} }
373
+ end
374
+
375
+ # Create a code units cache for the given encoding from the source.
376
+ def code_units_cache(encoding)
377
+ source.code_units_cache(encoding)
378
+ end
379
+
380
+ # Configure the filepath field for this repository and return self.
381
+ def filepath
382
+ raise ConfigurationError, "Can only specify filepath for a filepath source" unless source.is_a?(SourceFilepath)
383
+ field(:filepath, FilepathField.new(source.value))
384
+ end
385
+
386
+ # Configure the lines field for this repository and return self.
387
+ def lines
388
+ field(:lines, LinesField.new)
389
+ end
390
+
391
+ # Configure the offsets field for this repository and return self.
392
+ def offsets
393
+ field(:offsets, OffsetsField.new)
394
+ end
395
+
396
+ # Configure the character offsets field for this repository and return
397
+ # self.
398
+ def character_offsets
399
+ field(:character_offsets, CharacterOffsetsField.new)
400
+ end
401
+
402
+ # Configure the code unit offsets field for this repository for a specific
403
+ # encoding and return self.
404
+ def code_unit_offsets(encoding)
405
+ field(:code_unit_offsets, CodeUnitOffsetsField.new(self, encoding))
406
+ end
407
+
408
+ # Configure the columns field for this repository and return self.
409
+ def columns
410
+ field(:columns, ColumnsField.new)
411
+ end
412
+
413
+ # Configure the character columns field for this repository and return
414
+ # self.
415
+ def character_columns
416
+ field(:character_columns, CharacterColumnsField.new)
417
+ end
418
+
419
+ # Configure the code unit columns field for this repository for a specific
420
+ # encoding and return self.
421
+ def code_unit_columns(encoding)
422
+ field(:code_unit_columns, CodeUnitColumnsField.new(self, encoding))
423
+ end
424
+
425
+ # Configure the leading comments field for this repository and return
426
+ # self.
427
+ def leading_comments
428
+ field(:leading_comments, LeadingCommentsField.new)
429
+ end
430
+
431
+ # Configure the trailing comments field for this repository and return
432
+ # self.
433
+ def trailing_comments
434
+ field(:trailing_comments, TrailingCommentsField.new)
435
+ end
436
+
437
+ # Configure both the leading and trailing comment fields for this
438
+ # repository and return self.
439
+ def comments
440
+ leading_comments.trailing_comments
441
+ end
442
+
443
+ # This method is called from nodes and locations when they want to enter
444
+ # themselves into the repository. It it internal-only and meant to be
445
+ # called from the #save* APIs.
446
+ def enter(node_id, field_name) # :nodoc:
447
+ entry = Entry.new(self)
448
+ @entries[node_id][field_name] = entry
449
+ entry
450
+ end
451
+
452
+ # This method is called from the entries in the repository when they need
453
+ # to reify their values. It is internal-only and meant to be called from
454
+ # the various value APIs.
455
+ def reify! # :nodoc:
456
+ result = source.result
457
+
458
+ # Attach the comments if they have been requested as part of the
459
+ # configuration of this repository.
460
+ if fields.key?(:leading_comments) || fields.key?(:trailing_comments)
461
+ result.attach_comments!
462
+ end
463
+
464
+ queue = [result.value] #: Array[Prism::node]
465
+ while (node = queue.shift)
466
+ @entries[node.node_id].each do |field_name, entry|
467
+ value = node.public_send(field_name)
468
+ values = {} #: Hash[Symbol, untyped]
469
+
470
+ fields.each_value do |field|
471
+ values.merge!(field.fields(value))
472
+ end
473
+
474
+ entry.reify!(values)
475
+ end
476
+
477
+ queue.concat(node.compact_child_nodes)
478
+ end
479
+
480
+ @entries.clear
481
+ end
482
+
483
+ private
484
+
485
+ # Append the given field to the repository and return the repository so
486
+ # that these calls can be chained.
487
+ def field(name, value)
488
+ raise ConfigurationError, "Cannot specify multiple #{name} fields" if @fields.key?(name)
489
+ @fields[name] = value
490
+ self
491
+ end
492
+ end
493
+
494
+ # Create a new repository for the given filepath.
495
+ def self.filepath(value)
496
+ Repository.new(SourceFilepath.new(value))
497
+ end
498
+
499
+ # Create a new repository for the given string.
500
+ def self.string(value)
501
+ Repository.new(SourceString.new(value))
502
+ end
503
+ end
504
+ end