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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +337 -0
- data/lib/ibex/runtime/ast_data.rb +23 -0
- data/lib/ibex/runtime/cst/annotation.rb +16 -0
- data/lib/ibex/runtime/cst/cursor.rb +68 -0
- data/lib/ibex/runtime/cst/diff.rb +80 -0
- data/lib/ibex/runtime/cst/editing.rb +56 -0
- data/lib/ibex/runtime/cst/editor.rb +94 -0
- data/lib/ibex/runtime/cst/green/builder.rb +211 -0
- data/lib/ibex/runtime/cst/green/cache.rb +150 -0
- data/lib/ibex/runtime/cst/green/node.rb +78 -0
- data/lib/ibex/runtime/cst/green/token.rb +91 -0
- data/lib/ibex/runtime/cst/green/trivia.rb +36 -0
- data/lib/ibex/runtime/cst/incremental/blender.rb +233 -0
- data/lib/ibex/runtime/cst/incremental/lexed_syntax.rb +28 -0
- data/lib/ibex/runtime/cst/incremental/parse_memo.rb +95 -0
- data/lib/ibex/runtime/cst/incremental/relexer.rb +134 -0
- data/lib/ibex/runtime/cst/incremental/session.rb +229 -0
- data/lib/ibex/runtime/cst/incremental/token_memo.rb +66 -0
- data/lib/ibex/runtime/cst/kind.rb +133 -0
- data/lib/ibex/runtime/cst/parse_result.rb +41 -0
- data/lib/ibex/runtime/cst/rewriter.rb +75 -0
- data/lib/ibex/runtime/cst/serialize.rb +127 -0
- data/lib/ibex/runtime/cst/serialized_tree.rb +44 -0
- data/lib/ibex/runtime/cst/source_text.rb +112 -0
- data/lib/ibex/runtime/cst/syntax_node.rb +432 -0
- data/lib/ibex/runtime/cst/syntax_token.rb +160 -0
- data/lib/ibex/runtime/cst/text_edit.rb +77 -0
- data/lib/ibex/runtime/cst/typed_node.rb +87 -0
- data/lib/ibex/runtime/cst/validator.rb +428 -0
- data/lib/ibex/runtime/cst.rb +38 -0
- data/lib/ibex/runtime/event.rb +44 -0
- data/lib/ibex/runtime/event_jsonl_tracer.rb +37 -0
- data/lib/ibex/runtime/event_sanitizer.rb +273 -0
- data/lib/ibex/runtime/generated_lexer.rb +514 -0
- data/lib/ibex/runtime/jsonl_tracer.rb +75 -0
- data/lib/ibex/runtime/lexer_input.rb +107 -0
- data/lib/ibex/runtime/location_span.rb +114 -0
- data/lib/ibex/runtime/observation.rb +82 -0
- data/lib/ibex/runtime/parser.rb +3027 -0
- data/lib/ibex/runtime/parser_sync_recovery.rb +108 -0
- data/lib/ibex/runtime/repair.rb +126 -0
- data/lib/ibex/runtime/repair_priority_queue.rb +60 -0
- data/lib/ibex/runtime/repair_search.rb +318 -0
- data/lib/ibex/runtime/resource_limits.rb +64 -0
- data/lib/ibex/runtime/table_format.rb +11 -0
- data/lib/ibex/runtime/version.rb +7 -0
- data/lib/ibex/runtime.rb +27 -0
- data/lib/ibex/tables/compact.rb +186 -0
- data/lib/ibex/tables/compact_actions.rb +135 -0
- data/lib/ibex/tables/compact_productions.rb +167 -0
- data/sig/ibex/runtime/ast_data.rbs +12 -0
- data/sig/ibex/runtime/cst/annotation.rbs +13 -0
- data/sig/ibex/runtime/cst/cursor.rbs +34 -0
- data/sig/ibex/runtime/cst/diff.rbs +29 -0
- data/sig/ibex/runtime/cst/editing.rbs +23 -0
- data/sig/ibex/runtime/cst/editor.rbs +43 -0
- data/sig/ibex/runtime/cst/green/builder.rbs +84 -0
- data/sig/ibex/runtime/cst/green/cache.rbs +60 -0
- data/sig/ibex/runtime/cst/green/node.rbs +49 -0
- data/sig/ibex/runtime/cst/green/token.rbs +59 -0
- data/sig/ibex/runtime/cst/green/trivia.rbs +29 -0
- data/sig/ibex/runtime/cst/incremental/blender.rbs +114 -0
- data/sig/ibex/runtime/cst/incremental/lexed_syntax.rbs +20 -0
- data/sig/ibex/runtime/cst/incremental/parse_memo.rbs +59 -0
- data/sig/ibex/runtime/cst/incremental/relexer.rbs +54 -0
- data/sig/ibex/runtime/cst/incremental/session.rbs +89 -0
- data/sig/ibex/runtime/cst/incremental/token_memo.rbs +31 -0
- data/sig/ibex/runtime/cst/kind.rbs +93 -0
- data/sig/ibex/runtime/cst/parse_result.rbs +35 -0
- data/sig/ibex/runtime/cst/rewriter.rbs +31 -0
- data/sig/ibex/runtime/cst/serialize.rbs +39 -0
- data/sig/ibex/runtime/cst/serialized_tree.rbs +34 -0
- data/sig/ibex/runtime/cst/source_text.rbs +51 -0
- data/sig/ibex/runtime/cst/syntax_node.rbs +195 -0
- data/sig/ibex/runtime/cst/syntax_token.rbs +112 -0
- data/sig/ibex/runtime/cst/text_edit.rbs +33 -0
- data/sig/ibex/runtime/cst/typed_node.rbs +50 -0
- data/sig/ibex/runtime/cst/validator.rbs +108 -0
- data/sig/ibex/runtime/cst.rbs +9 -0
- data/sig/ibex/runtime/event.rbs +26 -0
- data/sig/ibex/runtime/event_jsonl_tracer.rbs +25 -0
- data/sig/ibex/runtime/event_sanitizer.rbs +92 -0
- data/sig/ibex/runtime/generated_lexer.rbs +169 -0
- data/sig/ibex/runtime/jsonl_tracer.rbs +42 -0
- data/sig/ibex/runtime/lexer_input.rbs +44 -0
- data/sig/ibex/runtime/location_span.rbs +64 -0
- data/sig/ibex/runtime/observation.rbs +35 -0
- data/sig/ibex/runtime/parser.rbs +926 -0
- data/sig/ibex/runtime/parser_sync_recovery.rbs +60 -0
- data/sig/ibex/runtime/repair.rbs +92 -0
- data/sig/ibex/runtime/repair_priority_queue.rbs +27 -0
- data/sig/ibex/runtime/repair_search.rbs +114 -0
- data/sig/ibex/runtime/resource_limits.rbs +39 -0
- data/sig/ibex/runtime/table_format.rbs +11 -0
- data/sig/ibex/runtime/version.rbs +7 -0
- data/sig/ibex/runtime.rbs +6 -0
- data/sig/ibex/tables/compact.rbs +70 -0
- data/sig/ibex/tables/compact_actions.rbs +57 -0
- data/sig/ibex/tables/compact_productions.rbs +68 -0
- 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
|