tree_haver 5.0.5 → 7.1.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 (59) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.md +13 -0
  4. data/README.md +299 -660
  5. data/lib/tree_haver/backend_api.rb +78 -35
  6. data/lib/tree_haver/backend_context.rb +28 -0
  7. data/lib/tree_haver/backend_registry.rb +119 -382
  8. data/lib/tree_haver/backends/citrus.rb +55 -53
  9. data/lib/tree_haver/backends/ffi.rb +105 -101
  10. data/lib/tree_haver/backends/java.rb +92 -76
  11. data/lib/tree_haver/backends/mri.rb +44 -39
  12. data/lib/tree_haver/backends/parslet.rb +53 -48
  13. data/lib/tree_haver/backends/prism.rb +140 -43
  14. data/lib/tree_haver/backends/psych.rb +30 -26
  15. data/lib/tree_haver/backends/rust.rb +21 -17
  16. data/lib/tree_haver/backends/tslp.rb +274 -0
  17. data/lib/tree_haver/base/comment.rb +320 -0
  18. data/lib/tree_haver/base/language.rb +2 -2
  19. data/lib/tree_haver/base/node.rb +39 -31
  20. data/lib/tree_haver/base/parser.rb +4 -0
  21. data/lib/tree_haver/base/point.rb +3 -3
  22. data/lib/tree_haver/base/tree.rb +1 -1
  23. data/lib/tree_haver/citrus_grammar_finder.rb +21 -26
  24. data/lib/tree_haver/contracts.rb +1025 -0
  25. data/lib/tree_haver/grammar_finder.rb +125 -70
  26. data/lib/tree_haver/kaitai_backend.rb +30 -0
  27. data/lib/tree_haver/language.rb +36 -37
  28. data/lib/tree_haver/language_pack.rb +41 -0
  29. data/lib/tree_haver/language_registry.rb +34 -3
  30. data/lib/tree_haver/library_path_utils.rb +7 -7
  31. data/lib/tree_haver/node.rb +40 -31
  32. data/lib/tree_haver/parser.rb +44 -37
  33. data/lib/tree_haver/parslet_grammar_finder.rb +19 -26
  34. data/lib/tree_haver/path_validator.rb +39 -36
  35. data/lib/tree_haver/peg_backends.rb +76 -0
  36. data/lib/tree_haver/rspec/dependency_tags.rb +23 -1363
  37. data/lib/tree_haver/rspec.rb +1 -31
  38. data/lib/tree_haver/tree.rb +16 -7
  39. data/lib/tree_haver/version.rb +5 -14
  40. data/lib/tree_haver.rb +536 -1240
  41. data/sig/tree_haver.rbs +1 -229
  42. data.tar.gz.sig +0 -0
  43. metadata +154 -70
  44. metadata.gz.sig +0 -0
  45. data/CHANGELOG.md +0 -1393
  46. data/CITATION.cff +0 -20
  47. data/CODE_OF_CONDUCT.md +0 -134
  48. data/CONTRIBUTING.md +0 -359
  49. data/FUNDING.md +0 -74
  50. data/LICENSE.txt +0 -21
  51. data/REEK +0 -0
  52. data/RUBOCOP.md +0 -71
  53. data/SECURITY.md +0 -21
  54. data/lib/tree_haver/base.rb +0 -12
  55. data/lib/tree_haver/compat.rb +0 -43
  56. data/lib/tree_haver/rspec/testable_node.rb +0 -217
  57. data/sig/tree_haver/backends.rbs +0 -352
  58. data/sig/tree_haver/grammar_finder.rbs +0 -29
  59. data/sig/tree_haver/path_validator.rbs +0 -32
