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,273 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Runtime
6
+ # Converts application-owned values into bounded, data-only event summaries.
7
+ # rubocop:disable Metrics/ModuleLength
8
+ module EventSanitizer
9
+ MAX_DEPTH = 3 #: Integer
10
+ MAX_COLLECTION_ENTRIES = 16 #: Integer
11
+ MAX_STRING_BYTES = 256 #: Integer
12
+ MAX_INTEGER_BITS = 64 #: Integer
13
+ STRING_INPUT_BYTES = 1_024 #: Integer
14
+ DATA_MAX_DEPTH = 16 #: Integer
15
+
16
+ class << self
17
+ # Copy a runtime payload into deeply frozen JSON data. Unknown objects are
18
+ # summarized instead of being retained, so callers cannot smuggle mutable
19
+ # application identities into an Event.
20
+ # @rbs (Hash[untyped, untyped] input) -> Hash[String, untyped]
21
+ def data(input)
22
+ result = {} #: Hash[String, untyped]
23
+ consumed = 0
24
+ core_call(Hash, :each_pair, input) do |key, raw|
25
+ break if consumed == MAX_COLLECTION_ENTRIES
26
+
27
+ consumed += 1
28
+ result[data_key(key)] = json_data(raw, depth: 0, seen: {})
29
+ end
30
+ result.freeze
31
+ rescue StandardError
32
+ { "unavailable" => "event_data" }.freeze
33
+ end
34
+
35
+ # @rbs (untyped input) -> untyped
36
+ def value(input)
37
+ summarize(input, depth: 0, seen: {})
38
+ rescue StandardError
39
+ unavailable("sanitization")
40
+ end
41
+
42
+ # @rbs (untyped location) -> Hash[String, untyped]?
43
+ def location(location)
44
+ return nil unless location
45
+
46
+ fields = {
47
+ "file" => location_field(location, :file),
48
+ "line" => location_field(location, :line),
49
+ "column" => location_field(location, :column),
50
+ "end_line" => location_field(location, :end_line),
51
+ "end_column" => location_field(location, :end_column)
52
+ }
53
+ fields.compact.transform_values { |entry| value(entry) }.freeze
54
+ rescue StandardError
55
+ unavailable("location")
56
+ end
57
+
58
+ # @rbs (untyped input) -> String
59
+ def string(input)
60
+ return "<unavailable>" unless core_type?(input, String)
61
+
62
+ prefix = core_call(String, :byteslice, input, 0, STRING_INPUT_BYTES) || ""
63
+ encoded = core_call(
64
+ String,
65
+ :encode,
66
+ prefix,
67
+ Encoding::UTF_8,
68
+ invalid: :replace,
69
+ undef: :replace,
70
+ replace: "\uFFFD"
71
+ )
72
+ encoded_bytes = core_call(String, :bytesize, encoded)
73
+ input_bytes = core_call(String, :bytesize, input)
74
+ return encoded.freeze if encoded_bytes <= MAX_STRING_BYTES && input_bytes <= STRING_INPUT_BYTES
75
+
76
+ truncate_utf8(encoded)
77
+ rescue StandardError
78
+ "<unavailable>"
79
+ end
80
+
81
+ # @rbs (untyped input) -> String
82
+ def class_name(input)
83
+ klass = core_call(Object, :class, input)
84
+ name = core_call(Module, :name, klass)
85
+ core_type?(name, String) && !core_call(String, :empty?, name) ? string(name) : "<anonymous>"
86
+ rescue StandardError
87
+ "<unknown>"
88
+ end
89
+
90
+ private
91
+
92
+ # @rbs (untyped input, depth: Integer, seen: Hash[Integer, bool]) -> untyped
93
+ def summarize(input, depth:, seen:)
94
+ return input if primitive?(input)
95
+ return bounded_integer(input) if core_type?(input, Integer)
96
+ return finite_float(input) if core_type?(input, Float)
97
+ return string(input) if core_type?(input, String)
98
+ return string(core_call(Symbol, :name, input)) if core_type?(input, Symbol)
99
+ return summarize_array(input, depth: depth, seen: seen) if core_type?(input, Array)
100
+ return summarize_hash(input, depth: depth, seen: seen) if core_type?(input, Hash)
101
+
102
+ { "type" => "object", "class" => class_name(input) }.freeze
103
+ end
104
+
105
+ # @rbs (untyped input, depth: Integer, seen: Hash[Integer, bool]) -> untyped
106
+ def json_data(input, depth:, seen:)
107
+ return unavailable("depth") if depth >= DATA_MAX_DEPTH
108
+ return input if primitive?(input)
109
+ return bounded_integer(input) if core_type?(input, Integer)
110
+ return finite_float(input) if core_type?(input, Float)
111
+ return string(input) if core_type?(input, String)
112
+ return string(core_call(Symbol, :name, input)) if core_type?(input, Symbol)
113
+ return json_array(input, depth: depth, seen: seen) if core_type?(input, Array)
114
+ return json_hash(input, depth: depth, seen: seen) if core_type?(input, Hash)
115
+
116
+ { "type" => "object", "class" => class_name(input) }.freeze
117
+ end
118
+
119
+ # @rbs (Array[untyped] input, depth: Integer, seen: Hash[Integer, bool]) -> untyped
120
+ def summarize_array(input, depth:, seen:)
121
+ return unavailable("depth") if depth >= MAX_DEPTH
122
+
123
+ copy_array(input, seen: seen) do |child, nested|
124
+ summarize(child, depth: depth + 1, seen: nested)
125
+ end
126
+ end
127
+
128
+ # @rbs (Hash[untyped, untyped] input, depth: Integer, seen: Hash[Integer, bool]) -> untyped
129
+ def summarize_hash(input, depth:, seen:)
130
+ return unavailable("depth") if depth >= MAX_DEPTH
131
+
132
+ identity = object_identity(input)
133
+ return { "cycle" => true }.freeze if seen[identity]
134
+
135
+ nested = seen.merge(identity => true)
136
+ entries = [] #: Array[untyped]
137
+ length = core_call(Hash, :length, input)
138
+ core_call(Hash, :each_pair, input) do |key, child|
139
+ break if entries.length == MAX_COLLECTION_ENTRIES
140
+
141
+ entries << [
142
+ summarize(key, depth: depth + 1, seen: nested),
143
+ summarize(child, depth: depth + 1, seen: nested)
144
+ ].freeze
145
+ end
146
+ summary = { "type" => "hash", "entries" => entries.freeze }
147
+ summary["omitted"] = length - entries.length if length > entries.length
148
+ summary.freeze
149
+ end
150
+
151
+ # @rbs (Array[untyped] input, depth: Integer, seen: Hash[Integer, bool]) -> untyped
152
+ def json_array(input, depth:, seen:)
153
+ copy_array(input, seen: seen) do |child, nested|
154
+ json_data(child, depth: depth + 1, seen: nested)
155
+ end
156
+ end
157
+
158
+ # @rbs (Hash[untyped, untyped] input, depth: Integer, seen: Hash[Integer, bool]) -> untyped
159
+ def json_hash(input, depth:, seen:)
160
+ identity = object_identity(input)
161
+ return { "cycle" => true }.freeze if seen[identity]
162
+
163
+ nested = seen.merge(identity => true)
164
+ result = {} #: Hash[String, untyped]
165
+ length = core_call(Hash, :length, input)
166
+ consumed = 0
167
+ core_call(Hash, :each_pair, input) do |key, child|
168
+ break if consumed == MAX_COLLECTION_ENTRIES
169
+
170
+ consumed += 1
171
+ result[data_key(key)] = json_data(child, depth: depth + 1, seen: nested)
172
+ end
173
+ result["omitted"] = length - consumed if length > consumed
174
+ result.freeze
175
+ end
176
+
177
+ # @rbs (Array[untyped] input, seen: Hash[Integer, bool])
178
+ # { (untyped, Hash[Integer, bool]) -> untyped } -> untyped
179
+ def copy_array(input, seen:)
180
+ identity = object_identity(input)
181
+ return { "cycle" => true }.freeze if seen[identity]
182
+
183
+ nested = seen.merge(identity => true)
184
+ length = core_call(Array, :length, input)
185
+ limit = [length, MAX_COLLECTION_ENTRIES].min
186
+ result = Array.new(limit) do |index|
187
+ child = core_call(Array, :fetch, input, index)
188
+ yield(child, nested)
189
+ end
190
+ result << { "omitted" => length - limit }.freeze if length > limit
191
+ result.freeze
192
+ end
193
+
194
+ # @rbs (untyped input) -> bool
195
+ def primitive?(input)
196
+ core_type?(input, NilClass) ||
197
+ core_type?(input, TrueClass) ||
198
+ core_type?(input, FalseClass)
199
+ end
200
+
201
+ # @rbs (Integer input) -> untyped
202
+ def bounded_integer(input)
203
+ bits = core_call(Integer, :bit_length, input)
204
+ return input if bits <= MAX_INTEGER_BITS
205
+
206
+ {
207
+ "type" => "integer",
208
+ "bits" => bits,
209
+ "sign" => core_call(Integer, :negative?, input) ? "negative" : "positive"
210
+ }.freeze
211
+ end
212
+
213
+ # @rbs (Float input) -> untyped
214
+ def finite_float(input)
215
+ core_call(Float, :finite?, input) ? input : string(core_call(Float, :to_s, input))
216
+ end
217
+
218
+ # @rbs (untyped input, Module type) -> bool
219
+ def core_type?(input, type)
220
+ core_call(Module, :===, type, input)
221
+ end
222
+
223
+ # @rbs (untyped key) -> String
224
+ def data_key(key)
225
+ return string(key) if core_type?(key, String)
226
+ return string(core_call(Symbol, :name, key)) if core_type?(key, Symbol)
227
+
228
+ "<#{class_name(key)}>"
229
+ end
230
+
231
+ # @rbs (String input) -> String
232
+ def truncate_utf8(input)
233
+ prefix = core_call(String, :byteslice, input, 0, MAX_STRING_BYTES - 3) || ""
234
+ until core_call(String, :valid_encoding?, prefix)
235
+ prefix = core_call(String, :byteslice, prefix, 0, core_call(String, :bytesize, prefix) - 1)
236
+ end
237
+ "#{prefix}\u2026".freeze
238
+ end
239
+
240
+ # @rbs (untyped input) -> Integer
241
+ def object_identity(input)
242
+ core_call(BasicObject, :__id__, input)
243
+ end
244
+
245
+ # @rbs (untyped location, Symbol field) -> untyped
246
+ def location_field(location, field)
247
+ if core_type?(location, Hash)
248
+ return core_call(Hash, :fetch, location, field) do
249
+ core_call(Hash, :fetch, location, core_call(Symbol, :name, field), nil)
250
+ end
251
+ end
252
+ return unless core_call(Kernel, :respond_to?, location, field)
253
+
254
+ core_call(Kernel, :public_send, location, field)
255
+ rescue StandardError
256
+ nil
257
+ end
258
+
259
+ # @rbs (String reason) -> Hash[String, untyped]
260
+ def unavailable(reason)
261
+ { "unavailable" => reason.freeze }.freeze
262
+ end
263
+
264
+ # @rbs (Module owner, Symbol method_name, untyped receiver, *untyped args, **untyped keywords)
265
+ # ?{ (*untyped) -> untyped } -> untyped
266
+ def core_call(owner, method_name, receiver, ...)
267
+ owner.instance_method(method_name).bind(receiver).call(...)
268
+ end
269
+ end
270
+ end
271
+ # rubocop:enable Metrics/ModuleLength
272
+ end
273
+ end