ibex-runtime 0.2.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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +337 -0
  4. data/lib/ibex/runtime/ast_data.rb +23 -0
  5. data/lib/ibex/runtime/cst/annotation.rb +16 -0
  6. data/lib/ibex/runtime/cst/cursor.rb +68 -0
  7. data/lib/ibex/runtime/cst/diff.rb +80 -0
  8. data/lib/ibex/runtime/cst/editing.rb +56 -0
  9. data/lib/ibex/runtime/cst/editor.rb +94 -0
  10. data/lib/ibex/runtime/cst/green/builder.rb +211 -0
  11. data/lib/ibex/runtime/cst/green/cache.rb +150 -0
  12. data/lib/ibex/runtime/cst/green/node.rb +78 -0
  13. data/lib/ibex/runtime/cst/green/token.rb +91 -0
  14. data/lib/ibex/runtime/cst/green/trivia.rb +36 -0
  15. data/lib/ibex/runtime/cst/incremental/blender.rb +233 -0
  16. data/lib/ibex/runtime/cst/incremental/lexed_syntax.rb +28 -0
  17. data/lib/ibex/runtime/cst/incremental/parse_memo.rb +95 -0
  18. data/lib/ibex/runtime/cst/incremental/relexer.rb +134 -0
  19. data/lib/ibex/runtime/cst/incremental/session.rb +229 -0
  20. data/lib/ibex/runtime/cst/incremental/token_memo.rb +66 -0
  21. data/lib/ibex/runtime/cst/kind.rb +133 -0
  22. data/lib/ibex/runtime/cst/parse_result.rb +41 -0
  23. data/lib/ibex/runtime/cst/rewriter.rb +75 -0
  24. data/lib/ibex/runtime/cst/serialize.rb +127 -0
  25. data/lib/ibex/runtime/cst/serialized_tree.rb +44 -0
  26. data/lib/ibex/runtime/cst/source_text.rb +112 -0
  27. data/lib/ibex/runtime/cst/syntax_node.rb +432 -0
  28. data/lib/ibex/runtime/cst/syntax_token.rb +160 -0
  29. data/lib/ibex/runtime/cst/text_edit.rb +77 -0
  30. data/lib/ibex/runtime/cst/typed_node.rb +87 -0
  31. data/lib/ibex/runtime/cst/validator.rb +428 -0
  32. data/lib/ibex/runtime/cst.rb +38 -0
  33. data/lib/ibex/runtime/event.rb +44 -0
  34. data/lib/ibex/runtime/event_jsonl_tracer.rb +37 -0
  35. data/lib/ibex/runtime/event_sanitizer.rb +273 -0
  36. data/lib/ibex/runtime/generated_lexer.rb +514 -0
  37. data/lib/ibex/runtime/jsonl_tracer.rb +75 -0
  38. data/lib/ibex/runtime/lexer_input.rb +107 -0
  39. data/lib/ibex/runtime/location_span.rb +114 -0
  40. data/lib/ibex/runtime/observation.rb +82 -0
  41. data/lib/ibex/runtime/parser.rb +3027 -0
  42. data/lib/ibex/runtime/parser_sync_recovery.rb +108 -0
  43. data/lib/ibex/runtime/repair.rb +126 -0
  44. data/lib/ibex/runtime/repair_priority_queue.rb +60 -0
  45. data/lib/ibex/runtime/repair_search.rb +318 -0
  46. data/lib/ibex/runtime/resource_limits.rb +64 -0
  47. data/lib/ibex/runtime/table_format.rb +11 -0
  48. data/lib/ibex/runtime/version.rb +7 -0
  49. data/lib/ibex/runtime.rb +27 -0
  50. data/lib/ibex/tables/compact.rb +186 -0
  51. data/lib/ibex/tables/compact_actions.rb +135 -0
  52. data/lib/ibex/tables/compact_productions.rb +167 -0
  53. data/sig/ibex/runtime/ast_data.rbs +12 -0
  54. data/sig/ibex/runtime/cst/annotation.rbs +13 -0
  55. data/sig/ibex/runtime/cst/cursor.rbs +34 -0
  56. data/sig/ibex/runtime/cst/diff.rbs +29 -0
  57. data/sig/ibex/runtime/cst/editing.rbs +23 -0
  58. data/sig/ibex/runtime/cst/editor.rbs +43 -0
  59. data/sig/ibex/runtime/cst/green/builder.rbs +84 -0
  60. data/sig/ibex/runtime/cst/green/cache.rbs +60 -0
  61. data/sig/ibex/runtime/cst/green/node.rbs +49 -0
  62. data/sig/ibex/runtime/cst/green/token.rbs +59 -0
  63. data/sig/ibex/runtime/cst/green/trivia.rbs +29 -0
  64. data/sig/ibex/runtime/cst/incremental/blender.rbs +114 -0
  65. data/sig/ibex/runtime/cst/incremental/lexed_syntax.rbs +20 -0
  66. data/sig/ibex/runtime/cst/incremental/parse_memo.rbs +59 -0
  67. data/sig/ibex/runtime/cst/incremental/relexer.rbs +54 -0
  68. data/sig/ibex/runtime/cst/incremental/session.rbs +89 -0
  69. data/sig/ibex/runtime/cst/incremental/token_memo.rbs +31 -0
  70. data/sig/ibex/runtime/cst/kind.rbs +93 -0
  71. data/sig/ibex/runtime/cst/parse_result.rbs +35 -0
  72. data/sig/ibex/runtime/cst/rewriter.rbs +31 -0
  73. data/sig/ibex/runtime/cst/serialize.rbs +39 -0
  74. data/sig/ibex/runtime/cst/serialized_tree.rbs +34 -0
  75. data/sig/ibex/runtime/cst/source_text.rbs +51 -0
  76. data/sig/ibex/runtime/cst/syntax_node.rbs +195 -0
  77. data/sig/ibex/runtime/cst/syntax_token.rbs +112 -0
  78. data/sig/ibex/runtime/cst/text_edit.rbs +33 -0
  79. data/sig/ibex/runtime/cst/typed_node.rbs +50 -0
  80. data/sig/ibex/runtime/cst/validator.rbs +108 -0
  81. data/sig/ibex/runtime/cst.rbs +9 -0
  82. data/sig/ibex/runtime/event.rbs +26 -0
  83. data/sig/ibex/runtime/event_jsonl_tracer.rbs +25 -0
  84. data/sig/ibex/runtime/event_sanitizer.rbs +92 -0
  85. data/sig/ibex/runtime/generated_lexer.rbs +169 -0
  86. data/sig/ibex/runtime/jsonl_tracer.rbs +42 -0
  87. data/sig/ibex/runtime/lexer_input.rbs +44 -0
  88. data/sig/ibex/runtime/location_span.rbs +64 -0
  89. data/sig/ibex/runtime/observation.rbs +35 -0
  90. data/sig/ibex/runtime/parser.rbs +926 -0
  91. data/sig/ibex/runtime/parser_sync_recovery.rbs +60 -0
  92. data/sig/ibex/runtime/repair.rbs +92 -0
  93. data/sig/ibex/runtime/repair_priority_queue.rbs +27 -0
  94. data/sig/ibex/runtime/repair_search.rbs +114 -0
  95. data/sig/ibex/runtime/resource_limits.rbs +39 -0
  96. data/sig/ibex/runtime/table_format.rbs +11 -0
  97. data/sig/ibex/runtime/version.rbs +7 -0
  98. data/sig/ibex/runtime.rbs +6 -0
  99. data/sig/ibex/tables/compact.rbs +70 -0
  100. data/sig/ibex/tables/compact_actions.rbs +57 -0
  101. data/sig/ibex/tables/compact_productions.rbs +68 -0
  102. metadata +144 -0
