master
py 1,449 lines 50.7 KB
Raw
1 # SPDX-License-Identifier: MIT
2
3 # Scanner produces tokens of the following types:
4 # STREAM-START
5 # STREAM-END
6 # DIRECTIVE(name, value)
7 # DOCUMENT-START
8 # DOCUMENT-END
9 # BLOCK-SEQUENCE-START
10 # BLOCK-MAPPING-START
11 # BLOCK-END
12 # FLOW-SEQUENCE-START
13 # FLOW-MAPPING-START
14 # FLOW-SEQUENCE-END
15 # FLOW-MAPPING-END
16 # BLOCK-ENTRY
17 # FLOW-ENTRY
18 # KEY
19 # VALUE
20 # ALIAS(value)
21 # ANCHOR(value)
22 # TAG(value)
23 # SCALAR(value, plain, style)
24 #
25 # Read comments in the Scanner code for more details.
26 #
27
28 __all__ = ['Scanner', 'ScannerError']
29
30 from .error import MarkedYAMLError
31 from .tokens import *
32
33 class ScannerError(MarkedYAMLError):
34 pass
35
36 class SimpleKey:
37 # See below simple keys treatment.
38
39 def __init__(self, token_number, required, index, line, column, mark):
40 self.token_number = token_number
41 self.required = required
42 self.index = index
43 self.line = line
44 self.column = column
45 self.mark = mark
46
47 class Scanner:
48
49 def __init__(self):
50 """Initialize the scanner."""
51 # It is assumed that Scanner and Reader will have a common descendant.
52 # Reader do the dirty work of checking for BOM and converting the
53 # input data to Unicode. It also adds NUL to the end.
54 #
55 # Reader supports the following methods
56 # self.peek(i=0) # peek the next i-th character
57 # self.prefix(l=1) # peek the next l characters
58 # self.forward(l=1) # read the next l characters and move the pointer.
59
60 # Had we reached the end of the stream?
61 self.done = False
62
63 # The number of unclosed '{' and '['. `flow_level == 0` means block
64 # context.
65 self.flow_level = 0
66
67 # List of processed tokens that are not yet emitted.
68 self.tokens = []
69
70 # Add the STREAM-START token.
71 self.fetch_stream_start()
72
73 # Number of tokens that were emitted through the `get_token` method.
74 self.tokens_taken = 0
75
76 # The current indentation level.
77 self.indent = -1
78
79 # Past indentation levels.
80 self.indents = []
81
82 # Variables related to simple keys treatment.
83
84 # A simple key is a key that is not denoted by the '?' indicator.
85 # Example of simple keys:
86 # ---
87 # block simple key: value
88 # ? not a simple key:
89 # : { flow simple key: value }
90 # We emit the KEY token before all keys, so when we find a potential
91 # simple key, we try to locate the corresponding ':' indicator.
92 # Simple keys should be limited to a single line and 1024 characters.
93
94 # Can a simple key start at the current position? A simple key may
95 # start:
96 # - at the beginning of the line, not counting indentation spaces
97 # (in block context),
98 # - after '{', '[', ',' (in the flow context),
99 # - after '?', ':', '-' (in the block context).
100 # In the block context, this flag also signifies if a block collection
101 # may start at the current position.
102 self.allow_simple_key = True
103
104 # Keep track of possible simple keys. This is a dictionary. The key
105 # is `flow_level`; there can be no more that one possible simple key
106 # for each level. The value is a SimpleKey record:
107 # (token_number, required, index, line, column, mark)
108 # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow),
109 # '[', or '{' tokens.
110 self.possible_simple_keys = {}
111
112 # Public methods.
113
114 def check_token(self, *choices):
115 # Check if the next token is one of the given types.
116 while self.need_more_tokens():
117 self.fetch_more_tokens()
118 if self.tokens:
119 if not choices:
120 return True
121 for choice in choices:
122 if isinstance(self.tokens[0], choice):
123 return True
124 return False
125
126 def peek_token(self):
127 # Return the next token, but do not delete if from the queue.
128 while self.need_more_tokens():
129 self.fetch_more_tokens()
130 if self.tokens:
131 return self.tokens[0]
132
133 def get_token(self):
134 # Return the next token.
135 while self.need_more_tokens():
136 self.fetch_more_tokens()
137 if self.tokens:
138 self.tokens_taken += 1
139 return self.tokens.pop(0)
140
141 # Private methods.
142
143 def need_more_tokens(self):
144 if self.done:
145 return False
146 if not self.tokens:
147 return True
148 # The current token may be a potential simple key, so we
149 # need to look further.
150 self.stale_possible_simple_keys()
151 if self.next_possible_simple_key() == self.tokens_taken:
152 return True
153
154 def fetch_more_tokens(self):
155
156 # Eat whitespaces and comments until we reach the next token.
157 self.scan_to_next_token()
158
159 # Remove obsolete possible simple keys.
160 self.stale_possible_simple_keys()
161
162 # Compare the current indentation and column. It may add some tokens
163 # and decrease the current indentation level.
164 self.unwind_indent(self.column)
165
166 # Peek the next character.
167 ch = self.peek()
168
169 # Is it the end of stream?
170 if ch == '\0':
171 return self.fetch_stream_end()
172
173 # Is it a directive?
174 if ch == '%' and self.check_directive():
175 return self.fetch_directive()
176
177 # Is it the document start?
178 if ch == '-' and self.check_document_start():
179 return self.fetch_document_start()
180
181 # Is it the document end?
182 if ch == '.' and self.check_document_end():
183 return self.fetch_document_end()
184
185 # TODO: support for BOM within a stream.
186 #if ch == '\uFEFF':
187 # return self.fetch_bom() <-- issue BOMToken
188
189 # Note: the order of the following checks is NOT significant.
190
191 # Is it the flow sequence start indicator?
192 if ch == '[':
193 return self.fetch_flow_sequence_start()
194
195 # Is it the flow mapping start indicator?
196 if ch == '{':
197 return self.fetch_flow_mapping_start()
198
199 # Is it the flow sequence end indicator?
200 if ch == ']':
201 return self.fetch_flow_sequence_end()
202
203 # Is it the flow mapping end indicator?
204 if ch == '}':
205 return self.fetch_flow_mapping_end()
206
207 # Is it the flow entry indicator?
208 if ch == ',':
209 return self.fetch_flow_entry()
210
211 # Is it the block entry indicator?
212 if ch == '-' and self.check_block_entry():
213 return self.fetch_block_entry()
214
215 # Is it the key indicator?
216 if ch == '?' and self.check_key():
217 return self.fetch_key()
218
219 # Is it the value indicator?
220 if ch == ':' and self.check_value():
221 return self.fetch_value()
222
223 # Is it an alias?
224 if ch == '*':
225 return self.fetch_alias()
226
227 # Is it an anchor?
228 if ch == '&':
229 return self.fetch_anchor()
230
231 # Is it a tag?
232 if ch == '!':
233 return self.fetch_tag()
234
235 # Is it a literal scalar?
236 if ch == '|' and not self.flow_level:
237 return self.fetch_literal()
238
239 # Is it a folded scalar?
240 if ch == '>' and not self.flow_level:
241 return self.fetch_folded()
242
243 # Is it a single quoted scalar?
244 if ch == '\'':
245 return self.fetch_single()
246
247 # Is it a double quoted scalar?
248 if ch == '\"':
249 return self.fetch_double()
250
251 # It must be a plain scalar then.
252 if self.check_plain():
253 return self.fetch_plain()
254
255 # No? It's an error. Let's produce a nice error message.
256 raise ScannerError("while scanning for the next token", None,
257 "found character %r that cannot start any token" % ch,
258 self.get_mark())
259
260 # Simple keys treatment.
261
262 def next_possible_simple_key(self):
263 # Return the number of the nearest possible simple key. Actually we
264 # don't need to loop through the whole dictionary. We may replace it
265 # with the following code:
266 # if not self.possible_simple_keys:
267 # return None
268 # return self.possible_simple_keys[
269 # min(self.possible_simple_keys.keys())].token_number
270 min_token_number = None
271 for level in self.possible_simple_keys:
272 key = self.possible_simple_keys[level]
273 if min_token_number is None or key.token_number < min_token_number:
274 min_token_number = key.token_number
275 return min_token_number
276
277 def stale_possible_simple_keys(self):
278 # Remove entries that are no longer possible simple keys. According to
279 # the YAML specification, simple keys
280 # - should be limited to a single line,
281 # - should be no longer than 1024 characters.
282 # Disabling this procedure will allow simple keys of any length and
283 # height (may cause problems if indentation is broken though).
284 for level in list(self.possible_simple_keys):
285 key = self.possible_simple_keys[level]
286 if key.line != self.line \
287 or self.index-key.index > 1024:
288 if key.required:
289 raise ScannerError("while scanning a simple key", key.mark,
290 "could not found expected ':'", self.get_mark())
291 del self.possible_simple_keys[level]
292
293 def save_possible_simple_key(self):
294 # The next token may start a simple key. We check if it's possible
295 # and save its position. This function is called for
296 # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'.
297
298 # Check if a simple key is required at the current position.
299 required = not self.flow_level and self.indent == self.column
300
301 # A simple key is required only if it is the first token in the current
302 # line. Therefore it is always allowed.
303 assert self.allow_simple_key or not required
304
305 # The next token might be a simple key. Let's save it's number and
306 # position.
307 if self.allow_simple_key:
308 self.remove_possible_simple_key()
309 token_number = self.tokens_taken+len(self.tokens)
310 key = SimpleKey(token_number, required,
311 self.index, self.line, self.column, self.get_mark())
312 self.possible_simple_keys[self.flow_level] = key
313
314 def remove_possible_simple_key(self):
315 # Remove the saved possible key position at the current flow level.
316 if self.flow_level in self.possible_simple_keys:
317 key = self.possible_simple_keys[self.flow_level]
318
319 if key.required:
320 raise ScannerError("while scanning a simple key", key.mark,
321 "could not found expected ':'", self.get_mark())
322
323 del self.possible_simple_keys[self.flow_level]
324
325 # Indentation functions.
326
327 def unwind_indent(self, column):
328
329 ## In flow context, tokens should respect indentation.
330 ## Actually the condition should be `self.indent >= column` according to
331 ## the spec. But this condition will prohibit intuitively correct
332 ## constructions such as
333 ## key : {
334 ## }
335 #if self.flow_level and self.indent > column:
336 # raise ScannerError(None, None,
337 # "invalid intendation or unclosed '[' or '{'",
338 # self.get_mark())
339
340 # In the flow context, indentation is ignored. We make the scanner less
341 # restrictive then specification requires.
342 if self.flow_level:
343 return
344
345 # In block context, we may need to issue the BLOCK-END tokens.
346 while self.indent > column:
347 mark = self.get_mark()
348 self.indent = self.indents.pop()
349 self.tokens.append(BlockEndToken(mark, mark))
350
351 def add_indent(self, column):
352 # Check if we need to increase indentation.
353 if self.indent < column:
354 self.indents.append(self.indent)
355 self.indent = column
356 return True
357 return False
358
359 # Fetchers.
360
361 def fetch_stream_start(self):
362 # We always add STREAM-START as the first token and STREAM-END as the
363 # last token.
364
365 # Read the token.
366 mark = self.get_mark()
367
368 # Add STREAM-START.
369 self.tokens.append(StreamStartToken(mark, mark,
370 encoding=self.encoding))
371
372
373 def fetch_stream_end(self):
374
375 # Set the current intendation to -1.
376 self.unwind_indent(-1)
377
378 # Reset simple keys.
379 self.remove_possible_simple_key()
380 self.allow_simple_key = False
381 self.possible_simple_keys = {}
382
383 # Read the token.
384 mark = self.get_mark()
385
386 # Add STREAM-END.
387 self.tokens.append(StreamEndToken(mark, mark))
388
389 # The steam is finished.
390 self.done = True
391
392 def fetch_directive(self):
393
394 # Set the current intendation to -1.
395 self.unwind_indent(-1)
396
397 # Reset simple keys.
398 self.remove_possible_simple_key()
399 self.allow_simple_key = False
400
401 # Scan and add DIRECTIVE.
402 self.tokens.append(self.scan_directive())
403
404 def fetch_document_start(self):
405 self.fetch_document_indicator(DocumentStartToken)
406
407 def fetch_document_end(self):
408 self.fetch_document_indicator(DocumentEndToken)
409
410 def fetch_document_indicator(self, TokenClass):
411
412 # Set the current intendation to -1.
413 self.unwind_indent(-1)
414
415 # Reset simple keys. Note that there could not be a block collection
416 # after '---'.
417 self.remove_possible_simple_key()
418 self.allow_simple_key = False
419
420 # Add DOCUMENT-START or DOCUMENT-END.
421 start_mark = self.get_mark()
422 self.forward(3)
423 end_mark = self.get_mark()
424 self.tokens.append(TokenClass(start_mark, end_mark))
425
426 def fetch_flow_sequence_start(self):
427 self.fetch_flow_collection_start(FlowSequenceStartToken)
428
429 def fetch_flow_mapping_start(self):
430 self.fetch_flow_collection_start(FlowMappingStartToken)
431
432 def fetch_flow_collection_start(self, TokenClass):
433
434 # '[' and '{' may start a simple key.
435 self.save_possible_simple_key()
436
437 # Increase the flow level.
438 self.flow_level += 1
439
440 # Simple keys are allowed after '[' and '{'.
441 self.allow_simple_key = True
442
443 # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START.
444 start_mark = self.get_mark()
445 self.forward()
446 end_mark = self.get_mark()
447 self.tokens.append(TokenClass(start_mark, end_mark))
448
449 def fetch_flow_sequence_end(self):
450 self.fetch_flow_collection_end(FlowSequenceEndToken)
451
452 def fetch_flow_mapping_end(self):
453 self.fetch_flow_collection_end(FlowMappingEndToken)
454
455 def fetch_flow_collection_end(self, TokenClass):
456
457 # Reset possible simple key on the current level.
458 self.remove_possible_simple_key()
459
460 # Decrease the flow level.
461 self.flow_level -= 1
462
463 # No simple keys after ']' or '}'.
464 self.allow_simple_key = False
465
466 # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END.
467 start_mark = self.get_mark()
468 self.forward()
469 end_mark = self.get_mark()
470 self.tokens.append(TokenClass(start_mark, end_mark))
471
472 def fetch_flow_entry(self):
473
474 # Simple keys are allowed after ','.
475 self.allow_simple_key = True
476
477 # Reset possible simple key on the current level.
478 self.remove_possible_simple_key()
479
480 # Add FLOW-ENTRY.
481 start_mark = self.get_mark()
482 self.forward()
483 end_mark = self.get_mark()
484 self.tokens.append(FlowEntryToken(start_mark, end_mark))
485
486 def fetch_block_entry(self):
487
488 # Block context needs additional checks.
489 if not self.flow_level:
490
491 # Are we allowed to start a new entry?
492 if not self.allow_simple_key:
493 raise ScannerError(None, None,
494 "sequence entries are not allowed here",
495 self.get_mark())
496
497 # We may need to add BLOCK-SEQUENCE-START.
498 if self.add_indent(self.column):
499 mark = self.get_mark()
500 self.tokens.append(BlockSequenceStartToken(mark, mark))
501
502 # It's an error for the block entry to occur in the flow context,
503 # but we let the parser detect this.
504 else:
505 pass
506
507 # Simple keys are allowed after '-'.
508 self.allow_simple_key = True
509
510 # Reset possible simple key on the current level.
511 self.remove_possible_simple_key()
512
513 # Add BLOCK-ENTRY.
514 start_mark = self.get_mark()
515 self.forward()
516 end_mark = self.get_mark()
517 self.tokens.append(BlockEntryToken(start_mark, end_mark))
518
519 def fetch_key(self):
520
521 # Block context needs additional checks.
522 if not self.flow_level:
523
524 # Are we allowed to start a key (not nessesary a simple)?
525 if not self.allow_simple_key:
526 raise ScannerError(None, None,
527 "mapping keys are not allowed here",
528 self.get_mark())
529
530 # We may need to add BLOCK-MAPPING-START.
531 if self.add_indent(self.column):
532 mark = self.get_mark()
533 self.tokens.append(BlockMappingStartToken(mark, mark))
534
535 # Simple keys are allowed after '?' in the block context.
536 self.allow_simple_key = not self.flow_level
537
538 # Reset possible simple key on the current level.
539 self.remove_possible_simple_key()
540
541 # Add KEY.
542 start_mark = self.get_mark()
543 self.forward()
544 end_mark = self.get_mark()
545 self.tokens.append(KeyToken(start_mark, end_mark))
546
547 def fetch_value(self):
548
549 # Do we determine a simple key?
550 if self.flow_level in self.possible_simple_keys:
551
552 # Add KEY.
553 key = self.possible_simple_keys[self.flow_level]
554 del self.possible_simple_keys[self.flow_level]
555 self.tokens.insert(key.token_number-self.tokens_taken,
556 KeyToken(key.mark, key.mark))
557
558 # If this key starts a new block mapping, we need to add
559 # BLOCK-MAPPING-START.
560 if not self.flow_level:
561 if self.add_indent(key.column):
562 self.tokens.insert(key.token_number-self.tokens_taken,
563 BlockMappingStartToken(key.mark, key.mark))
564
565 # There cannot be two simple keys one after another.
566 self.allow_simple_key = False
567
568 # It must be a part of a complex key.
569 else:
570
571 # Block context needs additional checks.
572 # (Do we really need them? They will be catched by the parser
573 # anyway.)
574 if not self.flow_level:
575
576 # We are allowed to start a complex value if and only if
577 # we can start a simple key.
578 if not self.allow_simple_key:
579 raise ScannerError(None, None,
580 "mapping values are not allowed here",
581 self.get_mark())
582
583 # If this value starts a new block mapping, we need to add
584 # BLOCK-MAPPING-START. It will be detected as an error later by
585 # the parser.
586 if not self.flow_level:
587 if self.add_indent(self.column):
588 mark = self.get_mark()
589 self.tokens.append(BlockMappingStartToken(mark, mark))
590
591 # Simple keys are allowed after ':' in the block context.
592 self.allow_simple_key = not self.flow_level
593
594 # Reset possible simple key on the current level.
595 self.remove_possible_simple_key()
596
597 # Add VALUE.
598 start_mark = self.get_mark()
599 self.forward()
600 end_mark = self.get_mark()
601 self.tokens.append(ValueToken(start_mark, end_mark))
602
603 def fetch_alias(self):
604
605 # ALIAS could be a simple key.
606 self.save_possible_simple_key()
607
608 # No simple keys after ALIAS.
609 self.allow_simple_key = False
610
611 # Scan and add ALIAS.
612 self.tokens.append(self.scan_anchor(AliasToken))
613
614 def fetch_anchor(self):
615
616 # ANCHOR could start a simple key.
617 self.save_possible_simple_key()
618
619 # No simple keys after ANCHOR.
620 self.allow_simple_key = False
621
622 # Scan and add ANCHOR.
623 self.tokens.append(self.scan_anchor(AnchorToken))
624
625 def fetch_tag(self):
626
627 # TAG could start a simple key.
628 self.save_possible_simple_key()
629
630 # No simple keys after TAG.
631 self.allow_simple_key = False
632
633 # Scan and add TAG.
634 self.tokens.append(self.scan_tag())
635
636 def fetch_literal(self):
637 self.fetch_block_scalar(style='|')
638
639 def fetch_folded(self):
640 self.fetch_block_scalar(style='>')
641
642 def fetch_block_scalar(self, style):
643
644 # A simple key may follow a block scalar.
645 self.allow_simple_key = True
646
647 # Reset possible simple key on the current level.
648 self.remove_possible_simple_key()
649
650 # Scan and add SCALAR.
651 self.tokens.append(self.scan_block_scalar(style))
652
653 def fetch_single(self):
654 self.fetch_flow_scalar(style='\'')
655
656 def fetch_double(self):
657 self.fetch_flow_scalar(style='"')
658
659 def fetch_flow_scalar(self, style):
660
661 # A flow scalar could be a simple key.
662 self.save_possible_simple_key()
663
664 # No simple keys after flow scalars.
665 self.allow_simple_key = False
666
667 # Scan and add SCALAR.
668 self.tokens.append(self.scan_flow_scalar(style))
669
670 def fetch_plain(self):
671
672 # A plain scalar could be a simple key.
673 self.save_possible_simple_key()
674
675 # No simple keys after plain scalars. But note that `scan_plain` will
676 # change this flag if the scan is finished at the beginning of the
677 # line.
678 self.allow_simple_key = False
679
680 # Scan and add SCALAR. May change `allow_simple_key`.
681 self.tokens.append(self.scan_plain())
682
683 # Checkers.
684
685 def check_directive(self):
686
687 # DIRECTIVE: ^ '%' ...
688 # The '%' indicator is already checked.
689 if self.column == 0:
690 return True
691
692 def check_document_start(self):
693
694 # DOCUMENT-START: ^ '---' (' '|'\n')
695 if self.column == 0:
696 if self.prefix(3) == '---' \
697 and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029':
698 return True
699
700 def check_document_end(self):
701
702 # DOCUMENT-END: ^ '...' (' '|'\n')
703 if self.column == 0:
704 if self.prefix(3) == '...' \
705 and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029':
706 return True
707
708 def check_block_entry(self):
709
710 # BLOCK-ENTRY: '-' (' '|'\n')
711 return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029'
712
713 def check_key(self):
714
715 # KEY(flow context): '?'
716 if self.flow_level:
717 return True
718
719 # KEY(block context): '?' (' '|'\n')
720 else:
721 return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029'
722
723 def check_value(self):
724
725 # VALUE(flow context): ':'
726 if self.flow_level:
727 return True
728
729 # VALUE(block context): ':' (' '|'\n')
730 else:
731 return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029'
732
733 def check_plain(self):
734
735 # A plain scalar may start with any non-space character except:
736 # '-', '?', ':', ',', '[', ']', '{', '}',
737 # '#', '&', '*', '!', '|', '>', '\'', '\"',
738 # '%', '@', '`'.
739 #
740 # It may also start with
741 # '-', '?', ':'
742 # if it is followed by a non-space character.
743 #
744 # Note that we limit the last rule to the block context (except the
745 # '-' character) because we want the flow context to be space
746 # independent.
747 ch = self.peek()
748 return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \
749 or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029'
750 and (ch == '-' or (not self.flow_level and ch in '?:')))
751
752 # Scanners.
753
754 def scan_to_next_token(self):
755 # We ignore spaces, line breaks and comments.
756 # If we find a line break in the block context, we set the flag
757 # `allow_simple_key` on.
758 # The byte order mark is stripped if it's the first character in the
759 # stream. We do not yet support BOM inside the stream as the
760 # specification requires. Any such mark will be considered as a part
761 # of the document.
762 #
763 # TODO: We need to make tab handling rules more sane. A good rule is
764 # Tabs cannot precede tokens
765 # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END,
766 # KEY(block), VALUE(block), BLOCK-ENTRY
767 # So the checking code is
768 # if <TAB>:
769 # self.allow_simple_keys = False
770 # We also need to add the check for `allow_simple_keys == True` to
771 # `unwind_indent` before issuing BLOCK-END.
772 # Scanners for block, flow, and plain scalars need to be modified.
773
774 if self.index == 0 and self.peek() == '\uFEFF':
775 self.forward()
776 found = False
777 while not found:
778 while self.peek() == ' ':
779 self.forward()
780 if self.peek() == '#':
781 while self.peek() not in '\0\r\n\x85\u2028\u2029':
782 self.forward()
783 if self.scan_line_break():
784 if not self.flow_level:
785 self.allow_simple_key = True
786 else:
787 found = True
788
789 def scan_directive(self):
790 # See the specification for details.
791 start_mark = self.get_mark()
792 self.forward()
793 name = self.scan_directive_name(start_mark)
794 value = None
795 if name == 'YAML':
796 value = self.scan_yaml_directive_value(start_mark)
797 end_mark = self.get_mark()
798 elif name == 'TAG':
799 value = self.scan_tag_directive_value(start_mark)
800 end_mark = self.get_mark()
801 else:
802 end_mark = self.get_mark()
803 while self.peek() not in '\0\r\n\x85\u2028\u2029':
804 self.forward()
805 self.scan_directive_ignored_line(start_mark)
806 return DirectiveToken(name, value, start_mark, end_mark)
807
808 def scan_directive_name(self, start_mark):
809 # See the specification for details.
810 length = 0
811 ch = self.peek(length)
812 while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
813 or ch in '-_':
814 length += 1
815 ch = self.peek(length)
816 if not length:
817 raise ScannerError("while scanning a directive", start_mark,
818 "expected alphabetic or numeric character, but found %r"
819 % ch, self.get_mark())
820 value = self.prefix(length)
821 self.forward(length)
822 ch = self.peek()
823 if ch not in '\0 \r\n\x85\u2028\u2029':
824 raise ScannerError("while scanning a directive", start_mark,
825 "expected alphabetic or numeric character, but found %r"
826 % ch, self.get_mark())
827 return value
828
829 def scan_yaml_directive_value(self, start_mark):
830 # See the specification for details.
831 while self.peek() == ' ':
832 self.forward()
833 major = self.scan_yaml_directive_number(start_mark)
834 if self.peek() != '.':
835 raise ScannerError("while scanning a directive", start_mark,
836 "expected a digit or '.', but found %r" % self.peek(),
837 self.get_mark())
838 self.forward()
839 minor = self.scan_yaml_directive_number(start_mark)
840 if self.peek() not in '\0 \r\n\x85\u2028\u2029':
841 raise ScannerError("while scanning a directive", start_mark,
842 "expected a digit or ' ', but found %r" % self.peek(),
843 self.get_mark())
844 return (major, minor)
845
846 def scan_yaml_directive_number(self, start_mark):
847 # See the specification for details.
848 ch = self.peek()
849 if not ('0' <= ch <= '9'):
850 raise ScannerError("while scanning a directive", start_mark,
851 "expected a digit, but found %r" % ch, self.get_mark())
852 length = 0
853 while '0' <= self.peek(length) <= '9':
854 length += 1
855 value = int(self.prefix(length))
856 self.forward(length)
857 return value
858
859 def scan_tag_directive_value(self, start_mark):
860 # See the specification for details.
861 while self.peek() == ' ':
862 self.forward()
863 handle = self.scan_tag_directive_handle(start_mark)
864 while self.peek() == ' ':
865 self.forward()
866 prefix = self.scan_tag_directive_prefix(start_mark)
867 return (handle, prefix)
868
869 def scan_tag_directive_handle(self, start_mark):
870 # See the specification for details.
871 value = self.scan_tag_handle('directive', start_mark)
872 ch = self.peek()
873 if ch != ' ':
874 raise ScannerError("while scanning a directive", start_mark,
875 "expected ' ', but found %r" % ch, self.get_mark())
876 return value
877
878 def scan_tag_directive_prefix(self, start_mark):
879 # See the specification for details.
880 value = self.scan_tag_uri('directive', start_mark)
881 ch = self.peek()
882 if ch not in '\0 \r\n\x85\u2028\u2029':
883 raise ScannerError("while scanning a directive", start_mark,
884 "expected ' ', but found %r" % ch, self.get_mark())
885 return value
886
887 def scan_directive_ignored_line(self, start_mark):
888 # See the specification for details.
889 while self.peek() == ' ':
890 self.forward()
891 if self.peek() == '#':
892 while self.peek() not in '\0\r\n\x85\u2028\u2029':
893 self.forward()
894 ch = self.peek()
895 if ch not in '\0\r\n\x85\u2028\u2029':
896 raise ScannerError("while scanning a directive", start_mark,
897 "expected a comment or a line break, but found %r"
898 % ch, self.get_mark())
899 self.scan_line_break()
900
901 def scan_anchor(self, TokenClass):
902 # The specification does not restrict characters for anchors and
903 # aliases. This may lead to problems, for instance, the document:
904 # [ *alias, value ]
905 # can be interpteted in two ways, as
906 # [ "value" ]
907 # and
908 # [ *alias , "value" ]
909 # Therefore we restrict aliases to numbers and ASCII letters.
910 start_mark = self.get_mark()
911 indicator = self.peek()
912 if indicator == '*':
913 name = 'alias'
914 else:
915 name = 'anchor'
916 self.forward()
917 length = 0
918 ch = self.peek(length)
919 while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
920 or ch in '-_':
921 length += 1
922 ch = self.peek(length)
923 if not length:
924 raise ScannerError("while scanning an %s" % name, start_mark,
925 "expected alphabetic or numeric character, but found %r"
926 % ch, self.get_mark())
927 value = self.prefix(length)
928 self.forward(length)
929 ch = self.peek()
930 if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`':
931 raise ScannerError("while scanning an %s" % name, start_mark,
932 "expected alphabetic or numeric character, but found %r"
933 % ch, self.get_mark())
934 end_mark = self.get_mark()
935 return TokenClass(value, start_mark, end_mark)
936
937 def scan_tag(self):
938 # See the specification for details.
939 start_mark = self.get_mark()
940 ch = self.peek(1)
941 if ch == '<':
942 handle = None
943 self.forward(2)
944 suffix = self.scan_tag_uri('tag', start_mark)
945 if self.peek() != '>':
946 raise ScannerError("while parsing a tag", start_mark,
947 "expected '>', but found %r" % self.peek(),
948 self.get_mark())
949 self.forward()
950 elif ch in '\0 \t\r\n\x85\u2028\u2029':
951 handle = None
952 suffix = '!'
953 self.forward()
954 else:
955 length = 1
956 use_handle = False
957 while ch not in '\0 \r\n\x85\u2028\u2029':
958 if ch == '!':
959 use_handle = True
960 break
961 length += 1
962 ch = self.peek(length)
963 handle = '!'
964 if use_handle:
965 handle = self.scan_tag_handle('tag', start_mark)
966 else:
967 handle = '!'
968 self.forward()
969 suffix = self.scan_tag_uri('tag', start_mark)
970 ch = self.peek()
971 if ch not in '\0 \r\n\x85\u2028\u2029':
972 raise ScannerError("while scanning a tag", start_mark,
973 "expected ' ', but found %r" % ch, self.get_mark())
974 value = (handle, suffix)
975 end_mark = self.get_mark()
976 return TagToken(value, start_mark, end_mark)
977
978 def scan_block_scalar(self, style):
979 # See the specification for details.
980
981 if style == '>':
982 folded = True
983 else:
984 folded = False
985
986 chunks = []
987 start_mark = self.get_mark()
988
989 # Scan the header.
990 self.forward()
991 chomping, increment = self.scan_block_scalar_indicators(start_mark)
992 self.scan_block_scalar_ignored_line(start_mark)
993
994 # Determine the indentation level and go to the first non-empty line.
995 min_indent = self.indent+1
996 if min_indent < 1:
997 min_indent = 1
998 if increment is None:
999 breaks, max_indent, end_mark = self.scan_block_scalar_indentation()
1000 indent = max(min_indent, max_indent)
1001 else:
1002 indent = min_indent+increment-1
1003 breaks, end_mark = self.scan_block_scalar_breaks(indent)
1004 line_break = ''
1005
1006 # Scan the inner part of the block scalar.
1007 while self.column == indent and self.peek() != '\0':
1008 chunks.extend(breaks)
1009 leading_non_space = self.peek() not in ' \t'
1010 length = 0
1011 while self.peek(length) not in '\0\r\n\x85\u2028\u2029':
1012 length += 1
1013 chunks.append(self.prefix(length))
1014 self.forward(length)
1015 line_break = self.scan_line_break()
1016 breaks, end_mark = self.scan_block_scalar_breaks(indent)
1017 if self.column == indent and self.peek() != '\0':
1018
1019 # Unfortunately, folding rules are ambiguous.
1020 #
1021 # This is the folding according to the specification:
1022
1023 if folded and line_break == '\n' \
1024 and leading_non_space and self.peek() not in ' \t':
1025 if not breaks:
1026 chunks.append(' ')
1027 else:
1028 chunks.append(line_break)
1029
1030 # This is Clark Evans's interpretation (also in the spec
1031 # examples):
1032 #
1033 #if folded and line_break == '\n':
1034 # if not breaks:
1035 # if self.peek() not in ' \t':
1036 # chunks.append(' ')
1037 # else:
1038 # chunks.append(line_break)
1039 #else:
1040 # chunks.append(line_break)
1041 else:
1042 break
1043
1044 # Chomp the tail.
1045 if chomping is not False:
1046 chunks.append(line_break)
1047 if chomping is True:
1048 chunks.extend(breaks)
1049
1050 # We are done.
1051 return ScalarToken(''.join(chunks), False, start_mark, end_mark,
1052 style)
1053
1054 def scan_block_scalar_indicators(self, start_mark):
1055 # See the specification for details.
1056 chomping = None
1057 increment = None
1058 ch = self.peek()
1059 if ch in '+-':
1060 if ch == '+':
1061 chomping = True
1062 else:
1063 chomping = False
1064 self.forward()
1065 ch = self.peek()
1066 if ch in '0123456789':
1067 increment = int(ch)
1068 if increment == 0:
1069 raise ScannerError("while scanning a block scalar", start_mark,
1070 "expected indentation indicator in the range 1-9, but found 0",
1071 self.get_mark())
1072 self.forward()
1073 elif ch in '0123456789':
1074 increment = int(ch)
1075 if increment == 0:
1076 raise ScannerError("while scanning a block scalar", start_mark,
1077 "expected indentation indicator in the range 1-9, but found 0",
1078 self.get_mark())
1079 self.forward()
1080 ch = self.peek()
1081 if ch in '+-':
1082 if ch == '+':
1083 chomping = True
1084 else:
1085 chomping = False
1086 self.forward()
1087 ch = self.peek()
1088 if ch not in '\0 \r\n\x85\u2028\u2029':
1089 raise ScannerError("while scanning a block scalar", start_mark,
1090 "expected chomping or indentation indicators, but found %r"
1091 % ch, self.get_mark())
1092 return chomping, increment
1093
1094 def scan_block_scalar_ignored_line(self, start_mark):
1095 # See the specification for details.
1096 while self.peek() == ' ':
1097 self.forward()
1098 if self.peek() == '#':
1099 while self.peek() not in '\0\r\n\x85\u2028\u2029':
1100 self.forward()
1101 ch = self.peek()
1102 if ch not in '\0\r\n\x85\u2028\u2029':
1103 raise ScannerError("while scanning a block scalar", start_mark,
1104 "expected a comment or a line break, but found %r" % ch,
1105 self.get_mark())
1106 self.scan_line_break()
1107
1108 def scan_block_scalar_indentation(self):
1109 # See the specification for details.
1110 chunks = []
1111 max_indent = 0
1112 end_mark = self.get_mark()
1113 while self.peek() in ' \r\n\x85\u2028\u2029':
1114 if self.peek() != ' ':
1115 chunks.append(self.scan_line_break())
1116 end_mark = self.get_mark()
1117 else:
1118 self.forward()
1119 if self.column > max_indent:
1120 max_indent = self.column
1121 return chunks, max_indent, end_mark
1122
1123 def scan_block_scalar_breaks(self, indent):
1124 # See the specification for details.
1125 chunks = []
1126 end_mark = self.get_mark()
1127 while self.column < indent and self.peek() == ' ':
1128 self.forward()
1129 while self.peek() in '\r\n\x85\u2028\u2029':
1130 chunks.append(self.scan_line_break())
1131 end_mark = self.get_mark()
1132 while self.column < indent and self.peek() == ' ':
1133 self.forward()
1134 return chunks, end_mark
1135
1136 def scan_flow_scalar(self, style):
1137 # See the specification for details.
1138 # Note that we loose indentation rules for quoted scalars. Quoted
1139 # scalars don't need to adhere indentation because " and ' clearly
1140 # mark the beginning and the end of them. Therefore we are less
1141 # restrictive then the specification requires. We only need to check
1142 # that document separators are not included in scalars.
1143 if style == '"':
1144 double = True
1145 else:
1146 double = False
1147 chunks = []
1148 start_mark = self.get_mark()
1149 quote = self.peek()
1150 self.forward()
1151 chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark))
1152 while self.peek() != quote:
1153 chunks.extend(self.scan_flow_scalar_spaces(double, start_mark))
1154 chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark))
1155 self.forward()
1156 end_mark = self.get_mark()
1157 return ScalarToken(''.join(chunks), False, start_mark, end_mark,
1158 style)
1159
1160 ESCAPE_REPLACEMENTS = {
1161 '0': '\0',
1162 'a': '\x07',
1163 'b': '\x08',
1164 't': '\x09',
1165 '\t': '\x09',
1166 'n': '\x0A',
1167 'v': '\x0B',
1168 'f': '\x0C',
1169 'r': '\x0D',
1170 'e': '\x1B',
1171 ' ': '\x20',
1172 '\"': '\"',
1173 '\\': '\\',
1174 'N': '\x85',
1175 '_': '\xA0',
1176 'L': '\u2028',
1177 'P': '\u2029',
1178 }
1179
1180 ESCAPE_CODES = {
1181 'x': 2,
1182 'u': 4,
1183 'U': 8,
1184 }
1185
1186 def scan_flow_scalar_non_spaces(self, double, start_mark):
1187 # See the specification for details.
1188 chunks = []
1189 while True:
1190 length = 0
1191 while self.peek(length) not in '\'\"\\\0 \t\r\n\x85\u2028\u2029':
1192 length += 1
1193 if length:
1194 chunks.append(self.prefix(length))
1195 self.forward(length)
1196 ch = self.peek()
1197 if not double and ch == '\'' and self.peek(1) == '\'':
1198 chunks.append('\'')
1199 self.forward(2)
1200 elif (double and ch == '\'') or (not double and ch in '\"\\'):
1201 chunks.append(ch)
1202 self.forward()
1203 elif double and ch == '\\':
1204 self.forward()
1205 ch = self.peek()
1206 if ch in self.ESCAPE_REPLACEMENTS:
1207 chunks.append(self.ESCAPE_REPLACEMENTS[ch])
1208 self.forward()
1209 elif ch in self.ESCAPE_CODES:
1210 length = self.ESCAPE_CODES[ch]
1211 self.forward()
1212 for k in range(length):
1213 if self.peek(k) not in '0123456789ABCDEFabcdef':
1214 raise ScannerError("while scanning a double-quoted scalar", start_mark,
1215 "expected escape sequence of %d hexdecimal numbers, but found %r" %
1216 (length, self.peek(k)), self.get_mark())
1217 code = int(self.prefix(length), 16)
1218 chunks.append(chr(code))
1219 self.forward(length)
1220 elif ch in '\r\n\x85\u2028\u2029':
1221 self.scan_line_break()
1222 chunks.extend(self.scan_flow_scalar_breaks(double, start_mark))
1223 else:
1224 raise ScannerError("while scanning a double-quoted scalar", start_mark,
1225 "found unknown escape character %r" % ch, self.get_mark())
1226 else:
1227 return chunks
1228
1229 def scan_flow_scalar_spaces(self, double, start_mark):
1230 # See the specification for details.
1231 chunks = []
1232 length = 0
1233 while self.peek(length) in ' \t':
1234 length += 1
1235 whitespaces = self.prefix(length)
1236 self.forward(length)
1237 ch = self.peek()
1238 if ch == '\0':
1239 raise ScannerError("while scanning a quoted scalar", start_mark,
1240 "found unexpected end of stream", self.get_mark())
1241 elif ch in '\r\n\x85\u2028\u2029':
1242 line_break = self.scan_line_break()
1243 breaks = self.scan_flow_scalar_breaks(double, start_mark)
1244 if line_break != '\n':
1245 chunks.append(line_break)
1246 elif not breaks:
1247 chunks.append(' ')
1248 chunks.extend(breaks)
1249 else:
1250 chunks.append(whitespaces)
1251 return chunks
1252
1253 def scan_flow_scalar_breaks(self, double, start_mark):
1254 # See the specification for details.
1255 chunks = []
1256 while True:
1257 # Instead of checking indentation, we check for document
1258 # separators.
1259 prefix = self.prefix(3)
1260 if (prefix == '---' or prefix == '...') \
1261 and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029':
1262 raise ScannerError("while scanning a quoted scalar", start_mark,
1263 "found unexpected document separator", self.get_mark())
1264 while self.peek() in ' \t':
1265 self.forward()
1266 if self.peek() in '\r\n\x85\u2028\u2029':
1267 chunks.append(self.scan_line_break())
1268 else:
1269 return chunks
1270
1271 def scan_plain(self):
1272 # See the specification for details.
1273 # We add an additional restriction for the flow context:
1274 # plain scalars in the flow context cannot contain ',', ':' and '?'.
1275 # We also keep track of the `allow_simple_key` flag here.
1276 # Indentation rules are loosed for the flow context.
1277 chunks = []
1278 start_mark = self.get_mark()
1279 end_mark = start_mark
1280 indent = self.indent+1
1281 # We allow zero indentation for scalars, but then we need to check for
1282 # document separators at the beginning of the line.
1283 #if indent == 0:
1284 # indent = 1
1285 spaces = []
1286 while True:
1287 length = 0
1288 if self.peek() == '#':
1289 break
1290 while True:
1291 ch = self.peek(length)
1292 if ch in '\0 \t\r\n\x85\u2028\u2029' \
1293 or (not self.flow_level and ch == ':' and
1294 self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029') \
1295 or (self.flow_level and ch in ',:?[]{}'):
1296 break
1297 length += 1
1298 # It's not clear what we should do with ':' in the flow context.
1299 if (self.flow_level and ch == ':'
1300 and self.peek(length+1) not in '\0 \t\r\n\x85\u2028\u2029,[]{}'):
1301 self.forward(length)
1302 raise ScannerError("while scanning a plain scalar", start_mark,
1303 "found unexpected ':'", self.get_mark(),
1304 "Please check http://pyyaml.org/wiki/YAMLColonInFlowContext for details.")
1305 if length == 0:
1306 break
1307 self.allow_simple_key = False
1308 chunks.extend(spaces)
1309 chunks.append(self.prefix(length))
1310 self.forward(length)
1311 end_mark = self.get_mark()
1312 spaces = self.scan_plain_spaces(indent, start_mark)
1313 if not spaces or self.peek() == '#' \
1314 or (not self.flow_level and self.column < indent):
1315 break
1316 return ScalarToken(''.join(chunks), True, start_mark, end_mark)
1317
1318 def scan_plain_spaces(self, indent, start_mark):
1319 # See the specification for details.
1320 # The specification is really confusing about tabs in plain scalars.
1321 # We just forbid them completely. Do not use tabs in YAML!
1322 chunks = []
1323 length = 0
1324 while self.peek(length) in ' ':
1325 length += 1
1326 whitespaces = self.prefix(length)
1327 self.forward(length)
1328 ch = self.peek()
1329 if ch in '\r\n\x85\u2028\u2029':
1330 line_break = self.scan_line_break()
1331 self.allow_simple_key = True
1332 prefix = self.prefix(3)
1333 if (prefix == '---' or prefix == '...') \
1334 and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029':
1335 return
1336 breaks = []
1337 while self.peek() in ' \r\n\x85\u2028\u2029':
1338 if self.peek() == ' ':
1339 self.forward()
1340 else:
1341 breaks.append(self.scan_line_break())
1342 prefix = self.prefix(3)
1343 if (prefix == '---' or prefix == '...') \
1344 and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029':
1345 return
1346 if line_break != '\n':
1347 chunks.append(line_break)
1348 elif not breaks:
1349 chunks.append(' ')
1350 chunks.extend(breaks)
1351 elif whitespaces:
1352 chunks.append(whitespaces)
1353 return chunks
1354
1355 def scan_tag_handle(self, name, start_mark):
1356 # See the specification for details.
1357 # For some strange reasons, the specification does not allow '_' in
1358 # tag handles. I have allowed it anyway.
1359 ch = self.peek()
1360 if ch != '!':
1361 raise ScannerError("while scanning a %s" % name, start_mark,
1362 "expected '!', but found %r" % ch, self.get_mark())
1363 length = 1
1364 ch = self.peek(length)
1365 if ch != ' ':
1366 while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
1367 or ch in '-_':
1368 length += 1
1369 ch = self.peek(length)
1370 if ch != '!':
1371 self.forward(length)
1372 raise ScannerError("while scanning a %s" % name, start_mark,
1373 "expected '!', but found %r" % ch, self.get_mark())
1374 length += 1
1375 value = self.prefix(length)
1376 self.forward(length)
1377 return value
1378
1379 def scan_tag_uri(self, name, start_mark):
1380 # See the specification for details.
1381 # Note: we do not check if URI is well-formed.
1382 chunks = []
1383 length = 0
1384 ch = self.peek(length)
1385 while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \
1386 or ch in '-;/?:@&=+$,_.!~*\'()[]%':
1387 if ch == '%':
1388 chunks.append(self.prefix(length))
1389 self.forward(length)
1390 length = 0
1391 chunks.append(self.scan_uri_escapes(name, start_mark))
1392 else:
1393 length += 1
1394 ch = self.peek(length)
1395 if length:
1396 chunks.append(self.prefix(length))
1397 self.forward(length)
1398 length = 0
1399 if not chunks:
1400 raise ScannerError("while parsing a %s" % name, start_mark,
1401 "expected URI, but found %r" % ch, self.get_mark())
1402 return ''.join(chunks)
1403
1404 def scan_uri_escapes(self, name, start_mark):
1405 # See the specification for details.
1406 codes = []
1407 mark = self.get_mark()
1408 while self.peek() == '%':
1409 self.forward()
1410 for k in range(2):
1411 if self.peek(k) not in '0123456789ABCDEFabcdef':
1412 raise ScannerError("while scanning a %s" % name, start_mark,
1413 "expected URI escape sequence of 2 hexdecimal numbers, but found %r"
1414 % self.peek(k), self.get_mark())
1415 codes.append(int(self.prefix(2), 16))
1416 self.forward(2)
1417 try:
1418 value = bytes(codes).decode('utf-8')
1419 except UnicodeDecodeError as exc:
1420 raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark)
1421 return value
1422
1423 def scan_line_break(self):
1424 # Transforms:
1425 # '\r\n' : '\n'
1426 # '\r' : '\n'
1427 # '\n' : '\n'
1428 # '\x85' : '\n'
1429 # '\u2028' : '\u2028'
1430 # '\u2029 : '\u2029'
1431 # default : ''
1432 ch = self.peek()
1433 if ch in '\r\n\x85':
1434 if self.prefix(2) == '\r\n':
1435 self.forward(2)
1436 else:
1437 self.forward()
1438 return '\n'
1439 elif ch in '\u2028\u2029':
1440 self.forward()
1441 return ch
1442 return ''
1443
1444 #try:
1445 # import psyco
1446 # psyco.bind(Scanner)
1447 #except ImportError:
1448 # pass
1449