// Copyright (c) 2012, 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. part of yaml; /// 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. /// /// 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*`. 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 source string being parsed. final String s; /// The current position in the source string. int pos = 0; /// The length of the string being parsed. final int len; /// The current (0-based) line in the source string. int line = 0; /// The current (0-based) column in the source string. int column = 0; /// 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 line number of the farthest position that has been parsed successfully /// before backtracking. Used for error reporting. int farthestLine = 0; /// The column number of the farthest position that has been parsed /// successfully before backtracking. Used for error reporting. int farthestColumn = 0; /// The farthest position in the source string that has been parsed /// successfully before backtracking. Used for error reporting. int farthestPos = 0; /// 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. List contextStack; /// Annotations attached to ranges of the source string that add extra /// information to any errors that occur in the annotated range. _RangeMap errorAnnotations; /// 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 s) : this.s = s, len = s.length, contextStack = ["document"], errorAnnotations = new _RangeMap(); /// Return the character at the current position, then move that position /// forward one character. Also updates the current line and column numbers. int next() { if (pos == len) return -1; var char = s.charCodeAt(pos++); if (isBreak(char)) { line++; column = 0; } else { column++; } if (farthestLine < line) { farthestLine = line; farthestColumn = column; farthestContext = contextStack.last; } else if (farthestLine == line && farthestColumn < column) { farthestColumn = column; farthestContext = contextStack.last; } farthestPos = pos; return char; } /// Returns the character at the current position, or the character [i] /// characters after the current position. /// /// Returns -1 if this would return a character after the end or before the /// beginning of the input string. int peek([int i = 0]) { var peekPos = pos + i; return (peekPos >= len || peekPos < 0) ? -1 : s.charCodeAt(peekPos); } /// 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; } /// Consumes the current character if it equals [char]. bool consumeChar(int char) => consume((c) => c == char); /// 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. /// /// 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. } /// 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 = pos; while (true) { var el = consumer(); if (!truth(el) || oldPos == pos) return out; oldPos = pos; out.add(el); } 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 consumers) { for (var c in consumers) { var res = c(); if (truth(res)) return res; } return null; } /// Calls [consumer] and returns its result, but rolls back the parser state /// if [consumer] returns a falsey value. transaction(consumer()) { var oldPos = pos; var oldLine = line; var oldColumn = column; var oldCaptureStart = captureStart; String capturedSoFar = capturedString == null ? null : capturedString.toString(); var res = consumer(); if (truth(res)) return res; pos = oldPos; line = oldLine; column = oldColumn; captureStart = oldCaptureStart; capturedString = capturedSoFar == null ? null : new StringBuffer(capturedSoFar); return res; } /// 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). /// /// 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; } 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.charCodeAt(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 = pos; capturedString = new StringBuffer(); var res = transaction(consumer); if (!truth(res)) { captureStart = null; capturedString = null; return null; } 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.add(transformation(s.substring(captureStart, pos))); captureStart = pos; return res; } void flushCapture() { capturedString.add(s.substring(captureStart, pos)); captureStart = pos; } /// 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) { var content = new Map<_Node, _Node>(); pairs.forEach((pair) => content[pair.first] = pair.last); return new _MappingNode("?", content); } /// 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); } } /// 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 = pos; var end; transaction(() { fn(); end = pos; return false; }); errorAnnotations[new _Range(start, end)] = message; } /// Throws an error with additional context information. error(String message) { // Line and column should be one-based. throw new SyntaxError(line + 1, column + 1, "$message (in $farthestContext)"); } /// If [result] is falsey, throws an error saying that [expected] was /// expected. expect(result, String expected) { if (truth(result)) return result; error("expected $expected"); } /// Throws an error saying that the parse failed. Uses [farthestLine], /// [farthestColumn], and [farthestContext] to provide additional information. parseFailed() { var message = "invalid YAML in $farthestContext"; var extraError = errorAnnotations[farthestPos]; if (extraError != null) message = "$message ($extraError)"; throw new SyntaxError(farthestLine + 1, farthestColumn + 1, message); } /// 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 maxSpacesLine = 0; var spaces = 0; transaction(() { do { spaces = captureString(() => zeroOrMore(() => consumeChar(SP))).length; if (spaces > maxSpaces) { maxSpaces = spaces; maxSpacesLine = line; } } 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) { throw new SyntaxError(maxSpacesLine + 1, maxSpaces, "Leading empty lines may not be indented more than the first " "non-empty line."); } return spaces - indent; } /// Returns whether the current position is at the beginning of a line. bool get atStartOfLine => column == 0; /// Returns whether the current position is at the end of the input. bool get atEndOfFile => pos == len; /// 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; } } // 1 bool isPrintable(int char) { 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 == 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; } 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 >= NUMBER_0 && char <= NUMBER_9; // 36 bool isHexDigit(int char) { 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); // 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"); } // 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))); } 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; } }); } break; } } 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); } }); // 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() => atEndOfFile || b_nonContent(); // 77 bool s_b_comment() { if (truth(s_separateInLine())) { zeroOrOne(c_nb_commentText); } 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"'; } } // 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); } 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); } 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); // 103 String ns_anchorName() => captureString(() => oneOrMore(() => consume(isAnchorChar))); // 104 _Node c_ns_aliasNode() { if (!truth(c_indicator(C_ALIAS))) return null; var name = expect(ns_anchorName(), 'anchor name'); return new _AliasNode(name); } // 105 _ScalarNode e_scalar() => new _ScalarNode("?", 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(() { 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("!", 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; } return true; }); // 111 void nb_doubleOneLine() { zeroOrMore(nb_doubleChar); } // 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(); })); } // 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; } // 116 void nb_doubleMultiLine(int indent) { nb_ns_doubleInLine(); or([ () => s_doubleNextLine(indent), () => zeroOrMore(() => consume(isSpace)) ]); } // 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(() { 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("!", 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; } return true; }); // 122 void nb_singleOneLine(int indent) { zeroOrMore(nb_singleChar); } // 123 void nb_ns_singleInLine() { zeroOrMore(() => transaction(() { zeroOrMore(() => consume(isSpace)); return ns_singleChar(); })); } // 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; } // 125 void nb_singleMultiLine(int indent) { nb_ns_singleInLine(); or([ () => s_singleNextLine(indent), () => zeroOrMore(() => consume(isSpace)) ]); } // 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"); } 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"'; } } // 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; } // 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"'; } }); }); // 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; } // 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; } // 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; } } // 137 _SequenceNode c_flowSequence(int indent, int ctx) => transaction(() { 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)); }); // 138 Collection<_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)); } if (rest == null) rest = new Queue<_Node>(); rest.addFirst(first); return rest; } // 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) { 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); } // 141 YamlMap ns_s_flowMapEntries(int indent, int ctx) { var first = ns_flowMapEntry(indent, ctx); if (!truth(first)) return new YamlMap(); zeroOrOne(() => s_separate(indent, ctx)); var rest; if (truth(c_indicator(C_COLLECT_ENTRY))) { zeroOrOne(() => s_separate(indent, ctx)); rest = ns_s_flowMapEntries(indent, ctx); } if (rest == null) rest = new YamlMap(); // 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; return rest; } // 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); } // 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); } // 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); } // 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 ]); } // 150 _Node ns_flowPair(int indent, int ctx) { 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]); } // 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 str = ns_plain(indent, ctx); if (!truth(str)) return null; return new _ScalarNode("?", content: str); } // 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); } ]); // 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; } } // 165 bool b_chompedLast(int chomping) { if (atEndOfFile) return true; switch (chomping) { case CHOMPING_STRIP: return b_nonContent(); case CHOMPING_CLIP: case CHOMPING_KEEP: return b_asLineFeed(); } } // 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; } } // 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; }); } // 168 void l_keepEmpty(int indent) { zeroOrMore(() => captureAs('\n', () => l_empty(indent, BLOCK_IN))); zeroOrOne(() => captureAs('', () => l_trailComments(indent))); } // 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(() { 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("!", 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(() { 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("!", 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; } // 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; } // 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; } // 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; } // 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 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); }); // 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]); } // 186 _Node ns_l_compactSequence(int indent) => context('sequence', () { 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.insertRange(0, 1, first); return new _SequenceNode("?", content); }); // 187 _Node l_blockMapping(int indent) => context('mapping', () { var additionalIndent = countIndentation() - indent; if (additionalIndent <= 0) return null; 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); }); // 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); } // 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 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.insertRange(0, 1, first); return map(pairs); }); // 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); } // 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) || atEndOfFile; return; }); return forbidden; } // 207 _Node l_bareDocument() { try { inBareDocument = true; return s_l_blockNode(-1, BLOCK_IN); } finally { inBareDocument = false; } } // 208 _Node l_explicitDocument() { if (!truth(c_directivesEnd())) return null; var doc = l_bareDocument(); if (truth(doc)) return doc; doc = e_node(); s_l_comments(); return doc; } // 209 _Node l_directiveDocument() { if (!truth(oneOrMore(l_directive))) return null; var doc = l_explicitDocument(); if (doc != null) return doc; parseFailed(); return null; // Unreachable. } // 210 _Node l_anyDocument() => or([l_directiveDocument, l_explicitDocument, l_bareDocument]); // 211 List<_Node> l_yamlStream() { 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; }); if (!atEndOfFile) parseFailed(); return docs; } } class SyntaxError extends YamlException { final int line; final int column; SyntaxError(this.line, this.column, String msg) : super(msg); String toString() => "Syntax error on line $line, column $column: $msg"; } /// A pair of values. class _Pair { E first; F last; _Pair(this.first, this.last); String toString() => '($first, $last)'; } /// The information in the header for a block scalar. class _BlockHeader { final int additionalIndent; final int chomping; _BlockHeader(this.additionalIndent, this.chomping); bool get autoDetectIndent => additionalIndent == null; } /// A range of characters in the YAML document, from [start] to [end] /// (inclusive). class _Range { /// The first character in the range. final int start; /// The last character in the range. final int end; _Range(this.start, this.end); /// Returns whether or not [pos] lies within this range. bool contains(int pos) => pos >= start && pos <= 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 { /// The ranges and their associated elements. final List<_Pair<_Range, E>> contents; _RangeMap() : this.contents = <_Pair<_Range, E>>[]; /// 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. TODO(nweiz): clean this up when issue 2804 is fixed. for (var i = contents.length - 1; i >= 0; i--) { var pair = contents[i]; if (pair.first.contains(pos)) return pair.last; } return null; } /// Associates [value] with [range]. operator[]=(_Range range, E value) => contents.add(new _Pair<_Range, E>(range, value)); }