@@ -0,0 +1,428 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+
6
+ module Ibex
7
+ module Runtime
8
+ module CST
9
+ # Structured failure raised for an invalid or incompatible CST document.
10
+ class ValidationError < StandardError
11
+ attr_reader :code #: Symbol
12
+ attr_reader :path #: String
13
+ attr_reader :expected #: untyped
14
+ attr_reader :actual #: untyped
15
+
16
+ # @rbs (code: Symbol, path: String, message: String, ?expected: untyped, ?actual: untyped) -> void
17
+ def initialize(code:, path:, message:, expected: nil, actual: nil)
18
+ @code = code
19
+ @path = path.dup.freeze
20
+ @expected = expected
21
+ @actual = actual
22
+ super("(cst):#{path}: #{message}")
23
+ end
24
+ end
25
+
26
+ # Validates schema-v1 CST documents while rebuilding all derived Green data.
27
+ module Validator # rubocop:disable Metrics/ModuleLength -- closed schema checks remain auditable in one boundary.
28
+ TOP_KEYS = %w[
29
+ ibex_ir schema_version grammar_digest table_format state_count production_count trivia_policy kinds root memo
30
+ ].freeze #: Array[String]
31
+ KIND_KEYS = %w[
32
+ names terminal_range nonterminal_range named named_nonterminals trivia synthetic
33
+ ].freeze #: Array[String]
34
+ KNOWN_FLAGS = (
35
+ Flags::CONTAINS_ERROR | Flags::CONTAINS_MISSING | Flags::CONTAINS_SKIPPED |
36
+ Flags::HAS_ANNOTATION | Flags::SYNTHETIC | Flags::INCOMPLETE_INPUT
37
+ ) #: Integer
38
+
39
+ # @rbs (String source, ?grammar_digest: String?, ?state_count: Integer?,
40
+ # ?production_count: Integer?) -> SerializedTree
41
+ def validate(source, grammar_digest: nil, state_count: nil, production_count: nil) # rubocop:disable Metrics/MethodLength
42
+ document = JSON.parse(source)
43
+ object!(document, "$")
44
+ exact_keys!(document, TOP_KEYS, "$")
45
+ value!(document, "ibex_ir", "cst", "$.ibex_ir")
46
+ value!(document, "schema_version", 1, "$.schema_version")
47
+ digest = string!(document.fetch("grammar_digest"), "$.grammar_digest")
48
+ unless digest.match?(/\Asha256:[0-9a-f]{64}\z/)
49
+ fail_validation(:invalid_digest, "$.grammar_digest", "expected a SHA-256 digest", actual: digest)
50
+ end
51
+ if grammar_digest && digest != grammar_digest
52
+ fail_validation(
53
+ :grammar_digest_mismatch, "$.grammar_digest",
54
+ "grammar digest does not match", expected: grammar_digest, actual: digest
55
+ )
56
+ end
57
+
58
+ kinds = load_kinds(document.fetch("kinds"))
59
+ root = load_element(document.fetch("root"), kinds, "$.root")
60
+ fail_validation(:invalid_root, "$.root", "root must be a node") unless root.is_a?(GreenNode)
61
+ validate_derived!(root, "$.root")
62
+ document_state_count = nonnegative_integer!(document.fetch("state_count"), "$.state_count")
63
+ document_production_count = nonnegative_integer!(
64
+ document.fetch("production_count"), "$.production_count"
65
+ )
66
+ memo = load_memo(
67
+ document.fetch("memo"),
68
+ root,
69
+ digest,
70
+ document_state_count,
71
+ document_production_count,
72
+ expected_state_count: state_count,
73
+ expected_production_count: production_count
74
+ )
75
+
76
+ SerializedTree.new(
77
+ grammar_digest: digest,
78
+ table_format: positive_integer!(document.fetch("table_format"), "$.table_format"),
79
+ state_count: document_state_count,
80
+ production_count: document_production_count,
81
+ trivia_policy: trivia_policy!(document.fetch("trivia_policy"), "$.trivia_policy"),
82
+ kinds: kinds, green_root: root, memo: memo
83
+ )
84
+ rescue JSON::ParserError => e
85
+ fail_validation(:invalid_json, "$", e.message)
86
+ rescue KeyError => e
87
+ fail_validation(:missing_field, "$", e.message)
88
+ end
89
+ module_function :validate
90
+
91
+ # @rbs (untyped value) -> Kind
92
+ def load_kinds(value)
93
+ object!(value, "$.kinds")
94
+ exact_keys!(value, KIND_KEYS, "$.kinds")
95
+ names = array!(value.fetch("names"), "$.kinds.names").map.with_index do |name, index|
96
+ string!(name, "$.kinds.names[#{index}]")
97
+ end
98
+ metadata = {
99
+ names: names,
100
+ terminal_range: integer_pair!(value.fetch("terminal_range"), "$.kinds.terminal_range"),
101
+ nonterminal_range: integer_pair!(value.fetch("nonterminal_range"), "$.kinds.nonterminal_range"),
102
+ named: string_integer_map!(value.fetch("named"), "$.kinds.named"),
103
+ named_nonterminals: integer_integer_map!(
104
+ value.fetch("named_nonterminals"), "$.kinds.named_nonterminals"
105
+ ),
106
+ trivia: string_integer_map!(value.fetch("trivia"), "$.kinds.trivia"),
107
+ synthetic: string_integer_map!(value.fetch("synthetic"), "$.kinds.synthetic")
108
+ } #: Kind::metadata
109
+ validate_kind_metadata!(metadata)
110
+ Kind.new(metadata)
111
+ end
112
+ module_function :load_kinds
113
+ private_class_method :load_kinds
114
+
115
+ # @rbs (untyped value, GreenNode root, String digest, Integer state_count, Integer production_count,
116
+ # expected_state_count: Integer?, expected_production_count: Integer?) -> ParseMemo?
117
+ def load_memo(value, root, digest, state_count, production_count,
118
+ expected_state_count:, expected_production_count:)
119
+ return if value.nil?
120
+
121
+ object!(value, "$.memo")
122
+ exact_keys!(value, %w[version left_states], "$.memo")
123
+ value!(value, "version", ParseMemo::VERSION, "$.memo.version")
124
+ states = load_memo_states(value.fetch("left_states"), state_count)
125
+ validate_memo_length!(states, root)
126
+ return if expected_state_count && expected_state_count != state_count
127
+ return if expected_production_count && expected_production_count != production_count
128
+
129
+ ParseMemo.new(
130
+ left_states: states,
131
+ grammar_digest: digest,
132
+ state_count: state_count,
133
+ production_count: production_count
134
+ )
135
+ end
136
+ module_function :load_memo
137
+ private_class_method :load_memo
138
+
139
+ # @rbs (untyped value, Integer state_count) -> Array[Integer?]
140
+ def load_memo_states(value, state_count)
141
+ array!(value, "$.memo.left_states").map.with_index do |state, index|
142
+ next if state.nil?
143
+
144
+ actual = nonnegative_integer!(state, "$.memo.left_states[#{index}]")
145
+ if actual >= state_count
146
+ fail_validation(
147
+ :invalid_memo_state,
148
+ "$.memo.left_states[#{index}]",
149
+ "memo state exceeds parser state count",
150
+ actual: actual
151
+ )
152
+ end
153
+ actual
154
+ end
155
+ end
156
+ module_function :load_memo_states
157
+ private_class_method :load_memo_states
158
+
159
+ # @rbs (Array[Integer?] states, GreenNode root) -> void
160
+ def validate_memo_length!(states, root)
161
+ return if states.length == root.descendant_count
162
+
163
+ fail_validation(
164
+ :invalid_memo_length,
165
+ "$.memo.left_states",
166
+ "memo length must equal root descendant count",
167
+ expected: root.descendant_count,
168
+ actual: states.length
169
+ )
170
+ end
171
+ module_function :validate_memo_length!
172
+ private_class_method :validate_memo_length!
173
+
174
+ # @rbs (untyped value, Kind kinds, String path) -> (GreenNode | GreenToken)
175
+ def load_element(value, kinds, path)
176
+ object!(value, path)
177
+ kind = nonnegative_integer!(value.fetch("k"), "#{path}.k")
178
+ validate_kind!(kind, kinds, "#{path}.k")
179
+ flags = flags!(value.fetch("f"), "#{path}.f")
180
+ if value.key?("c")
181
+ exact_keys!(value, %w[k f c], path)
182
+ children = array!(value.fetch("c"), "#{path}.c").map.with_index do |child, index|
183
+ load_element(child, kinds, "#{path}.c[#{index}]")
184
+ end
185
+ return GreenNode.new(kind: kind, children: children, flags: flags)
186
+ end
187
+
188
+ exact_keys!(value, %w[k f e t l r], path)
189
+ expected = value.fetch("e")
190
+ expected_kind = expected.nil? ? nil : nonnegative_integer!(expected, "#{path}.e")
191
+ validate_kind!(expected_kind, kinds, "#{path}.e") if expected_kind
192
+ GreenToken.new(
193
+ kind: kind, flags: flags, expected_kind: expected_kind,
194
+ text: decode_text(value.fetch("t"), "#{path}.t"),
195
+ leading: load_trivia(value.fetch("l"), kinds, "#{path}.l"),
196
+ trailing: load_trivia(value.fetch("r"), kinds, "#{path}.r")
197
+ )
198
+ end
199
+ module_function :load_element
200
+ private_class_method :load_element
201
+
202
+ # @rbs (untyped value, Kind kinds, String path) -> Array[GreenTrivia]
203
+ def load_trivia(value, kinds, path)
204
+ array!(value, path).map.with_index do |item, index|
205
+ pair = array!(item, "#{path}[#{index}]")
206
+ unless pair.length == 2
207
+ fail_validation(:invalid_trivia, "#{path}[#{index}]", "trivia must contain two items")
208
+ end
209
+
210
+ kind = nonnegative_integer!(pair.fetch(0), "#{path}[#{index}][0]")
211
+ validate_kind!(kind, kinds, "#{path}[#{index}][0]")
212
+ unless kinds.trivia?(kind)
213
+ fail_validation(:invalid_trivia_kind, "#{path}[#{index}][0]", "kind is not trivia", actual: kind)
214
+ end
215
+ GreenTrivia.new(kind: kind, text: decode_text(pair.fetch(1), "#{path}[#{index}][1]"))
216
+ end
217
+ end
218
+ module_function :load_trivia
219
+ private_class_method :load_trivia
220
+
221
+ # @rbs (untyped value, String path) -> String
222
+ def decode_text(value, path)
223
+ return value.b if value.is_a?(String)
224
+
225
+ object!(value, path)
226
+ exact_keys!(value, ["b64"], path)
227
+ encoded = string!(value.fetch("b64"), "#{path}.b64")
228
+ decoded = encoded.unpack1("m0")
229
+ fail_validation(:invalid_base64, path, "invalid Base64 text") unless decoded.is_a?(String)
230
+ decoded = decoded.b
231
+ fail_validation(:invalid_base64, path, "invalid Base64 text") unless [decoded].pack("m0") == encoded
232
+
233
+ decoded
234
+ rescue ArgumentError
235
+ fail_validation(:invalid_base64, path, "invalid Base64 text")
236
+ end
237
+ module_function :decode_text
238
+ private_class_method :decode_text
239
+
240
+ # @rbs (GreenNode | GreenToken element, String path) -> void
241
+ def validate_derived!(element, path)
242
+ unless element.to_source.bytesize == element.full_width
243
+ fail_validation(:invalid_width, path, "derived full width does not match source bytes")
244
+ end
245
+ if element.leading_width + element.trailing_width > element.full_width
246
+ fail_validation(:invalid_trim_width, path, "derived trim widths exceed full width")
247
+ end
248
+ return unless element.is_a?(GreenNode)
249
+
250
+ expected = 1 + element.children.sum(&:descendant_count)
251
+ fail_validation(:invalid_descendant_count, path, "derived descendant count is invalid") unless
252
+ element.descendant_count == expected
253
+ element.children.each_with_index do |child, index|
254
+ validate_derived!(child, "#{path}.c[#{index}]")
255
+ end
256
+ end
257
+ module_function :validate_derived!
258
+ private_class_method :validate_derived!
259
+
260
+ # @rbs (Integer kind, Kind kinds, String path) -> void
261
+ def validate_kind!(kind, kinds, path)
262
+ kinds.name(kind)
263
+ rescue IndexError
264
+ fail_validation(:invalid_kind, path, "kind id is outside the kind table", actual: kind)
265
+ end
266
+ module_function :validate_kind!
267
+ private_class_method :validate_kind!
268
+
269
+ # @rbs (untyped value, String path) -> Hash[String, untyped]
270
+ def object!(value, path)
271
+ return value if value.is_a?(Hash)
272
+
273
+ fail_validation(:invalid_type, path, "expected object", actual: value.class.name)
274
+ end
275
+ module_function :object!
276
+ private_class_method :object!
277
+
278
+ # @rbs (untyped value, String path) -> Array[untyped]
279
+ def array!(value, path)
280
+ return value if value.is_a?(Array)
281
+
282
+ fail_validation(:invalid_type, path, "expected array", actual: value.class.name)
283
+ end
284
+ module_function :array!
285
+ private_class_method :array!
286
+
287
+ # @rbs (untyped value, String path) -> String
288
+ def string!(value, path)
289
+ return value if value.is_a?(String)
290
+
291
+ fail_validation(:invalid_type, path, "expected string", actual: value.class.name)
292
+ end
293
+ module_function :string!
294
+ private_class_method :string!
295
+
296
+ # @rbs (untyped value, String path) -> Integer
297
+ def nonnegative_integer!(value, path)
298
+ return value if value.is_a?(Integer) && !value.negative?
299
+
300
+ fail_validation(:invalid_type, path, "expected non-negative integer", actual: value)
301
+ end
302
+ module_function :nonnegative_integer!
303
+ private_class_method :nonnegative_integer!
304
+
305
+ # @rbs (untyped value, String path) -> Integer
306
+ def positive_integer!(value, path)
307
+ integer = nonnegative_integer!(value, path)
308
+ return integer if integer.positive?
309
+
310
+ fail_validation(:invalid_type, path, "expected positive integer", actual: value)
311
+ end
312
+ module_function :positive_integer!
313
+ private_class_method :positive_integer!
314
+
315
+ # @rbs (untyped value, String path) -> Symbol
316
+ def trivia_policy!(value, path)
317
+ policy = string!(value, path).to_sym
318
+ return policy if %i[leading balanced drop].include?(policy)
319
+
320
+ fail_validation(:invalid_value, path, "unknown trivia policy", actual: value)
321
+ end
322
+ module_function :trivia_policy!
323
+ private_class_method :trivia_policy!
324
+
325
+ # @rbs (untyped value, String path) -> Integer
326
+ def flags!(value, path)
327
+ flags = nonnegative_integer!(value, path)
328
+ fail_validation(:invalid_flags, path, "flags contain unknown bits", actual: flags) unless
329
+ flags.nobits?(~KNOWN_FLAGS)
330
+ fail_validation(:invalid_flags, path, "serialized annotations are unsupported") if
331
+ flags.anybits?(Flags::HAS_ANNOTATION)
332
+ flags
333
+ end
334
+ module_function :flags!
335
+ private_class_method :flags!
336
+
337
+ # @rbs (untyped value, String path) -> Array[Integer]
338
+ def integer_pair!(value, path)
339
+ pair = array!(value, path)
340
+ fail_validation(:invalid_type, path, "expected two integers") unless pair.length == 2
341
+ pair.map.with_index { |item, index| nonnegative_integer!(item, "#{path}[#{index}]") }
342
+ end
343
+ module_function :integer_pair!
344
+ private_class_method :integer_pair!
345
+
346
+ # @rbs (untyped value, String path) -> Hash[String, Integer]
347
+ def string_integer_map!(value, path)
348
+ object!(value, path).to_h do |key, item|
349
+ [
350
+ string!(key, "#{path}.<key>"),
351
+ nonnegative_integer!(item, "#{path}.#{key}")
352
+ ]
353
+ end
354
+ end
355
+ module_function :string_integer_map!
356
+ private_class_method :string_integer_map!
357
+
358
+ # @rbs (untyped value, String path) -> Hash[Integer, Integer]
359
+ def integer_integer_map!(value, path)
360
+ object!(value, path).to_h do |key, item|
361
+ integer_key = Integer(string!(key, "#{path}.<key>"), 10)
362
+ [integer_key, nonnegative_integer!(item, "#{path}.#{key}")]
363
+ end
364
+ rescue ArgumentError
365
+ fail_validation(:invalid_type, path, "expected integer object keys")
366
+ end
367
+ module_function :integer_integer_map!
368
+ private_class_method :integer_integer_map!
369
+
370
+ # @rbs (Kind::metadata metadata) -> void
371
+ def validate_kind_metadata!(metadata)
372
+ names = metadata.fetch(:names)
373
+ limit = names.length
374
+ validate_kind_range!(metadata.fetch(:terminal_range), limit, "$.kinds.terminal_range")
375
+ validate_kind_range!(metadata.fetch(:nonterminal_range), limit, "$.kinds.nonterminal_range")
376
+ ids = [] #: Array[Integer]
377
+ ids.concat(metadata.fetch(:named).values)
378
+ ids.concat(metadata.fetch(:named_nonterminals).keys)
379
+ ids.concat(metadata.fetch(:named_nonterminals).values)
380
+ ids.concat(metadata.fetch(:trivia).values)
381
+ ids.concat(metadata.fetch(:synthetic).values)
382
+ invalid = ids.find { |id| id >= limit }
383
+ fail_validation(:invalid_kind, "$.kinds", "kind map exceeds names", actual: invalid) if invalid
384
+ end
385
+ module_function :validate_kind_metadata!
386
+ private_class_method :validate_kind_metadata!
387
+
388
+ # @rbs (Array[Integer] range, Integer limit, String path) -> void
389
+ def validate_kind_range!(range, limit, path)
390
+ return if range.fetch(1).between?(range.fetch(0), limit)
391
+
392
+ fail_validation(:invalid_kind_range, path, "kind range exceeds names")
393
+ end
394
+ module_function :validate_kind_range!
395
+ private_class_method :validate_kind_range!
396
+
397
+ # @rbs (Hash[String, untyped] value, Array[String] expected, String path) -> void
398
+ def exact_keys!(value, expected, path)
399
+ actual = value.keys.sort
400
+ return if actual == expected.sort
401
+
402
+ fail_validation(:invalid_keys, path, "object keys do not match schema", expected: expected, actual: actual)
403
+ end
404
+ module_function :exact_keys!
405
+ private_class_method :exact_keys!
406
+
407
+ # @rbs (Hash[String, untyped] value, String key, untyped expected, String path) -> void
408
+ def value!(value, key, expected, path)
409
+ actual = value.fetch(key)
410
+ return if actual == expected
411
+
412
+ fail_validation(:invalid_value, path, "unexpected value", expected: expected, actual: actual)
413
+ end
414
+ module_function :value!
415
+ private_class_method :value!
416
+
417
+ # @rbs (Symbol code, String path, String message, ?expected: untyped, ?actual: untyped) -> bot
418
+ def fail_validation(code, path, message, expected: nil, actual: nil)
419
+ raise ValidationError.new(
420
+ code: code, path: path, message: message, expected: expected, actual: actual
421
+ )
422
+ end
423
+ module_function :fail_validation
424
+ private_class_method :fail_validation
425
+ end
426
+ end
427
+ end
428
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "cst/kind" unless defined?(Ibex::Runtime::CST::Kind)
5
+ require_relative "cst/annotation" unless defined?(Ibex::Runtime::CST::SyntaxAnnotation)
6
+ require_relative "cst/green/trivia" unless defined?(Ibex::Runtime::CST::GreenTrivia)
7
+ require_relative "cst/green/token" unless defined?(Ibex::Runtime::CST::GreenToken)
8
+ require_relative "cst/green/node" unless defined?(Ibex::Runtime::CST::GreenNode)
9
+ require_relative "cst/green/cache" unless defined?(Ibex::Runtime::CST::NodeCache)
10
+ require_relative "cst/green/builder" unless defined?(Ibex::Runtime::CST::GreenBuilder)
11
+ require_relative "cst/source_text" unless defined?(Ibex::Runtime::CST::SourceText)
12
+ require_relative "cst/syntax_token" unless defined?(Ibex::Runtime::CST::SyntaxToken)
13
+ require_relative "cst/syntax_node" unless defined?(Ibex::Runtime::CST::SyntaxNode)
14
+ require_relative "cst/cursor" unless defined?(Ibex::Runtime::CST::Cursor)
15
+ require_relative "cst/typed_node" unless defined?(Ibex::Runtime::CST::TypedNode)
16
+ require_relative "cst/parse_result" unless defined?(Ibex::Runtime::CST::ParseResult)
17
+ require_relative "cst/editing" unless defined?(Ibex::Runtime::CST::Editing)
18
+ require_relative "cst/text_edit" unless defined?(Ibex::Runtime::CST::TextEdit)
19
+ require_relative "cst/rewriter" unless defined?(Ibex::Runtime::CST::SyntaxRewriter)
20
+ require_relative "cst/editor" unless defined?(Ibex::Runtime::CST::SyntaxEditor)
21
+ require_relative "cst/diff" unless defined?(Ibex::Runtime::CST::Diff)
22
+ require_relative "cst/serialized_tree" unless defined?(Ibex::Runtime::CST::SerializedTree)
23
+ require_relative "cst/validator" unless defined?(Ibex::Runtime::CST::Validator)
24
+ require_relative "cst/serialize" unless defined?(Ibex::Runtime::CST::Serialize)
25
+ require_relative "cst/incremental/token_memo" unless defined?(Ibex::Runtime::CST::TokenMemo)
26
+ require_relative "cst/incremental/relexer" unless defined?(Ibex::Runtime::CST::Relexer)
27
+ require_relative "cst/incremental/parse_memo" unless defined?(Ibex::Runtime::CST::ParseMemo)
28
+ require_relative "cst/incremental/lexed_syntax" unless defined?(Ibex::Runtime::CST::LexedSyntax)
29
+ require_relative "cst/incremental/blender" unless defined?(Ibex::Runtime::CST::Blender)
30
+ require_relative "cst/incremental/session" unless defined?(Ibex::Runtime::CST::IncrementalParseSession)
31
+
32
+ module Ibex
33
+ module Runtime
34
+ # Current Red/Green concrete-syntax APIs.
35
+ module CST
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "event_sanitizer" unless defined?(Ibex::Runtime::EventSanitizer)
5
+
6
+ module Ibex
7
+ module Runtime
8
+ # One immutable versioned parser observation.
9
+ class Event
10
+ SCHEMA_VERSION = 1 #: Integer
11
+ IDENTIFIER = "runtime-event" #: String
12
+ TYPES = %i[
13
+ start shift reduce error recover discard accept reject cst_built cst_fallback cst_reuse
14
+ ].freeze #: Array[Symbol]
15
+
16
+ attr_reader :type #: Symbol
17
+ attr_reader :sequence #: Integer
18
+ attr_reader :data #: Hash[String, untyped]
19
+
20
+ # @rbs (type: Symbol, sequence: Integer, data: Hash[untyped, untyped]) -> void
21
+ def initialize(type:, sequence:, data:)
22
+ raise ArgumentError, "unknown runtime event type #{type.inspect}" unless TYPES.include?(type)
23
+ raise ArgumentError, "runtime event sequence must be positive" unless sequence.positive?
24
+
25
+ @type = type
26
+ @sequence = sequence
27
+ @data = EventSanitizer.data(data)
28
+ @document = {
29
+ "ibex_runtime_event" => IDENTIFIER,
30
+ "schema_version" => SCHEMA_VERSION,
31
+ "sequence" => @sequence,
32
+ "event" => @type.to_s.freeze,
33
+ "data" => @data
34
+ }.freeze #: Hash[String, untyped]
35
+ freeze
36
+ end
37
+
38
+ # @rbs () -> Hash[String, untyped]
39
+ def to_h
40
+ @document
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require_relative "parser" unless defined?(Ibex::Runtime::Parser)
6
+
7
+ module Ibex
8
+ module Runtime
9
+ # Emits schema-v1 immutable runtime events as JSON Lines.
10
+ class EventJSONLTracer
11
+ # @rbs!
12
+ # interface _EventTraceOutput
13
+ # def puts: (String) -> untyped
14
+ # end
15
+
16
+ attr_reader :parser #: Parser
17
+ attr_reader :subscription #: Observation::Subscription
18
+
19
+ # @rbs (Parser parser, _EventTraceOutput output) -> void
20
+ def initialize(parser, output)
21
+ @parser = parser
22
+ @output = output
23
+ @subscription = parser.observe { |event| output.puts(JSON.generate(event.to_h)) }
24
+ end
25
+
26
+ # @rbs () -> bool
27
+ def detach
28
+ @parser.unobserve(@subscription)
29
+ end
30
+
31
+ # @rbs [P < Parser] (P parser, io: _EventTraceOutput) -> EventJSONLTracer
32
+ def self.attach(parser, io:)
33
+ new(parser, io)
34
+ end
35
+ end
36
+ end
37
+ end