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,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
|