Index: pkg/yaml/lib/src/parser.dart |
diff --git a/pkg/yaml/lib/src/parser.dart b/pkg/yaml/lib/src/parser.dart |
index 94f551f21a8745a13ed98faea3d76176c06ce093..72c01dcf0ddeacdf45bab2968fdbe4caed9f372b 100644 |
--- a/pkg/yaml/lib/src/parser.dart |
+++ b/pkg/yaml/lib/src/parser.dart |
@@ -1,1900 +1,817 @@ |
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
// for details. All rights reserved. Use of this source code is governed by a |
// BSD-style license that can be found in the LICENSE file. |
library yaml.parser; |
-import 'dart:collection'; |
- |
import 'package:source_span/source_span.dart'; |
import 'package:string_scanner/string_scanner.dart'; |
-import 'equality.dart'; |
-import 'model.dart'; |
+import 'event.dart'; |
+import 'scanner.dart'; |
+import 'style.dart'; |
+import 'token.dart'; |
import 'utils.dart'; |
+import 'yaml_document.dart'; |
+import 'yaml_exception.dart'; |
-/// Translates a string of characters into a YAML serialization tree. |
-/// |
-/// This parser is designed to closely follow the spec. All productions in the |
-/// spec are numbered, and the corresponding methods in the parser have the same |
-/// numbers. This is certainly not the most efficient way of parsing YAML, but |
-/// it is the easiest to write and read in the context of the spec. |
+/// A parser that reads [Token]s emitted by a [Scanner] and emits [Event]s. |
/// |
-/// Methods corresponding to productions are also named as in the spec, |
-/// translating the name of the method (although not the annotation characters) |
-/// into camel-case for dart style.. For example, the spec has a production |
-/// named `nb-ns-plain-in-line`, and the method implementing it is named |
-/// `nb_ns_plainInLine`. The exception to that rule is methods that just |
-/// recognize character classes; these are named `is*`. |
+/// This is based on the libyaml parser, available at |
+/// https://github.com/yaml/libyaml/blob/master/src/parser.c. The license for |
+/// that is available in ../../libyaml-license.txt. |
class Parser { |
- static const TAB = 0x9; |
- static const LF = 0xA; |
- static const CR = 0xD; |
- static const SP = 0x20; |
- static const TILDE = 0x7E; |
- static const NEL = 0x85; |
- static const PLUS = 0x2B; |
- static const HYPHEN = 0x2D; |
- static const QUESTION_MARK = 0x3F; |
- static const COLON = 0x3A; |
- static const COMMA = 0x2C; |
- static const LEFT_BRACKET = 0x5B; |
- static const RIGHT_BRACKET = 0x5D; |
- static const LEFT_BRACE = 0x7B; |
- static const RIGHT_BRACE = 0x7D; |
- static const HASH = 0x23; |
- static const AMPERSAND = 0x26; |
- static const ASTERISK = 0x2A; |
- static const EXCLAMATION = 0x21; |
- static const VERTICAL_BAR = 0x7C; |
- static const GREATER_THAN = 0x3E; |
- static const SINGLE_QUOTE = 0x27; |
- static const DOUBLE_QUOTE = 0x22; |
- static const PERCENT = 0x25; |
- static const AT = 0x40; |
- static const GRAVE_ACCENT = 0x60; |
- |
- static const NULL = 0x0; |
- static const BELL = 0x7; |
- static const BACKSPACE = 0x8; |
- static const VERTICAL_TAB = 0xB; |
- static const FORM_FEED = 0xC; |
- static const ESCAPE = 0x1B; |
- static const SLASH = 0x2F; |
- static const BACKSLASH = 0x5C; |
- static const UNDERSCORE = 0x5F; |
- static const NBSP = 0xA0; |
- static const LINE_SEPARATOR = 0x2028; |
- static const PARAGRAPH_SEPARATOR = 0x2029; |
- |
- static const NUMBER_0 = 0x30; |
- static const NUMBER_9 = 0x39; |
- |
- static const LETTER_A = 0x61; |
- static const LETTER_B = 0x62; |
- static const LETTER_E = 0x65; |
- static const LETTER_F = 0x66; |
- static const LETTER_N = 0x6E; |
- static const LETTER_R = 0x72; |
- static const LETTER_T = 0x74; |
- static const LETTER_U = 0x75; |
- static const LETTER_V = 0x76; |
- static const LETTER_X = 0x78; |
- |
- static const LETTER_CAP_A = 0x41; |
- static const LETTER_CAP_F = 0x46; |
- static const LETTER_CAP_L = 0x4C; |
- static const LETTER_CAP_N = 0x4E; |
- static const LETTER_CAP_P = 0x50; |
- static const LETTER_CAP_U = 0x55; |
- static const LETTER_CAP_X = 0x58; |
- |
- static const C_SEQUENCE_ENTRY = 4; |
- static const C_MAPPING_KEY = 5; |
- static const C_MAPPING_VALUE = 6; |
- static const C_COLLECT_ENTRY = 7; |
- static const C_SEQUENCE_START = 8; |
- static const C_SEQUENCE_END = 9; |
- static const C_MAPPING_START = 10; |
- static const C_MAPPING_END = 11; |
- static const C_COMMENT = 12; |
- static const C_ANCHOR = 13; |
- static const C_ALIAS = 14; |
- static const C_TAG = 15; |
- static const C_LITERAL = 16; |
- static const C_FOLDED = 17; |
- static const C_SINGLE_QUOTE = 18; |
- static const C_DOUBLE_QUOTE = 19; |
- static const C_DIRECTIVE = 20; |
- static const C_RESERVED = 21; |
- |
- static const BLOCK_OUT = 0; |
- static const BLOCK_IN = 1; |
- static const FLOW_OUT = 2; |
- static const FLOW_IN = 3; |
- static const BLOCK_KEY = 4; |
- static const FLOW_KEY = 5; |
- |
- static const CHOMPING_STRIP = 0; |
- static const CHOMPING_KEEP = 1; |
- static const CHOMPING_CLIP = 2; |
- |
- /// The scanner that's used to scan through the document. |
- final SpanScanner _scanner; |
- |
- /// Whether we're parsing a bare document (that is, one that doesn't begin |
- /// with `---`). Bare documents don't allow `%` immediately following |
- /// newlines. |
- bool _inBareDocument = false; |
- |
- /// The state of the scanner when it was the farthest in the document it's |
- /// been. |
- LineScannerState _farthestState; |
- |
- /// The name of the context of the farthest position that has been parsed |
- /// successfully before backtracking. Used for error reporting. |
- String _farthestContext = "document"; |
- |
- /// A stack of the names of parse contexts. Used for error reporting. |
- final _contextStack = <String>["document"]; |
- |
- /// Annotations attached to ranges of the source string that add extra |
- /// information to any errors that occur in the annotated range. |
- final _errorAnnotations = new _RangeMap<String>(); |
- |
- /// The buffer containing the string currently being captured. |
- StringBuffer _capturedString; |
- |
- /// The beginning of the current section of the captured string. |
- int _captureStart; |
- |
- /// Whether the current string capture is being overridden. |
- bool _capturingAs = false; |
- |
- Parser(String yaml, sourceUrl) |
- : _scanner = new SpanScanner(yaml, sourceUrl: sourceUrl) { |
- _farthestState = _scanner.state; |
- } |
+ /// The underlying [Scanner] that generates [Token]s. |
+ final Scanner _scanner; |
- /// Returns the character at the current position, then moves that position |
- /// forward one character. |
- int next() => _scanner.readChar(); |
+ /// The stack of parse states for nested contexts. |
+ final _states = new List<_State>(); |
- /// Returns the code unit at the current position, or the character [i] |
- /// characters after the current position. |
- int peek([int i = 0]) => _scanner.peekChar(i); |
+ /// The current parse state. |
+ var _state = _State.STREAM_START; |
- /// The truthiness operator. Returns `false` if [obj] is `null` or `false`, |
- /// `true` otherwise. |
- bool truth(obj) => obj != null && obj != false; |
- |
- /// Consumes the current character if it matches [matcher]. Returns the result |
- /// of [matcher]. |
- bool consume(bool matcher(int)) { |
- if (matcher(peek())) { |
- next(); |
- return true; |
- } |
- return false; |
- } |
+ /// The custom tag directives, by tag handle. |
+ final _tagDirectives = new Map<String, TagDirective>(); |
- /// Consumes the current character if it equals [char]. |
- bool consumeChar(int char) => consume((c) => c == char); |
+ /// Whether the parser has finished parsing. |
+ bool get isDone => _state == _State.END; |
- /// Calls [consumer] until it returns a falsey value. Returns a list of all |
- /// truthy return values of [consumer], or null if it didn't consume anything. |
+ /// Creates a parser that parses [source]. |
/// |
- /// Conceptually, repeats a production one or more times. |
- List oneOrMore(consumer()) { |
- var first = consumer(); |
- if (!truth(first)) return null; |
- var out = [first]; |
- while (true) { |
- var el = consumer(); |
- if (!truth(el)) return out; |
- out.add(el); |
- } |
- return null; // Unreachable. |
- } |
+ /// [sourceUrl] can be a String or a [Uri]. |
+ Parser(String source, {sourceUrl}) |
+ : _scanner = new Scanner(source, sourceUrl: sourceUrl); |
- /// Calls [consumer] until it returns a falsey value. Returns a list of all |
- /// truthy return values of [consumer], or the empty list if it didn't consume |
- /// anything. |
- /// |
- /// Conceptually, repeats a production any number of times. |
- List zeroOrMore(consumer()) { |
- var out = []; |
- var oldPos = _scanner.position; |
- while (true) { |
- var el = consumer(); |
- if (!truth(el) || oldPos == _scanner.position) return out; |
- oldPos = _scanner.position; |
- out.add(el); |
+ /// Consumes and returns the next event. |
+ Event parse() { |
+ try { |
+ if (isDone) throw new StateError("No more events."); |
+ var event = _stateMachine(); |
+ return event; |
+ } on StringScannerException catch (error) { |
+ throw new YamlException(error.message, error.span); |
} |
- return null; // Unreachable. |
} |
- /// Just calls [consumer] and returns its result. Used to make it explicit |
- /// that a production is intended to be optional. |
- zeroOrOne(consumer()) => consumer(); |
- |
- /// Calls each function in [consumers] until one returns a truthy value, then |
- /// returns that. |
- or(List<Function> consumers) { |
- for (var c in consumers) { |
- var res = c(); |
- if (truth(res)) return res; |
+ /// Dispatches parsing based on the current state. |
+ Event _stateMachine() { |
+ switch (_state) { |
+ case _State.STREAM_START: |
+ return _parseStreamStart(); |
+ case _State.DOCUMENT_START: |
+ return _parseDocumentStart(); |
+ case _State.DOCUMENT_CONTENT: |
+ return _parseDocumentContent(); |
+ case _State.DOCUMENT_END: |
+ return _parseDocumentEnd(); |
+ case _State.BLOCK_NODE: |
+ return _parseNode(block: true); |
+ case _State.BLOCK_NODE_OR_INDENTLESS_SEQUENCE: |
+ return _parseNode(block: true, indentlessSequence: true); |
+ case _State.FLOW_NODE: |
+ return _parseNode(); |
+ case _State.BLOCK_SEQUENCE_FIRST_ENTRY: |
+ // Scan past the `BLOCK-SEQUENCE-FIRST-ENTRY` token to the |
+ // `BLOCK-SEQUENCE-ENTRY` token. |
+ _scanner.scan(); |
+ return _parseBlockSequenceEntry(); |
+ case _State.BLOCK_SEQUENCE_ENTRY: |
+ return _parseBlockSequenceEntry(); |
+ case _State.INDENTLESS_SEQUENCE_ENTRY: |
+ return _parseIndentlessSequenceEntry(); |
+ case _State.BLOCK_MAPPING_FIRST_KEY: |
+ // Scan past the `BLOCK-MAPPING-FIRST-KEY` token to the |
+ // `BLOCK-MAPPING-KEY` token. |
+ _scanner.scan(); |
+ return _parseBlockMappingKey(); |
+ case _State.BLOCK_MAPPING_KEY: |
+ return _parseBlockMappingKey(); |
+ case _State.BLOCK_MAPPING_VALUE: |
+ return _parseBlockMappingValue(); |
+ case _State.FLOW_SEQUENCE_FIRST_ENTRY: |
+ return _parseFlowSequenceEntry(first: true); |
+ case _State.FLOW_SEQUENCE_ENTRY: |
+ return _parseFlowSequenceEntry(); |
+ case _State.FLOW_SEQUENCE_ENTRY_MAPPING_KEY: |
+ return _parseFlowSequenceEntryMappingKey(); |
+ case _State.FLOW_SEQUENCE_ENTRY_MAPPING_VALUE: |
+ return _parseFlowSequenceEntryMappingValue(); |
+ case _State.FLOW_SEQUENCE_ENTRY_MAPPING_END: |
+ return _parseFlowSequenceEntryMappingEnd(); |
+ case _State.FLOW_MAPPING_FIRST_KEY: |
+ return _parseFlowMappingKey(first: true); |
+ case _State.FLOW_MAPPING_KEY: |
+ return _parseFlowMappingKey(); |
+ case _State.FLOW_MAPPING_VALUE: |
+ return _parseFlowMappingValue(); |
+ case _State.FLOW_MAPPING_EMPTY_VALUE: |
+ return _parseFlowMappingValue(empty: true); |
+ default: |
+ throw "Unreachable"; |
} |
- return null; |
} |
- /// Calls [consumer] and returns its result, but rolls back the parser state |
- /// if [consumer] returns a falsey value. |
- transaction(consumer()) { |
- var oldState = _scanner.state; |
- var oldCaptureStart = _captureStart; |
- String capturedSoFar = _capturedString == null ? null : |
- _capturedString.toString(); |
- var res = consumer(); |
- _refreshFarthestState(); |
- if (truth(res)) return res; |
- |
- _scanner.state = oldState; |
- _captureStart = oldCaptureStart; |
- _capturedString = capturedSoFar == null ? null : |
- new StringBuffer(capturedSoFar); |
- return res; |
+ /// Parses the production: |
+ /// |
+ /// stream ::= |
+ /// STREAM-START implicit_document? explicit_document* STREAM-END |
+ /// ************ |
+ Event _parseStreamStart() { |
+ var token = _scanner.scan(); |
+ assert(token.type == TokenType.STREAM_START); |
+ |
+ _state = _State.DOCUMENT_START; |
+ return new Event(EventType.STREAM_START, token.span); |
} |
- /// Consumes [n] characters matching [matcher], or none if there isn't a |
- /// complete match. The first argument to [matcher] is the character code, the |
- /// second is the index (from 0 to [n] - 1). |
+ /// Parses the productions: |
/// |
- /// Returns whether or not the characters were consumed. |
- bool nAtOnce(int n, bool matcher(int c, int i)) => transaction(() { |
- for (int i = 0; i < n; i++) { |
- if (!consume((c) => matcher(c, i))) return false; |
+ /// implicit_document ::= block_node DOCUMENT-END* |
+ /// * |
+ /// explicit_document ::= |
+ /// DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* |
+ /// ************************* |
+ Event _parseDocumentStart() { |
+ var token = _scanner.peek(); |
+ |
+ // libyaml requires any document beyond the first in the stream to have an |
+ // explicit document start indicator, but the spec allows it to be omitted |
+ // as long as there was an end indicator. |
+ |
+ // Parse extra document end indicators. |
+ while (token.type == TokenType.DOCUMENT_END) { |
+ token = _scanner.advance(); |
} |
- return true; |
- }); |
- /// Consumes the exact characters in [str], or nothing. |
- /// |
- /// Returns whether or not the string was consumed. |
- bool rawString(String str) => |
- nAtOnce(str.length, (c, i) => str.codeUnitAt(i) == c); |
- |
- /// Consumes and returns a string of characters matching [matcher], or null if |
- /// there are no such characters. |
- String stringOf(bool matcher(int)) => |
- captureString(() => oneOrMore(() => consume(matcher))); |
- |
- /// Calls [consumer] and returns the string that was consumed while doing so, |
- /// or null if [consumer] returned a falsey value. Automatically wraps |
- /// [consumer] in `transaction`. |
- String captureString(consumer()) { |
- // captureString calls may not be nested |
- assert(_capturedString == null); |
- |
- _captureStart = _scanner.position; |
- _capturedString = new StringBuffer(); |
- var res = transaction(consumer); |
- if (!truth(res)) { |
- _captureStart = null; |
- _capturedString = null; |
- return null; |
+ if (token.type != TokenType.VERSION_DIRECTIVE && |
+ token.type != TokenType.TAG_DIRECTIVE && |
+ token.type != TokenType.DOCUMENT_START && |
+ token.type != TokenType.STREAM_END) { |
+ // Parse an implicit document. |
+ _processDirectives(); |
+ _states.add(_State.DOCUMENT_END); |
+ _state = _State.BLOCK_NODE; |
+ return new DocumentStartEvent(token.span.start.pointSpan()); |
} |
- flushCapture(); |
- var result = _capturedString.toString(); |
- _captureStart = null; |
- _capturedString = null; |
- return result; |
- } |
- |
- captureAs(String replacement, consumer()) => |
- captureAndTransform(consumer, (_) => replacement); |
- |
- captureAndTransform(consumer(), String transformation(String captured)) { |
- if (_capturedString == null) return consumer(); |
- if (_capturingAs) return consumer(); |
- |
- flushCapture(); |
- _capturingAs = true; |
- var res = consumer(); |
- _capturingAs = false; |
- if (!truth(res)) return res; |
- |
- _capturedString.write(transformation( |
- _scanner.string.substring(_captureStart, _scanner.position))); |
- _captureStart = _scanner.position; |
- return res; |
- } |
- |
- void flushCapture() { |
- _capturedString.write(_scanner.string.substring( |
- _captureStart, _scanner.position)); |
- _captureStart = _scanner.position; |
- } |
- |
- /// Adds a tag and an anchor to [node], if they're defined. |
- Node addProps(Node node, Pair<Tag, String> props) { |
- if (props == null || node == null) return node; |
- if (truth(props.first)) node.tag = props.first; |
- if (truth(props.last)) node.anchor = props.last; |
- return node; |
- } |
- |
- /// Creates a MappingNode from [pairs]. |
- MappingNode map(List<Pair<Node, Node>> pairs, SourceSpan span) { |
- var content = new Map<Node, Node>(); |
- pairs.forEach((pair) => content[pair.first] = pair.last); |
- return new MappingNode("?", content, span); |
- } |
- |
- /// Runs [fn] in a context named [name]. Used for error reporting. |
- context(String name, fn()) { |
- try { |
- _contextStack.add(name); |
- return fn(); |
- } finally { |
- var popped = _contextStack.removeLast(); |
- assert(popped == name); |
+ if (token.type == TokenType.STREAM_END) { |
+ _state = _State.END; |
+ _scanner.scan(); |
+ return new Event(EventType.STREAM_END, token.span); |
} |
- } |
- /// Adds [message] as extra information to any errors that occur between the |
- /// current position and the position of the cursor after running [fn]. The |
- /// cursor is reset after [fn] is run. |
- annotateError(String message, fn()) { |
- var start = _scanner.position; |
- var end; |
- transaction(() { |
- fn(); |
- end = _scanner.position; |
- return false; |
- }); |
- _errorAnnotations[new _Range(start, end)] = message; |
- } |
- |
- /// Throws an error with additional context information. |
- void error(String message) => |
- _scanner.error("$message (in $_farthestContext)."); |
+ // Parse an explicit document. |
+ var start = token.span; |
+ var pair = _processDirectives(); |
+ var versionDirective = pair.first; |
+ var tagDirectives = pair.last; |
+ token = _scanner.peek(); |
+ if (token.type != TokenType.DOCUMENT_START) { |
+ throw new YamlException("Expected document start.", token.span); |
+ } |
- /// If [result] is falsey, throws an error saying that [expected] was |
- /// expected. |
- expect(result, String expected) { |
- if (truth(result)) return result; |
- error("Expected $expected"); |
+ _states.add(_State.DOCUMENT_END); |
+ _state = _State.DOCUMENT_CONTENT; |
+ _scanner.scan(); |
+ return new DocumentStartEvent(start.expand(token.span), |
+ versionDirective: versionDirective, |
+ tagDirectives: tagDirectives, |
+ isImplicit: false); |
} |
- /// Throws an error saying that the parse failed. |
+ /// Parses the productions: |
/// |
- /// Uses [_farthestState] and [_farthestContext] to provide additional |
- /// information. |
- parseFailed() { |
- var message = "Invalid YAML in $_farthestContext"; |
- _refreshFarthestState(); |
- _scanner.state = _farthestState; |
- |
- var extraError = _errorAnnotations[_scanner.position]; |
- if (extraError != null) message = "$message ($extraError)"; |
- _scanner.error("$message."); |
- } |
- |
- /// Update [_farthestState] if the scanner is farther than it's been before. |
- void _refreshFarthestState() { |
- if (_scanner.position <= _farthestState.position) return; |
- _farthestState = _scanner.state; |
- } |
- |
- /// Returns the number of spaces after the current position. |
- int countIndentation() { |
- var i = 0; |
- while (peek(i) == SP) i++; |
- return i; |
- } |
- |
- /// Returns the indentation for a block scalar. |
- int blockScalarAdditionalIndentation(_BlockHeader header, int indent) { |
- if (!header.autoDetectIndent) return header.additionalIndent; |
- |
- var maxSpaces = 0; |
- var spaces = 0; |
- transaction(() { |
- do { |
- spaces = captureString(() => zeroOrMore(() => consumeChar(SP))).length; |
- if (spaces > maxSpaces) maxSpaces = spaces; |
- } while (b_break()); |
- return false; |
- }); |
- |
- // If the next non-empty line isn't indented further than the start of the |
- // block scalar, that means the scalar is going to be empty. Returning any |
- // value > 0 will cause the parser not to consume any text. |
- if (spaces <= indent) return 1; |
- |
- // It's an error for a leading empty line to be indented more than the first |
- // non-empty line. |
- if (maxSpaces > spaces) { |
- _scanner.error("Leading empty lines may not be indented more than the " |
- "first non-empty line."); |
+ /// explicit_document ::= |
+ /// DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* |
+ /// *********** |
+ Event _parseDocumentContent() { |
+ var token = _scanner.peek(); |
+ |
+ switch (token.type) { |
+ case TokenType.VERSION_DIRECTIVE: |
+ case TokenType.TAG_DIRECTIVE: |
+ case TokenType.DOCUMENT_START: |
+ case TokenType.DOCUMENT_END: |
+ case TokenType.STREAM_END: |
+ _state = _states.removeLast(); |
+ return _processEmptyScalar(token.span.start); |
+ default: |
+ return _parseNode(block: true); |
} |
- |
- return spaces - indent; |
} |
- /// Returns whether the current position is at the beginning of a line. |
- bool get atStartOfLine => _scanner.column == 0; |
- |
- /// Given an indicator character, returns the type of that indicator (or null |
- /// if the indicator isn't found. |
- int indicatorType(int char) { |
- switch (char) { |
- case HYPHEN: return C_SEQUENCE_ENTRY; |
- case QUESTION_MARK: return C_MAPPING_KEY; |
- case COLON: return C_MAPPING_VALUE; |
- case COMMA: return C_COLLECT_ENTRY; |
- case LEFT_BRACKET: return C_SEQUENCE_START; |
- case RIGHT_BRACKET: return C_SEQUENCE_END; |
- case LEFT_BRACE: return C_MAPPING_START; |
- case RIGHT_BRACE: return C_MAPPING_END; |
- case HASH: return C_COMMENT; |
- case AMPERSAND: return C_ANCHOR; |
- case ASTERISK: return C_ALIAS; |
- case EXCLAMATION: return C_TAG; |
- case VERTICAL_BAR: return C_LITERAL; |
- case GREATER_THAN: return C_FOLDED; |
- case SINGLE_QUOTE: return C_SINGLE_QUOTE; |
- case DOUBLE_QUOTE: return C_DOUBLE_QUOTE; |
- case PERCENT: return C_DIRECTIVE; |
- case AT: |
- case GRAVE_ACCENT: |
- return C_RESERVED; |
- default: return null; |
+ /// Parses the productions: |
+ /// |
+ /// implicit_document ::= block_node DOCUMENT-END* |
+ /// ************* |
+ /// explicit_document ::= |
+ /// DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* |
+ /// ************* |
+ Event _parseDocumentEnd() { |
+ _tagDirectives.clear(); |
+ _state = _State.DOCUMENT_START; |
+ |
+ var token = _scanner.peek(); |
+ if (token.type == TokenType.DOCUMENT_END) { |
+ _scanner.scan(); |
+ return new DocumentEndEvent(token.span, isImplicit: false); |
+ } else { |
+ return new DocumentEndEvent( |
+ token.span.start.pointSpan(), isImplicit: true); |
} |
} |
- // 1 |
- bool isPrintable(int char) { |
- if (char == null) return false; |
- return char == TAB || |
- char == LF || |
- char == CR || |
- (char >= SP && char <= TILDE) || |
- char == NEL || |
- (char >= 0xA0 && char <= 0xD7FF) || |
- (char >= 0xE000 && char <= 0xFFFD) || |
- (char >= 0x10000 && char <= 0x10FFFF); |
- } |
- |
- // 2 |
- bool isJson(int char) => char != null && |
- (char == TAB || (char >= SP && char <= 0x10FFFF)); |
- |
- // 22 |
- bool c_indicator(int type) => consume((c) => indicatorType(c) == type); |
- |
- // 23 |
- bool isFlowIndicator(int char) { |
- var indicator = indicatorType(char); |
- return indicator == C_COLLECT_ENTRY || |
- indicator == C_SEQUENCE_START || |
- indicator == C_SEQUENCE_END || |
- indicator == C_MAPPING_START || |
- indicator == C_MAPPING_END; |
- } |
- |
- // 26 |
- bool isBreak(int char) => char == LF || char == CR; |
- |
- // 27 |
- bool isNonBreak(int char) => isPrintable(char) && !isBreak(char); |
- |
- // 28 |
- bool b_break() { |
- if (consumeChar(CR)) { |
- zeroOrOne(() => consumeChar(LF)); |
- return true; |
+ /// Parses the productions: |
+ /// |
+ /// block_node_or_indentless_sequence ::= |
+ /// ALIAS |
+ /// ***** |
+ /// | properties (block_content | indentless_block_sequence)? |
+ /// ********** * |
+ /// | block_content | indentless_block_sequence |
+ /// * |
+ /// block_node ::= ALIAS |
+ /// ***** |
+ /// | properties block_content? |
+ /// ********** * |
+ /// | block_content |
+ /// * |
+ /// flow_node ::= ALIAS |
+ /// ***** |
+ /// | properties flow_content? |
+ /// ********** * |
+ /// | flow_content |
+ /// * |
+ /// properties ::= TAG ANCHOR? | ANCHOR TAG? |
+ /// ************************* |
+ /// block_content ::= block_collection | flow_collection | SCALAR |
+ /// ****** |
+ /// flow_content ::= flow_collection | SCALAR |
+ /// ****** |
+ Event _parseNode({bool block: false, bool indentlessSequence: false}) { |
+ var token = _scanner.peek(); |
+ |
+ if (token is AliasToken) { |
+ _scanner.scan(); |
+ _state = _states.removeLast(); |
+ return new AliasEvent(token.span, token.name); |
} |
- return consumeChar(LF); |
- } |
- |
- // 29 |
- bool b_asLineFeed() => captureAs("\n", () => b_break()); |
- |
- // 30 |
- bool b_nonContent() => captureAs("", () => b_break()); |
- |
- // 33 |
- bool isSpace(int char) => char == SP || char == TAB; |
- |
- // 34 |
- bool isNonSpace(int char) => isNonBreak(char) && !isSpace(char); |
- |
- // 35 |
- bool isDecDigit(int char) => char != null && char >= NUMBER_0 && |
- char <= NUMBER_9; |
- |
- // 36 |
- bool isHexDigit(int char) { |
- if (char == null) return false; |
- return isDecDigit(char) || |
- (char >= LETTER_A && char <= LETTER_F) || |
- (char >= LETTER_CAP_A && char <= LETTER_CAP_F); |
- } |
- |
- // 41 |
- bool c_escape() => captureAs("", () => consumeChar(BACKSLASH)); |
- |
- // 42 |
- bool ns_escNull() => captureAs("\x00", () => consumeChar(NUMBER_0)); |
- |
- // 43 |
- bool ns_escBell() => captureAs("\x07", () => consumeChar(LETTER_A)); |
- |
- // 44 |
- bool ns_escBackspace() => captureAs("\b", () => consumeChar(LETTER_B)); |
- |
- // 45 |
- bool ns_escHorizontalTab() => captureAs("\t", () { |
- return consume((c) => c == LETTER_T || c == TAB); |
- }); |
- |
- // 46 |
- bool ns_escLineFeed() => captureAs("\n", () => consumeChar(LETTER_N)); |
- |
- // 47 |
- bool ns_escVerticalTab() => captureAs("\v", () => consumeChar(LETTER_V)); |
- |
- // 48 |
- bool ns_escFormFeed() => captureAs("\f", () => consumeChar(LETTER_F)); |
- |
- // 49 |
- bool ns_escCarriageReturn() => captureAs("\r", () => consumeChar(LETTER_R)); |
- |
- // 50 |
- bool ns_escEscape() => captureAs("\x1B", () => consumeChar(LETTER_E)); |
- |
- // 51 |
- bool ns_escSpace() => consumeChar(SP); |
- |
- // 52 |
- bool ns_escDoubleQuote() => consumeChar(DOUBLE_QUOTE); |
- |
- // 53 |
- bool ns_escSlash() => consumeChar(SLASH); |
- |
- // 54 |
- bool ns_escBackslash() => consumeChar(BACKSLASH); |
- // 55 |
- bool ns_escNextLine() => captureAs("\x85", () => consumeChar(LETTER_CAP_N)); |
- |
- // 56 |
- bool ns_escNonBreakingSpace() => |
- captureAs("\xA0", () => consumeChar(UNDERSCORE)); |
- |
- // 57 |
- bool ns_escLineSeparator() => |
- captureAs("\u2028", () => consumeChar(LETTER_CAP_L)); |
- |
- // 58 |
- bool ns_escParagraphSeparator() => |
- captureAs("\u2029", () => consumeChar(LETTER_CAP_P)); |
- |
- // 59 |
- bool ns_esc8Bit() => ns_escNBit(LETTER_X, 2); |
- |
- // 60 |
- bool ns_esc16Bit() => ns_escNBit(LETTER_U, 4); |
- |
- // 61 |
- bool ns_esc32Bit() => ns_escNBit(LETTER_CAP_U, 8); |
+ var anchor; |
+ var tagToken; |
+ var span = token.span.start.pointSpan(); |
+ parseAnchor() { |
+ anchor = token.name; |
+ span = span.expand(token.span); |
+ token = _scanner.advance(); |
+ } |
- // Helper method for 59 - 61 |
- bool ns_escNBit(int char, int digits) { |
- if (!captureAs('', () => consumeChar(char))) return false; |
- var captured = captureAndTransform( |
- () => nAtOnce(digits, (c, _) => isHexDigit(c)), |
- (hex) => new String.fromCharCodes([int.parse("0x$hex")])); |
- return expect(captured, "$digits hexidecimal digits"); |
- } |
+ parseTag() { |
+ tagToken = token; |
+ span = span.expand(token.span); |
+ token = _scanner.advance(); |
+ } |
- // 62 |
- bool c_ns_escChar() => context('escape sequence', () => transaction(() { |
- if (!truth(c_escape())) return false; |
- return truth(or([ |
- ns_escNull, ns_escBell, ns_escBackspace, ns_escHorizontalTab, |
- ns_escLineFeed, ns_escVerticalTab, ns_escFormFeed, ns_escCarriageReturn, |
- ns_escEscape, ns_escSpace, ns_escDoubleQuote, ns_escSlash, |
- ns_escBackslash, ns_escNextLine, ns_escNonBreakingSpace, |
- ns_escLineSeparator, ns_escParagraphSeparator, ns_esc8Bit, ns_esc16Bit, |
- ns_esc32Bit |
- ])); |
- })); |
- |
- // 63 |
- bool s_indent(int indent) { |
- var result = nAtOnce(indent, (c, i) => c == SP); |
- if (peek() == TAB) { |
- annotateError("tab characters are not allowed as indentation in YAML", |
- () => zeroOrMore(() => consume(isSpace))); |
+ if (token is AnchorToken) { |
+ parseAnchor(); |
+ if (token is TagToken) parseTag(); |
+ } else if (token is TagToken) { |
+ parseTag(); |
+ if (token is AnchorToken) parseAnchor(); |
} |
- return result; |
- } |
- // 64 |
- bool s_indentLessThan(int indent) { |
- for (int i = 0; i < indent - 1; i++) { |
- if (!consumeChar(SP)) { |
- if (peek() == TAB) { |
- annotateError("tab characters are not allowed as indentation in YAML", |
- () { |
- for (; i < indent - 1; i++) { |
- if (!consume(isSpace)) break; |
- } |
- }); |
+ var tag; |
+ if (tagToken != null) { |
+ if (tagToken.handle == null) { |
+ tag = tagToken.suffix; |
+ } else { |
+ var tagDirective = _tagDirectives[tagToken.handle]; |
+ if (tagDirective == null) { |
+ throw new YamlException("Undefined tag handle.", tagToken.span); |
} |
- break; |
+ |
+ tag = tagDirective.prefix + tagToken.suffix; |
} |
} |
- return true; |
- } |
- // 65 |
- bool s_indentLessThanOrEqualTo(int indent) => s_indentLessThan(indent + 1); |
- |
- // 66 |
- bool s_separateInLine() => transaction(() { |
- return captureAs('', () => |
- truth(oneOrMore(() => consume(isSpace))) || atStartOfLine); |
- }); |
- |
- // 67 |
- bool s_linePrefix(int indent, int ctx) => captureAs("", () { |
- switch (ctx) { |
- case BLOCK_OUT: |
- case BLOCK_IN: |
- return s_blockLinePrefix(indent); |
- case FLOW_OUT: |
- case FLOW_IN: |
- return s_flowLinePrefix(indent); |
+ if (indentlessSequence && token.type == TokenType.BLOCK_ENTRY) { |
+ _state = _State.INDENTLESS_SEQUENCE_ENTRY; |
+ return new SequenceStartEvent( |
+ span.expand(token.span), CollectionStyle.BLOCK, |
+ anchor: anchor, tag: tag); |
} |
- }); |
- |
- // 68 |
- bool s_blockLinePrefix(int indent) => s_indent(indent); |
- |
- // 69 |
- bool s_flowLinePrefix(int indent) => captureAs('', () { |
- if (!truth(s_indent(indent))) return false; |
- zeroOrOne(s_separateInLine); |
- return true; |
- }); |
- |
- // 70 |
- bool l_empty(int indent, int ctx) => transaction(() { |
- var start = or([ |
- () => s_linePrefix(indent, ctx), |
- () => s_indentLessThan(indent) |
- ]); |
- if (!truth(start)) return false; |
- return b_asLineFeed(); |
- }); |
- |
- // 71 |
- bool b_asSpace() => captureAs(" ", () => consume(isBreak)); |
- |
- // 72 |
- bool b_l_trimmed(int indent, int ctx) => transaction(() { |
- if (!truth(b_nonContent())) return false; |
- return truth(oneOrMore(() => captureAs("\n", () => l_empty(indent, ctx)))); |
- }); |
- |
- // 73 |
- bool b_l_folded(int indent, int ctx) => |
- or([() => b_l_trimmed(indent, ctx), b_asSpace]); |
- |
- // 74 |
- bool s_flowFolded(int indent) => transaction(() { |
- zeroOrOne(s_separateInLine); |
- if (!truth(b_l_folded(indent, FLOW_IN))) return false; |
- return s_flowLinePrefix(indent); |
- }); |
- |
- // 75 |
- bool c_nb_commentText() { |
- if (!truth(c_indicator(C_COMMENT))) return false; |
- zeroOrMore(() => consume(isNonBreak)); |
- return true; |
- } |
- // 76 |
- bool b_comment() => _scanner.isDone || b_nonContent(); |
+ if (token is ScalarToken) { |
+ // All non-plain scalars have the "!" tag by default. |
+ if (tag == null && token.style != ScalarStyle.PLAIN) tag = "!"; |
- // 77 |
- bool s_b_comment() { |
- if (truth(s_separateInLine())) { |
- zeroOrOne(c_nb_commentText); |
+ _state = _states.removeLast(); |
+ _scanner.scan(); |
+ return new ScalarEvent( |
+ span.expand(token.span), token.value, token.style, |
+ anchor: anchor, tag: tag); |
} |
- return b_comment(); |
- } |
- |
- // 78 |
- bool l_comment() => transaction(() { |
- if (!truth(s_separateInLine())) return false; |
- zeroOrOne(c_nb_commentText); |
- return b_comment(); |
- }); |
- |
- // 79 |
- bool s_l_comments() { |
- if (!truth(s_b_comment()) && !atStartOfLine) return false; |
- zeroOrMore(l_comment); |
- return true; |
- } |
- // 80 |
- bool s_separate(int indent, int ctx) { |
- switch (ctx) { |
- case BLOCK_OUT: |
- case BLOCK_IN: |
- case FLOW_OUT: |
- case FLOW_IN: |
- return s_separateLines(indent); |
- case BLOCK_KEY: |
- case FLOW_KEY: |
- return s_separateInLine(); |
- default: throw 'Invalid context "$ctx".'; |
+ if (token.type == TokenType.FLOW_SEQUENCE_START) { |
+ _state = _State.FLOW_SEQUENCE_FIRST_ENTRY; |
+ return new SequenceStartEvent( |
+ span.expand(token.span), CollectionStyle.FLOW, |
+ anchor: anchor, tag: tag); |
} |
- } |
- |
- // 81 |
- bool s_separateLines(int indent) { |
- return transaction(() => s_l_comments() && s_flowLinePrefix(indent)) || |
- s_separateInLine(); |
- } |
- // 82 |
- bool l_directive() => false; // TODO(nweiz): implement |
- |
- // 96 |
- Pair<Tag, String> c_ns_properties(int indent, int ctx) { |
- var tag, anchor; |
- tag = c_ns_tagProperty(); |
- if (truth(tag)) { |
- anchor = transaction(() { |
- if (!truth(s_separate(indent, ctx))) return null; |
- return c_ns_anchorProperty(); |
- }); |
- return new Pair<Tag, String>(tag, anchor); |
+ if (token.type == TokenType.FLOW_MAPPING_START) { |
+ _state = _State.FLOW_MAPPING_FIRST_KEY; |
+ return new MappingStartEvent( |
+ span.expand(token.span), CollectionStyle.FLOW, |
+ anchor: anchor, tag: tag); |
} |
- anchor = c_ns_anchorProperty(); |
- if (truth(anchor)) { |
- tag = transaction(() { |
- if (!truth(s_separate(indent, ctx))) return null; |
- return c_ns_tagProperty(); |
- }); |
- return new Pair<Tag, String>(tag, anchor); |
+ if (block && token.type == TokenType.BLOCK_SEQUENCE_START) { |
+ _state = _State.BLOCK_SEQUENCE_FIRST_ENTRY; |
+ return new SequenceStartEvent( |
+ span.expand(token.span), CollectionStyle.BLOCK, |
+ anchor: anchor, tag: tag); |
} |
- return null; |
- } |
- |
- // 97 |
- Tag c_ns_tagProperty() => null; // TODO(nweiz): implement |
- // 101 |
- String c_ns_anchorProperty() => null; // TODO(nweiz): implement |
- |
- // 102 |
- bool isAnchorChar(int char) => isNonSpace(char) && !isFlowIndicator(char); |
+ if (block && token.type == TokenType.BLOCK_MAPPING_START) { |
+ _state = _State.BLOCK_MAPPING_FIRST_KEY; |
+ return new MappingStartEvent( |
+ span.expand(token.span), CollectionStyle.BLOCK, |
+ anchor: anchor, tag: tag); |
+ } |
- // 103 |
- String ns_anchorName() => |
- captureString(() => oneOrMore(() => consume(isAnchorChar))); |
+ if (anchor != null || tag != null) { |
+ _state = _states.removeLast(); |
+ return new ScalarEvent( |
+ span, '', ScalarStyle.PLAIN, |
+ anchor: anchor, tag: tag); |
+ } |
- // 104 |
- Node c_ns_aliasNode() { |
- var start = _scanner.state; |
- if (!truth(c_indicator(C_ALIAS))) return null; |
- var name = expect(ns_anchorName(), 'anchor name'); |
- return new AliasNode(name, _scanner.spanFrom(start)); |
+ throw new YamlException("Expected node content.", span); |
} |
- // 105 |
- ScalarNode e_scalar() => new ScalarNode("?", _scanner.emptySpan, content: ""); |
- |
- // 106 |
- ScalarNode e_node() => e_scalar(); |
- |
- // 107 |
- bool nb_doubleChar() => or([ |
- c_ns_escChar, |
- () => consume((c) => isJson(c) && c != BACKSLASH && c != DOUBLE_QUOTE) |
- ]); |
- |
- // 108 |
- bool ns_doubleChar() => !isSpace(peek()) && truth(nb_doubleChar()); |
- |
- // 109 |
- Node c_doubleQuoted(int indent, int ctx) => context('string', () { |
- return transaction(() { |
- var start = _scanner.state; |
- if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null; |
- var contents = nb_doubleText(indent, ctx); |
- if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null; |
- return new ScalarNode("!", _scanner.spanFrom(start), content: contents); |
- }); |
- }); |
- |
- // 110 |
- String nb_doubleText(int indent, int ctx) => captureString(() { |
- switch (ctx) { |
- case FLOW_OUT: |
- case FLOW_IN: |
- nb_doubleMultiLine(indent); |
- break; |
- case BLOCK_KEY: |
- case FLOW_KEY: |
- nb_doubleOneLine(); |
- break; |
+ /// Parses the productions: |
+ /// |
+ /// block_sequence ::= |
+ /// BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END |
+ /// ******************** *********** * ********* |
+ Event _parseBlockSequenceEntry() { |
+ var token = _scanner.peek(); |
+ |
+ if (token.type == TokenType.BLOCK_ENTRY) { |
+ token = _scanner.advance(); |
+ |
+ if (token.type == TokenType.BLOCK_ENTRY || |
+ token.type == TokenType.BLOCK_END) { |
+ _state = _State.BLOCK_SEQUENCE_ENTRY; |
+ return _processEmptyScalar(token.span.end); |
+ } else { |
+ _states.add(_State.BLOCK_SEQUENCE_ENTRY); |
+ return _parseNode(block: true); |
+ } |
} |
- return true; |
- }); |
- // 111 |
- void nb_doubleOneLine() { |
- zeroOrMore(nb_doubleChar); |
- } |
+ if (token.type == TokenType.BLOCK_END) { |
+ _scanner.scan(); |
+ _state = _states.removeLast(); |
+ return new Event(EventType.SEQUENCE_END, token.span); |
+ } |
- // 112 |
- bool s_doubleEscaped(int indent) => transaction(() { |
- zeroOrMore(() => consume(isSpace)); |
- if (!captureAs("", () => consumeChar(BACKSLASH))) return false; |
- if (!truth(b_nonContent())) return false; |
- zeroOrMore(() => captureAs("\n", () => l_empty(indent, FLOW_IN))); |
- return s_flowLinePrefix(indent); |
- }); |
- |
- // 113 |
- bool s_doubleBreak(int indent) => or([ |
- () => s_doubleEscaped(indent), |
- () => s_flowFolded(indent) |
- ]); |
- |
- // 114 |
- void nb_ns_doubleInLine() { |
- zeroOrMore(() => transaction(() { |
- zeroOrMore(() => consume(isSpace)); |
- return ns_doubleChar(); |
- })); |
+ throw new YamlException("While parsing a block collection, expected '-'.", |
+ token.span.start.pointSpan()); |
} |
- // 115 |
- bool s_doubleNextLine(int indent) { |
- if (!truth(s_doubleBreak(indent))) return false; |
- zeroOrOne(() { |
- if (!truth(ns_doubleChar())) return; |
- nb_ns_doubleInLine(); |
- or([ |
- () => s_doubleNextLine(indent), |
- () => zeroOrMore(() => consume(isSpace)) |
- ]); |
- }); |
- return true; |
- } |
+ /// Parses the productions: |
+ /// |
+ /// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ |
+ /// *********** * |
+ Event _parseIndentlessSequenceEntry() { |
+ var token = _scanner.peek(); |
+ |
+ if (token.type != TokenType.BLOCK_ENTRY) { |
+ _state = _states.removeLast(); |
+ return new Event(EventType.SEQUENCE_END, token.span.start.pointSpan()); |
+ } |
- // 116 |
- void nb_doubleMultiLine(int indent) { |
- nb_ns_doubleInLine(); |
- or([ |
- () => s_doubleNextLine(indent), |
- () => zeroOrMore(() => consume(isSpace)) |
- ]); |
+ var start = token.span.start; |
+ token = _scanner.advance(); |
+ |
+ if (token.type == TokenType.BLOCK_ENTRY || |
+ token.type == TokenType.KEY || |
+ token.type == TokenType.VALUE || |
+ token.type == TokenType.BLOCK_END) { |
+ _state = _State.INDENTLESS_SEQUENCE_ENTRY; |
+ return _processEmptyScalar(start); |
+ } else { |
+ _states.add(_State.INDENTLESS_SEQUENCE_ENTRY); |
+ return _parseNode(block: true); |
+ } |
} |
- // 117 |
- bool c_quotedQuote() => captureAs("'", () => rawString("''")); |
- |
- // 118 |
- bool nb_singleChar() => or([ |
- c_quotedQuote, |
- () => consume((c) => isJson(c) && c != SINGLE_QUOTE) |
- ]); |
- |
- // 119 |
- bool ns_singleChar() => !isSpace(peek()) && truth(nb_singleChar()); |
- |
- // 120 |
- Node c_singleQuoted(int indent, int ctx) => context('string', () { |
- return transaction(() { |
- var start = _scanner.state; |
- if (!truth(c_indicator(C_SINGLE_QUOTE))) return null; |
- var contents = nb_singleText(indent, ctx); |
- if (!truth(c_indicator(C_SINGLE_QUOTE))) return null; |
- return new ScalarNode("!", _scanner.spanFrom(start), content: contents); |
- }); |
- }); |
- |
- // 121 |
- String nb_singleText(int indent, int ctx) => captureString(() { |
- switch (ctx) { |
- case FLOW_OUT: |
- case FLOW_IN: |
- nb_singleMultiLine(indent); |
- break; |
- case BLOCK_KEY: |
- case FLOW_KEY: |
- nb_singleOneLine(indent); |
- break; |
+ /// Parses the productions: |
+ /// |
+ /// block_mapping ::= BLOCK-MAPPING_START |
+ /// ******************* |
+ /// ((KEY block_node_or_indentless_sequence?)? |
+ /// *** * |
+ /// (VALUE block_node_or_indentless_sequence?)?)* |
+ /// |
+ /// BLOCK-END |
+ /// ********* |
+ Event _parseBlockMappingKey() { |
+ var token = _scanner.peek(); |
+ if (token.type == TokenType.KEY) { |
+ var start = token.span.start; |
+ token = _scanner.advance(); |
+ |
+ if (token.type == TokenType.KEY || |
+ token.type == TokenType.VALUE || |
+ token.type == TokenType.BLOCK_END) { |
+ _state = _State.BLOCK_MAPPING_VALUE; |
+ return _processEmptyScalar(start); |
+ } else { |
+ _states.add(_State.BLOCK_MAPPING_VALUE); |
+ return _parseNode(block: true, indentlessSequence: true); |
+ } |
} |
- return true; |
- }); |
- // 122 |
- void nb_singleOneLine(int indent) { |
- zeroOrMore(nb_singleChar); |
- } |
+ // libyaml doesn't allow empty keys without an explicit key indicator, but |
+ // the spec does. See example 8.18: |
+ // http://yaml.org/spec/1.2/spec.html#id2798896. |
+ if (token.type == TokenType.VALUE) { |
+ _state = _State.BLOCK_MAPPING_VALUE; |
+ return _processEmptyScalar(token.span.start); |
+ } |
- // 123 |
- void nb_ns_singleInLine() { |
- zeroOrMore(() => transaction(() { |
- zeroOrMore(() => consume(isSpace)); |
- return ns_singleChar(); |
- })); |
- } |
+ if (token.type == TokenType.BLOCK_END) { |
+ _scanner.scan(); |
+ _state = _states.removeLast(); |
+ return new Event(EventType.MAPPING_END, token.span); |
+ } |
- // 124 |
- bool s_singleNextLine(int indent) { |
- if (!truth(s_flowFolded(indent))) return false; |
- zeroOrOne(() { |
- if (!truth(ns_singleChar())) return; |
- nb_ns_singleInLine(); |
- or([ |
- () => s_singleNextLine(indent), |
- () => zeroOrMore(() => consume(isSpace)) |
- ]); |
- }); |
- return true; |
+ throw new YamlException("Expected a key while parsing a block mapping.", |
+ token.span.start.pointSpan()); |
} |
- // 125 |
- void nb_singleMultiLine(int indent) { |
- nb_ns_singleInLine(); |
- or([ |
- () => s_singleNextLine(indent), |
- () => zeroOrMore(() => consume(isSpace)) |
- ]); |
- } |
+ /// Parses the productions: |
+ /// |
+ /// block_mapping ::= BLOCK-MAPPING_START |
+ /// |
+ /// ((KEY block_node_or_indentless_sequence?)? |
+ /// |
+ /// (VALUE block_node_or_indentless_sequence?)?)* |
+ /// ***** * |
+ /// BLOCK-END |
+ /// |
+ Event _parseBlockMappingValue() { |
+ var token = _scanner.peek(); |
- // 126 |
- bool ns_plainFirst(int ctx) { |
- var char = peek(); |
- var indicator = indicatorType(char); |
- if (indicator == C_RESERVED) { |
- error("Reserved indicators can't start a plain scalar"); |
+ if (token.type != TokenType.VALUE) { |
+ _state = _State.BLOCK_MAPPING_KEY; |
+ return _processEmptyScalar(token.span.start); |
} |
- var match = (isNonSpace(char) && indicator == null) || |
- ((indicator == C_MAPPING_KEY || |
- indicator == C_MAPPING_VALUE || |
- indicator == C_SEQUENCE_ENTRY) && |
- isPlainSafe(ctx, peek(1))); |
- |
- if (match) next(); |
- return match; |
- } |
- // 127 |
- bool isPlainSafe(int ctx, int char) { |
- switch (ctx) { |
- case FLOW_OUT: |
- case BLOCK_KEY: |
- // 128 |
- return isNonSpace(char); |
- case FLOW_IN: |
- case FLOW_KEY: |
- // 129 |
- return isNonSpace(char) && !isFlowIndicator(char); |
- default: throw 'Invalid context "$ctx".'; |
+ var start = token.span.start; |
+ token = _scanner.advance(); |
+ if (token.type == TokenType.KEY || |
+ token.type == TokenType.VALUE || |
+ token.type == TokenType.BLOCK_END) { |
+ _state = _State.BLOCK_MAPPING_KEY; |
+ return _processEmptyScalar(start); |
+ } else { |
+ _states.add(_State.BLOCK_MAPPING_KEY); |
+ return _parseNode(block: true, indentlessSequence: true); |
} |
} |
- // 130 |
- bool ns_plainChar(int ctx) { |
- var char = peek(); |
- var indicator = indicatorType(char); |
- var safeChar = isPlainSafe(ctx, char) && indicator != C_MAPPING_VALUE && |
- indicator != C_COMMENT; |
- var nonCommentHash = isNonSpace(peek(-1)) && indicator == C_COMMENT; |
- var nonMappingColon = indicator == C_MAPPING_VALUE && |
- isPlainSafe(ctx, peek(1)); |
- var match = safeChar || nonCommentHash || nonMappingColon; |
- |
- if (match) next(); |
- return match; |
- } |
+ /// Parses the productions: |
+ /// |
+ /// flow_sequence ::= FLOW-SEQUENCE-START |
+ /// ******************* |
+ /// (flow_sequence_entry FLOW-ENTRY)* |
+ /// * ********** |
+ /// flow_sequence_entry? |
+ /// * |
+ /// FLOW-SEQUENCE-END |
+ /// ***************** |
+ /// flow_sequence_entry ::= |
+ /// flow_node | KEY flow_node? (VALUE flow_node?)? |
+ /// * |
+ Event _parseFlowSequenceEntry({bool first: false}) { |
+ if (first) _scanner.scan(); |
+ var token = _scanner.peek(); |
+ |
+ if (token.type != TokenType.FLOW_SEQUENCE_END) { |
+ if (!first) { |
+ if (token.type != TokenType.FLOW_ENTRY) { |
+ throw new YamlException( |
+ "While parsing a flow sequence, expected ',' or ']'.", |
+ token.span.start.pointSpan()); |
+ } |
- // 131 |
- String ns_plain(int indent, int ctx) => context('plain scalar', () { |
- return captureString(() { |
- switch (ctx) { |
- case FLOW_OUT: |
- case FLOW_IN: |
- return ns_plainMultiLine(indent, ctx); |
- case BLOCK_KEY: |
- case FLOW_KEY: |
- return ns_plainOneLine(ctx); |
- default: throw 'Invalid context "$ctx".'; |
+ token = _scanner.advance(); |
} |
- }); |
- }); |
- |
- // 132 |
- void nb_ns_plainInLine(int ctx) { |
- zeroOrMore(() => transaction(() { |
- zeroOrMore(() => consume(isSpace)); |
- return ns_plainChar(ctx); |
- })); |
- } |
- // 133 |
- bool ns_plainOneLine(int ctx) { |
- if (truth(c_forbidden())) return false; |
- if (!truth(ns_plainFirst(ctx))) return false; |
- nb_ns_plainInLine(ctx); |
- return true; |
- } |
+ if (token.type == TokenType.KEY) { |
+ _state = _State.FLOW_SEQUENCE_ENTRY_MAPPING_KEY; |
+ _scanner.scan(); |
+ return new MappingStartEvent( |
+ token.span, CollectionStyle.FLOW); |
+ } else if (token.type != TokenType.FLOW_SEQUENCE_END) { |
+ _states.add(_State.FLOW_SEQUENCE_ENTRY); |
+ return _parseNode(); |
+ } |
+ } |
- // 134 |
- bool s_ns_plainNextLine(int indent, int ctx) => transaction(() { |
- if (!truth(s_flowFolded(indent))) return false; |
- if (truth(c_forbidden())) return false; |
- if (!truth(ns_plainChar(ctx))) return false; |
- nb_ns_plainInLine(ctx); |
- return true; |
- }); |
- |
- // 135 |
- bool ns_plainMultiLine(int indent, int ctx) { |
- if (!truth(ns_plainOneLine(ctx))) return false; |
- zeroOrMore(() => s_ns_plainNextLine(indent, ctx)); |
- return true; |
- } |
+ _scanner.scan(); |
+ _state = _states.removeLast(); |
+ return new Event(EventType.SEQUENCE_END, token.span); |
+ } |
- // 136 |
- int inFlow(int ctx) { |
- switch (ctx) { |
- case FLOW_OUT: |
- case FLOW_IN: |
- return FLOW_IN; |
- case BLOCK_KEY: |
- case FLOW_KEY: |
- return FLOW_KEY; |
+ /// Parses the productions: |
+ /// |
+ /// flow_sequence_entry ::= |
+ /// flow_node | KEY flow_node? (VALUE flow_node?)? |
+ /// *** * |
+ Event _parseFlowSequenceEntryMappingKey() { |
+ var token = _scanner.peek(); |
+ |
+ if (token.type == TokenType.VALUE || |
+ token.type == TokenType.FLOW_ENTRY || |
+ token.type == TokenType.FLOW_SEQUENCE_END) { |
+ // libyaml consumes the token here, but that seems like a bug, since it |
+ // always causes [_parseFlowSequenceEntryMappingValue] to emit an empty |
+ // scalar. |
+ |
+ var start = token.span.start; |
+ _state = _State.FLOW_SEQUENCE_ENTRY_MAPPING_VALUE; |
+ return _processEmptyScalar(start); |
+ } else { |
+ _states.add(_State.FLOW_SEQUENCE_ENTRY_MAPPING_VALUE); |
+ return _parseNode(); |
} |
- throw "unreachable"; |
} |
- // 137 |
- SequenceNode c_flowSequence(int indent, int ctx) => transaction(() { |
- var start = _scanner.state; |
- if (!truth(c_indicator(C_SEQUENCE_START))) return null; |
- zeroOrOne(() => s_separate(indent, ctx)); |
- var content = zeroOrOne(() => ns_s_flowSeqEntries(indent, inFlow(ctx))); |
- if (!truth(c_indicator(C_SEQUENCE_END))) return null; |
- return new SequenceNode("?", new List<Node>.from(content), |
- _scanner.spanFrom(start)); |
- }); |
- |
- // 138 |
- Iterable<Node> ns_s_flowSeqEntries(int indent, int ctx) { |
- var first = ns_flowSeqEntry(indent, ctx); |
- if (!truth(first)) return new Queue<Node>(); |
- zeroOrOne(() => s_separate(indent, ctx)); |
- |
- var rest; |
- if (truth(c_indicator(C_COLLECT_ENTRY))) { |
- zeroOrOne(() => s_separate(indent, ctx)); |
- rest = zeroOrOne(() => ns_s_flowSeqEntries(indent, ctx)); |
+ /// Parses the productions: |
+ /// |
+ /// flow_sequence_entry ::= |
+ /// flow_node | KEY flow_node? (VALUE flow_node?)? |
+ /// ***** * |
+ Event _parseFlowSequenceEntryMappingValue() { |
+ var token = _scanner.peek(); |
+ |
+ if (token.type == TokenType.VALUE) { |
+ token = _scanner.advance(); |
+ if (token.type != TokenType.FLOW_ENTRY && |
+ token.type != TokenType.FLOW_SEQUENCE_END) { |
+ _states.add(_State.FLOW_SEQUENCE_ENTRY_MAPPING_END); |
+ return _parseNode(); |
+ } |
} |
- if (rest == null) rest = new Queue<Node>(); |
- rest.addFirst(first); |
- |
- return rest; |
+ _state = _State.FLOW_SEQUENCE_ENTRY_MAPPING_END; |
+ return _processEmptyScalar(token.span.start); |
} |
- // 139 |
- Node ns_flowSeqEntry(int indent, int ctx) => or([ |
- () => ns_flowPair(indent, ctx), |
- () => ns_flowNode(indent, ctx) |
- ]); |
- |
- // 140 |
- Node c_flowMapping(int indent, int ctx) { |
- var start = _scanner.state; |
- if (!truth(c_indicator(C_MAPPING_START))) return null; |
- zeroOrOne(() => s_separate(indent, ctx)); |
- var content = zeroOrOne(() => ns_s_flowMapEntries(indent, inFlow(ctx))); |
- if (!truth(c_indicator(C_MAPPING_END))) return null; |
- return new MappingNode("?", content, _scanner.spanFrom(start)); |
+ /// Parses the productions: |
+ /// |
+ /// flow_sequence_entry ::= |
+ /// flow_node | KEY flow_node? (VALUE flow_node?)? |
+ /// * |
+ Event _parseFlowSequenceEntryMappingEnd() { |
+ _state = _State.FLOW_SEQUENCE_ENTRY; |
+ return new Event(EventType.MAPPING_END, |
+ _scanner.peek().span.start.pointSpan()); |
} |
- // 141 |
- Map ns_s_flowMapEntries(int indent, int ctx) { |
- var first = ns_flowMapEntry(indent, ctx); |
- if (!truth(first)) return deepEqualsMap(); |
- zeroOrOne(() => s_separate(indent, ctx)); |
+ /// Parses the productions: |
+ /// |
+ /// flow_mapping ::= FLOW-MAPPING-START |
+ /// ****************** |
+ /// (flow_mapping_entry FLOW-ENTRY)* |
+ /// * ********** |
+ /// flow_mapping_entry? |
+ /// ****************** |
+ /// FLOW-MAPPING-END |
+ /// **************** |
+ /// flow_mapping_entry ::= |
+ /// flow_node | KEY flow_node? (VALUE flow_node?)? |
+ /// * *** * |
+ Event _parseFlowMappingKey({bool first: false}) { |
+ if (first) _scanner.scan(); |
+ var token = _scanner.peek(); |
+ |
+ if (token.type != TokenType.FLOW_MAPPING_END) { |
+ if (!first) { |
+ if (token.type != TokenType.FLOW_ENTRY) { |
+ throw new YamlException( |
+ "While parsing a flow mapping, expected ',' or '}'.", |
+ token.span.start.pointSpan()); |
+ } |
+ |
+ token = _scanner.advance(); |
+ } |
- var rest; |
- if (truth(c_indicator(C_COLLECT_ENTRY))) { |
- zeroOrOne(() => s_separate(indent, ctx)); |
- rest = ns_s_flowMapEntries(indent, ctx); |
+ if (token.type == TokenType.KEY) { |
+ token = _scanner.advance(); |
+ if (token.type != TokenType.VALUE && |
+ token.type != TokenType.FLOW_ENTRY && |
+ token.type != TokenType.FLOW_MAPPING_END) { |
+ _states.add(_State.FLOW_MAPPING_VALUE); |
+ return _parseNode(); |
+ } else { |
+ _state = _State.FLOW_MAPPING_VALUE; |
+ return _processEmptyScalar(token.span.start); |
+ } |
+ } else if (token.type != TokenType.FLOW_MAPPING_END) { |
+ _states.add(_State.FLOW_MAPPING_EMPTY_VALUE); |
+ return _parseNode(); |
+ } |
} |
- if (rest == null) rest = deepEqualsMap(); |
+ _scanner.scan(); |
+ _state = _states.removeLast(); |
+ return new Event(EventType.MAPPING_END, token.span); |
+ } |
- // TODO(nweiz): Duplicate keys should be an error. This includes keys with |
- // different representations but the same value (e.g. 10 vs 0xa). To make |
- // this user-friendly we'll probably also want to associate nodes with a |
- // source range. |
- if (!rest.containsKey(first.first)) rest[first.first] = first.last; |
+ /// Parses the productions: |
+ /// |
+ /// flow_mapping_entry ::= |
+ /// flow_node | KEY flow_node? (VALUE flow_node?)? |
+ /// * ***** * |
+ Event _parseFlowMappingValue({bool empty: false}) { |
+ var token = _scanner.peek(); |
+ |
+ if (empty) { |
+ _state = _State.FLOW_MAPPING_KEY; |
+ return _processEmptyScalar(token.span.start); |
+ } |
- return rest; |
- } |
+ if (token.type == TokenType.VALUE) { |
+ token = _scanner.advance(); |
+ if (token.type != TokenType.FLOW_ENTRY && |
+ token.type != TokenType.FLOW_MAPPING_END) { |
+ _states.add(_State.FLOW_MAPPING_KEY); |
+ return _parseNode(); |
+ } |
+ } |
- // 142 |
- Pair<Node, Node> ns_flowMapEntry(int indent, int ctx) => or([ |
- () => transaction(() { |
- if (!truth(c_indicator(C_MAPPING_KEY))) return false; |
- if (!truth(s_separate(indent, ctx))) return false; |
- return ns_flowMapExplicitEntry(indent, ctx); |
- }), |
- () => ns_flowMapImplicitEntry(indent, ctx) |
- ]); |
- |
- // 143 |
- Pair<Node, Node> ns_flowMapExplicitEntry(int indent, int ctx) => or([ |
- () => ns_flowMapImplicitEntry(indent, ctx), |
- () => new Pair<Node, Node>(e_node(), e_node()) |
- ]); |
- |
- // 144 |
- Pair<Node, Node> ns_flowMapImplicitEntry(int indent, int ctx) => or([ |
- () => ns_flowMapYamlKeyEntry(indent, ctx), |
- () => c_ns_flowMapEmptyKeyEntry(indent, ctx), |
- () => c_ns_flowMapJsonKeyEntry(indent, ctx) |
- ]); |
- |
- // 145 |
- Pair<Node, Node> ns_flowMapYamlKeyEntry(int indent, int ctx) { |
- var key = ns_flowYamlNode(indent, ctx); |
- if (!truth(key)) return null; |
- var value = or([ |
- () => transaction(() { |
- zeroOrOne(() => s_separate(indent, ctx)); |
- return c_ns_flowMapSeparateValue(indent, ctx); |
- }), |
- e_node |
- ]); |
- return new Pair<Node, Node>(key, value); |
+ _state = _State.FLOW_MAPPING_KEY; |
+ return _processEmptyScalar(token.span.start); |
} |
- // 146 |
- Pair<Node, Node> c_ns_flowMapEmptyKeyEntry(int indent, int ctx) { |
- var value = c_ns_flowMapSeparateValue(indent, ctx); |
- if (!truth(value)) return null; |
- return new Pair<Node, Node>(e_node(), value); |
- } |
+ /// Generate an empty scalar event. |
+ Event _processEmptyScalar(SourceLocation location) => |
+ new ScalarEvent(location.pointSpan(), '', ScalarStyle.PLAIN); |
- // 147 |
- Node c_ns_flowMapSeparateValue(int indent, int ctx) => transaction(() { |
- if (!truth(c_indicator(C_MAPPING_VALUE))) return null; |
- if (isPlainSafe(ctx, peek())) return null; |
- |
- return or([ |
- () => transaction(() { |
- if (!s_separate(indent, ctx)) return null; |
- return ns_flowNode(indent, ctx); |
- }), |
- e_node |
- ]); |
- }); |
- |
- // 148 |
- Pair<Node, Node> c_ns_flowMapJsonKeyEntry(int indent, int ctx) { |
- var key = c_flowJsonNode(indent, ctx); |
- if (!truth(key)) return null; |
- var value = or([ |
- () => transaction(() { |
- zeroOrOne(() => s_separate(indent, ctx)); |
- return c_ns_flowMapAdjacentValue(indent, ctx); |
- }), |
- e_node |
- ]); |
- return new Pair<Node, Node>(key, value); |
- } |
+ /// Parses directives. |
+ Pair<VersionDirective, List<TagDirective>> _processDirectives() { |
+ var token = _scanner.peek(); |
- // 149 |
- Node c_ns_flowMapAdjacentValue(int indent, int ctx) { |
- if (!truth(c_indicator(C_MAPPING_VALUE))) return null; |
- return or([ |
- () => transaction(() { |
- zeroOrOne(() => s_separate(indent, ctx)); |
- return ns_flowNode(indent, ctx); |
- }), |
- e_node |
- ]); |
- } |
+ var versionDirective; |
+ var tagDirectives = []; |
+ var reservedDirectives = []; |
+ while (token.type == TokenType.VERSION_DIRECTIVE || |
+ token.type == TokenType.TAG_DIRECTIVE) { |
+ if (token is VersionDirectiveToken) { |
+ if (versionDirective != null) { |
+ throw new YamlException("Duplicate %YAML directive.", token.span); |
+ } |
- // 150 |
- Node ns_flowPair(int indent, int ctx) { |
- var start = _scanner.state; |
- var pair = or([ |
- () => transaction(() { |
- if (!truth(c_indicator(C_MAPPING_KEY))) return null; |
- if (!truth(s_separate(indent, ctx))) return null; |
- return ns_flowMapExplicitEntry(indent, ctx); |
- }), |
- () => ns_flowPairEntry(indent, ctx) |
- ]); |
- if (!truth(pair)) return null; |
- |
- return map([pair], _scanner.spanFrom(start)); |
- } |
+ if (token.major != 1 || token.minor == 0) { |
+ throw new YamlException( |
+ "Incompatible YAML document. This parser only supports YAML 1.1 " |
+ "and 1.2.", |
+ token.span); |
+ } else if (token.minor > 2) { |
+ // TODO(nweiz): Print to stderr when issue 6943 is fixed and dart:io |
+ // is available. |
+ warn("Warning: this parser only supports YAML 1.1 and 1.2.", |
+ token.span); |
+ } |
- // 151 |
- Pair<Node, Node> ns_flowPairEntry(int indent, int ctx) => or([ |
- () => ns_flowPairYamlKeyEntry(indent, ctx), |
- () => c_ns_flowMapEmptyKeyEntry(indent, ctx), |
- () => c_ns_flowPairJsonKeyEntry(indent, ctx) |
- ]); |
- |
- // 152 |
- Pair<Node, Node> ns_flowPairYamlKeyEntry(int indent, int ctx) => |
- transaction(() { |
- var key = ns_s_implicitYamlKey(FLOW_KEY); |
- if (!truth(key)) return null; |
- var value = c_ns_flowMapSeparateValue(indent, ctx); |
- if (!truth(value)) return null; |
- return new Pair<Node, Node>(key, value); |
- }); |
- |
- // 153 |
- Pair<Node, Node> c_ns_flowPairJsonKeyEntry(int indent, int ctx) => |
- transaction(() { |
- var key = c_s_implicitJsonKey(FLOW_KEY); |
- if (!truth(key)) return null; |
- var value = c_ns_flowMapAdjacentValue(indent, ctx); |
- if (!truth(value)) return null; |
- return new Pair<Node, Node>(key, value); |
- }); |
- |
- // 154 |
- Node ns_s_implicitYamlKey(int ctx) => transaction(() { |
- // TODO(nweiz): this is supposed to be limited to 1024 characters. |
- |
- // The indentation parameter is "null" since it's unused in this path |
- var node = ns_flowYamlNode(null, ctx); |
- if (!truth(node)) return null; |
- zeroOrOne(s_separateInLine); |
- return node; |
- }); |
- |
- // 155 |
- Node c_s_implicitJsonKey(int ctx) => transaction(() { |
- // TODO(nweiz): this is supposed to be limited to 1024 characters. |
- |
- // The indentation parameter is "null" since it's unused in this path |
- var node = c_flowJsonNode(null, ctx); |
- if (!truth(node)) return null; |
- zeroOrOne(s_separateInLine); |
- return node; |
- }); |
- |
- // 156 |
- Node ns_flowYamlContent(int indent, int ctx) { |
- var start = _scanner.state; |
- var str = ns_plain(indent, ctx); |
- if (!truth(str)) return null; |
- return new ScalarNode("?", _scanner.spanFrom(start), content: str); |
- } |
+ versionDirective = new VersionDirective(token.major, token.minor); |
+ } else if (token is TagDirectiveToken) { |
+ var tagDirective = new TagDirective(token.handle, token.prefix); |
+ _appendTagDirective(tagDirective, token.span); |
+ tagDirectives.add(tagDirective); |
+ } |
- // 157 |
- Node c_flowJsonContent(int indent, int ctx) => or([ |
- () => c_flowSequence(indent, ctx), |
- () => c_flowMapping(indent, ctx), |
- () => c_singleQuoted(indent, ctx), |
- () => c_doubleQuoted(indent, ctx) |
- ]); |
- |
- // 158 |
- Node ns_flowContent(int indent, int ctx) => or([ |
- () => ns_flowYamlContent(indent, ctx), |
- () => c_flowJsonContent(indent, ctx) |
- ]); |
- |
- // 159 |
- Node ns_flowYamlNode(int indent, int ctx) => or([ |
- c_ns_aliasNode, |
- () => ns_flowYamlContent(indent, ctx), |
- () { |
- var props = c_ns_properties(indent, ctx); |
- if (!truth(props)) return null; |
- var node = or([ |
- () => transaction(() { |
- if (!truth(s_separate(indent, ctx))) return null; |
- return ns_flowYamlContent(indent, ctx); |
- }), |
- e_scalar |
- ]); |
- return addProps(node, props); |
+ token = _scanner.advance(); |
} |
- ]); |
- |
- // 160 |
- Node c_flowJsonNode(int indent, int ctx) => transaction(() { |
- var props; |
- zeroOrOne(() => transaction(() { |
- props = c_ns_properties(indent, ctx); |
- if (!truth(props)) return null; |
- return s_separate(indent, ctx); |
- })); |
- |
- return addProps(c_flowJsonContent(indent, ctx), props); |
- }); |
- |
- // 161 |
- Node ns_flowNode(int indent, int ctx) => or([ |
- c_ns_aliasNode, |
- () => ns_flowContent(indent, ctx), |
- () => transaction(() { |
- var props = c_ns_properties(indent, ctx); |
- if (!truth(props)) return null; |
- var node = or([ |
- () => transaction(() => s_separate(indent, ctx) ? |
- ns_flowContent(indent, ctx) : null), |
- e_scalar]); |
- return addProps(node, props); |
- }) |
- ]); |
- |
- // 162 |
- _BlockHeader c_b_blockHeader() => transaction(() { |
- var indentation = c_indentationIndicator(); |
- var chomping = c_chompingIndicator(); |
- if (!truth(indentation)) indentation = c_indentationIndicator(); |
- if (!truth(s_b_comment())) return null; |
- |
- return new _BlockHeader(indentation, chomping); |
- }); |
- |
- // 163 |
- int c_indentationIndicator() { |
- if (!isDecDigit(peek())) return null; |
- return next() - NUMBER_0; |
- } |
- |
- // 164 |
- int c_chompingIndicator() { |
- switch (peek()) { |
- case HYPHEN: |
- next(); |
- return CHOMPING_STRIP; |
- case PLUS: |
- next(); |
- return CHOMPING_KEEP; |
- default: |
- return CHOMPING_CLIP; |
+ |
+ _appendTagDirective( |
+ new TagDirective("!", "!"), |
+ token.span.start.pointSpan(), |
+ allowDuplicates: true); |
+ _appendTagDirective( |
+ new TagDirective("!!", "tag:yaml.org,2002:"), |
+ token.span.start.pointSpan(), |
+ allowDuplicates: true); |
+ |
+ return new Pair(versionDirective, tagDirectives); |
+ } |
+ |
+ /// Adds a tag directive to the directives stack. |
+ void _appendTagDirective(TagDirective newDirective, FileSpan span, |
+ {bool allowDuplicates: false}) { |
+ if (_tagDirectives.containsKey(newDirective.handle)) { |
+ if (allowDuplicates) return; |
+ throw new YamlException("Duplicate %TAG directive.", span); |
} |
- } |
- // 165 |
- bool b_chompedLast(int chomping) { |
- if (_scanner.isDone) return true; |
- switch (chomping) { |
- case CHOMPING_STRIP: |
- return b_nonContent(); |
- case CHOMPING_CLIP: |
- case CHOMPING_KEEP: |
- return b_asLineFeed(); |
- } |
- throw "unreachable"; |
+ _tagDirectives[newDirective.handle] = newDirective; |
} |
+} |
- // 166 |
- void l_chompedEmpty(int indent, int chomping) { |
- switch (chomping) { |
- case CHOMPING_STRIP: |
- case CHOMPING_CLIP: |
- l_stripEmpty(indent); |
- break; |
- case CHOMPING_KEEP: |
- l_keepEmpty(indent); |
- break; |
- } |
- } |
+/// The possible states for the parser. |
+class _State { |
+ /// Expect [TokenType.STREAM_START]. |
+ static const STREAM_START = const _State("STREAM_START"); |
- // 167 |
- void l_stripEmpty(int indent) { |
- captureAs('', () { |
- zeroOrMore(() => transaction(() { |
- if (!truth(s_indentLessThanOrEqualTo(indent))) return false; |
- return b_nonContent(); |
- })); |
- zeroOrOne(() => l_trailComments(indent)); |
- return true; |
- }); |
- } |
+ /// Expect the beginning of an implicit document. |
+ static const IMPLICIT_DOCUMENT_START = |
+ const _State("IMPLICIT_DOCUMENT_START"); |
- // 168 |
- void l_keepEmpty(int indent) { |
- zeroOrMore(() => captureAs('\n', () => l_empty(indent, BLOCK_IN))); |
- zeroOrOne(() => captureAs('', () => l_trailComments(indent))); |
- } |
+ /// Expect [TokenType.DOCUMENT_START]. |
+ static const DOCUMENT_START = const _State("DOCUMENT_START"); |
- // 169 |
- bool l_trailComments(int indent) => transaction(() { |
- if (!truth(s_indentLessThanOrEqualTo(indent))) return false; |
- if (!truth(c_nb_commentText())) return false; |
- if (!truth(b_comment())) return false; |
- zeroOrMore(l_comment); |
- return true; |
- }); |
- |
- // 170 |
- Node c_l_literal(int indent) => transaction(() { |
- var start = _scanner.state; |
- if (!truth(c_indicator(C_LITERAL))) return null; |
- var header = c_b_blockHeader(); |
- if (!truth(header)) return null; |
- |
- var additionalIndent = blockScalarAdditionalIndentation(header, indent); |
- var content = l_literalContent(indent + additionalIndent, header.chomping); |
- if (!truth(content)) return null; |
- |
- return new ScalarNode("!", _scanner.spanFrom(start), content: content); |
- }); |
- |
- // 171 |
- bool l_nb_literalText(int indent) => transaction(() { |
- zeroOrMore(() => captureAs("\n", () => l_empty(indent, BLOCK_IN))); |
- if (!truth(captureAs("", () => s_indent(indent)))) return false; |
- return truth(oneOrMore(() => consume(isNonBreak))); |
- }); |
- |
- // 172 |
- bool b_nb_literalNext(int indent) => transaction(() { |
- if (!truth(b_asLineFeed())) return false; |
- return l_nb_literalText(indent); |
- }); |
- |
- // 173 |
- String l_literalContent(int indent, int chomping) => captureString(() { |
- transaction(() { |
- if (!truth(l_nb_literalText(indent))) return false; |
- zeroOrMore(() => b_nb_literalNext(indent)); |
- return b_chompedLast(chomping); |
- }); |
- l_chompedEmpty(indent, chomping); |
- return true; |
- }); |
- |
- // 174 |
- Node c_l_folded(int indent) => transaction(() { |
- var start = _scanner.state; |
- if (!truth(c_indicator(C_FOLDED))) return null; |
- var header = c_b_blockHeader(); |
- if (!truth(header)) return null; |
- |
- var additionalIndent = blockScalarAdditionalIndentation(header, indent); |
- var content = l_foldedContent(indent + additionalIndent, header.chomping); |
- if (!truth(content)) return null; |
- |
- return new ScalarNode("!", _scanner.spanFrom(start), content: content); |
- }); |
- |
- // 175 |
- bool s_nb_foldedText(int indent) => transaction(() { |
- if (!truth(captureAs('', () => s_indent(indent)))) return false; |
- if (!truth(consume(isNonSpace))) return false; |
- zeroOrMore(() => consume(isNonBreak)); |
- return true; |
- }); |
- |
- // 176 |
- bool l_nb_foldedLines(int indent) { |
- if (!truth(s_nb_foldedText(indent))) return false; |
- zeroOrMore(() => transaction(() { |
- if (!truth(b_l_folded(indent, BLOCK_IN))) return false; |
- return s_nb_foldedText(indent); |
- })); |
- return true; |
- } |
+ /// Expect the content of a document. |
+ static const DOCUMENT_CONTENT = const _State("DOCUMENT_CONTENT"); |
- // 177 |
- bool s_nb_spacedText(int indent) => transaction(() { |
- if (!truth(captureAs('', () => s_indent(indent)))) return false; |
- if (!truth(consume(isSpace))) return false; |
- zeroOrMore(() => consume(isNonBreak)); |
- return true; |
- }); |
- |
- // 178 |
- bool b_l_spaced(int indent) { |
- if (!truth(b_asLineFeed())) return false; |
- zeroOrMore(() => captureAs("\n", () => l_empty(indent, BLOCK_IN))); |
- return true; |
- } |
+ /// Expect [TokenType.DOCUMENT_END]. |
+ static const DOCUMENT_END = const _State("DOCUMENT_END"); |
- // 179 |
- bool l_nb_spacedLines(int indent) { |
- if (!truth(s_nb_spacedText(indent))) return false; |
- zeroOrMore(() => transaction(() { |
- if (!truth(b_l_spaced(indent))) return false; |
- return s_nb_spacedText(indent); |
- })); |
- return true; |
- } |
+ /// Expect a block node. |
+ static const BLOCK_NODE = const _State("BLOCK_NODE"); |
- // 180 |
- bool l_nb_sameLines(int indent) => transaction(() { |
- zeroOrMore(() => captureAs('\n', () => l_empty(indent, BLOCK_IN))); |
- return or([ |
- () => l_nb_foldedLines(indent), |
- () => l_nb_spacedLines(indent) |
- ]); |
- }); |
- |
- // 181 |
- bool l_nb_diffLines(int indent) { |
- if (!truth(l_nb_sameLines(indent))) return false; |
- zeroOrMore(() => transaction(() { |
- if (!truth(b_asLineFeed())) return false; |
- return l_nb_sameLines(indent); |
- })); |
- return true; |
- } |
+ /// Expect a block node or indentless sequence. |
+ static const BLOCK_NODE_OR_INDENTLESS_SEQUENCE = |
+ const _State("BLOCK_NODE_OR_INDENTLESS_SEQUENCE"); |
- // 182 |
- String l_foldedContent(int indent, int chomping) => captureString(() { |
- transaction(() { |
- if (!truth(l_nb_diffLines(indent))) return false; |
- return b_chompedLast(chomping); |
- }); |
- l_chompedEmpty(indent, chomping); |
- return true; |
- }); |
- |
- // 183 |
- SequenceNode l_blockSequence(int indent) => context('sequence', () { |
- var additionalIndent = countIndentation() - indent; |
- if (additionalIndent <= 0) return null; |
- |
- var start = _scanner.state; |
- var content = oneOrMore(() => transaction(() { |
- if (!truth(s_indent(indent + additionalIndent))) return null; |
- return c_l_blockSeqEntry(indent + additionalIndent); |
- })); |
- if (!truth(content)) return null; |
- |
- return new SequenceNode("?", content, _scanner.spanFrom(start)); |
- }); |
- |
- // 184 |
- Node c_l_blockSeqEntry(int indent) => transaction(() { |
- if (!truth(c_indicator(C_SEQUENCE_ENTRY))) return null; |
- if (isNonSpace(peek())) return null; |
- |
- return s_l_blockIndented(indent, BLOCK_IN); |
- }); |
- |
- // 185 |
- Node s_l_blockIndented(int indent, int ctx) { |
- var additionalIndent = countIndentation(); |
- return or([ |
- () => transaction(() { |
- if (!truth(s_indent(additionalIndent))) return null; |
- return or([ |
- () => ns_l_compactSequence(indent + 1 + additionalIndent), |
- () => ns_l_compactMapping(indent + 1 + additionalIndent)]); |
- }), |
- () => s_l_blockNode(indent, ctx), |
- () => s_l_comments() ? e_node() : null]); |
- } |
+ /// Expect a flow node. |
+ static const FLOW_NODE = const _State("FLOW_NODE"); |
- // 186 |
- Node ns_l_compactSequence(int indent) => context('sequence', () { |
- var start = _scanner.state; |
- var first = c_l_blockSeqEntry(indent); |
- if (!truth(first)) return null; |
- |
- var content = zeroOrMore(() => transaction(() { |
- if (!truth(s_indent(indent))) return null; |
- return c_l_blockSeqEntry(indent); |
- })); |
- content.insert(0, first); |
- |
- return new SequenceNode("?", content, _scanner.spanFrom(start)); |
- }); |
- |
- // 187 |
- Node l_blockMapping(int indent) => context('mapping', () { |
- var additionalIndent = countIndentation() - indent; |
- if (additionalIndent <= 0) return null; |
- |
- var start = _scanner.state; |
- var pairs = oneOrMore(() => transaction(() { |
- if (!truth(s_indent(indent + additionalIndent))) return null; |
- return ns_l_blockMapEntry(indent + additionalIndent); |
- })); |
- if (!truth(pairs)) return null; |
- |
- return map(pairs, _scanner.spanFrom(start)); |
- }); |
- |
- // 188 |
- Pair<Node, Node> ns_l_blockMapEntry(int indent) => or([ |
- () => c_l_blockMapExplicitEntry(indent), |
- () => ns_l_blockMapImplicitEntry(indent) |
- ]); |
- |
- // 189 |
- Pair<Node, Node> c_l_blockMapExplicitEntry(int indent) { |
- var key = c_l_blockMapExplicitKey(indent); |
- if (!truth(key)) return null; |
- |
- var value = or([ |
- () => l_blockMapExplicitValue(indent), |
- e_node |
- ]); |
- |
- return new Pair<Node, Node>(key, value); |
- } |
+ /// Expect the first entry of a block sequence. |
+ static const BLOCK_SEQUENCE_FIRST_ENTRY = |
+ const _State("BLOCK_SEQUENCE_FIRST_ENTRY"); |
- // 190 |
- Node c_l_blockMapExplicitKey(int indent) => transaction(() { |
- if (!truth(c_indicator(C_MAPPING_KEY))) return null; |
- return s_l_blockIndented(indent, BLOCK_OUT); |
- }); |
- |
- // 191 |
- Node l_blockMapExplicitValue(int indent) => transaction(() { |
- if (!truth(s_indent(indent))) return null; |
- if (!truth(c_indicator(C_MAPPING_VALUE))) return null; |
- return s_l_blockIndented(indent, BLOCK_OUT); |
- }); |
- |
- // 192 |
- Pair<Node, Node> ns_l_blockMapImplicitEntry(int indent) => transaction(() { |
- var key = or([ns_s_blockMapImplicitKey, e_node]); |
- var value = c_l_blockMapImplicitValue(indent); |
- return truth(value) ? new Pair<Node, Node>(key, value) : null; |
- }); |
- |
- // 193 |
- Node ns_s_blockMapImplicitKey() => context('mapping key', () => or([ |
- () => c_s_implicitJsonKey(BLOCK_KEY), |
- () => ns_s_implicitYamlKey(BLOCK_KEY) |
- ])); |
- |
- // 194 |
- Node c_l_blockMapImplicitValue(int indent) => context('mapping value', () => |
- transaction(() { |
- if (!truth(c_indicator(C_MAPPING_VALUE))) return null; |
- return or([ |
- () => s_l_blockNode(indent, BLOCK_OUT), |
- () => s_l_comments() ? e_node() : null |
- ]); |
- })); |
- |
- // 195 |
- Node ns_l_compactMapping(int indent) => context('mapping', () { |
- var start = _scanner.state; |
- var first = ns_l_blockMapEntry(indent); |
- if (!truth(first)) return null; |
- |
- var pairs = zeroOrMore(() => transaction(() { |
- if (!truth(s_indent(indent))) return null; |
- return ns_l_blockMapEntry(indent); |
- })); |
- pairs.insert(0, first); |
- |
- return map(pairs, _scanner.spanFrom(start)); |
- }); |
- |
- // 196 |
- Node s_l_blockNode(int indent, int ctx) => or([ |
- () => s_l_blockInBlock(indent, ctx), |
- () => s_l_flowInBlock(indent) |
- ]); |
- |
- // 197 |
- Node s_l_flowInBlock(int indent) => transaction(() { |
- if (!truth(s_separate(indent + 1, FLOW_OUT))) return null; |
- var node = ns_flowNode(indent + 1, FLOW_OUT); |
- if (!truth(node)) return null; |
- if (!truth(s_l_comments())) return null; |
- return node; |
- }); |
- |
- // 198 |
- Node s_l_blockInBlock(int indent, int ctx) => or([ |
- () => s_l_blockScalar(indent, ctx), |
- () => s_l_blockCollection(indent, ctx) |
- ]); |
- |
- // 199 |
- Node s_l_blockScalar(int indent, int ctx) => transaction(() { |
- if (!truth(s_separate(indent + 1, ctx))) return null; |
- var props = transaction(() { |
- var innerProps = c_ns_properties(indent + 1, ctx); |
- if (!truth(innerProps)) return null; |
- if (!truth(s_separate(indent + 1, ctx))) return null; |
- return innerProps; |
- }); |
- |
- var node = or([() => c_l_literal(indent), () => c_l_folded(indent)]); |
- if (!truth(node)) return null; |
- return addProps(node, props); |
- }); |
- |
- // 200 |
- Node s_l_blockCollection(int indent, int ctx) => transaction(() { |
- var props = transaction(() { |
- if (!truth(s_separate(indent + 1, ctx))) return null; |
- return c_ns_properties(indent + 1, ctx); |
- }); |
- |
- if (!truth(s_l_comments())) return null; |
- return or([ |
- () => l_blockSequence(seqSpaces(indent, ctx)), |
- () => l_blockMapping(indent)]); |
- }); |
- |
- // 201 |
- int seqSpaces(int indent, int ctx) => ctx == BLOCK_OUT ? indent - 1 : indent; |
- |
- // 202 |
- void l_documentPrefix() { |
- zeroOrMore(l_comment); |
- } |
+ /// Expect an entry of a block sequence. |
+ static const BLOCK_SEQUENCE_ENTRY = const _State("BLOCK_SEQUENCE_ENTRY"); |
- // 203 |
- bool c_directivesEnd() => rawString("---"); |
- |
- // 204 |
- bool c_documentEnd() => rawString("..."); |
- |
- // 205 |
- bool l_documentSuffix() => transaction(() { |
- if (!truth(c_documentEnd())) return false; |
- return s_l_comments(); |
- }); |
- |
- // 206 |
- bool c_forbidden() { |
- if (!_inBareDocument || !atStartOfLine) return false; |
- var forbidden = false; |
- transaction(() { |
- if (!truth(or([c_directivesEnd, c_documentEnd]))) return; |
- var char = peek(); |
- forbidden = isBreak(char) || isSpace(char) || _scanner.isDone; |
- return; |
- }); |
- return forbidden; |
- } |
+ /// Expect an entry of an indentless sequence. |
+ static const INDENTLESS_SEQUENCE_ENTRY = |
+ const _State("INDENTLESS_SEQUENCE_ENTRY"); |
- // 207 |
- Node l_bareDocument() { |
- try { |
- _inBareDocument = true; |
- return s_l_blockNode(-1, BLOCK_IN); |
- } finally { |
- _inBareDocument = false; |
- } |
- } |
+ /// Expect the first key of a block mapping. |
+ static const BLOCK_MAPPING_FIRST_KEY = |
+ const _State("BLOCK_MAPPING_FIRST_KEY"); |
- // 208 |
- Node l_explicitDocument() { |
- if (!truth(c_directivesEnd())) return null; |
- var doc = l_bareDocument(); |
- if (truth(doc)) return doc; |
+ /// Expect a block mapping key. |
+ static const BLOCK_MAPPING_KEY = const _State("BLOCK_MAPPING_KEY"); |
- doc = e_node(); |
- s_l_comments(); |
- return doc; |
- } |
+ /// Expect a block mapping value. |
+ static const BLOCK_MAPPING_VALUE = const _State("BLOCK_MAPPING_VALUE"); |
- // 209 |
- Node l_directiveDocument() { |
- if (!truth(oneOrMore(l_directive))) return null; |
- var doc = l_explicitDocument(); |
- if (doc != null) return doc; |
- parseFailed(); |
- return null; // Unreachable. |
- } |
+ /// Expect the first entry of a flow sequence. |
+ static const FLOW_SEQUENCE_FIRST_ENTRY = |
+ const _State("FLOW_SEQUENCE_FIRST_ENTRY"); |
- // 210 |
- Node l_anyDocument() => |
- or([l_directiveDocument, l_explicitDocument, l_bareDocument]); |
- |
- // 211 |
- Pair<List<Node>, SourceSpan> l_yamlStream() { |
- var start = _scanner.state; |
- var docs = []; |
- zeroOrMore(l_documentPrefix); |
- var first = zeroOrOne(l_anyDocument); |
- if (!truth(first)) first = e_node(); |
- docs.add(first); |
- |
- zeroOrMore(() { |
- var doc; |
- if (truth(oneOrMore(l_documentSuffix))) { |
- zeroOrMore(l_documentPrefix); |
- doc = zeroOrOne(l_anyDocument); |
- } else { |
- zeroOrMore(l_documentPrefix); |
- doc = zeroOrOne(l_explicitDocument); |
- } |
- if (truth(doc)) docs.add(doc); |
- return doc; |
- }); |
+ /// Expect an entry of a flow sequence. |
+ static const FLOW_SEQUENCE_ENTRY = const _State("FLOW_SEQUENCE_ENTRY"); |
- if (!_scanner.isDone) parseFailed(); |
- return new Pair(docs, _scanner.spanFrom(start)); |
- } |
-} |
+ /// Expect a key of an ordered mapping. |
+ static const FLOW_SEQUENCE_ENTRY_MAPPING_KEY = |
+ const _State("FLOW_SEQUENCE_ENTRY_MAPPING_KEY"); |
-/// The information in the header for a block scalar. |
-class _BlockHeader { |
- final int additionalIndent; |
- final int chomping; |
+ /// Expect a value of an ordered mapping. |
+ static const FLOW_SEQUENCE_ENTRY_MAPPING_VALUE = |
+ const _State("FLOW_SEQUENCE_ENTRY_MAPPING_VALUE"); |
- _BlockHeader(this.additionalIndent, this.chomping); |
+ /// Expect the and of an ordered mapping entry. |
+ static const FLOW_SEQUENCE_ENTRY_MAPPING_END = |
+ const _State("FLOW_SEQUENCE_ENTRY_MAPPING_END"); |
- bool get autoDetectIndent => additionalIndent == null; |
-} |
+ /// Expect the first key of a flow mapping. |
+ static const FLOW_MAPPING_FIRST_KEY = const _State("FLOW_MAPPING_FIRST_KEY"); |
-/// A range of characters in the YAML document, from [start] to [end] |
-/// (inclusive). |
-class _Range { |
- /// The first character in the range. |
- final int start; |
+ /// Expect a key of a flow mapping. |
+ static const FLOW_MAPPING_KEY = const _State("FLOW_MAPPING_KEY"); |
- /// The last character in the range. |
- final int end; |
+ /// Expect a value of a flow mapping. |
+ static const FLOW_MAPPING_VALUE = const _State("FLOW_MAPPING_VALUE"); |
- _Range(this.start, this.end); |
+ /// Expect an empty value of a flow mapping. |
+ static const FLOW_MAPPING_EMPTY_VALUE = |
+ const _State("FLOW_MAPPING_EMPTY_VALUE"); |
- /// Returns whether or not [pos] lies within this range. |
- bool contains(int pos) => pos >= start && pos <= end; |
-} |
+ /// Expect nothing. |
+ static const END = const _State("END"); |
-/// A map that associates [E] values with [_Range]s. It's efficient to create |
-/// new associations, but finding the value associated with a position is more |
-/// expensive. |
-class _RangeMap<E> { |
- /// The ranges and their associated elements. |
- final List<Pair<_Range, E>> _contents = <Pair<_Range, E>>[]; |
- |
- _RangeMap(); |
- |
- /// Returns the value associated with the range in which [pos] lies, or null |
- /// if there is no such range. If there's more than one such range, the most |
- /// recently set one is used. |
- E operator[](int pos) { |
- // Iterate backwards through contents so the more recent range takes |
- // precedence. |
- for (var pair in _contents.reversed) { |
- if (pair.first.contains(pos)) return pair.last; |
- } |
- return null; |
- } |
+ final String name; |
+ |
+ const _State(this.name); |
- /// Associates [value] with [range]. |
- operator[]=(_Range range, E value) => |
- _contents.add(new Pair<_Range, E>(range, value)); |
+ String toString() => name; |
} |