| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * Translates a string of characters into a YAML serialization tree. | |
| 7 * | |
| 8 * This parser is designed to closely follow the spec. All productions in the | |
| 9 * spec are numbered, and the corresponding methods in the parser have the same | |
| 10 * numbers. This is certainly not the most efficient way of parsing YAML, but it | |
| 11 * is the easiest to write and read in the context of the spec. | |
| 12 * | |
| 13 * Methods corresponding to productions are also named as in the spec, | |
| 14 * translating the name of the method (although not the annotation characters) | |
| 15 * into camel-case for dart style.. For example, the spec has a production named | |
| 16 * `nb-ns-plain-in-line`, and the method implementing it is named | |
| 17 * `nb_ns_plainInLine`. The exception to that rule is methods that just | |
| 18 * recognize character classes; these are named `is*`. | |
| 19 */ | |
| 20 class _Parser { | |
| 21 static const TAB = 0x9; | |
| 22 static const LF = 0xA; | |
| 23 static const CR = 0xD; | |
| 24 static const SP = 0x20; | |
| 25 static const TILDE = 0x7E; | |
| 26 static const NEL = 0x85; | |
| 27 static const PLUS = 0x2B; | |
| 28 static const HYPHEN = 0x2D; | |
| 29 static const QUESTION_MARK = 0x3F; | |
| 30 static const COLON = 0x3A; | |
| 31 static const COMMA = 0x2C; | |
| 32 static const LEFT_BRACKET = 0x5B; | |
| 33 static const RIGHT_BRACKET = 0x5D; | |
| 34 static const LEFT_BRACE = 0x7B; | |
| 35 static const RIGHT_BRACE = 0x7D; | |
| 36 static const HASH = 0x23; | |
| 37 static const AMPERSAND = 0x26; | |
| 38 static const ASTERISK = 0x2A; | |
| 39 static const EXCLAMATION = 0x21; | |
| 40 static const VERTICAL_BAR = 0x7C; | |
| 41 static const GREATER_THAN = 0x3E; | |
| 42 static const SINGLE_QUOTE = 0x27; | |
| 43 static const DOUBLE_QUOTE = 0x22; | |
| 44 static const PERCENT = 0x25; | |
| 45 static const AT = 0x40; | |
| 46 static const GRAVE_ACCENT = 0x60; | |
| 47 | |
| 48 static const NULL = 0x0; | |
| 49 static const BELL = 0x7; | |
| 50 static const BACKSPACE = 0x8; | |
| 51 static const VERTICAL_TAB = 0xB; | |
| 52 static const FORM_FEED = 0xC; | |
| 53 static const ESCAPE = 0x1B; | |
| 54 static const SLASH = 0x2F; | |
| 55 static const BACKSLASH = 0x5C; | |
| 56 static const UNDERSCORE = 0x5F; | |
| 57 static const NBSP = 0xA0; | |
| 58 static const LINE_SEPARATOR = 0x2028; | |
| 59 static const PARAGRAPH_SEPARATOR = 0x2029; | |
| 60 | |
| 61 static const NUMBER_0 = 0x30; | |
| 62 static const NUMBER_9 = 0x39; | |
| 63 | |
| 64 static const LETTER_A = 0x61; | |
| 65 static const LETTER_B = 0x62; | |
| 66 static const LETTER_E = 0x65; | |
| 67 static const LETTER_F = 0x66; | |
| 68 static const LETTER_N = 0x6E; | |
| 69 static const LETTER_R = 0x72; | |
| 70 static const LETTER_T = 0x74; | |
| 71 static const LETTER_U = 0x75; | |
| 72 static const LETTER_V = 0x76; | |
| 73 static const LETTER_X = 0x78; | |
| 74 | |
| 75 static const LETTER_CAP_A = 0x41; | |
| 76 static const LETTER_CAP_F = 0x46; | |
| 77 static const LETTER_CAP_L = 0x4C; | |
| 78 static const LETTER_CAP_N = 0x4E; | |
| 79 static const LETTER_CAP_P = 0x50; | |
| 80 static const LETTER_CAP_U = 0x55; | |
| 81 static const LETTER_CAP_X = 0x58; | |
| 82 | |
| 83 static const C_SEQUENCE_ENTRY = 4; | |
| 84 static const C_MAPPING_KEY = 5; | |
| 85 static const C_MAPPING_VALUE = 6; | |
| 86 static const C_COLLECT_ENTRY = 7; | |
| 87 static const C_SEQUENCE_START = 8; | |
| 88 static const C_SEQUENCE_END = 9; | |
| 89 static const C_MAPPING_START = 10; | |
| 90 static const C_MAPPING_END = 11; | |
| 91 static const C_COMMENT = 12; | |
| 92 static const C_ANCHOR = 13; | |
| 93 static const C_ALIAS = 14; | |
| 94 static const C_TAG = 15; | |
| 95 static const C_LITERAL = 16; | |
| 96 static const C_FOLDED = 17; | |
| 97 static const C_SINGLE_QUOTE = 18; | |
| 98 static const C_DOUBLE_QUOTE = 19; | |
| 99 static const C_DIRECTIVE = 20; | |
| 100 static const C_RESERVED = 21; | |
| 101 | |
| 102 static const BLOCK_OUT = 0; | |
| 103 static const BLOCK_IN = 1; | |
| 104 static const FLOW_OUT = 2; | |
| 105 static const FLOW_IN = 3; | |
| 106 static const BLOCK_KEY = 4; | |
| 107 static const FLOW_KEY = 5; | |
| 108 | |
| 109 static const CHOMPING_STRIP = 0; | |
| 110 static const CHOMPING_KEEP = 1; | |
| 111 static const CHOMPING_CLIP = 2; | |
| 112 | |
| 113 /** The source string being parsed. */ | |
| 114 final String s; | |
| 115 | |
| 116 /** The current position in the source string. */ | |
| 117 int pos = 0; | |
| 118 | |
| 119 /** The length of the string being parsed. */ | |
| 120 final int len; | |
| 121 | |
| 122 /** The current (0-based) line in the source string. */ | |
| 123 int line = 0; | |
| 124 | |
| 125 /** The current (0-based) column in the source string. */ | |
| 126 int column = 0; | |
| 127 | |
| 128 /** | |
| 129 * Whether we're parsing a bare document (that is, one that doesn't begin with | |
| 130 * `---`). Bare documents don't allow `%` immediately following newlines. | |
| 131 */ | |
| 132 bool inBareDocument = false; | |
| 133 | |
| 134 /** | |
| 135 * The line number of the farthest position that has been parsed successfully | |
| 136 * before backtracking. Used for error reporting. | |
| 137 */ | |
| 138 int farthestLine = 0; | |
| 139 | |
| 140 /** | |
| 141 * The column number of the farthest position that has been parsed | |
| 142 * successfully before backtracking. Used for error reporting. | |
| 143 */ | |
| 144 int farthestColumn = 0; | |
| 145 | |
| 146 /** | |
| 147 * The farthest position in the source string that has been parsed | |
| 148 * successfully before backtracking. Used for error reporting. | |
| 149 */ | |
| 150 int farthestPos = 0; | |
| 151 | |
| 152 /** | |
| 153 * The name of the context of the farthest position that has been parsed | |
| 154 * successfully before backtracking. Used for error reporting. | |
| 155 */ | |
| 156 String farthestContext = "document"; | |
| 157 | |
| 158 /** A stack of the names of parse contexts. Used for error reporting. */ | |
| 159 List<String> contextStack; | |
| 160 | |
| 161 /** | |
| 162 * Annotations attached to ranges of the source string that add extra | |
| 163 * information to any errors that occur in the annotated range. | |
| 164 */ | |
| 165 _RangeMap<String> errorAnnotations; | |
| 166 | |
| 167 /** | |
| 168 * The buffer containing the string currently being captured. | |
| 169 */ | |
| 170 StringBuffer capturedString; | |
| 171 | |
| 172 /** | |
| 173 * The beginning of the current section of the captured string. | |
| 174 */ | |
| 175 int captureStart; | |
| 176 | |
| 177 /** | |
| 178 * Whether the current string capture is being overridden. | |
| 179 */ | |
| 180 bool capturingAs = false; | |
| 181 | |
| 182 _Parser(String s) | |
| 183 : this.s = s, | |
| 184 len = s.length, | |
| 185 contextStack = <String>["document"], | |
| 186 errorAnnotations = new _RangeMap(); | |
| 187 | |
| 188 /** | |
| 189 * Return the character at the current position, then move that position | |
| 190 * forward one character. Also updates the current line and column numbers. | |
| 191 */ | |
| 192 int next() { | |
| 193 if (pos == len) return -1; | |
| 194 var char = s.charCodeAt(pos++); | |
| 195 if (isBreak(char)) { | |
| 196 line++; | |
| 197 column = 0; | |
| 198 } else { | |
| 199 column++; | |
| 200 } | |
| 201 | |
| 202 if (farthestLine < line) { | |
| 203 farthestLine = line; | |
| 204 farthestColumn = column; | |
| 205 farthestContext = contextStack.last; | |
| 206 } else if (farthestLine == line && farthestColumn < column) { | |
| 207 farthestColumn = column; | |
| 208 farthestContext = contextStack.last; | |
| 209 } | |
| 210 farthestPos = pos; | |
| 211 | |
| 212 return char; | |
| 213 } | |
| 214 | |
| 215 /** | |
| 216 * Returns the character at the current position, or the character [i] | |
| 217 * characters after the current position. | |
| 218 * | |
| 219 * Returns -1 if this would return a character after the end or before the | |
| 220 * beginning of the input string. | |
| 221 */ | |
| 222 int peek([int i = 0]) { | |
| 223 var peekPos = pos + i; | |
| 224 return (peekPos >= len || peekPos < 0) ? -1 : s.charCodeAt(peekPos); | |
| 225 } | |
| 226 | |
| 227 /** | |
| 228 * The truthiness operator. Returns `false` if [obj] is `null` or `false`, | |
| 229 * `true` otherwise. | |
| 230 */ | |
| 231 bool truth(obj) => obj != null && obj != false; | |
| 232 | |
| 233 /** | |
| 234 * Consumes the current character if it matches [matcher]. Returns the result | |
| 235 * of [matcher]. | |
| 236 */ | |
| 237 bool consume(bool matcher(int)) { | |
| 238 if (matcher(peek())) { | |
| 239 next(); | |
| 240 return true; | |
| 241 } | |
| 242 return false; | |
| 243 } | |
| 244 | |
| 245 /** | |
| 246 * Consumes the current character if it equals [char]. | |
| 247 */ | |
| 248 bool consumeChar(int char) => consume((c) => c == char); | |
| 249 | |
| 250 /** | |
| 251 * Calls [consumer] until it returns a falsey value. Returns a list of all | |
| 252 * truthy return values of [consumer], or null if it didn't consume anything. | |
| 253 * | |
| 254 * Conceptually, repeats a production one or more times. | |
| 255 */ | |
| 256 List oneOrMore(consumer()) { | |
| 257 var first = consumer(); | |
| 258 if (!truth(first)) return null; | |
| 259 var out = [first]; | |
| 260 while (true) { | |
| 261 var el = consumer(); | |
| 262 if (!truth(el)) return out; | |
| 263 out.add(el); | |
| 264 } | |
| 265 return null; // Unreachable. | |
| 266 } | |
| 267 | |
| 268 /** | |
| 269 * Calls [consumer] until it returns a falsey value. Returns a list of all | |
| 270 * truthy return values of [consumer], or the empty list if it didn't consume | |
| 271 * anything. | |
| 272 * | |
| 273 * Conceptually, repeats a production any number of times. | |
| 274 */ | |
| 275 List zeroOrMore(consumer()) { | |
| 276 var out = []; | |
| 277 var oldPos = pos; | |
| 278 while (true) { | |
| 279 var el = consumer(); | |
| 280 if (!truth(el) || oldPos == pos) return out; | |
| 281 oldPos = pos; | |
| 282 out.add(el); | |
| 283 } | |
| 284 return null; // Unreachable. | |
| 285 } | |
| 286 | |
| 287 /** | |
| 288 * Just calls [consumer] and returns its result. Used to make it explicit that | |
| 289 * a production is intended to be optional. | |
| 290 */ | |
| 291 zeroOrOne(consumer()) => consumer(); | |
| 292 | |
| 293 /** | |
| 294 * Calls each function in [consumers] until one returns a truthy value, then | |
| 295 * returns that. | |
| 296 */ | |
| 297 or(List<Function> consumers) { | |
| 298 for (var c in consumers) { | |
| 299 var res = c(); | |
| 300 if (truth(res)) return res; | |
| 301 } | |
| 302 return null; | |
| 303 } | |
| 304 | |
| 305 /** | |
| 306 * Calls [consumer] and returns its result, but rolls back the parser state if | |
| 307 * [consumer] returns a falsey value. | |
| 308 */ | |
| 309 transaction(consumer()) { | |
| 310 var oldPos = pos; | |
| 311 var oldLine = line; | |
| 312 var oldColumn = column; | |
| 313 var oldCaptureStart = captureStart; | |
| 314 String capturedSoFar = capturedString == null ? null : | |
| 315 capturedString.toString(); | |
| 316 var res = consumer(); | |
| 317 if (truth(res)) return res; | |
| 318 | |
| 319 pos = oldPos; | |
| 320 line = oldLine; | |
| 321 column = oldColumn; | |
| 322 captureStart = oldCaptureStart; | |
| 323 capturedString = capturedSoFar == null ? null : | |
| 324 new StringBuffer(capturedSoFar); | |
| 325 return res; | |
| 326 } | |
| 327 | |
| 328 /** | |
| 329 * Consumes [n] characters matching [matcher], or none if there isn't a | |
| 330 * complete match. The first argument to [matcher] is the character code, the | |
| 331 * second is the index (from 0 to [n] - 1). | |
| 332 * | |
| 333 * Returns whether or not the characters were consumed. | |
| 334 */ | |
| 335 bool nAtOnce(int n, bool matcher(int c, int i)) => transaction(() { | |
| 336 for (int i = 0; i < n; i++) { | |
| 337 if (!consume((c) => matcher(c, i))) return false; | |
| 338 } | |
| 339 return true; | |
| 340 }); | |
| 341 | |
| 342 /** | |
| 343 * Consumes the exact characters in [str], or nothing. | |
| 344 * | |
| 345 * Returns whether or not the string was consumed. | |
| 346 */ | |
| 347 bool rawString(String str) => | |
| 348 nAtOnce(str.length, (c, i) => str.charCodeAt(i) == c); | |
| 349 | |
| 350 /** | |
| 351 * Consumes and returns a string of characters matching [matcher], or null if | |
| 352 * there are no such characters. | |
| 353 */ | |
| 354 String stringOf(bool matcher(int)) => | |
| 355 captureString(() => oneOrMore(() => consume(matcher))); | |
| 356 | |
| 357 /** | |
| 358 * Calls [consumer] and returns the string that was consumed while doing so, | |
| 359 * or null if [consumer] returned a falsey value. Automatically wraps | |
| 360 * [consumer] in `transaction`. | |
| 361 */ | |
| 362 String captureString(consumer()) { | |
| 363 // captureString calls may not be nested | |
| 364 assert(capturedString == null); | |
| 365 | |
| 366 captureStart = pos; | |
| 367 capturedString = new StringBuffer(); | |
| 368 var res = transaction(consumer); | |
| 369 if (!truth(res)) { | |
| 370 captureStart = null; | |
| 371 capturedString = null; | |
| 372 return null; | |
| 373 } | |
| 374 | |
| 375 flushCapture(); | |
| 376 var result = capturedString.toString(); | |
| 377 captureStart = null; | |
| 378 capturedString = null; | |
| 379 return result; | |
| 380 } | |
| 381 | |
| 382 captureAs(String replacement, consumer()) => | |
| 383 captureAndTransform(consumer, (_) => replacement); | |
| 384 | |
| 385 captureAndTransform(consumer(), String transformation(String captured)) { | |
| 386 if (capturedString == null) return consumer(); | |
| 387 if (capturingAs) return consumer(); | |
| 388 | |
| 389 flushCapture(); | |
| 390 capturingAs = true; | |
| 391 var res = consumer(); | |
| 392 capturingAs = false; | |
| 393 if (!truth(res)) return res; | |
| 394 | |
| 395 capturedString.add(transformation(s.substring(captureStart, pos))); | |
| 396 captureStart = pos; | |
| 397 return res; | |
| 398 } | |
| 399 | |
| 400 void flushCapture() { | |
| 401 capturedString.add(s.substring(captureStart, pos)); | |
| 402 captureStart = pos; | |
| 403 } | |
| 404 | |
| 405 /** | |
| 406 * Adds a tag and an anchor to [node], if they're defined. | |
| 407 */ | |
| 408 _Node addProps(_Node node, _Pair<_Tag, String> props) { | |
| 409 if (props == null || node == null) return node; | |
| 410 if (truth(props.first)) node.tag = props.first; | |
| 411 if (truth(props.last)) node.anchor = props.last; | |
| 412 return node; | |
| 413 } | |
| 414 | |
| 415 /** Creates a MappingNode from [pairs]. */ | |
| 416 _MappingNode map(List<_Pair<_Node, _Node>> pairs) { | |
| 417 var content = new Map<_Node, _Node>(); | |
| 418 pairs.forEach((pair) => content[pair.first] = pair.last); | |
| 419 return new _MappingNode("?", content); | |
| 420 } | |
| 421 | |
| 422 /** Runs [fn] in a context named [name]. Used for error reporting. */ | |
| 423 context(String name, fn()) { | |
| 424 try { | |
| 425 contextStack.add(name); | |
| 426 return fn(); | |
| 427 } finally { | |
| 428 var popped = contextStack.removeLast(); | |
| 429 assert(popped == name); | |
| 430 } | |
| 431 } | |
| 432 | |
| 433 /** | |
| 434 * Adds [message] as extra information to any errors that occur between the | |
| 435 * current position and the position of the cursor after running [fn]. The | |
| 436 * cursor is reset after [fn] is run. | |
| 437 */ | |
| 438 annotateError(String message, fn()) { | |
| 439 var start = pos; | |
| 440 var end; | |
| 441 transaction(() { | |
| 442 fn(); | |
| 443 end = pos; | |
| 444 return false; | |
| 445 }); | |
| 446 errorAnnotations[new _Range(start, end)] = message; | |
| 447 } | |
| 448 | |
| 449 /** Throws an error with additional context information. */ | |
| 450 error(String message) { | |
| 451 // Line and column should be one-based. | |
| 452 throw new SyntaxError(line + 1, column + 1, | |
| 453 "$message (in $farthestContext)"); | |
| 454 } | |
| 455 | |
| 456 /** | |
| 457 * If [result] is falsey, throws an error saying that [expected] was | |
| 458 * expected. | |
| 459 */ | |
| 460 expect(result, String expected) { | |
| 461 if (truth(result)) return result; | |
| 462 error("expected $expected"); | |
| 463 } | |
| 464 | |
| 465 /** | |
| 466 * Throws an error saying that the parse failed. Uses [farthestLine], | |
| 467 * [farthestColumn], and [farthestContext] to provide additional information. | |
| 468 */ | |
| 469 parseFailed() { | |
| 470 var message = "invalid YAML in $farthestContext"; | |
| 471 var extraError = errorAnnotations[farthestPos]; | |
| 472 if (extraError != null) message = "$message ($extraError)"; | |
| 473 throw new SyntaxError(farthestLine + 1, farthestColumn + 1, message); | |
| 474 } | |
| 475 | |
| 476 /** Returns the number of spaces after the current position. */ | |
| 477 int countIndentation() { | |
| 478 var i = 0; | |
| 479 while (peek(i) == SP) i++; | |
| 480 return i; | |
| 481 } | |
| 482 | |
| 483 /** Returns the indentation for a block scalar. */ | |
| 484 int blockScalarAdditionalIndentation(_BlockHeader header, int indent) { | |
| 485 if (!header.autoDetectIndent) return header.additionalIndent; | |
| 486 | |
| 487 var maxSpaces = 0; | |
| 488 var maxSpacesLine = 0; | |
| 489 var spaces = 0; | |
| 490 transaction(() { | |
| 491 do { | |
| 492 spaces = captureString(() => zeroOrMore(() => consumeChar(SP))).length; | |
| 493 if (spaces > maxSpaces) { | |
| 494 maxSpaces = spaces; | |
| 495 maxSpacesLine = line; | |
| 496 } | |
| 497 } while (b_break()); | |
| 498 return false; | |
| 499 }); | |
| 500 | |
| 501 // If the next non-empty line isn't indented further than the start of the | |
| 502 // block scalar, that means the scalar is going to be empty. Returning any | |
| 503 // value > 0 will cause the parser not to consume any text. | |
| 504 if (spaces <= indent) return 1; | |
| 505 | |
| 506 // It's an error for a leading empty line to be indented more than the first | |
| 507 // non-empty line. | |
| 508 if (maxSpaces > spaces) { | |
| 509 throw new SyntaxError(maxSpacesLine + 1, maxSpaces, | |
| 510 "Leading empty lines may not be indented more than the first " | |
| 511 "non-empty line."); | |
| 512 } | |
| 513 | |
| 514 return spaces - indent; | |
| 515 } | |
| 516 | |
| 517 /** Returns whether the current position is at the beginning of a line. */ | |
| 518 bool get atStartOfLine => column == 0; | |
| 519 | |
| 520 /** Returns whether the current position is at the end of the input. */ | |
| 521 bool get atEndOfFile => pos == len; | |
| 522 | |
| 523 /** | |
| 524 * Given an indicator character, returns the type of that indicator (or null | |
| 525 * if the indicator isn't found. | |
| 526 */ | |
| 527 int indicatorType(int char) { | |
| 528 switch (char) { | |
| 529 case HYPHEN: return C_SEQUENCE_ENTRY; | |
| 530 case QUESTION_MARK: return C_MAPPING_KEY; | |
| 531 case COLON: return C_MAPPING_VALUE; | |
| 532 case COMMA: return C_COLLECT_ENTRY; | |
| 533 case LEFT_BRACKET: return C_SEQUENCE_START; | |
| 534 case RIGHT_BRACKET: return C_SEQUENCE_END; | |
| 535 case LEFT_BRACE: return C_MAPPING_START; | |
| 536 case RIGHT_BRACE: return C_MAPPING_END; | |
| 537 case HASH: return C_COMMENT; | |
| 538 case AMPERSAND: return C_ANCHOR; | |
| 539 case ASTERISK: return C_ALIAS; | |
| 540 case EXCLAMATION: return C_TAG; | |
| 541 case VERTICAL_BAR: return C_LITERAL; | |
| 542 case GREATER_THAN: return C_FOLDED; | |
| 543 case SINGLE_QUOTE: return C_SINGLE_QUOTE; | |
| 544 case DOUBLE_QUOTE: return C_DOUBLE_QUOTE; | |
| 545 case PERCENT: return C_DIRECTIVE; | |
| 546 case AT: | |
| 547 case GRAVE_ACCENT: | |
| 548 return C_RESERVED; | |
| 549 default: return null; | |
| 550 } | |
| 551 } | |
| 552 | |
| 553 // 1 | |
| 554 bool isPrintable(int char) { | |
| 555 return char == TAB || | |
| 556 char == LF || | |
| 557 char == CR || | |
| 558 (char >= SP && char <= TILDE) || | |
| 559 char == NEL || | |
| 560 (char >= 0xA0 && char <= 0xD7FF) || | |
| 561 (char >= 0xE000 && char <= 0xFFFD) || | |
| 562 (char >= 0x10000 && char <= 0x10FFFF); | |
| 563 } | |
| 564 | |
| 565 // 2 | |
| 566 bool isJson(int char) => char == TAB || (char >= SP && char <= 0x10FFFF); | |
| 567 | |
| 568 // 22 | |
| 569 bool c_indicator(int type) => consume((c) => indicatorType(c) == type); | |
| 570 | |
| 571 // 23 | |
| 572 bool isFlowIndicator(int char) { | |
| 573 var indicator = indicatorType(char); | |
| 574 return indicator == C_COLLECT_ENTRY || | |
| 575 indicator == C_SEQUENCE_START || | |
| 576 indicator == C_SEQUENCE_END || | |
| 577 indicator == C_MAPPING_START || | |
| 578 indicator == C_MAPPING_END; | |
| 579 } | |
| 580 | |
| 581 // 26 | |
| 582 bool isBreak(int char) => char == LF || char == CR; | |
| 583 | |
| 584 // 27 | |
| 585 bool isNonBreak(int char) => isPrintable(char) && !isBreak(char); | |
| 586 | |
| 587 // 28 | |
| 588 bool b_break() { | |
| 589 if (consumeChar(CR)) { | |
| 590 zeroOrOne(() => consumeChar(LF)); | |
| 591 return true; | |
| 592 } | |
| 593 return consumeChar(LF); | |
| 594 } | |
| 595 | |
| 596 // 29 | |
| 597 bool b_asLineFeed() => captureAs("\n", () => b_break()); | |
| 598 | |
| 599 // 30 | |
| 600 bool b_nonContent() => captureAs("", () => b_break()); | |
| 601 | |
| 602 // 33 | |
| 603 bool isSpace(int char) => char == SP || char == TAB; | |
| 604 | |
| 605 // 34 | |
| 606 bool isNonSpace(int char) => isNonBreak(char) && !isSpace(char); | |
| 607 | |
| 608 // 35 | |
| 609 bool isDecDigit(int char) => char >= NUMBER_0 && char <= NUMBER_9; | |
| 610 | |
| 611 // 36 | |
| 612 bool isHexDigit(int char) { | |
| 613 return isDecDigit(char) || | |
| 614 (char >= LETTER_A && char <= LETTER_F) || | |
| 615 (char >= LETTER_CAP_A && char <= LETTER_CAP_F); | |
| 616 } | |
| 617 | |
| 618 // 41 | |
| 619 bool c_escape() => captureAs("", () => consumeChar(BACKSLASH)); | |
| 620 | |
| 621 // 42 | |
| 622 bool ns_escNull() => captureAs("\x00", () => consumeChar(NUMBER_0)); | |
| 623 | |
| 624 // 43 | |
| 625 bool ns_escBell() => captureAs("\x07", () => consumeChar(LETTER_A)); | |
| 626 | |
| 627 // 44 | |
| 628 bool ns_escBackspace() => captureAs("\b", () => consumeChar(LETTER_B)); | |
| 629 | |
| 630 // 45 | |
| 631 bool ns_escHorizontalTab() => captureAs("\t", () { | |
| 632 return consume((c) => c == LETTER_T || c == TAB); | |
| 633 }); | |
| 634 | |
| 635 // 46 | |
| 636 bool ns_escLineFeed() => captureAs("\n", () => consumeChar(LETTER_N)); | |
| 637 | |
| 638 // 47 | |
| 639 bool ns_escVerticalTab() => captureAs("\v", () => consumeChar(LETTER_V)); | |
| 640 | |
| 641 // 48 | |
| 642 bool ns_escFormFeed() => captureAs("\f", () => consumeChar(LETTER_F)); | |
| 643 | |
| 644 // 49 | |
| 645 bool ns_escCarriageReturn() => captureAs("\r", () => consumeChar(LETTER_R)); | |
| 646 | |
| 647 // 50 | |
| 648 bool ns_escEscape() => captureAs("\x1B", () => consumeChar(LETTER_E)); | |
| 649 | |
| 650 // 51 | |
| 651 bool ns_escSpace() => consumeChar(SP); | |
| 652 | |
| 653 // 52 | |
| 654 bool ns_escDoubleQuote() => consumeChar(DOUBLE_QUOTE); | |
| 655 | |
| 656 // 53 | |
| 657 bool ns_escSlash() => consumeChar(SLASH); | |
| 658 | |
| 659 // 54 | |
| 660 bool ns_escBackslash() => consumeChar(BACKSLASH); | |
| 661 | |
| 662 // 55 | |
| 663 bool ns_escNextLine() => captureAs("\x85", () => consumeChar(LETTER_CAP_N)); | |
| 664 | |
| 665 // 56 | |
| 666 bool ns_escNonBreakingSpace() => | |
| 667 captureAs("\xA0", () => consumeChar(UNDERSCORE)); | |
| 668 | |
| 669 // 57 | |
| 670 bool ns_escLineSeparator() => | |
| 671 captureAs("\u2028", () => consumeChar(LETTER_CAP_L)); | |
| 672 | |
| 673 // 58 | |
| 674 bool ns_escParagraphSeparator() => | |
| 675 captureAs("\u2029", () => consumeChar(LETTER_CAP_P)); | |
| 676 | |
| 677 // 59 | |
| 678 bool ns_esc8Bit() => ns_escNBit(LETTER_X, 2); | |
| 679 | |
| 680 // 60 | |
| 681 bool ns_esc16Bit() => ns_escNBit(LETTER_U, 4); | |
| 682 | |
| 683 // 61 | |
| 684 bool ns_esc32Bit() => ns_escNBit(LETTER_CAP_U, 8); | |
| 685 | |
| 686 // Helper method for 59 - 61 | |
| 687 bool ns_escNBit(int char, int digits) { | |
| 688 if (!captureAs('', () => consumeChar(char))) return false; | |
| 689 var captured = captureAndTransform( | |
| 690 () => nAtOnce(digits, (c, _) => isHexDigit(c)), | |
| 691 (hex) => new String.fromCharCodes([Math.parseInt("0x$hex")])); | |
| 692 return expect(captured, "$digits hexidecimal digits"); | |
| 693 } | |
| 694 | |
| 695 // 62 | |
| 696 bool c_ns_escChar() => context('escape sequence', () => transaction(() { | |
| 697 if (!truth(c_escape())) return false; | |
| 698 return truth(or([ | |
| 699 ns_escNull, ns_escBell, ns_escBackspace, ns_escHorizontalTab, | |
| 700 ns_escLineFeed, ns_escVerticalTab, ns_escFormFeed, ns_escCarriageReturn, | |
| 701 ns_escEscape, ns_escSpace, ns_escDoubleQuote, ns_escSlash, | |
| 702 ns_escBackslash, ns_escNextLine, ns_escNonBreakingSpace, | |
| 703 ns_escLineSeparator, ns_escParagraphSeparator, ns_esc8Bit, ns_esc16Bit, | |
| 704 ns_esc32Bit | |
| 705 ])); | |
| 706 })); | |
| 707 | |
| 708 // 63 | |
| 709 bool s_indent(int indent) { | |
| 710 var result = nAtOnce(indent, (c, i) => c == SP); | |
| 711 if (peek() == TAB) { | |
| 712 annotateError("tab characters are not allowed as indentation in YAML", | |
| 713 () => zeroOrMore(() => consume(isSpace))); | |
| 714 } | |
| 715 return result; | |
| 716 } | |
| 717 | |
| 718 // 64 | |
| 719 bool s_indentLessThan(int indent) { | |
| 720 for (int i = 0; i < indent - 1; i++) { | |
| 721 if (!consumeChar(SP)) { | |
| 722 if (peek() == TAB) { | |
| 723 annotateError("tab characters are not allowed as indentation in YAML", | |
| 724 () { | |
| 725 for (; i < indent - 1; i++) { | |
| 726 if (!consume(isSpace)) break; | |
| 727 } | |
| 728 }); | |
| 729 } | |
| 730 break; | |
| 731 } | |
| 732 } | |
| 733 return true; | |
| 734 } | |
| 735 | |
| 736 // 65 | |
| 737 bool s_indentLessThanOrEqualTo(int indent) => s_indentLessThan(indent + 1); | |
| 738 | |
| 739 // 66 | |
| 740 bool s_separateInLine() => transaction(() { | |
| 741 return captureAs('', () => | |
| 742 truth(oneOrMore(() => consume(isSpace))) || atStartOfLine); | |
| 743 }); | |
| 744 | |
| 745 // 67 | |
| 746 bool s_linePrefix(int indent, int ctx) => captureAs("", () { | |
| 747 switch (ctx) { | |
| 748 case BLOCK_OUT: | |
| 749 case BLOCK_IN: | |
| 750 return s_blockLinePrefix(indent); | |
| 751 case FLOW_OUT: | |
| 752 case FLOW_IN: | |
| 753 return s_flowLinePrefix(indent); | |
| 754 } | |
| 755 }); | |
| 756 | |
| 757 // 68 | |
| 758 bool s_blockLinePrefix(int indent) => s_indent(indent); | |
| 759 | |
| 760 // 69 | |
| 761 bool s_flowLinePrefix(int indent) => captureAs('', () { | |
| 762 if (!truth(s_indent(indent))) return false; | |
| 763 zeroOrOne(s_separateInLine); | |
| 764 return true; | |
| 765 }); | |
| 766 | |
| 767 // 70 | |
| 768 bool l_empty(int indent, int ctx) => transaction(() { | |
| 769 var start = or([ | |
| 770 () => s_linePrefix(indent, ctx), | |
| 771 () => s_indentLessThan(indent) | |
| 772 ]); | |
| 773 if (!truth(start)) return false; | |
| 774 return b_asLineFeed(); | |
| 775 }); | |
| 776 | |
| 777 // 71 | |
| 778 bool b_asSpace() => captureAs(" ", () => consume(isBreak)); | |
| 779 | |
| 780 // 72 | |
| 781 bool b_l_trimmed(int indent, int ctx) => transaction(() { | |
| 782 if (!truth(b_nonContent())) return false; | |
| 783 return truth(oneOrMore(() => captureAs("\n", () => l_empty(indent, ctx)))); | |
| 784 }); | |
| 785 | |
| 786 // 73 | |
| 787 bool b_l_folded(int indent, int ctx) => | |
| 788 or([() => b_l_trimmed(indent, ctx), b_asSpace]); | |
| 789 | |
| 790 // 74 | |
| 791 bool s_flowFolded(int indent) => transaction(() { | |
| 792 zeroOrOne(s_separateInLine); | |
| 793 if (!truth(b_l_folded(indent, FLOW_IN))) return false; | |
| 794 return s_flowLinePrefix(indent); | |
| 795 }); | |
| 796 | |
| 797 // 75 | |
| 798 bool c_nb_commentText() { | |
| 799 if (!truth(c_indicator(C_COMMENT))) return false; | |
| 800 zeroOrMore(() => consume(isNonBreak)); | |
| 801 return true; | |
| 802 } | |
| 803 | |
| 804 // 76 | |
| 805 bool b_comment() => atEndOfFile || b_nonContent(); | |
| 806 | |
| 807 // 77 | |
| 808 bool s_b_comment() { | |
| 809 if (truth(s_separateInLine())) { | |
| 810 zeroOrOne(c_nb_commentText); | |
| 811 } | |
| 812 return b_comment(); | |
| 813 } | |
| 814 | |
| 815 // 78 | |
| 816 bool l_comment() => transaction(() { | |
| 817 if (!truth(s_separateInLine())) return false; | |
| 818 zeroOrOne(c_nb_commentText); | |
| 819 return b_comment(); | |
| 820 }); | |
| 821 | |
| 822 // 79 | |
| 823 bool s_l_comments() { | |
| 824 if (!truth(s_b_comment()) && !atStartOfLine) return false; | |
| 825 zeroOrMore(l_comment); | |
| 826 return true; | |
| 827 } | |
| 828 | |
| 829 // 80 | |
| 830 bool s_separate(int indent, int ctx) { | |
| 831 switch (ctx) { | |
| 832 case BLOCK_OUT: | |
| 833 case BLOCK_IN: | |
| 834 case FLOW_OUT: | |
| 835 case FLOW_IN: | |
| 836 return s_separateLines(indent); | |
| 837 case BLOCK_KEY: | |
| 838 case FLOW_KEY: | |
| 839 return s_separateInLine(); | |
| 840 default: throw 'invalid context "$ctx"'; | |
| 841 } | |
| 842 } | |
| 843 | |
| 844 // 81 | |
| 845 bool s_separateLines(int indent) { | |
| 846 return transaction(() => s_l_comments() && s_flowLinePrefix(indent)) || | |
| 847 s_separateInLine(); | |
| 848 } | |
| 849 | |
| 850 // 82 | |
| 851 bool l_directive() => false; // TODO(nweiz): implement | |
| 852 | |
| 853 // 96 | |
| 854 _Pair<_Tag, String> c_ns_properties(int indent, int ctx) { | |
| 855 var tag, anchor; | |
| 856 tag = c_ns_tagProperty(); | |
| 857 if (truth(tag)) { | |
| 858 anchor = transaction(() { | |
| 859 if (!truth(s_separate(indent, ctx))) return null; | |
| 860 return c_ns_anchorProperty(); | |
| 861 }); | |
| 862 return new _Pair<_Tag, String>(tag, anchor); | |
| 863 } | |
| 864 | |
| 865 anchor = c_ns_anchorProperty(); | |
| 866 if (truth(anchor)) { | |
| 867 tag = transaction(() { | |
| 868 if (!truth(s_separate(indent, ctx))) return null; | |
| 869 return c_ns_tagProperty(); | |
| 870 }); | |
| 871 return new _Pair<_Tag, String>(tag, anchor); | |
| 872 } | |
| 873 | |
| 874 return null; | |
| 875 } | |
| 876 | |
| 877 // 97 | |
| 878 _Tag c_ns_tagProperty() => null; // TODO(nweiz): implement | |
| 879 | |
| 880 // 101 | |
| 881 String c_ns_anchorProperty() => null; // TODO(nweiz): implement | |
| 882 | |
| 883 // 102 | |
| 884 bool isAnchorChar(int char) => isNonSpace(char) && !isFlowIndicator(char); | |
| 885 | |
| 886 // 103 | |
| 887 String ns_anchorName() => | |
| 888 captureString(() => oneOrMore(() => consume(isAnchorChar))); | |
| 889 | |
| 890 // 104 | |
| 891 _Node c_ns_aliasNode() { | |
| 892 if (!truth(c_indicator(C_ALIAS))) return null; | |
| 893 var name = expect(ns_anchorName(), 'anchor name'); | |
| 894 return new _AliasNode(name); | |
| 895 } | |
| 896 | |
| 897 // 105 | |
| 898 _ScalarNode e_scalar() => new _ScalarNode("?", content: ""); | |
| 899 | |
| 900 // 106 | |
| 901 _ScalarNode e_node() => e_scalar(); | |
| 902 | |
| 903 // 107 | |
| 904 bool nb_doubleChar() => or([ | |
| 905 c_ns_escChar, | |
| 906 () => consume((c) => isJson(c) && c != BACKSLASH && c != DOUBLE_QUOTE) | |
| 907 ]); | |
| 908 | |
| 909 // 108 | |
| 910 bool ns_doubleChar() => !isSpace(peek()) && truth(nb_doubleChar()); | |
| 911 | |
| 912 // 109 | |
| 913 _Node c_doubleQuoted(int indent, int ctx) => context('string', () { | |
| 914 return transaction(() { | |
| 915 if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null; | |
| 916 var contents = nb_doubleText(indent, ctx); | |
| 917 if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null; | |
| 918 return new _ScalarNode("!", content: contents); | |
| 919 }); | |
| 920 }); | |
| 921 | |
| 922 // 110 | |
| 923 String nb_doubleText(int indent, int ctx) => captureString(() { | |
| 924 switch (ctx) { | |
| 925 case FLOW_OUT: | |
| 926 case FLOW_IN: | |
| 927 nb_doubleMultiLine(indent); | |
| 928 break; | |
| 929 case BLOCK_KEY: | |
| 930 case FLOW_KEY: | |
| 931 nb_doubleOneLine(); | |
| 932 break; | |
| 933 } | |
| 934 return true; | |
| 935 }); | |
| 936 | |
| 937 // 111 | |
| 938 void nb_doubleOneLine() { | |
| 939 zeroOrMore(nb_doubleChar); | |
| 940 } | |
| 941 | |
| 942 // 112 | |
| 943 bool s_doubleEscaped(int indent) => transaction(() { | |
| 944 zeroOrMore(() => consume(isSpace)); | |
| 945 if (!captureAs("", () => consumeChar(BACKSLASH))) return false; | |
| 946 if (!truth(b_nonContent())) return false; | |
| 947 zeroOrMore(() => captureAs("\n", () => l_empty(indent, FLOW_IN))); | |
| 948 return s_flowLinePrefix(indent); | |
| 949 }); | |
| 950 | |
| 951 // 113 | |
| 952 bool s_doubleBreak(int indent) => or([ | |
| 953 () => s_doubleEscaped(indent), | |
| 954 () => s_flowFolded(indent) | |
| 955 ]); | |
| 956 | |
| 957 // 114 | |
| 958 void nb_ns_doubleInLine() { | |
| 959 zeroOrMore(() => transaction(() { | |
| 960 zeroOrMore(() => consume(isSpace)); | |
| 961 return ns_doubleChar(); | |
| 962 })); | |
| 963 } | |
| 964 | |
| 965 // 115 | |
| 966 bool s_doubleNextLine(int indent) { | |
| 967 if (!truth(s_doubleBreak(indent))) return false; | |
| 968 zeroOrOne(() { | |
| 969 if (!truth(ns_doubleChar())) return; | |
| 970 nb_ns_doubleInLine(); | |
| 971 or([ | |
| 972 () => s_doubleNextLine(indent), | |
| 973 () => zeroOrMore(() => consume(isSpace)) | |
| 974 ]); | |
| 975 }); | |
| 976 return true; | |
| 977 } | |
| 978 | |
| 979 // 116 | |
| 980 void nb_doubleMultiLine(int indent) { | |
| 981 nb_ns_doubleInLine(); | |
| 982 or([ | |
| 983 () => s_doubleNextLine(indent), | |
| 984 () => zeroOrMore(() => consume(isSpace)) | |
| 985 ]); | |
| 986 } | |
| 987 | |
| 988 // 117 | |
| 989 bool c_quotedQuote() => captureAs("'", () => rawString("''")); | |
| 990 | |
| 991 // 118 | |
| 992 bool nb_singleChar() => or([ | |
| 993 c_quotedQuote, | |
| 994 () => consume((c) => isJson(c) && c != SINGLE_QUOTE) | |
| 995 ]); | |
| 996 | |
| 997 // 119 | |
| 998 bool ns_singleChar() => !isSpace(peek()) && truth(nb_singleChar()); | |
| 999 | |
| 1000 // 120 | |
| 1001 _Node c_singleQuoted(int indent, int ctx) => context('string', () { | |
| 1002 return transaction(() { | |
| 1003 if (!truth(c_indicator(C_SINGLE_QUOTE))) return null; | |
| 1004 var contents = nb_singleText(indent, ctx); | |
| 1005 if (!truth(c_indicator(C_SINGLE_QUOTE))) return null; | |
| 1006 return new _ScalarNode("!", content: contents); | |
| 1007 }); | |
| 1008 }); | |
| 1009 | |
| 1010 // 121 | |
| 1011 String nb_singleText(int indent, int ctx) => captureString(() { | |
| 1012 switch (ctx) { | |
| 1013 case FLOW_OUT: | |
| 1014 case FLOW_IN: | |
| 1015 nb_singleMultiLine(indent); | |
| 1016 break; | |
| 1017 case BLOCK_KEY: | |
| 1018 case FLOW_KEY: | |
| 1019 nb_singleOneLine(indent); | |
| 1020 break; | |
| 1021 } | |
| 1022 return true; | |
| 1023 }); | |
| 1024 | |
| 1025 // 122 | |
| 1026 void nb_singleOneLine(int indent) { | |
| 1027 zeroOrMore(nb_singleChar); | |
| 1028 } | |
| 1029 | |
| 1030 // 123 | |
| 1031 void nb_ns_singleInLine() { | |
| 1032 zeroOrMore(() => transaction(() { | |
| 1033 zeroOrMore(() => consume(isSpace)); | |
| 1034 return ns_singleChar(); | |
| 1035 })); | |
| 1036 } | |
| 1037 | |
| 1038 // 124 | |
| 1039 bool s_singleNextLine(int indent) { | |
| 1040 if (!truth(s_flowFolded(indent))) return false; | |
| 1041 zeroOrOne(() { | |
| 1042 if (!truth(ns_singleChar())) return; | |
| 1043 nb_ns_singleInLine(); | |
| 1044 or([ | |
| 1045 () => s_singleNextLine(indent), | |
| 1046 () => zeroOrMore(() => consume(isSpace)) | |
| 1047 ]); | |
| 1048 }); | |
| 1049 return true; | |
| 1050 } | |
| 1051 | |
| 1052 // 125 | |
| 1053 void nb_singleMultiLine(int indent) { | |
| 1054 nb_ns_singleInLine(); | |
| 1055 or([ | |
| 1056 () => s_singleNextLine(indent), | |
| 1057 () => zeroOrMore(() => consume(isSpace)) | |
| 1058 ]); | |
| 1059 } | |
| 1060 | |
| 1061 // 126 | |
| 1062 bool ns_plainFirst(int ctx) { | |
| 1063 var char = peek(); | |
| 1064 var indicator = indicatorType(char); | |
| 1065 if (indicator == C_RESERVED) { | |
| 1066 error("reserved indicators can't start a plain scalar"); | |
| 1067 } | |
| 1068 var match = (isNonSpace(char) && indicator == null) || | |
| 1069 ((indicator == C_MAPPING_KEY || | |
| 1070 indicator == C_MAPPING_VALUE || | |
| 1071 indicator == C_SEQUENCE_ENTRY) && | |
| 1072 isPlainSafe(ctx, peek(1))); | |
| 1073 | |
| 1074 if (match) next(); | |
| 1075 return match; | |
| 1076 } | |
| 1077 | |
| 1078 // 127 | |
| 1079 bool isPlainSafe(int ctx, int char) { | |
| 1080 switch (ctx) { | |
| 1081 case FLOW_OUT: | |
| 1082 case BLOCK_KEY: | |
| 1083 // 128 | |
| 1084 return isNonSpace(char); | |
| 1085 case FLOW_IN: | |
| 1086 case FLOW_KEY: | |
| 1087 // 129 | |
| 1088 return isNonSpace(char) && !isFlowIndicator(char); | |
| 1089 default: throw 'invalid context "$ctx"'; | |
| 1090 } | |
| 1091 } | |
| 1092 | |
| 1093 // 130 | |
| 1094 bool ns_plainChar(int ctx) { | |
| 1095 var char = peek(); | |
| 1096 var indicator = indicatorType(char); | |
| 1097 var safeChar = isPlainSafe(ctx, char) && indicator != C_MAPPING_VALUE && | |
| 1098 indicator != C_COMMENT; | |
| 1099 var nonCommentHash = isNonSpace(peek(-1)) && indicator == C_COMMENT; | |
| 1100 var nonMappingColon = indicator == C_MAPPING_VALUE && | |
| 1101 isPlainSafe(ctx, peek(1)); | |
| 1102 var match = safeChar || nonCommentHash || nonMappingColon; | |
| 1103 | |
| 1104 if (match) next(); | |
| 1105 return match; | |
| 1106 } | |
| 1107 | |
| 1108 // 131 | |
| 1109 String ns_plain(int indent, int ctx) => context('plain scalar', () { | |
| 1110 return captureString(() { | |
| 1111 switch (ctx) { | |
| 1112 case FLOW_OUT: | |
| 1113 case FLOW_IN: | |
| 1114 return ns_plainMultiLine(indent, ctx); | |
| 1115 case BLOCK_KEY: | |
| 1116 case FLOW_KEY: | |
| 1117 return ns_plainOneLine(ctx); | |
| 1118 default: throw 'invalid context "$ctx"'; | |
| 1119 } | |
| 1120 }); | |
| 1121 }); | |
| 1122 | |
| 1123 // 132 | |
| 1124 void nb_ns_plainInLine(int ctx) { | |
| 1125 zeroOrMore(() => transaction(() { | |
| 1126 zeroOrMore(() => consume(isSpace)); | |
| 1127 return ns_plainChar(ctx); | |
| 1128 })); | |
| 1129 } | |
| 1130 | |
| 1131 // 133 | |
| 1132 bool ns_plainOneLine(int ctx) { | |
| 1133 if (truth(c_forbidden())) return false; | |
| 1134 if (!truth(ns_plainFirst(ctx))) return false; | |
| 1135 nb_ns_plainInLine(ctx); | |
| 1136 return true; | |
| 1137 } | |
| 1138 | |
| 1139 // 134 | |
| 1140 bool s_ns_plainNextLine(int indent, int ctx) => transaction(() { | |
| 1141 if (!truth(s_flowFolded(indent))) return false; | |
| 1142 if (truth(c_forbidden())) return false; | |
| 1143 if (!truth(ns_plainChar(ctx))) return false; | |
| 1144 nb_ns_plainInLine(ctx); | |
| 1145 return true; | |
| 1146 }); | |
| 1147 | |
| 1148 // 135 | |
| 1149 bool ns_plainMultiLine(int indent, int ctx) { | |
| 1150 if (!truth(ns_plainOneLine(ctx))) return false; | |
| 1151 zeroOrMore(() => s_ns_plainNextLine(indent, ctx)); | |
| 1152 return true; | |
| 1153 } | |
| 1154 | |
| 1155 // 136 | |
| 1156 int inFlow(int ctx) { | |
| 1157 switch (ctx) { | |
| 1158 case FLOW_OUT: | |
| 1159 case FLOW_IN: | |
| 1160 return FLOW_IN; | |
| 1161 case BLOCK_KEY: | |
| 1162 case FLOW_KEY: | |
| 1163 return FLOW_KEY; | |
| 1164 } | |
| 1165 } | |
| 1166 | |
| 1167 // 137 | |
| 1168 _SequenceNode c_flowSequence(int indent, int ctx) => transaction(() { | |
| 1169 if (!truth(c_indicator(C_SEQUENCE_START))) return null; | |
| 1170 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1171 var content = zeroOrOne(() => ns_s_flowSeqEntries(indent, inFlow(ctx))); | |
| 1172 if (!truth(c_indicator(C_SEQUENCE_END))) return null; | |
| 1173 return new _SequenceNode("?", new List<_Node>.from(content)); | |
| 1174 }); | |
| 1175 | |
| 1176 // 138 | |
| 1177 Collection<_Node> ns_s_flowSeqEntries(int indent, int ctx) { | |
| 1178 var first = ns_flowSeqEntry(indent, ctx); | |
| 1179 if (!truth(first)) return new Queue<_Node>(); | |
| 1180 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1181 | |
| 1182 var rest; | |
| 1183 if (truth(c_indicator(C_COLLECT_ENTRY))) { | |
| 1184 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1185 rest = zeroOrOne(() => ns_s_flowSeqEntries(indent, ctx)); | |
| 1186 } | |
| 1187 | |
| 1188 if (rest == null) rest = new Queue<_Node>(); | |
| 1189 rest.addFirst(first); | |
| 1190 | |
| 1191 return rest; | |
| 1192 } | |
| 1193 | |
| 1194 // 139 | |
| 1195 _Node ns_flowSeqEntry(int indent, int ctx) => or([ | |
| 1196 () => ns_flowPair(indent, ctx), | |
| 1197 () => ns_flowNode(indent, ctx) | |
| 1198 ]); | |
| 1199 | |
| 1200 // 140 | |
| 1201 _Node c_flowMapping(int indent, int ctx) { | |
| 1202 if (!truth(c_indicator(C_MAPPING_START))) return null; | |
| 1203 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1204 var content = zeroOrOne(() => ns_s_flowMapEntries(indent, inFlow(ctx))); | |
| 1205 if (!truth(c_indicator(C_MAPPING_END))) return null; | |
| 1206 return new _MappingNode("?", content); | |
| 1207 } | |
| 1208 | |
| 1209 // 141 | |
| 1210 YamlMap ns_s_flowMapEntries(int indent, int ctx) { | |
| 1211 var first = ns_flowMapEntry(indent, ctx); | |
| 1212 if (!truth(first)) return new YamlMap(); | |
| 1213 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1214 | |
| 1215 var rest; | |
| 1216 if (truth(c_indicator(C_COLLECT_ENTRY))) { | |
| 1217 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1218 rest = ns_s_flowMapEntries(indent, ctx); | |
| 1219 } | |
| 1220 | |
| 1221 if (rest == null) rest = new YamlMap(); | |
| 1222 | |
| 1223 // TODO(nweiz): Duplicate keys should be an error. This includes keys with | |
| 1224 // different representations but the same value (e.g. 10 vs 0xa). To make | |
| 1225 // this user-friendly we'll probably also want to associate nodes with a | |
| 1226 // source range. | |
| 1227 if (!rest.containsKey(first.first)) rest[first.first] = first.last; | |
| 1228 | |
| 1229 return rest; | |
| 1230 } | |
| 1231 | |
| 1232 // 142 | |
| 1233 _Pair<_Node, _Node> ns_flowMapEntry(int indent, int ctx) => or([ | |
| 1234 () => transaction(() { | |
| 1235 if (!truth(c_indicator(C_MAPPING_KEY))) return false; | |
| 1236 if (!truth(s_separate(indent, ctx))) return false; | |
| 1237 return ns_flowMapExplicitEntry(indent, ctx); | |
| 1238 }), | |
| 1239 () => ns_flowMapImplicitEntry(indent, ctx) | |
| 1240 ]); | |
| 1241 | |
| 1242 // 143 | |
| 1243 _Pair<_Node, _Node> ns_flowMapExplicitEntry(int indent, int ctx) => or([ | |
| 1244 () => ns_flowMapImplicitEntry(indent, ctx), | |
| 1245 () => new _Pair<_Node, _Node>(e_node(), e_node()) | |
| 1246 ]); | |
| 1247 | |
| 1248 // 144 | |
| 1249 _Pair<_Node, _Node> ns_flowMapImplicitEntry(int indent, int ctx) => or([ | |
| 1250 () => ns_flowMapYamlKeyEntry(indent, ctx), | |
| 1251 () => c_ns_flowMapEmptyKeyEntry(indent, ctx), | |
| 1252 () => c_ns_flowMapJsonKeyEntry(indent, ctx) | |
| 1253 ]); | |
| 1254 | |
| 1255 // 145 | |
| 1256 _Pair<_Node, _Node> ns_flowMapYamlKeyEntry(int indent, int ctx) { | |
| 1257 var key = ns_flowYamlNode(indent, ctx); | |
| 1258 if (!truth(key)) return null; | |
| 1259 var value = or([ | |
| 1260 () => transaction(() { | |
| 1261 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1262 return c_ns_flowMapSeparateValue(indent, ctx); | |
| 1263 }), | |
| 1264 e_node | |
| 1265 ]); | |
| 1266 return new _Pair<_Node, _Node>(key, value); | |
| 1267 } | |
| 1268 | |
| 1269 // 146 | |
| 1270 _Pair<_Node, _Node> c_ns_flowMapEmptyKeyEntry(int indent, int ctx) { | |
| 1271 var value = c_ns_flowMapSeparateValue(indent, ctx); | |
| 1272 if (!truth(value)) return null; | |
| 1273 return new _Pair<_Node, _Node>(e_node(), value); | |
| 1274 } | |
| 1275 | |
| 1276 // 147 | |
| 1277 _Node c_ns_flowMapSeparateValue(int indent, int ctx) => transaction(() { | |
| 1278 if (!truth(c_indicator(C_MAPPING_VALUE))) return null; | |
| 1279 if (isPlainSafe(ctx, peek())) return null; | |
| 1280 | |
| 1281 return or([ | |
| 1282 () => transaction(() { | |
| 1283 if (!s_separate(indent, ctx)) return null; | |
| 1284 return ns_flowNode(indent, ctx); | |
| 1285 }), | |
| 1286 e_node | |
| 1287 ]); | |
| 1288 }); | |
| 1289 | |
| 1290 // 148 | |
| 1291 _Pair<_Node, _Node> c_ns_flowMapJsonKeyEntry(int indent, int ctx) { | |
| 1292 var key = c_flowJsonNode(indent, ctx); | |
| 1293 if (!truth(key)) return null; | |
| 1294 var value = or([ | |
| 1295 () => transaction(() { | |
| 1296 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1297 return c_ns_flowMapAdjacentValue(indent, ctx); | |
| 1298 }), | |
| 1299 e_node | |
| 1300 ]); | |
| 1301 return new _Pair<_Node, _Node>(key, value); | |
| 1302 } | |
| 1303 | |
| 1304 // 149 | |
| 1305 _Node c_ns_flowMapAdjacentValue(int indent, int ctx) { | |
| 1306 if (!truth(c_indicator(C_MAPPING_VALUE))) return null; | |
| 1307 return or([ | |
| 1308 () => transaction(() { | |
| 1309 zeroOrOne(() => s_separate(indent, ctx)); | |
| 1310 return ns_flowNode(indent, ctx); | |
| 1311 }), | |
| 1312 e_node | |
| 1313 ]); | |
| 1314 } | |
| 1315 | |
| 1316 // 150 | |
| 1317 _Node ns_flowPair(int indent, int ctx) { | |
| 1318 var pair = or([ | |
| 1319 () => transaction(() { | |
| 1320 if (!truth(c_indicator(C_MAPPING_KEY))) return null; | |
| 1321 if (!truth(s_separate(indent, ctx))) return null; | |
| 1322 return ns_flowMapExplicitEntry(indent, ctx); | |
| 1323 }), | |
| 1324 () => ns_flowPairEntry(indent, ctx) | |
| 1325 ]); | |
| 1326 if (!truth(pair)) return null; | |
| 1327 | |
| 1328 return map([pair]); | |
| 1329 } | |
| 1330 | |
| 1331 // 151 | |
| 1332 _Pair<_Node, _Node> ns_flowPairEntry(int indent, int ctx) => or([ | |
| 1333 () => ns_flowPairYamlKeyEntry(indent, ctx), | |
| 1334 () => c_ns_flowMapEmptyKeyEntry(indent, ctx), | |
| 1335 () => c_ns_flowPairJsonKeyEntry(indent, ctx) | |
| 1336 ]); | |
| 1337 | |
| 1338 // 152 | |
| 1339 _Pair<_Node, _Node> ns_flowPairYamlKeyEntry(int indent, int ctx) => | |
| 1340 transaction(() { | |
| 1341 var key = ns_s_implicitYamlKey(FLOW_KEY); | |
| 1342 if (!truth(key)) return null; | |
| 1343 var value = c_ns_flowMapSeparateValue(indent, ctx); | |
| 1344 if (!truth(value)) return null; | |
| 1345 return new _Pair<_Node, _Node>(key, value); | |
| 1346 }); | |
| 1347 | |
| 1348 // 153 | |
| 1349 _Pair<_Node, _Node> c_ns_flowPairJsonKeyEntry(int indent, int ctx) => | |
| 1350 transaction(() { | |
| 1351 var key = c_s_implicitJsonKey(FLOW_KEY); | |
| 1352 if (!truth(key)) return null; | |
| 1353 var value = c_ns_flowMapAdjacentValue(indent, ctx); | |
| 1354 if (!truth(value)) return null; | |
| 1355 return new _Pair<_Node, _Node>(key, value); | |
| 1356 }); | |
| 1357 | |
| 1358 // 154 | |
| 1359 _Node ns_s_implicitYamlKey(int ctx) => transaction(() { | |
| 1360 // TODO(nweiz): this is supposed to be limited to 1024 characters. | |
| 1361 | |
| 1362 // The indentation parameter is "null" since it's unused in this path | |
| 1363 var node = ns_flowYamlNode(null, ctx); | |
| 1364 if (!truth(node)) return null; | |
| 1365 zeroOrOne(s_separateInLine); | |
| 1366 return node; | |
| 1367 }); | |
| 1368 | |
| 1369 // 155 | |
| 1370 _Node c_s_implicitJsonKey(int ctx) => transaction(() { | |
| 1371 // TODO(nweiz): this is supposed to be limited to 1024 characters. | |
| 1372 | |
| 1373 // The indentation parameter is "null" since it's unused in this path | |
| 1374 var node = c_flowJsonNode(null, ctx); | |
| 1375 if (!truth(node)) return null; | |
| 1376 zeroOrOne(s_separateInLine); | |
| 1377 return node; | |
| 1378 }); | |
| 1379 | |
| 1380 // 156 | |
| 1381 _Node ns_flowYamlContent(int indent, int ctx) { | |
| 1382 var str = ns_plain(indent, ctx); | |
| 1383 if (!truth(str)) return null; | |
| 1384 return new _ScalarNode("?", content: str); | |
| 1385 } | |
| 1386 | |
| 1387 // 157 | |
| 1388 _Node c_flowJsonContent(int indent, int ctx) => or([ | |
| 1389 () => c_flowSequence(indent, ctx), | |
| 1390 () => c_flowMapping(indent, ctx), | |
| 1391 () => c_singleQuoted(indent, ctx), | |
| 1392 () => c_doubleQuoted(indent, ctx) | |
| 1393 ]); | |
| 1394 | |
| 1395 // 158 | |
| 1396 _Node ns_flowContent(int indent, int ctx) => or([ | |
| 1397 () => ns_flowYamlContent(indent, ctx), | |
| 1398 () => c_flowJsonContent(indent, ctx) | |
| 1399 ]); | |
| 1400 | |
| 1401 // 159 | |
| 1402 _Node ns_flowYamlNode(int indent, int ctx) => or([ | |
| 1403 c_ns_aliasNode, | |
| 1404 () => ns_flowYamlContent(indent, ctx), | |
| 1405 () { | |
| 1406 var props = c_ns_properties(indent, ctx); | |
| 1407 if (!truth(props)) return null; | |
| 1408 var node = or([ | |
| 1409 () => transaction(() { | |
| 1410 if (!truth(s_separate(indent, ctx))) return null; | |
| 1411 return ns_flowYamlContent(indent, ctx); | |
| 1412 }), | |
| 1413 e_scalar | |
| 1414 ]); | |
| 1415 return addProps(node, props); | |
| 1416 } | |
| 1417 ]); | |
| 1418 | |
| 1419 // 160 | |
| 1420 _Node c_flowJsonNode(int indent, int ctx) => transaction(() { | |
| 1421 var props; | |
| 1422 zeroOrOne(() => transaction(() { | |
| 1423 props = c_ns_properties(indent, ctx); | |
| 1424 if (!truth(props)) return null; | |
| 1425 return s_separate(indent, ctx); | |
| 1426 })); | |
| 1427 | |
| 1428 return addProps(c_flowJsonContent(indent, ctx), props); | |
| 1429 }); | |
| 1430 | |
| 1431 // 161 | |
| 1432 _Node ns_flowNode(int indent, int ctx) => or([ | |
| 1433 c_ns_aliasNode, | |
| 1434 () => ns_flowContent(indent, ctx), | |
| 1435 () => transaction(() { | |
| 1436 var props = c_ns_properties(indent, ctx); | |
| 1437 if (!truth(props)) return null; | |
| 1438 var node = or([ | |
| 1439 () => transaction(() => s_separate(indent, ctx) ? | |
| 1440 ns_flowContent(indent, ctx) : null), | |
| 1441 e_scalar]); | |
| 1442 return addProps(node, props); | |
| 1443 }) | |
| 1444 ]); | |
| 1445 | |
| 1446 // 162 | |
| 1447 _BlockHeader c_b_blockHeader() => transaction(() { | |
| 1448 var indentation = c_indentationIndicator(); | |
| 1449 var chomping = c_chompingIndicator(); | |
| 1450 if (!truth(indentation)) indentation = c_indentationIndicator(); | |
| 1451 if (!truth(s_b_comment())) return null; | |
| 1452 | |
| 1453 return new _BlockHeader(indentation, chomping); | |
| 1454 }); | |
| 1455 | |
| 1456 // 163 | |
| 1457 int c_indentationIndicator() { | |
| 1458 if (!isDecDigit(peek())) return null; | |
| 1459 return next() - NUMBER_0; | |
| 1460 } | |
| 1461 | |
| 1462 // 164 | |
| 1463 int c_chompingIndicator() { | |
| 1464 switch (peek()) { | |
| 1465 case HYPHEN: | |
| 1466 next(); | |
| 1467 return CHOMPING_STRIP; | |
| 1468 case PLUS: | |
| 1469 next(); | |
| 1470 return CHOMPING_KEEP; | |
| 1471 default: | |
| 1472 return CHOMPING_CLIP; | |
| 1473 } | |
| 1474 } | |
| 1475 | |
| 1476 // 165 | |
| 1477 bool b_chompedLast(int chomping) { | |
| 1478 if (atEndOfFile) return true; | |
| 1479 switch (chomping) { | |
| 1480 case CHOMPING_STRIP: | |
| 1481 return b_nonContent(); | |
| 1482 case CHOMPING_CLIP: | |
| 1483 case CHOMPING_KEEP: | |
| 1484 return b_asLineFeed(); | |
| 1485 } | |
| 1486 } | |
| 1487 | |
| 1488 // 166 | |
| 1489 void l_chompedEmpty(int indent, int chomping) { | |
| 1490 switch (chomping) { | |
| 1491 case CHOMPING_STRIP: | |
| 1492 case CHOMPING_CLIP: | |
| 1493 l_stripEmpty(indent); | |
| 1494 break; | |
| 1495 case CHOMPING_KEEP: | |
| 1496 l_keepEmpty(indent); | |
| 1497 break; | |
| 1498 } | |
| 1499 } | |
| 1500 | |
| 1501 // 167 | |
| 1502 void l_stripEmpty(int indent) { | |
| 1503 captureAs('', () { | |
| 1504 zeroOrMore(() => transaction(() { | |
| 1505 if (!truth(s_indentLessThanOrEqualTo(indent))) return false; | |
| 1506 return b_nonContent(); | |
| 1507 })); | |
| 1508 zeroOrOne(() => l_trailComments(indent)); | |
| 1509 return true; | |
| 1510 }); | |
| 1511 } | |
| 1512 | |
| 1513 // 168 | |
| 1514 void l_keepEmpty(int indent) { | |
| 1515 zeroOrMore(() => captureAs('\n', () => l_empty(indent, BLOCK_IN))); | |
| 1516 zeroOrOne(() => captureAs('', () => l_trailComments(indent))); | |
| 1517 } | |
| 1518 | |
| 1519 // 169 | |
| 1520 bool l_trailComments(int indent) => transaction(() { | |
| 1521 if (!truth(s_indentLessThanOrEqualTo(indent))) return false; | |
| 1522 if (!truth(c_nb_commentText())) return false; | |
| 1523 if (!truth(b_comment())) return false; | |
| 1524 zeroOrMore(l_comment); | |
| 1525 return true; | |
| 1526 }); | |
| 1527 | |
| 1528 // 170 | |
| 1529 _Node c_l_literal(int indent) => transaction(() { | |
| 1530 if (!truth(c_indicator(C_LITERAL))) return null; | |
| 1531 var header = c_b_blockHeader(); | |
| 1532 if (!truth(header)) return null; | |
| 1533 | |
| 1534 var additionalIndent = blockScalarAdditionalIndentation(header, indent); | |
| 1535 var content = l_literalContent(indent + additionalIndent, header.chomping); | |
| 1536 if (!truth(content)) return null; | |
| 1537 | |
| 1538 return new _ScalarNode("!", content: content); | |
| 1539 }); | |
| 1540 | |
| 1541 // 171 | |
| 1542 bool l_nb_literalText(int indent) => transaction(() { | |
| 1543 zeroOrMore(() => captureAs("\n", () => l_empty(indent, BLOCK_IN))); | |
| 1544 if (!truth(captureAs("", () => s_indent(indent)))) return false; | |
| 1545 return truth(oneOrMore(() => consume(isNonBreak))); | |
| 1546 }); | |
| 1547 | |
| 1548 // 172 | |
| 1549 bool b_nb_literalNext(int indent) => transaction(() { | |
| 1550 if (!truth(b_asLineFeed())) return false; | |
| 1551 return l_nb_literalText(indent); | |
| 1552 }); | |
| 1553 | |
| 1554 // 173 | |
| 1555 String l_literalContent(int indent, int chomping) => captureString(() { | |
| 1556 transaction(() { | |
| 1557 if (!truth(l_nb_literalText(indent))) return false; | |
| 1558 zeroOrMore(() => b_nb_literalNext(indent)); | |
| 1559 return b_chompedLast(chomping); | |
| 1560 }); | |
| 1561 l_chompedEmpty(indent, chomping); | |
| 1562 return true; | |
| 1563 }); | |
| 1564 | |
| 1565 // 174 | |
| 1566 _Node c_l_folded(int indent) => transaction(() { | |
| 1567 if (!truth(c_indicator(C_FOLDED))) return null; | |
| 1568 var header = c_b_blockHeader(); | |
| 1569 if (!truth(header)) return null; | |
| 1570 | |
| 1571 var additionalIndent = blockScalarAdditionalIndentation(header, indent); | |
| 1572 var content = l_foldedContent(indent + additionalIndent, header.chomping); | |
| 1573 if (!truth(content)) return null; | |
| 1574 | |
| 1575 return new _ScalarNode("!", content: content); | |
| 1576 }); | |
| 1577 | |
| 1578 // 175 | |
| 1579 bool s_nb_foldedText(int indent) => transaction(() { | |
| 1580 if (!truth(captureAs('', () => s_indent(indent)))) return false; | |
| 1581 if (!truth(consume(isNonSpace))) return false; | |
| 1582 zeroOrMore(() => consume(isNonBreak)); | |
| 1583 return true; | |
| 1584 }); | |
| 1585 | |
| 1586 // 176 | |
| 1587 bool l_nb_foldedLines(int indent) { | |
| 1588 if (!truth(s_nb_foldedText(indent))) return false; | |
| 1589 zeroOrMore(() => transaction(() { | |
| 1590 if (!truth(b_l_folded(indent, BLOCK_IN))) return false; | |
| 1591 return s_nb_foldedText(indent); | |
| 1592 })); | |
| 1593 return true; | |
| 1594 } | |
| 1595 | |
| 1596 // 177 | |
| 1597 bool s_nb_spacedText(int indent) => transaction(() { | |
| 1598 if (!truth(captureAs('', () => s_indent(indent)))) return false; | |
| 1599 if (!truth(consume(isSpace))) return false; | |
| 1600 zeroOrMore(() => consume(isNonBreak)); | |
| 1601 return true; | |
| 1602 }); | |
| 1603 | |
| 1604 // 178 | |
| 1605 bool b_l_spaced(int indent) { | |
| 1606 if (!truth(b_asLineFeed())) return false; | |
| 1607 zeroOrMore(() => captureAs("\n", () => l_empty(indent, BLOCK_IN))); | |
| 1608 return true; | |
| 1609 } | |
| 1610 | |
| 1611 // 179 | |
| 1612 bool l_nb_spacedLines(int indent) { | |
| 1613 if (!truth(s_nb_spacedText(indent))) return false; | |
| 1614 zeroOrMore(() => transaction(() { | |
| 1615 if (!truth(b_l_spaced(indent))) return false; | |
| 1616 return s_nb_spacedText(indent); | |
| 1617 })); | |
| 1618 return true; | |
| 1619 } | |
| 1620 | |
| 1621 // 180 | |
| 1622 bool l_nb_sameLines(int indent) => transaction(() { | |
| 1623 zeroOrMore(() => captureAs('\n', () => l_empty(indent, BLOCK_IN))); | |
| 1624 return or([ | |
| 1625 () => l_nb_foldedLines(indent), | |
| 1626 () => l_nb_spacedLines(indent) | |
| 1627 ]); | |
| 1628 }); | |
| 1629 | |
| 1630 // 181 | |
| 1631 bool l_nb_diffLines(int indent) { | |
| 1632 if (!truth(l_nb_sameLines(indent))) return false; | |
| 1633 zeroOrMore(() => transaction(() { | |
| 1634 if (!truth(b_asLineFeed())) return false; | |
| 1635 return l_nb_sameLines(indent); | |
| 1636 })); | |
| 1637 return true; | |
| 1638 } | |
| 1639 | |
| 1640 // 182 | |
| 1641 String l_foldedContent(int indent, int chomping) => captureString(() { | |
| 1642 transaction(() { | |
| 1643 if (!truth(l_nb_diffLines(indent))) return false; | |
| 1644 return b_chompedLast(chomping); | |
| 1645 }); | |
| 1646 l_chompedEmpty(indent, chomping); | |
| 1647 return true; | |
| 1648 }); | |
| 1649 | |
| 1650 // 183 | |
| 1651 _SequenceNode l_blockSequence(int indent) => context('sequence', () { | |
| 1652 var additionalIndent = countIndentation() - indent; | |
| 1653 if (additionalIndent <= 0) return null; | |
| 1654 | |
| 1655 var content = oneOrMore(() => transaction(() { | |
| 1656 if (!truth(s_indent(indent + additionalIndent))) return null; | |
| 1657 return c_l_blockSeqEntry(indent + additionalIndent); | |
| 1658 })); | |
| 1659 if (!truth(content)) return null; | |
| 1660 | |
| 1661 return new _SequenceNode("?", content); | |
| 1662 }); | |
| 1663 | |
| 1664 // 184 | |
| 1665 _Node c_l_blockSeqEntry(int indent) => transaction(() { | |
| 1666 if (!truth(c_indicator(C_SEQUENCE_ENTRY))) return null; | |
| 1667 if (isNonSpace(peek())) return null; | |
| 1668 | |
| 1669 return s_l_blockIndented(indent, BLOCK_IN); | |
| 1670 }); | |
| 1671 | |
| 1672 // 185 | |
| 1673 _Node s_l_blockIndented(int indent, int ctx) { | |
| 1674 var additionalIndent = countIndentation(); | |
| 1675 return or([ | |
| 1676 () => transaction(() { | |
| 1677 if (!truth(s_indent(additionalIndent))) return null; | |
| 1678 return or([ | |
| 1679 () => ns_l_compactSequence(indent + 1 + additionalIndent), | |
| 1680 () => ns_l_compactMapping(indent + 1 + additionalIndent)]); | |
| 1681 }), | |
| 1682 () => s_l_blockNode(indent, ctx), | |
| 1683 () => s_l_comments() ? e_node() : null]); | |
| 1684 } | |
| 1685 | |
| 1686 // 186 | |
| 1687 _Node ns_l_compactSequence(int indent) => context('sequence', () { | |
| 1688 var first = c_l_blockSeqEntry(indent); | |
| 1689 if (!truth(first)) return null; | |
| 1690 | |
| 1691 var content = zeroOrMore(() => transaction(() { | |
| 1692 if (!truth(s_indent(indent))) return null; | |
| 1693 return c_l_blockSeqEntry(indent); | |
| 1694 })); | |
| 1695 content.insertRange(0, 1, first); | |
| 1696 | |
| 1697 return new _SequenceNode("?", content); | |
| 1698 }); | |
| 1699 | |
| 1700 // 187 | |
| 1701 _Node l_blockMapping(int indent) => context('mapping', () { | |
| 1702 var additionalIndent = countIndentation() - indent; | |
| 1703 if (additionalIndent <= 0) return null; | |
| 1704 | |
| 1705 var pairs = oneOrMore(() => transaction(() { | |
| 1706 if (!truth(s_indent(indent + additionalIndent))) return null; | |
| 1707 return ns_l_blockMapEntry(indent + additionalIndent); | |
| 1708 })); | |
| 1709 if (!truth(pairs)) return null; | |
| 1710 | |
| 1711 return map(pairs); | |
| 1712 }); | |
| 1713 | |
| 1714 // 188 | |
| 1715 _Pair<_Node, _Node> ns_l_blockMapEntry(int indent) => or([ | |
| 1716 () => c_l_blockMapExplicitEntry(indent), | |
| 1717 () => ns_l_blockMapImplicitEntry(indent) | |
| 1718 ]); | |
| 1719 | |
| 1720 // 189 | |
| 1721 _Pair<_Node, _Node> c_l_blockMapExplicitEntry(int indent) { | |
| 1722 var key = c_l_blockMapExplicitKey(indent); | |
| 1723 if (!truth(key)) return null; | |
| 1724 | |
| 1725 var value = or([ | |
| 1726 () => l_blockMapExplicitValue(indent), | |
| 1727 e_node | |
| 1728 ]); | |
| 1729 | |
| 1730 return new _Pair<_Node, _Node>(key, value); | |
| 1731 } | |
| 1732 | |
| 1733 // 190 | |
| 1734 _Node c_l_blockMapExplicitKey(int indent) => transaction(() { | |
| 1735 if (!truth(c_indicator(C_MAPPING_KEY))) return null; | |
| 1736 return s_l_blockIndented(indent, BLOCK_OUT); | |
| 1737 }); | |
| 1738 | |
| 1739 // 191 | |
| 1740 _Node l_blockMapExplicitValue(int indent) => transaction(() { | |
| 1741 if (!truth(s_indent(indent))) return null; | |
| 1742 if (!truth(c_indicator(C_MAPPING_VALUE))) return null; | |
| 1743 return s_l_blockIndented(indent, BLOCK_OUT); | |
| 1744 }); | |
| 1745 | |
| 1746 // 192 | |
| 1747 _Pair<_Node, _Node> ns_l_blockMapImplicitEntry(int indent) => transaction(() { | |
| 1748 var key = or([ns_s_blockMapImplicitKey, e_node]); | |
| 1749 var value = c_l_blockMapImplicitValue(indent); | |
| 1750 return truth(value) ? new _Pair<_Node, _Node>(key, value) : null; | |
| 1751 }); | |
| 1752 | |
| 1753 // 193 | |
| 1754 _Node ns_s_blockMapImplicitKey() => context('mapping key', () => or([ | |
| 1755 () => c_s_implicitJsonKey(BLOCK_KEY), | |
| 1756 () => ns_s_implicitYamlKey(BLOCK_KEY) | |
| 1757 ])); | |
| 1758 | |
| 1759 // 194 | |
| 1760 _Node c_l_blockMapImplicitValue(int indent) => context('mapping value', () => | |
| 1761 transaction(() { | |
| 1762 if (!truth(c_indicator(C_MAPPING_VALUE))) return null; | |
| 1763 return or([ | |
| 1764 () => s_l_blockNode(indent, BLOCK_OUT), | |
| 1765 () => s_l_comments() ? e_node() : null | |
| 1766 ]); | |
| 1767 })); | |
| 1768 | |
| 1769 // 195 | |
| 1770 _Node ns_l_compactMapping(int indent) => context('mapping', () { | |
| 1771 var first = ns_l_blockMapEntry(indent); | |
| 1772 if (!truth(first)) return null; | |
| 1773 | |
| 1774 var pairs = zeroOrMore(() => transaction(() { | |
| 1775 if (!truth(s_indent(indent))) return null; | |
| 1776 return ns_l_blockMapEntry(indent); | |
| 1777 })); | |
| 1778 pairs.insertRange(0, 1, first); | |
| 1779 | |
| 1780 return map(pairs); | |
| 1781 }); | |
| 1782 | |
| 1783 // 196 | |
| 1784 _Node s_l_blockNode(int indent, int ctx) => or([ | |
| 1785 () => s_l_blockInBlock(indent, ctx), | |
| 1786 () => s_l_flowInBlock(indent) | |
| 1787 ]); | |
| 1788 | |
| 1789 // 197 | |
| 1790 _Node s_l_flowInBlock(int indent) => transaction(() { | |
| 1791 if (!truth(s_separate(indent + 1, FLOW_OUT))) return null; | |
| 1792 var node = ns_flowNode(indent + 1, FLOW_OUT); | |
| 1793 if (!truth(node)) return null; | |
| 1794 if (!truth(s_l_comments())) return null; | |
| 1795 return node; | |
| 1796 }); | |
| 1797 | |
| 1798 // 198 | |
| 1799 _Node s_l_blockInBlock(int indent, int ctx) => or([ | |
| 1800 () => s_l_blockScalar(indent, ctx), | |
| 1801 () => s_l_blockCollection(indent, ctx) | |
| 1802 ]); | |
| 1803 | |
| 1804 // 199 | |
| 1805 _Node s_l_blockScalar(int indent, int ctx) => transaction(() { | |
| 1806 if (!truth(s_separate(indent + 1, ctx))) return null; | |
| 1807 var props = transaction(() { | |
| 1808 var props = c_ns_properties(indent + 1, ctx); | |
| 1809 if (!truth(props)) return null; | |
| 1810 if (!truth(s_separate(indent + 1, ctx))) return null; | |
| 1811 return props; | |
| 1812 }); | |
| 1813 | |
| 1814 var node = or([() => c_l_literal(indent), () => c_l_folded(indent)]); | |
| 1815 if (!truth(node)) return null; | |
| 1816 return addProps(node, props); | |
| 1817 }); | |
| 1818 | |
| 1819 // 200 | |
| 1820 _Node s_l_blockCollection(int indent, int ctx) => transaction(() { | |
| 1821 var props = transaction(() { | |
| 1822 if (!truth(s_separate(indent + 1, ctx))) return null; | |
| 1823 return c_ns_properties(indent + 1, ctx); | |
| 1824 }); | |
| 1825 | |
| 1826 if (!truth(s_l_comments())) return null; | |
| 1827 return or([ | |
| 1828 () => l_blockSequence(seqSpaces(indent, ctx)), | |
| 1829 () => l_blockMapping(indent)]); | |
| 1830 }); | |
| 1831 | |
| 1832 // 201 | |
| 1833 int seqSpaces(int indent, int ctx) => ctx == BLOCK_OUT ? indent - 1 : indent; | |
| 1834 | |
| 1835 // 202 | |
| 1836 void l_documentPrefix() { | |
| 1837 zeroOrMore(l_comment); | |
| 1838 } | |
| 1839 | |
| 1840 // 203 | |
| 1841 bool c_directivesEnd() => rawString("---"); | |
| 1842 | |
| 1843 // 204 | |
| 1844 bool c_documentEnd() => rawString("..."); | |
| 1845 | |
| 1846 // 205 | |
| 1847 bool l_documentSuffix() => transaction(() { | |
| 1848 if (!truth(c_documentEnd())) return false; | |
| 1849 return s_l_comments(); | |
| 1850 }); | |
| 1851 | |
| 1852 // 206 | |
| 1853 bool c_forbidden() { | |
| 1854 if (!inBareDocument || !atStartOfLine) return false; | |
| 1855 var forbidden = false; | |
| 1856 transaction(() { | |
| 1857 if (!truth(or([c_directivesEnd, c_documentEnd]))) return; | |
| 1858 var char = peek(); | |
| 1859 forbidden = isBreak(char) || isSpace(char) || atEndOfFile; | |
| 1860 return; | |
| 1861 }); | |
| 1862 return forbidden; | |
| 1863 } | |
| 1864 | |
| 1865 // 207 | |
| 1866 _Node l_bareDocument() { | |
| 1867 try { | |
| 1868 inBareDocument = true; | |
| 1869 return s_l_blockNode(-1, BLOCK_IN); | |
| 1870 } finally { | |
| 1871 inBareDocument = false; | |
| 1872 } | |
| 1873 } | |
| 1874 | |
| 1875 // 208 | |
| 1876 _Node l_explicitDocument() { | |
| 1877 if (!truth(c_directivesEnd())) return null; | |
| 1878 var doc = l_bareDocument(); | |
| 1879 if (truth(doc)) return doc; | |
| 1880 | |
| 1881 doc = e_node(); | |
| 1882 s_l_comments(); | |
| 1883 return doc; | |
| 1884 } | |
| 1885 | |
| 1886 // 209 | |
| 1887 _Node l_directiveDocument() { | |
| 1888 if (!truth(oneOrMore(l_directive))) return null; | |
| 1889 var doc = l_explicitDocument(); | |
| 1890 if (doc != null) return doc; | |
| 1891 parseFailed(); | |
| 1892 return null; // Unreachable. | |
| 1893 } | |
| 1894 | |
| 1895 // 210 | |
| 1896 _Node l_anyDocument() => | |
| 1897 or([l_directiveDocument, l_explicitDocument, l_bareDocument]); | |
| 1898 | |
| 1899 // 211 | |
| 1900 List<_Node> l_yamlStream() { | |
| 1901 var docs = []; | |
| 1902 zeroOrMore(l_documentPrefix); | |
| 1903 var first = zeroOrOne(l_anyDocument); | |
| 1904 if (!truth(first)) first = e_node(); | |
| 1905 docs.add(first); | |
| 1906 | |
| 1907 zeroOrMore(() { | |
| 1908 var doc; | |
| 1909 if (truth(oneOrMore(l_documentSuffix))) { | |
| 1910 zeroOrMore(l_documentPrefix); | |
| 1911 doc = zeroOrOne(l_anyDocument); | |
| 1912 } else { | |
| 1913 zeroOrMore(l_documentPrefix); | |
| 1914 doc = zeroOrOne(l_explicitDocument); | |
| 1915 } | |
| 1916 if (truth(doc)) docs.add(doc); | |
| 1917 return doc; | |
| 1918 }); | |
| 1919 | |
| 1920 if (!atEndOfFile) parseFailed(); | |
| 1921 return docs; | |
| 1922 } | |
| 1923 } | |
| 1924 | |
| 1925 class SyntaxError extends YamlException { | |
| 1926 final int line; | |
| 1927 final int column; | |
| 1928 | |
| 1929 SyntaxError(this.line, this.column, String msg) : super(msg); | |
| 1930 | |
| 1931 String toString() => "Syntax error on line $line, column $column: $msg"; | |
| 1932 } | |
| 1933 | |
| 1934 /** A pair of values. */ | |
| 1935 class _Pair<E, F> { | |
| 1936 E first; | |
| 1937 F last; | |
| 1938 | |
| 1939 _Pair(this.first, this.last); | |
| 1940 | |
| 1941 String toString() => '($first, $last)'; | |
| 1942 } | |
| 1943 | |
| 1944 /** The information in the header for a block scalar. */ | |
| 1945 class _BlockHeader { | |
| 1946 final int additionalIndent; | |
| 1947 final int chomping; | |
| 1948 | |
| 1949 _BlockHeader(this.additionalIndent, this.chomping); | |
| 1950 | |
| 1951 bool get autoDetectIndent => additionalIndent == null; | |
| 1952 } | |
| 1953 | |
| 1954 /** | |
| 1955 * A range of characters in the YAML document, from [start] to [end] (inclusive)
. | |
| 1956 */ | |
| 1957 class _Range { | |
| 1958 /** The first character in the range. */ | |
| 1959 final int start; | |
| 1960 | |
| 1961 /** The last character in the range. */ | |
| 1962 final int end; | |
| 1963 | |
| 1964 _Range(this.start, this.end); | |
| 1965 | |
| 1966 /** Returns whether or not [pos] lies within this range. */ | |
| 1967 bool contains(int pos) => pos >= start && pos <= end; | |
| 1968 } | |
| 1969 | |
| 1970 /** | |
| 1971 * A map that associates [E] values with [_Range]s. It's efficient to create new | |
| 1972 * associations, but finding the value associated with a position is more | |
| 1973 * expensive. | |
| 1974 */ | |
| 1975 class _RangeMap<E> { | |
| 1976 /** The ranges and their associated elements. */ | |
| 1977 final List<_Pair<_Range, E>> contents; | |
| 1978 | |
| 1979 _RangeMap() : this.contents = <_Pair<_Range, E>>[]; | |
| 1980 | |
| 1981 /** | |
| 1982 * Returns the value associated with the range in which [pos] lies, or null if | |
| 1983 * there is no such range. If there's more than one such range, the most | |
| 1984 * recently set one is used. | |
| 1985 */ | |
| 1986 E operator[](int pos) { | |
| 1987 // Iterate backwards through contents so the more recent range takes | |
| 1988 // precedence. TODO(nweiz): clean this up when issue 2804 is fixed. | |
| 1989 for (var i = contents.length - 1; i >= 0; i--) { | |
| 1990 var pair = contents[i]; | |
| 1991 if (pair.first.contains(pos)) return pair.last; | |
| 1992 } | |
| 1993 return null; | |
| 1994 } | |
| 1995 | |
| 1996 /** Associates [value] with [range]. */ | |
| 1997 operator[]=(_Range range, E value) => | |
| 1998 contents.add(new _Pair<_Range, E>(range, value)); | |
| 1999 } | |
| OLD | NEW |