@@ -0,0 +1,1025 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeHaver
4
+ ParserRequest = Struct.new(:source, :language, :dialect, keyword_init: true) do
5
+ def to_h
6
+ {
7
+ source: source,
8
+ language: language,
9
+ **(dialect ? { dialect: dialect } : {})
10
+ }
11
+ end
12
+ end
13
+
14
+ BackendReference = Struct.new(:id, :family, keyword_init: true) do
15
+ def to_h
16
+ { id: id, family: family }
17
+ end
18
+ end
19
+
20
+ AdapterInfo = Struct.new(:backend, :backend_ref, :supports_dialects, :supported_policies, keyword_init: true) do
21
+ def to_h
22
+ {
23
+ backend: backend,
24
+ **(backend_ref ? { backend_ref: backend_ref.to_h } : {}),
25
+ supports_dialects: supports_dialects,
26
+ supported_policies: deep_dup(supported_policies || [])
27
+ }
28
+ end
29
+
30
+ private
31
+
32
+ def deep_dup(value)
33
+ Marshal.load(Marshal.dump(value))
34
+ end
35
+ end
36
+
37
+ FeatureProfile = Struct.new(:backend, :backend_ref, :supports_dialects, :supported_policies, keyword_init: true) do
38
+ def to_h
39
+ {
40
+ backend: backend,
41
+ **(backend_ref ? { backend_ref: backend_ref.to_h } : {}),
42
+ supports_dialects: supports_dialects,
43
+ supported_policies: deep_dup(supported_policies || [])
44
+ }
45
+ end
46
+
47
+ private
48
+
49
+ def deep_dup(value)
50
+ Marshal.load(Marshal.dump(value))
51
+ end
52
+ end
53
+
54
+ ParserIdentity = Struct.new(:name, :version, :implementation, keyword_init: true) do
55
+ def to_h
56
+ {
57
+ name: name,
58
+ version: version,
59
+ implementation: implementation
60
+ }
61
+ end
62
+ end
63
+
64
+ LanguageVersion = Struct.new(:version, :dialect, keyword_init: true) do
65
+ def to_h
66
+ {
67
+ version: version,
68
+ dialect: dialect
69
+ }
70
+ end
71
+ end
72
+
73
+ BackendCapability = Struct.new(
74
+ :backend_ref,
75
+ :language,
76
+ :parser_identity,
77
+ :language_version,
78
+ :parse_error_behavior,
79
+ :source_span_support,
80
+ :source_fragment_support,
81
+ :render_strategies,
82
+ :semantic_role_support,
83
+ :normalized_tree_support,
84
+ :native_node_access,
85
+ :known_node_kinds,
86
+ :known_fields,
87
+ :grammar_inventory,
88
+ :diagnostics,
89
+ keyword_init: true
90
+ ) do
91
+ def to_h
92
+ {
93
+ backend_ref: backend_ref.to_h,
94
+ language: language,
95
+ parser_identity: parser_identity.to_h,
96
+ language_version: language_version.to_h,
97
+ parse_error_behavior: parse_error_behavior,
98
+ source_span_support: source_span_support,
99
+ source_fragment_support: source_fragment_support,
100
+ render_strategies: render_strategies,
101
+ semantic_role_support: semantic_role_support,
102
+ normalized_tree_support: normalized_tree_support,
103
+ native_node_access: native_node_access,
104
+ known_node_kinds: known_node_kinds || [],
105
+ known_fields: known_fields || [],
106
+ grammar_inventory: grammar_inventory,
107
+ diagnostics: diagnostics
108
+ }
109
+ end
110
+ end
111
+
112
+ ParseErrorNode = Struct.new(:kind, :span, :message, keyword_init: true) do
113
+ def to_h
114
+ {
115
+ kind: kind,
116
+ span: span.to_h,
117
+ message: message
118
+ }
119
+ end
120
+ end
121
+
122
+ ParseErrorTolerance = Struct.new(:backend_ref, :language, :behavior, :tolerates_errors, :error_nodes, :diagnostics,
123
+ keyword_init: true) do
124
+ def to_h
125
+ {
126
+ backend_ref: backend_ref.to_h,
127
+ language: language,
128
+ behavior: behavior,
129
+ tolerates_errors: tolerates_errors,
130
+ error_nodes: (error_nodes || []).map(&:to_h),
131
+ diagnostics: diagnostics
132
+ }
133
+ end
134
+ end
135
+
136
+ NativeParserProvider = Struct.new(:id, :family, :language, :operations, :retains_native_tree,
137
+ :native_tree_visibility, :metadata_policy, keyword_init: true) do
138
+ def to_h
139
+ {
140
+ id: id,
141
+ family: family,
142
+ language: language,
143
+ operations: operations || [],
144
+ retains_native_tree: retains_native_tree,
145
+ native_tree_visibility: native_tree_visibility,
146
+ metadata_policy: metadata_policy
147
+ }
148
+ end
149
+ end
150
+
151
+ NativeProviderMetadata = Struct.new(
152
+ :provider_id,
153
+ :family,
154
+ :host_language,
155
+ :target_language,
156
+ :parser_name,
157
+ :parser_version,
158
+ :language_version,
159
+ :dialect,
160
+ :parse_error_behavior,
161
+ :source_span_support,
162
+ :render_support,
163
+ :semantic_role_support,
164
+ :retains_native_tree,
165
+ :native_tree_visibility,
166
+ :metadata_policy,
167
+ :diagnostics,
168
+ keyword_init: true
169
+ ) do
170
+ def to_h
171
+ {
172
+ provider_id: provider_id,
173
+ family: family,
174
+ host_language: host_language,
175
+ target_language: target_language,
176
+ parser_name: parser_name,
177
+ parser_version: parser_version,
178
+ language_version: language_version,
179
+ dialect: dialect,
180
+ parse_error_behavior: parse_error_behavior,
181
+ source_span_support: source_span_support,
182
+ render_support: render_support,
183
+ semantic_role_support: semantic_role_support,
184
+ retains_native_tree: retains_native_tree,
185
+ native_tree_visibility: native_tree_visibility,
186
+ metadata_policy: metadata_policy,
187
+ diagnostics: diagnostics || []
188
+ }
189
+ end
190
+ end
191
+
192
+ NormalizedParseResult = Struct.new(
193
+ :ok,
194
+ :backend_capability,
195
+ :root_id,
196
+ :nodes,
197
+ :parse_error_tolerance,
198
+ :source_fragments_available,
199
+ :diagnostics,
200
+ :metadata,
201
+ keyword_init: true
202
+ ) do
203
+ def to_h
204
+ {
205
+ ok: ok,
206
+ backend_capability: backend_capability.to_h,
207
+ root_id: root_id,
208
+ nodes: (nodes || []).map(&:to_h),
209
+ parse_error_tolerance: parse_error_tolerance.to_h,
210
+ source_fragments_available: source_fragments_available,
211
+ diagnostics: diagnostics || [],
212
+ metadata: metadata || {}
213
+ }
214
+ end
215
+ end
216
+
217
+ TreeHaverProfile = Struct.new(
218
+ :profile_id,
219
+ :language,
220
+ :backend_ref,
221
+ :provider_id,
222
+ :node_roles,
223
+ :normalized_node_fields,
224
+ :optional_node_features,
225
+ :unsupported_defaults,
226
+ :capability,
227
+ :fixture_slices,
228
+ :diagnostics,
229
+ keyword_init: true
230
+ ) do
231
+ def to_h
232
+ {
233
+ profile_id: profile_id,
234
+ language: language,
235
+ backend_ref: backend_ref.to_h,
236
+ provider_id: provider_id,
237
+ node_roles: node_roles || [],
238
+ normalized_node_fields: normalized_node_fields || [],
239
+ optional_node_features: optional_node_features || [],
240
+ unsupported_defaults: unsupported_defaults || {},
241
+ capability: capability.to_h,
242
+ fixture_slices: fixture_slices || [],
243
+ diagnostics: diagnostics || []
244
+ }
245
+ end
246
+ end
247
+
248
+ OrderedSiblingEdge = Struct.new(:parent_id, :node_id, :previous_sibling_id, :next_sibling_id, keyword_init: true) do
249
+ def to_h
250
+ {
251
+ parent_id: parent_id,
252
+ node_id: node_id,
253
+ previous_sibling_id: previous_sibling_id,
254
+ next_sibling_id: next_sibling_id
255
+ }
256
+ end
257
+ end
258
+
259
+ OrderedTreePrimitives = Struct.new(:root_id, :child_order, :sibling_edges, :diagnostics, keyword_init: true) do
260
+ def to_h
261
+ {
262
+ root_id: root_id,
263
+ child_order: child_order || {},
264
+ sibling_edges: (sibling_edges || []).map(&:to_h),
265
+ diagnostics: diagnostics || []
266
+ }
267
+ end
268
+ end
269
+
270
+ ParserDiagnostics = Struct.new(:backend, :backend_ref, :diagnostics, keyword_init: true) do
271
+ def to_h
272
+ {
273
+ backend: backend,
274
+ **(backend_ref ? { backend_ref: backend_ref.to_h } : {}),
275
+ diagnostics: deep_dup(diagnostics || [])
276
+ }
277
+ end
278
+
279
+ private
280
+
281
+ def deep_dup(value)
282
+ Marshal.load(Marshal.dump(value))
283
+ end
284
+ end
285
+
286
+ ByteRange = Struct.new(:start_byte, :end_byte, keyword_init: true) do
287
+ def valid?
288
+ start_byte.to_i >= 0 && end_byte.to_i >= start_byte.to_i
289
+ end
290
+
291
+ def length
292
+ valid? ? end_byte.to_i - start_byte.to_i : 0
293
+ end
294
+
295
+ def contains_byte?(offset)
296
+ valid? && offset.to_i >= start_byte.to_i && offset.to_i < end_byte.to_i
297
+ end
298
+
299
+ def contains_range?(other)
300
+ valid? && other.valid? && other.start_byte.to_i >= start_byte.to_i && other.end_byte.to_i <= end_byte.to_i
301
+ end
302
+
303
+ def overlaps?(other)
304
+ valid? && other.valid? && start_byte.to_i < other.end_byte.to_i && other.start_byte.to_i < end_byte.to_i
305
+ end
306
+
307
+ def to_h
308
+ {
309
+ start_byte: start_byte,
310
+ end_byte: end_byte
311
+ }
312
+ end
313
+ end
314
+
315
+ EditProjectionSupport = Struct.new(
316
+ :backend_ref,
317
+ :language,
318
+ :supports_edit_projection,
319
+ :native_edit_target,
320
+ :normalized_edit_target,
321
+ :supported_operations,
322
+ :required_node_fields,
323
+ :correlation_keys,
324
+ :preserves_source_fragments,
325
+ :unsupported_reason,
326
+ :diagnostics,
327
+ keyword_init: true
328
+ ) do
329
+ def to_h
330
+ {
331
+ backend_ref: backend_ref.to_h,
332
+ language: language,
333
+ supports_edit_projection: supports_edit_projection,
334
+ native_edit_target: native_edit_target,
335
+ normalized_edit_target: normalized_edit_target,
336
+ supported_operations: supported_operations || [],
337
+ required_node_fields: required_node_fields || [],
338
+ correlation_keys: correlation_keys || [],
339
+ preserves_source_fragments: preserves_source_fragments,
340
+ unsupported_reason: unsupported_reason,
341
+ diagnostics: diagnostics || []
342
+ }
343
+ end
344
+ end
345
+
346
+ LibraryPathValidation = Struct.new(:path, :valid, :errors, keyword_init: true) do
347
+ def to_h
348
+ {
349
+ path: path,
350
+ valid: valid,
351
+ errors: errors || []
352
+ }
353
+ end
354
+ end
355
+
356
+ BackendAvailabilityCheck = Struct.new(:name, :status, :required, :diagnostics, keyword_init: true) do
357
+ def to_h
358
+ {
359
+ name: name,
360
+ status: status,
361
+ required: required,
362
+ diagnostics: diagnostics || []
363
+ }
364
+ end
365
+ end
366
+
367
+ BackendAvailabilityReport = Struct.new(:backend_ref, :status, :checks, :diagnostics, keyword_init: true) do
368
+ def to_h
369
+ {
370
+ backend_ref: backend_ref.to_h,
371
+ status: status,
372
+ checks: (checks || []).map(&:to_h),
373
+ diagnostics: diagnostics || []
374
+ }
375
+ end
376
+ end
377
+
378
+ ProviderDiagnostic = Struct.new(:severity, :category, :code, :message, :path, :blocking, keyword_init: true) do
379
+ def to_h
380
+ {
381
+ severity: severity,
382
+ category: category,
383
+ code: code,
384
+ message: message,
385
+ path: path,
386
+ blocking: blocking
387
+ }
388
+ end
389
+ end
390
+
391
+ ProviderDiagnosticsReport = Struct.new(:provider_id, :backend_ref, :language, :status, :diagnostics,
392
+ keyword_init: true) do
393
+ def to_h
394
+ {
395
+ provider_id: provider_id,
396
+ backend_ref: backend_ref.to_h,
397
+ language: language,
398
+ status: status,
399
+ diagnostics: (diagnostics || []).map(&:to_h)
400
+ }
401
+ end
402
+ end
403
+
404
+ EditProjectionOperationRequest = Struct.new(:operation, :target_node_id, :target_node_path, :replacement_source,
405
+ keyword_init: true) do
406
+ def to_h
407
+ {
408
+ operation: operation,
409
+ target_node_id: target_node_id,
410
+ target_node_path: target_node_path,
411
+ replacement_source: replacement_source
412
+ }
413
+ end
414
+ end
415
+
416
+ EditProjectionExecutionRequest = Struct.new(:provider_id, :backend_ref, :language, :source, :operations,
417
+ keyword_init: true) do
418
+ def to_h
419
+ {
420
+ provider_id: provider_id,
421
+ backend_ref: backend_ref.to_h,
422
+ language: language,
423
+ source: source,
424
+ operations: (operations || []).map(&:to_h)
425
+ }
426
+ end
427
+ end
428
+
429
+ AppliedEditProjectionOperation = Struct.new(:operation, :target_node_id, :correlation_key, :correlation_value,
430
+ keyword_init: true) do
431
+ def to_h
432
+ {
433
+ operation: operation,
434
+ target_node_id: target_node_id,
435
+ correlation_key: correlation_key,
436
+ correlation_value: correlation_value
437
+ }
438
+ end
439
+ end
440
+
441
+ EditProjectionExecutionResult = Struct.new(:ok, :status, :source, :applied_operations, :diagnostics,
442
+ keyword_init: true) do
443
+ def to_h
444
+ {
445
+ ok: ok,
446
+ status: status,
447
+ source: source,
448
+ applied_operations: (applied_operations || []).map(&:to_h),
449
+ diagnostics: (diagnostics || []).map(&:to_h)
450
+ }
451
+ end
452
+ end
453
+
454
+ EditProjectionProviderOperation = Struct.new(
455
+ :operation,
456
+ :status,
457
+ :node_scope,
458
+ :correlation_keys,
459
+ :fixture_slices,
460
+ :formatting_preservation,
461
+ :diagnostics,
462
+ keyword_init: true
463
+ ) do
464
+ def to_h
465
+ {
466
+ operation: operation,
467
+ status: status,
468
+ node_scope: node_scope,
469
+ correlation_keys: correlation_keys || [],
470
+ fixture_slices: fixture_slices || [],
471
+ formatting_preservation: formatting_preservation,
472
+ diagnostics: diagnostics || []
473
+ }
474
+ end
475
+ end
476
+
477
+ EditProjectionProviderMatrixEntry = Struct.new(
478
+ :provider_id,
479
+ :backend_ref,
480
+ :language,
481
+ :formatting_preservation,
482
+ :preserves_source_fragments,
483
+ :operations,
484
+ keyword_init: true
485
+ ) do
486
+ def to_h
487
+ {
488
+ provider_id: provider_id,
489
+ backend_ref: backend_ref.to_h,
490
+ language: language,
491
+ formatting_preservation: formatting_preservation,
492
+ preserves_source_fragments: preserves_source_fragments,
493
+ operations: (operations || []).map(&:to_h)
494
+ }
495
+ end
496
+ end
497
+
498
+ EditProjectionProviderMatrix = Struct.new(:operations, :providers, :diagnostics, keyword_init: true) do
499
+ def to_h
500
+ {
501
+ operations: operations || [],
502
+ providers: (providers || []).map(&:to_h),
503
+ diagnostics: diagnostics || []
504
+ }
505
+ end
506
+ end
507
+
508
+ SourcePoint = Struct.new(:row, :column, keyword_init: true) do
509
+ def to_h
510
+ {
511
+ row: row,
512
+ column: column
513
+ }
514
+ end
515
+ end
516
+
517
+ SourceSpan = Struct.new(:range, :start_point, :end_point, keyword_init: true) do
518
+ def to_h
519
+ {
520
+ range: range.to_h,
521
+ start_point: start_point.to_h,
522
+ end_point: end_point.to_h
523
+ }
524
+ end
525
+ end
526
+
527
+ NODE_ROLES = %w[
528
+ structural
529
+ token
530
+ trivia
531
+ comment
532
+ delimiter
533
+ separator
534
+ virtual
535
+ error
536
+ opaque
537
+ ].freeze
538
+
539
+ NormalizedTreeNode = Struct.new(
540
+ :id,
541
+ :kind,
542
+ :role,
543
+ :parent_id,
544
+ :child_ids,
545
+ :span,
546
+ :field_name,
547
+ :named,
548
+ :anonymous,
549
+ :has_source_text,
550
+ :source_fragment,
551
+ :backend_kind,
552
+ :semantic_roles,
553
+ :backend_roles,
554
+ :unsupported_features,
555
+ :metadata,
556
+ keyword_init: true
557
+ ) do
558
+ def to_h
559
+ {
560
+ id: id,
561
+ kind: kind,
562
+ role: role,
563
+ parent_id: parent_id,
564
+ child_ids: child_ids,
565
+ span: span.to_h,
566
+ field_name: field_name,
567
+ named: named,
568
+ anonymous: anonymous,
569
+ has_source_text: has_source_text,
570
+ source_fragment: source_fragment,
571
+ backend_kind: backend_kind,
572
+ semantic_roles: semantic_roles || [],
573
+ backend_roles: backend_roles || [],
574
+ unsupported_features: unsupported_features || [],
575
+ metadata: metadata || {}
576
+ }
577
+ end
578
+ end
579
+
580
+ SourceFragment = Struct.new(:text, :span, :available, :strategy, :byte_length, :diagnostics, keyword_init: true) do
581
+ def to_h
582
+ {
583
+ text: text,
584
+ span: span.to_h,
585
+ available: available,
586
+ strategy: strategy,
587
+ byte_length: byte_length,
588
+ diagnostics: diagnostics
589
+ }
590
+ end
591
+ end
592
+
593
+ def node_roles
594
+ NODE_ROLES.dup
595
+ end
596
+ module_function :node_roles
597
+
598
+ MAX_LIBRARY_PATH_LENGTH = 4096
599
+ VALID_LIBRARY_FILENAME_PATTERN = /\A[A-Za-z0-9][A-Za-z0-9._-]*\z/
600
+ VALID_LANGUAGE_NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/
601
+ VALID_SYMBOL_NAME_PATTERN = /\A[A-Za-z_][A-Za-z0-9_]*\z/
602
+ VERSIONED_SHARED_OBJECT_PATTERN = /\.so\.\d+\z/
603
+
604
+ def validate_library_path(path)
605
+ errors = library_path_errors(path)
606
+ LibraryPathValidation.new(path: path, valid: errors.empty?, errors: errors)
607
+ end
608
+ module_function :validate_library_path
609
+
610
+ def library_path_errors(path)
611
+ value = path.to_s
612
+ errors = []
613
+
614
+ errors << 'path_empty' if value.empty?
615
+ errors << 'path_too_long' if value.length > MAX_LIBRARY_PATH_LENGTH
616
+ errors << 'path_contains_null_byte' if value.include?("\0")
617
+ errors << 'path_not_absolute' unless value.start_with?('/') || windows_absolute_path?(value)
618
+
619
+ segments = value.split(%r{[/\\]})
620
+ errors << 'path_contains_parent_traversal' if segments.include?('..')
621
+ errors << 'path_contains_current_directory_traversal' if segments.include?('.')
622
+ errors << 'path_extension_not_allowed' unless allowed_library_extension?(value)
623
+ unless VALID_LIBRARY_FILENAME_PATTERN.match?(library_filename(value))
624
+ errors << 'filename_contains_invalid_characters'
625
+ end
626
+
627
+ errors
628
+ end
629
+ module_function :library_path_errors
630
+
631
+ def safe_language_name?(name)
632
+ VALID_LANGUAGE_NAME_PATTERN.match?(name.to_s)
633
+ end
634
+ module_function :safe_language_name?
635
+
636
+ def sanitize_language_name(name)
637
+ sanitized = name.to_s.downcase.gsub(/[^a-z0-9_]/, '')
638
+ safe_language_name?(sanitized) ? sanitized : nil
639
+ end
640
+ module_function :sanitize_language_name
641
+
642
+ def safe_symbol_name?(symbol)
643
+ VALID_SYMBOL_NAME_PATTERN.match?(symbol.to_s)
644
+ end
645
+ module_function :safe_symbol_name?
646
+
647
+ def safe_backend_name?(name)
648
+ name.to_s == 'auto' || !BackendRegistry.fetch(name.to_s).nil?
649
+ end
650
+ module_function :safe_backend_name?
651
+
652
+ def build_backend_availability_report(backend_ref, checks)
653
+ if checks.empty?
654
+ return BackendAvailabilityReport.new(
655
+ backend_ref: backend_ref,
656
+ status: 'unknown',
657
+ checks: [],
658
+ diagnostics: ['backend availability unknown: no checks supplied']
659
+ )
660
+ end
661
+
662
+ diagnostics = []
663
+ status = 'available'
664
+ checks.each do |check|
665
+ next unless check.required && check.status != 'available'
666
+
667
+ status = 'unavailable'
668
+ diagnostics << "backend unavailable: required check #{check.name} is #{check.status}"
669
+ end
670
+ BackendAvailabilityReport.new(backend_ref: backend_ref, status: status, checks: checks, diagnostics: diagnostics)
671
+ end
672
+ module_function :build_backend_availability_report
673
+
674
+ def build_provider_diagnostics_report(provider_id, backend_ref, language, diagnostics)
675
+ status = 'clean'
676
+ diagnostics.each do |diagnostic|
677
+ if diagnostic.blocking
678
+ status = 'blocked'
679
+ break
680
+ end
681
+ status = 'warning' if diagnostic.severity == 'warning'
682
+ end
683
+ ProviderDiagnosticsReport.new(
684
+ provider_id: provider_id,
685
+ backend_ref: backend_ref,
686
+ language: language,
687
+ status: status,
688
+ diagnostics: diagnostics
689
+ )
690
+ end
691
+ module_function :build_provider_diagnostics_report
692
+
693
+ def build_edit_projection_execution_result(source, applied_operations, diagnostics)
694
+ if diagnostics.any?(&:blocking)
695
+ return EditProjectionExecutionResult.new(
696
+ ok: false,
697
+ status: 'rejected',
698
+ source: source,
699
+ applied_operations: [],
700
+ diagnostics: diagnostics
701
+ )
702
+ end
703
+
704
+ EditProjectionExecutionResult.new(
705
+ ok: true,
706
+ status: 'applied',
707
+ source: source,
708
+ applied_operations: applied_operations,
709
+ diagnostics: diagnostics
710
+ )
711
+ end
712
+ module_function :build_edit_projection_execution_result
713
+
714
+ def build_edit_projection_provider_matrix(operations, providers, diagnostics)
715
+ EditProjectionProviderMatrix.new(
716
+ operations: operations || [],
717
+ providers: providers || [],
718
+ diagnostics: diagnostics || []
719
+ )
720
+ end
721
+ module_function :build_edit_projection_provider_matrix
722
+
723
+ def windows_absolute_path?(path)
724
+ %r{\A[A-Za-z]:[/\\]}.match?(path)
725
+ end
726
+ module_function :windows_absolute_path?
727
+ private_class_method :windows_absolute_path?
728
+
729
+ def allowed_library_extension?(path)
730
+ normalized_path = path.downcase
731
+ normalized_path.end_with?('.so', '.dylib', '.dll') || VERSIONED_SHARED_OBJECT_PATTERN.match?(normalized_path)
732
+ end
733
+ module_function :allowed_library_extension?
734
+ private_class_method :allowed_library_extension?
735
+
736
+ def library_filename(path)
737
+ path.split(%r{[/\\]}).last.to_s
738
+ end
739
+ module_function :library_filename
740
+ private_class_method :library_filename
741
+
742
+ ByteEditSpan = Struct.new(:start_byte, :old_end_byte, :new_end_byte, :start_point, :old_end_point, :new_end_point,
743
+ keyword_init: true) do
744
+ def old_range
745
+ ByteRange.new(start_byte: start_byte, end_byte: old_end_byte)
746
+ end
747
+
748
+ def new_range
749
+ ByteRange.new(start_byte: start_byte, end_byte: new_end_byte)
750
+ end
751
+
752
+ def byte_delta
753
+ new_end_byte.to_i - old_end_byte.to_i
754
+ end
755
+
756
+ def to_h
757
+ {
758
+ start_byte: start_byte,
759
+ old_end_byte: old_end_byte,
760
+ new_end_byte: new_end_byte,
761
+ start_point: start_point.to_h,
762
+ old_end_point: old_end_point.to_h,
763
+ new_end_point: new_end_point.to_h
764
+ }
765
+ end
766
+ end
767
+
768
+ BinaryScalarValue = Struct.new(:kind, :value, :symbol, :raw_value, :encoding, :format, :description,
769
+ keyword_init: true) do
770
+ def to_h
771
+ {
772
+ kind: kind,
773
+ **(value.nil? ? {} : { value: value }),
774
+ **(symbol.nil? ? {} : { symbol: symbol }),
775
+ **(raw_value.nil? ? {} : { raw_value: raw_value }),
776
+ **(encoding.nil? ? {} : { encoding: encoding }),
777
+ **(format.nil? ? {} : { format: format }),
778
+ **(description.nil? ? {} : { description: description })
779
+ }
780
+ end
781
+ end
782
+
783
+ BinaryRenderPolicy = Struct.new(:schema_path, :byte_range, :operation, :disposition, :reason, keyword_init: true) do
784
+ def to_h
785
+ {
786
+ schema_path: schema_path,
787
+ **(byte_range ? { byte_range: byte_range.to_h } : {}),
788
+ operation: operation,
789
+ disposition: disposition,
790
+ reason: reason
791
+ }
792
+ end
793
+ end
794
+
795
+ BinaryDiagnostic = Struct.new(:severity, :category, :message, :schema_path, :byte_range, keyword_init: true) do
796
+ def to_h
797
+ {
798
+ severity: severity,
799
+ category: category,
800
+ message: message,
801
+ schema_path: schema_path,
802
+ **(byte_range ? { byte_range: byte_range.to_h } : {})
803
+ }
804
+ end
805
+ end
806
+
807
+ BinaryNestedDispatch = Struct.new(:schema_path, :family, :status, keyword_init: true) do
808
+ def to_h
809
+ {
810
+ schema_path: schema_path,
811
+ family: family,
812
+ status: status
813
+ }
814
+ end
815
+ end
816
+
817
+ BinaryPayloadRegion = Struct.new(:kind, :schema_path, :byte_range, :expected_hex, keyword_init: true) do
818
+ def to_h
819
+ {
820
+ kind: kind,
821
+ schema_path: schema_path,
822
+ byte_range: byte_range.to_h,
823
+ expected_hex: expected_hex
824
+ }
825
+ end
826
+ end
827
+
828
+ BinaryRawPayload = Struct.new(:encoding, :value, :byte_length, :regions, keyword_init: true) do
829
+ def to_h
830
+ {
831
+ encoding: encoding,
832
+ value: value,
833
+ byte_length: byte_length,
834
+ regions: (regions || []).map(&:to_h)
835
+ }
836
+ end
837
+ end
838
+
839
+ BinaryMergeReport = Struct.new(:format, :schema, :matched_schema_paths, :preserved_ranges, :rewritten_nodes,
840
+ :checksum_updates, :nested_dispatches, :diagnostics, keyword_init: true) do
841
+ def to_h
842
+ {
843
+ format: format,
844
+ schema: schema,
845
+ matched_schema_paths: deep_dup(matched_schema_paths || []),
846
+ preserved_ranges: (preserved_ranges || []).map(&:to_h),
847
+ rewritten_nodes: deep_dup(rewritten_nodes || []),
848
+ checksum_updates: deep_dup(checksum_updates || []),
849
+ nested_dispatches: (nested_dispatches || []).map(&:to_h),
850
+ diagnostics: (diagnostics || []).map(&:to_h)
851
+ }
852
+ end
853
+
854
+ private
855
+
856
+ def deep_dup(value)
857
+ Marshal.load(Marshal.dump(value))
858
+ end
859
+ end
860
+
861
+ ZipArchiveInfo = Struct.new(:format, :schema, :entry_count, :central_directory_range, keyword_init: true) do
862
+ def to_h
863
+ {
864
+ format: format,
865
+ schema: schema,
866
+ entry_count: entry_count,
867
+ central_directory_range: central_directory_range.to_h
868
+ }
869
+ end
870
+ end
871
+
872
+ ZipArchiveEntry = Struct.new(:path, :normalized_path, :directory, :compression, :compressed_size, :uncompressed_size,
873
+ :crc32, :local_header_range, :data_range, :central_directory_range, keyword_init: true) do
874
+ def to_h
875
+ {
876
+ path: path,
877
+ normalized_path: normalized_path,
878
+ directory: directory,
879
+ compression: compression,
880
+ compressed_size: compressed_size,
881
+ uncompressed_size: uncompressed_size,
882
+ crc32: crc32,
883
+ local_header_range: local_header_range.to_h,
884
+ data_range: data_range.to_h,
885
+ central_directory_range: central_directory_range.to_h
886
+ }
887
+ end
888
+ end
889
+
890
+ ZipMemberDecision = Struct.new(:normalized_path, :operation, :disposition, :nested_family, :reason,
891
+ keyword_init: true) do
892
+ def to_h
893
+ {
894
+ normalized_path: normalized_path,
895
+ operation: operation,
896
+ disposition: disposition,
897
+ **(nested_family ? { nested_family: nested_family } : {}),
898
+ reason: reason
899
+ }
900
+ end
901
+ end
902
+
903
+ ZipUnsafeEntry = Struct.new(:path, :normalized_path, :category, :reason, keyword_init: true) do
904
+ def to_h
905
+ {
906
+ path: path,
907
+ normalized_path: normalized_path,
908
+ category: category,
909
+ reason: reason
910
+ }
911
+ end
912
+ end
913
+
914
+ ZipFamilyReport = Struct.new(:archive, :entries, :member_decisions, :merge_report, :unsafe_entries,
915
+ keyword_init: true) do
916
+ def to_h
917
+ {
918
+ archive: archive.to_h,
919
+ entries: (entries || []).map(&:to_h),
920
+ member_decisions: (member_decisions || []).map(&:to_h),
921
+ unsafe_entries: (unsafe_entries || []).map(&:to_h),
922
+ merge_report: merge_report.to_h
923
+ }
924
+ end
925
+ end
926
+
927
+ def self.slice_byte_range(source, byte_range)
928
+ source_bytesize = source.to_s.bytesize
929
+ unless byte_range.valid? && byte_range.end_byte.to_i <= source_bytesize
930
+ raise RangeError,
931
+ "invalid byte range [#{byte_range.start_byte}, #{byte_range.end_byte}) for source length #{source_bytesize}"
932
+ end
933
+
934
+ source.to_s.byteslice(byte_range.start_byte.to_i...byte_range.end_byte.to_i)
935
+ end
936
+
937
+ def self.extract_source_fragment(source, span, strategy)
938
+ text = slice_byte_range(source, span.range)
939
+ SourceFragment.new(
940
+ text: text,
941
+ span: span,
942
+ available: true,
943
+ strategy: strategy,
944
+ byte_length: text.bytesize,
945
+ diagnostics: []
946
+ )
947
+ rescue RangeError => e
948
+ SourceFragment.new(
949
+ text: '',
950
+ span: span,
951
+ available: false,
952
+ strategy: strategy,
953
+ byte_length: 0,
954
+ diagnostics: [e.message]
955
+ )
956
+ end
957
+
958
+ def self.byte_offset_for_point(source, point)
959
+ if point.row.to_i.negative? || point.column.to_i.negative?
960
+ raise RangeError,
961
+ "invalid source point (#{point.row}, #{point.column})"
962
+ end
963
+
964
+ row = 0
965
+ column = 0
966
+ source.to_s.bytes.each_with_index do |byte, offset|
967
+ return offset if row == point.row.to_i && column == point.column.to_i
968
+
969
+ if byte == 10
970
+ row += 1
971
+ column = 0
972
+ else
973
+ column += 1
974
+ end
975
+ end
976
+ return source.to_s.bytesize if row == point.row.to_i && column == point.column.to_i
977
+
978
+ raise RangeError, "source point (#{point.row}, #{point.column}) is outside source"
979
+ end
980
+
981
+ KaitaiByteSpan = Struct.new(:start_byte, :end_byte, keyword_init: true) do
982
+ def to_h
983
+ {
984
+ start_byte: start_byte,
985
+ end_byte: end_byte
986
+ }
987
+ end
988
+ end
989
+
990
+ KaitaiTreeNode = Struct.new(:kind, :schema_path, :span, :fields, :children, keyword_init: true) do
991
+ def to_h
992
+ {
993
+ kind: kind,
994
+ schema_path: schema_path,
995
+ span: span.to_h,
996
+ fields: deep_dup(fields || {}),
997
+ children: (children || []).map(&:to_h)
998
+ }
999
+ end
1000
+
1001
+ private
1002
+
1003
+ def deep_dup(value)
1004
+ Marshal.load(Marshal.dump(value))
1005
+ end
1006
+ end
1007
+
1008
+ KaitaiTreeAnalysis = Struct.new(:schema, :root, :backend_ref, :source_byte_length, :diagnostics,
1009
+ keyword_init: true) do
1010
+ def kind
1011
+ 'kaitai-tree'
1012
+ end
1013
+
1014
+ def to_h
1015
+ {
1016
+ kind: kind,
1017
+ schema: schema,
1018
+ **(source_byte_length.nil? ? {} : { source_byte_length: source_byte_length }),
1019
+ root: root.to_h,
1020
+ backend_ref: backend_ref.to_h,
1021
+ diagnostics: (diagnostics || []).map(&:to_h)
1022
+ }
1023
+ end
1024
+ end
1025
+ end