-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
pull_parser.cr
341 lines (291 loc) · 7.94 KB
/
pull_parser.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# A pull parser allows parsing a YAML document by events.
#
# When creating an instance, the parser is positioned in
# the first event. To get the event kind invoke `kind`.
# If the event is a scalar you can invoke `value` to get
# its **string** value. Other methods like `tag`, `anchor`
# and `scalar_style` let you inspect other information from events.
#
# Invoking `read_next` reads the next event.
class YAML::PullParser
protected getter content
def initialize(@content : String | IO)
@parser = Pointer(Void).malloc(LibYAML::PARSER_SIZE).as(LibYAML::Parser*)
@event = LibYAML::Event.new
@closed = false
LibYAML.yaml_parser_initialize(@parser)
if content.is_a?(String)
LibYAML.yaml_parser_set_input_string(@parser, content, content.bytesize)
else
LibYAML.yaml_parser_set_input(@parser, ->(data, buffer, size, size_read) {
parser = data.as(YAML::PullParser)
io = parser.content.as(IO)
slice = Slice.new(buffer, size)
actual_read_bytes = io.read(slice)
size_read.value = LibC::SizeT.new(actual_read_bytes)
LibC::Int.new(1)
}, self.as(Void*))
end
read_next
raise "Expected STREAM_START" unless kind.stream_start?
end
# Creates a parser, yields it to the block, and closes
# the parser at the end of it.
def self.new(content)
parser = new(content)
yield parser ensure parser.close
end
# The current event kind.
def kind : EventKind
@event.type
end
# Returns the tag associated to the current event, or `nil`
# if there's no tag.
def tag : String?
case kind
when .mapping_start?
ptr = @event.data.mapping_start.tag
when .sequence_start?
ptr = @event.data.sequence_start.tag
when .scalar?
ptr = @event.data.scalar.tag
else
# no tag
end
ptr ? String.new(ptr) : nil
end
# Returns the scalar value, assuming the pull parser
# is located at a scalar. Raises otherwise.
def value : String
expect_kind EventKind::SCALAR
ptr = @event.data.scalar.value
ptr ? String.new(ptr, @event.data.scalar.length) : ""
end
# Returns the anchor associated to the current event, or `nil`
# if there's no anchor.
def anchor : String?
case kind
when .scalar?
read_anchor @event.data.scalar.anchor
when .sequence_start?
read_anchor @event.data.sequence_start.anchor
when .mapping_start?
read_anchor @event.data.mapping_start.anchor
when .alias?
read_anchor @event.data.alias.anchor
else
nil
end
end
# Returns the sequence style, assuming the pull parser is located
# at a sequence begin event. Raises otherwise.
def sequence_style : SequenceStyle
expect_kind EventKind::SEQUENCE_START
@event.data.sequence_start.style
end
# Returns the mapping style, assuming the pull parser is located
# at a mapping begin event. Raises otherwise.
def mapping_style : MappingStyle
expect_kind EventKind::MAPPING_START
@event.data.mapping_start.style
end
# Returns the scalar style, assuming the pull parser is located
# at a scalar event. Raises otherwise.
def scalar_style : ScalarStyle
expect_kind EventKind::SCALAR
@event.data.scalar.style
end
# Reads the next event.
def read_next : EventKind
LibYAML.yaml_event_delete(pointerof(@event))
LibYAML.yaml_parser_parse(@parser, pointerof(@event))
if problem = problem?
msg = String.new(problem)
location = {problem_line_number, problem_column_number}
if context = context?
context_info = {String.new(context), context_line_number, context_column_number}
end
raise msg, *location, context_info
end
kind
end
# Reads a "stream start" event, yields to the block,
# and then reads a "stream end" event.
def read_stream
read_stream_start
value = yield
read_stream_end
value
end
# Reads a "document start" event, yields to the block,
# and then reads a "document end" event.
def read_document
read_document_start
value = yield
read_document_end
value
end
# Reads a "sequence start" event, yields to the block,
# and then reads a "sequence end" event.
def read_sequence
read_sequence_start
value = yield
read_sequence_end
value
end
# Reads a "mapping start" event, yields to the block,
# and then reads a "mapping end" event.
def read_mapping
read_mapping_start
value = yield
read_mapping_end
value
end
# Reads an alias event, returning its anchor.
def read_alias : String?
expect_kind EventKind::ALIAS
anchor = self.anchor
read_next
anchor
end
# Reads a scalar, returning its value.
def read_scalar : String
expect_kind EventKind::SCALAR
value = self.value
read_next
value
end
# Reads a "stream start" event.
def read_stream_start
read EventKind::STREAM_START
end
# Reads a "stream end" event.
def read_stream_end
read EventKind::STREAM_END
end
# Reads a "document start" event.
def read_document_start
read EventKind::DOCUMENT_START
end
# Reads a "document end" event.
def read_document_end
read EventKind::DOCUMENT_END
end
# Reads a "sequence start" event.
def read_sequence_start
read EventKind::SEQUENCE_START
end
# Reads a "sequence end" event.
def read_sequence_end
read EventKind::SEQUENCE_END
end
# Reads a "mapping start" event.
def read_mapping_start
read EventKind::MAPPING_START
end
# Reads a "mapping end" event.
def read_mapping_end
read EventKind::MAPPING_END
end
# Reads an expected event kind.
def read(expected_kind : EventKind) : EventKind
expect_kind expected_kind
read_next
end
def skip : YAML::EventKind
case kind
when .scalar?
read_next
when .alias?
read_next
when .sequence_start?
read_next
until kind.sequence_end?
skip
end
read_next
when .mapping_start?
read_next
until kind.mapping_end?
skip
skip
end
read_next
when .document_start?
read_next
until kind.document_end?
skip
end
read_next
when .stream_start?
read_next
until kind.stream_end?
skip
end
read_next
else
read_next
end
end
# Note: YAML starts counting from 0, we want to count from 1
def location : {Int32, Int32}
{start_line, start_column}
end
def start_line : Int32
@event.start_mark.line.to_i32 + 1
end
def start_column : Int32
@event.start_mark.column.to_i32 + 1
end
def end_line : Int32
@event.end_mark.line.to_i32 + 1
end
def end_column : Int32
@event.end_mark.column.to_i32 + 1
end
private def problem_line_number
(problem? ? problem_mark?.line : start_line) + 1
end
private def problem_column_number
(problem? ? problem_mark?.column : start_column) + 1
end
private def problem_mark?
@parser.value.problem_mark
end
private def problem?
@parser.value.problem
end
private def context?
@parser.value.context
end
private def context_mark?
@parser.value.context_mark
end
private def context_line_number
# YAML starts counting from 0, we want to count from 1
context_mark?.line + 1
end
private def context_column_number
# YAML starts counting from 0, we want to count from 1
context_mark?.column + 1
end
def finalize
return if @closed
LibYAML.yaml_parser_delete(@parser)
LibYAML.yaml_event_delete(pointerof(@event))
end
def close : Nil
finalize
@closed = true
end
# Raises if the current kind is not the expected one.
def expect_kind(kind : EventKind) : Nil
raise "Expected #{kind} but was #{self.kind}" unless kind == self.kind
end
private def read_anchor(anchor)
anchor ? String.new(anchor) : nil
end
def raise(msg : String, line_number = self.start_line, column_number = self.start_column, context_info = nil) : NoReturn
::raise ParseException.new(msg, line_number, column_number, context_info)
end
end