| 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 final TAB = 0x9; | |
| 22 static final LF = 0xA; | |
| 23 static final CR = 0xD; | |
| 24 static final SP = 0x20; | |
| 25 static final TILDE = 0x7E; | |
| 26 static final NEL = 0x85; | |
| 27 static final HYPHEN = 0x2D; | |
| 28 static final QUESTION_MARK = 0x3F; | |
| 29 static final COLON = 0x3A; | |
| 30 static final COMMA = 0x2C; | |
| 31 static final LEFT_BRACKET = 0x5B; | |
| 32 static final RIGHT_BRACKET = 0x5D; | |
| 33 static final LEFT_BRACE = 0x7B; | |
| 34 static final RIGHT_BRACE = 0x7D; | |
| 35 static final HASH = 0x23; | |
| 36 static final AMPERSAND = 0x26; | |
| 37 static final ASTERISK = 0x2A; | |
| 38 static final EXCLAMATION = 0x21; | |
| 39 static final VERTICAL_BAR = 0x7C; | |
| 40 static final GREATER_THAN = 0x3E; | |
| 41 static final SINGLE_QUOTE = 0x27; | |
| 42 static final DOUBLE_QUOTE = 0x22; | |
| 43 static final PERCENT = 0x25; | |
| 44 static final AT = 0x40; | |
| 45 static final GRAVE_ACCENT = 0x60; | |
| 46 | |
| 47 static final NULL = 0x0; | |
| 48 static final BELL = 0x7; | |
| 49 static final BACKSPACE = 0x8; | |
| 50 static final VERTICAL_TAB = 0xB; | |
| 51 static final FORM_FEED = 0xC; | |
| 52 static final ESCAPE = 0x1B; | |
| 53 static final BACKSLASH = 0x5C; | |
| 54 static final NBSP = 0xA0; | |
| 55 static final LINE_SEPARATOR = 0x2028; | |
| 56 static final PARAGRAPH_SEPARATOR = 0x2029; | |
| 57 | |
| 58 static final C_SEQUENCE_ENTRY = 4; | |
| 59 static final C_MAPPING_KEY = 5; | |
| 60 static final C_MAPPING_VALUE = 6; | |
| 61 static final C_COLLECT_ENTRY = 7; | |
| 62 static final C_SEQUENCE_START = 8; | |
| 63 static final C_SEQUENCE_END = 9; | |
| 64 static final C_MAPPING_START = 10; | |
| 65 static final C_MAPPING_END = 11; | |
| 66 static final C_COMMENT = 12; | |
| 67 static final C_ANCHOR = 13; | |
| 68 static final C_ALIAS = 14; | |
| 69 static final C_TAG = 15; | |
| 70 static final C_LITERAL = 16; | |
| 71 static final C_FOLDED = 17; | |
| 72 static final C_SINGLE_QUOTE = 18; | |
| 73 static final C_DOUBLE_QUOTE = 19; | |
| 74 static final C_DIRECTIVE = 20; | |
| 75 static final C_RESERVED = 21; | |
| 76 | |
| 77 static final BLOCK_OUT = 0; | |
| 78 static final BLOCK_IN = 1; | |
| 79 static final FLOW_OUT = 2; | |
| 80 static final FLOW_IN = 3; | |
| 81 static final BLOCK_KEY = 4; | |
| 82 static final FLOW_KEY = 5; | |
| 83 | |
| 84 /** The source string being parsed. */ | |
| 85 final String s; | |
| 86 | |
| 87 /** The current position in the source string. */ | |
| 88 int pos = 0; | |
| 89 | |
| 90 /** The length of the string being parsed. */ | |
| 91 final int len; | |
| 92 | |
| 93 /** The current (0-based) line in the source string. */ | |
| 94 int line = 0; | |
| 95 | |
| 96 /** The current (0-based) column in the source string. */ | |
| 97 int column = 0; | |
| 98 | |
| 99 /** | |
| 100 * Whether we're parsing a bare document (that is, one that doesn't begin with | |
| 101 * `---`). Bare documents don't allow `%` immediately following newlines. | |
| 102 */ | |
| 103 bool inBareDocument = false; | |
| 104 | |
| 105 /** | |
| 106 * The line number of the farthest position that has been parsed successfully | |
| 107 * before backtracking. Used for error reporting. | |
| 108 */ | |
| 109 int farthestLine = 0; | |
| 110 | |
| 111 /** | |
| 112 * The column number of the farthest position that has been parsed | |
| 113 * successfully before backtracking. Used for error reporting. | |
| 114 */ | |
| 115 int farthestColumn = 0; | |
| 116 | |
| 117 /** | |
| 118 * The name of the context of the farthest position that has been parsed | |
| 119 * successfully before backtracking. Used for error reporting. | |
| 120 */ | |
| 121 String farthestContext = "document"; | |
| 122 | |
| 123 /** A stack of the names of parse contexts. Used for error reporting. */ | |
| 124 List<String> contextStack; | |
| 125 | |
| 126 _Parser(String s) | |
| 127 : this.s = s, | |
| 128 len = s.length, | |
| 129 contextStack = <String>["document"]; | |
| 130 | |
| 131 /** | |
| 132 * Return the character at the current position, then move that position | |
| 133 * forward one character. Also updates the current line and column numbers. | |
| 134 */ | |
| 135 int next() { | |
| 136 if (pos == len) return -1; | |
| 137 var char = s.charCodeAt(pos++); | |
| 138 if (isBreak(char)) { | |
| 139 line++; | |
| 140 column = 0; | |
| 141 } else { | |
| 142 column++; | |
| 143 } | |
| 144 | |
| 145 if (farthestLine < line) { | |
| 146 farthestLine = line; | |
| 147 farthestColumn = column; | |
| 148 farthestContext = contextStack.last(); | |
| 149 } else if (farthestLine == line && farthestColumn < column) { | |
| 150 farthestColumn = column; | |
| 151 farthestContext = contextStack.last(); | |
| 152 } | |
| 153 | |
| 154 return char; | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Returns the character at the current position, or the character [i] | |
| 159 * characters after the current position. | |
| 160 * | |
| 161 * Returns -1 if this would return a character after the end or before the | |
| 162 * beginning of the input string. | |
| 163 */ | |
| 164 int peek([int i = 0]) { | |
| 165 var peekPos = pos + i; | |
| 166 return (peekPos >= len || peekPos < 0) ? -1 : s.charCodeAt(peekPos); | |
| 167 } | |
| 168 | |
| 169 /** | |
| 170 * The truthiness operator. Returns `false` if [obj] is `null` or `false`, | |
| 171 * `true` otherwise. | |
| 172 */ | |
| 173 bool truth(obj) => obj != null && obj != false; | |
| 174 | |
| 175 /** | |
| 176 * Consumes the current character if it matches [matcher]. Returns the result | |
| 177 * of [matcher]. | |
| 178 */ | |
| 179 bool consume(bool matcher(int)) { | |
| 180 if (matcher(peek())) { | |
| 181 next(); | |
| 182 return true; | |
| 183 } | |
| 184 return false; | |
| 185 } | |
| 186 | |
| 187 /** | |
| 188 * Calls [consumer] until it returns a falsey value. Returns a list of all | |
| 189 * truthy return values of [consumer], or null if it didn't consume anything. | |
| 190 * | |
| 191 * Conceptually, repeats a production one or more times. | |
| 192 */ | |
| 193 List oneOrMore(consumer()) { | |
| 194 var first = consumer(); | |
| 195 if (!truth(first)) return null; | |
| 196 var out = [first]; | |
| 197 while (true) { | |
| 198 var el = consumer(); | |
| 199 if (!truth(el)) return out; | |
| 200 out.add(el); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * Calls [consumer] until it returns a falsey value. Returns a list of all | |
| 206 * truthy return values of [consumer], or the empty list if it didn't consume | |
| 207 * anything. | |
| 208 * | |
| 209 * Conceptually, repeats a production any number of times. | |
| 210 */ | |
| 211 List zeroOrMore(consumer()) { | |
| 212 var out = []; | |
| 213 var oldPos = pos; | |
| 214 while (true) { | |
| 215 var el = consumer(); | |
| 216 if (!truth(el) || oldPos == pos) return out; | |
| 217 oldPos = pos; | |
| 218 out.add(el); | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 /** | |
| 223 * Just calls [consumer] and returns its result. Used to make it explicit that | |
| 224 * a production is intended to be optional. | |
| 225 */ | |
| 226 zeroOrOne(consumer()) => consumer(); | |
| 227 | |
| 228 /** | |
| 229 * Calls each function in [consumers] until one returns a truthy value, then | |
| 230 * returns that. | |
| 231 */ | |
| 232 or(List<Function> consumers) { | |
| 233 for (var c in consumers) { | |
| 234 var res = c(); | |
| 235 if (truth(res)) return res; | |
| 236 } | |
| 237 return null; | |
| 238 } | |
| 239 | |
| 240 /** | |
| 241 * Calls [consumer] and returns its result, but rolls back the parser state if | |
| 242 * [consumer] returns a falsey value. | |
| 243 */ | |
| 244 transaction(consumer()) { | |
| 245 int oldPos = pos, oldLine = line, oldColumn = column; | |
| 246 var res = consumer(); | |
| 247 if (truth(res)) return res; | |
| 248 | |
| 249 pos = oldPos; | |
| 250 line = oldLine; | |
| 251 column = oldColumn; | |
| 252 return res; | |
| 253 } | |
| 254 | |
| 255 /** | |
| 256 * Consumes [n] characters matching [matcher], or none if there isn't a | |
| 257 * complete match. The first argument to [matcher] is the character code, the | |
| 258 * second is the index (from 0 to [n] - 1). | |
| 259 * | |
| 260 * Returns whether or not the characters were consumed. | |
| 261 */ | |
| 262 bool nAtOnce(int n, bool matcher(int c, int i)) => transaction(() { | |
| 263 for (int i = 0; i < n; i++) { | |
| 264 if (!consume((c) => matcher(c, i))) return false; | |
| 265 } | |
| 266 return true; | |
| 267 }); | |
| 268 | |
| 269 /** | |
| 270 * Consumes the exact characters in [str], or nothing. | |
| 271 * | |
| 272 * Returns whether or not the string was consumed. | |
| 273 */ | |
| 274 bool rawString(String str) => | |
| 275 nAtOnce(str.length, (c, i) => str.charCodeAt(i) == c); | |
| 276 | |
| 277 /** | |
| 278 * Consumes and returns a string of characters matching [matcher], or null if | |
| 279 * there are no such characters. | |
| 280 */ | |
| 281 String stringOf(bool matcher(int)) => | |
| 282 captureString(() => oneOrMore(() => consume(matcher))); | |
| 283 | |
| 284 /** | |
| 285 * Calls [consumer] and returns the string that was consumed while doing so, | |
| 286 * or null if [consumer] returned a falsey value. Automatically wraps | |
| 287 * [consumer] in `transaction`. | |
| 288 */ | |
| 289 String captureString(consumer()) { | |
| 290 int start = pos; | |
| 291 var res = transaction(consumer); | |
| 292 if (!truth(res)) return null; | |
| 293 return s.substring(start, pos); | |
| 294 } | |
| 295 | |
| 296 /** | |
| 297 * Adds a tag and an anchor to [node], if they're defined. | |
| 298 */ | |
| 299 _Node addProps(_Node node, _Pair<_Tag, String> props) { | |
| 300 if (truth(props.first)) node.tag = props.first; | |
| 301 if (truth(props.last)) node.anchor = props.last; | |
| 302 return node; | |
| 303 } | |
| 304 | |
| 305 /** Creates a MappingNode from [pairs]. */ | |
| 306 _MappingNode map(List<_Pair<_Node, _Node>> pairs) { | |
| 307 var content = new Map<_Node, _Node>(); | |
| 308 pairs.forEach((pair) => content[pair.first] = pair.last); | |
| 309 return new _MappingNode("?", content); | |
| 310 } | |
| 311 | |
| 312 /** Runs [fn] in a context named [name]. Used for error reporting. */ | |
| 313 context(String name, fn()) { | |
| 314 try { | |
| 315 contextStack.add(name); | |
| 316 return fn(); | |
| 317 } finally { | |
| 318 var popped = contextStack.removeLast(); | |
| 319 assert(popped == name); | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 /** Throws an error with additional context information. */ | |
| 324 error(String message) { | |
| 325 // Line and column should be one-based. | |
| 326 throw new SyntaxError(line + 1, column + 1, | |
| 327 "$message (in $farthestContext)"); | |
| 328 } | |
| 329 | |
| 330 /** | |
| 331 * If [result] is falsey, throws an error saying that [expected] was | |
| 332 * expected. | |
| 333 */ | |
| 334 expect(result, String expected) { | |
| 335 if (truth(result)) return result; | |
| 336 error("expected $expected"); | |
| 337 } | |
| 338 | |
| 339 /** | |
| 340 * Throws an error saying that the parse failed. Uses [farthestLine], | |
| 341 * [farthestColumn], and [farthestContext] to provide additional information. | |
| 342 */ | |
| 343 parseFailed() { | |
| 344 throw new SyntaxError(farthestLine + 1, farthestColumn + 1, | |
| 345 "invalid YAML in $farthestContext"); | |
| 346 } | |
| 347 | |
| 348 /** Returns the number of spaces after the current position. */ | |
| 349 int countIndentation() { | |
| 350 var i = 0; | |
| 351 while (peek(i) == SP) i++; | |
| 352 return i; | |
| 353 } | |
| 354 | |
| 355 /** Returns whether the current position is at the beginning of a line. */ | |
| 356 bool get atStartOfLine() => column == 0; | |
| 357 | |
| 358 /** Returns whether the current position is at the end of the input. */ | |
| 359 bool get atEndOfFile() => pos == len; | |
| 360 | |
| 361 /** | |
| 362 * Given an indicator character, returns the type of that indicator (or null | |
| 363 * if the indicator isn't found. | |
| 364 */ | |
| 365 int indicatorType(int char) { | |
| 366 switch (char) { | |
| 367 case HYPHEN: return C_SEQUENCE_ENTRY; | |
| 368 case QUESTION_MARK: return C_MAPPING_KEY; | |
| 369 case COLON: return C_MAPPING_VALUE; | |
| 370 case COMMA: return C_COLLECT_ENTRY; | |
| 371 case LEFT_BRACKET: return C_SEQUENCE_START; | |
| 372 case RIGHT_BRACKET: return C_SEQUENCE_END; | |
| 373 case LEFT_BRACE: return C_MAPPING_START; | |
| 374 case RIGHT_BRACE: return C_MAPPING_END; | |
| 375 case HASH: return C_COMMENT; | |
| 376 case AMPERSAND: return C_ANCHOR; | |
| 377 case ASTERISK: return C_ALIAS; | |
| 378 case EXCLAMATION: return C_TAG; | |
| 379 case VERTICAL_BAR: return C_LITERAL; | |
| 380 case GREATER_THAN: return C_FOLDED; | |
| 381 case SINGLE_QUOTE: return C_SINGLE_QUOTE; | |
| 382 case DOUBLE_QUOTE: return C_DOUBLE_QUOTE; | |
| 383 case PERCENT: return C_DIRECTIVE; | |
| 384 case AT: | |
| 385 case GRAVE_ACCENT: | |
| 386 return C_RESERVED; | |
| 387 default: return null; | |
| 388 } | |
| 389 } | |
| 390 | |
| 391 // 1 | |
| 392 bool isPrintable(int char) { | |
| 393 return char == TAB || | |
| 394 char == LF || | |
| 395 char == CR || | |
| 396 (char >= SP && char <= TILDE) || | |
| 397 char == NEL || | |
| 398 (char >= 0xA0 && char <= 0xD7FF) || | |
| 399 (char >= 0xE000 && char <= 0xFFFD) || | |
| 400 (char >= 0x10000 && char <= 0x10FFFF); | |
| 401 } | |
| 402 | |
| 403 // 22 | |
| 404 bool c_indicator(int type) => consume((c) => indicatorType(c) == type); | |
| 405 | |
| 406 // 23 | |
| 407 bool isFlowIndicator(int char) { | |
| 408 var indicator = indicatorType(char); | |
| 409 return indicator == C_COLLECT_ENTRY || | |
| 410 indicator == C_SEQUENCE_START || | |
| 411 indicator == C_SEQUENCE_END || | |
| 412 indicator == C_MAPPING_START || | |
| 413 indicator == C_MAPPING_END; | |
| 414 } | |
| 415 | |
| 416 // 26 | |
| 417 bool isBreak(int char) => char == LF || char == CR; | |
| 418 | |
| 419 // 27 | |
| 420 bool isNonBreak(int char) => isPrintable(char) && !isBreak(char); | |
| 421 | |
| 422 // 30 | |
| 423 bool b_non_content() => consume(isBreak); | |
| 424 | |
| 425 // 33 | |
| 426 bool isSpace(int char) => char == SP || char == TAB; | |
| 427 | |
| 428 // 34 | |
| 429 bool isNonSpace(int char) => isNonBreak(char) && !isSpace(char); | |
| 430 | |
| 431 // 63 | |
| 432 bool s_indent(int indent) => nAtOnce(indent, (c, i) => c == SP); | |
| 433 | |
| 434 // 66 | |
| 435 bool s_separateInLine() => transaction(() => | |
| 436 truth(oneOrMore(() => consume(isSpace))) || atStartOfLine); | |
| 437 | |
| 438 // 69 | |
| 439 bool s_flowLinePrefix(int indent) { | |
| 440 if (!s_indent(indent)) return false; | |
| 441 zeroOrOne(s_separateInLine); | |
| 442 return true; | |
| 443 } | |
| 444 | |
| 445 // 74 | |
| 446 bool s_flowFolded(int indent) => false; // TODO(nweiz): implement | |
| 447 | |
| 448 // 75 | |
| 449 bool c_nb_commentText() { | |
| 450 if (!c_indicator(C_COMMENT)) return false; | |
| 451 zeroOrMore(() => consume(isNonBreak)); | |
| 452 return true; | |
| 453 } | |
| 454 | |
| 455 // 76 | |
| 456 bool b_comment() => atEndOfFile || b_non_content(); | |
| 457 | |
| 458 // 77 | |
| 459 bool s_b_comment() { | |
| 460 if (s_separateInLine()) { | |
| 461 zeroOrOne(c_nb_commentText); | |
| 462 } | |
| 463 return b_comment(); | |
| 464 } | |
| 465 | |
| 466 // 78 | |
| 467 bool l_comment() => transaction(() { | |
| 468 if (!s_separateInLine()) return false; | |
| 469 zeroOrOne(c_nb_commentText); | |
| 470 return b_comment(); | |
| 471 }); | |
| 472 | |
| 473 // 79 | |
| 474 bool s_l_comments() { | |
| 475 if (!s_b_comment() && !atStartOfLine) return false; | |
| 476 zeroOrMore(l_comment); | |
| 477 return true; | |
| 478 } | |
| 479 | |
| 480 // 80 | |
| 481 bool s_separate(int indent, int ctx) { | |
| 482 switch (ctx) { | |
| 483 case BLOCK_OUT: | |
| 484 case BLOCK_IN: | |
| 485 case FLOW_OUT: | |
| 486 case FLOW_IN: | |
| 487 return s_separateLines(indent); | |
| 488 case BLOCK_KEY: | |
| 489 case FLOW_KEY: | |
| 490 return s_separateInLine(); | |
| 491 default: throw 'invalid context "$ctx"'; | |
| 492 } | |
| 493 } | |
| 494 | |
| 495 // 81 | |
| 496 bool s_separateLines(int indent) { | |
| 497 return transaction(() => s_l_comments() && s_flowLinePrefix(indent)) || | |
| 498 s_separateInLine(); | |
| 499 } | |
| 500 | |
| 501 // 82 | |
| 502 bool l_directive() => false; // TODO(nweiz): implement | |
| 503 | |
| 504 // 96 | |
| 505 _Pair<_Tag, String> c_ns_properties(int indent, int ctx) { | |
| 506 var tag, anchor; | |
| 507 tag = c_ns_tagProperty(); | |
| 508 if (truth(tag)) { | |
| 509 anchor = transaction(() { | |
| 510 if (!s_separate(indent, ctx)) return null; | |
| 511 return c_ns_anchorProperty(); | |
| 512 }); | |
| 513 return new _Pair<_Tag, String>(tag, anchor); | |
| 514 } | |
| 515 | |
| 516 anchor = c_ns_anchorProperty(); | |
| 517 if (truth(anchor)) { | |
| 518 tag = transaction(() { | |
| 519 if (!s_separate(indent, ctx)) return null; | |
| 520 return c_ns_tagProperty(); | |
| 521 }); | |
| 522 return new _Pair<_Tag, String>(tag, anchor); | |
| 523 } | |
| 524 | |
| 525 return null; | |
| 526 } | |
| 527 | |
| 528 // 97 | |
| 529 _Tag c_ns_tagProperty() => null; // TODO(nweiz): implement | |
| 530 | |
| 531 // 101 | |
| 532 String c_ns_anchorProperty() => null; // TODO(nweiz): implement | |
| 533 | |
| 534 // 102 | |
| 535 bool isAnchorChar(int char) => isNonSpace(char) && !isFlowIndicator(char); | |
| 536 | |
| 537 // 103 | |
| 538 String ns_anchorName() => | |
| 539 captureString(() => oneOrMore(() => consume(isAnchorChar))); | |
| 540 | |
| 541 // 104 | |
| 542 _Node c_ns_aliasNode() { | |
| 543 if (!c_indicator(C_ALIAS)) return null; | |
| 544 var name = expect(ns_anchorName(), 'anchor name'); | |
| 545 return new _AliasNode(name); | |
| 546 } | |
| 547 | |
| 548 // 105 | |
| 549 _ScalarNode e_scalar() => new _ScalarNode("?", content: ""); | |
| 550 | |
| 551 // 106 | |
| 552 _ScalarNode e_node() => e_scalar(); | |
| 553 | |
| 554 // 126 | |
| 555 bool ns_plainFirst(int ctx) { | |
| 556 var char = peek(); | |
| 557 var indicator = indicatorType(char); | |
| 558 if (indicator == C_RESERVED) { | |
| 559 error("reserved indicators can't start a plain scalar"); | |
| 560 } | |
| 561 var match = (isNonSpace(char) && indicator == null) || | |
| 562 ((indicator == C_MAPPING_KEY || | |
| 563 indicator == C_MAPPING_VALUE || | |
| 564 indicator == C_SEQUENCE_ENTRY) && | |
| 565 isPlainSafe(ctx, peek(1))); | |
| 566 | |
| 567 if (match) next(); | |
| 568 return match; | |
| 569 } | |
| 570 | |
| 571 // 127 | |
| 572 bool isPlainSafe(int ctx, int char) { | |
| 573 switch (ctx) { | |
| 574 case FLOW_OUT: | |
| 575 case BLOCK_KEY: | |
| 576 // 128 | |
| 577 return isNonSpace(char); | |
| 578 case FLOW_IN: | |
| 579 case FLOW_KEY: | |
| 580 // 129 | |
| 581 return isNonSpace(char) && !isFlowIndicator(char); | |
| 582 default: throw 'invalid context "$ctx"'; | |
| 583 } | |
| 584 } | |
| 585 | |
| 586 // 130 | |
| 587 bool ns_plainChar(int ctx) { | |
| 588 var char = peek(); | |
| 589 var indicator = indicatorType(char); | |
| 590 var safeChar = isPlainSafe(ctx, char) && indicator != C_MAPPING_VALUE && | |
| 591 indicator != C_COMMENT; | |
| 592 var nonCommentHash = isNonSpace(peek(-1)) && indicator == C_COMMENT; | |
| 593 var nonMappingColon = indicator == C_MAPPING_VALUE && | |
| 594 isPlainSafe(ctx, peek(1)); | |
| 595 var match = safeChar || nonCommentHash || nonMappingColon; | |
| 596 | |
| 597 if (match) next(); | |
| 598 return match; | |
| 599 } | |
| 600 | |
| 601 // 131 | |
| 602 String ns_plain(int indent, int ctx) => context('plain scalar', () { | |
| 603 switch (ctx) { | |
| 604 case FLOW_OUT: | |
| 605 case FLOW_IN: | |
| 606 return ns_plainMultiLine(indent, ctx); | |
| 607 case BLOCK_KEY: | |
| 608 case FLOW_KEY: | |
| 609 return ns_plainOneLine(ctx); | |
| 610 default: throw 'invalid context "$ctx"'; | |
| 611 } | |
| 612 }); | |
| 613 | |
| 614 // 132 | |
| 615 void nb_ns_plainInLine(int ctx) { | |
| 616 zeroOrMore(() => transaction(() { | |
| 617 zeroOrMore(() => consume(isSpace)); | |
| 618 return ns_plainChar(ctx); | |
| 619 })); | |
| 620 } | |
| 621 | |
| 622 // 133 | |
| 623 String ns_plainOneLine(int ctx) => captureString(() { | |
| 624 if (c_forbidden()) return false; | |
| 625 if (!ns_plainFirst(ctx)) return false; | |
| 626 nb_ns_plainInLine(ctx); | |
| 627 return true; | |
| 628 }); | |
| 629 | |
| 630 // 134 | |
| 631 bool s_ns_plainNextLine(int indent, int ctx) => transaction(() { | |
| 632 if (c_forbidden()) return false; | |
| 633 if (!s_flowFolded(indent)) return false; | |
| 634 if (!ns_plainChar(ctx)) return false; | |
| 635 nb_ns_plainInLine(ctx); | |
| 636 return true; | |
| 637 }); | |
| 638 | |
| 639 // 135 | |
| 640 String ns_plainMultiLine(int indent, int ctx) => captureString(() { | |
| 641 if (!truth(ns_plainOneLine(ctx))) return false; | |
| 642 zeroOrMore(() => s_ns_plainNextLine(indent, ctx)); | |
| 643 return true; | |
| 644 }); | |
| 645 | |
| 646 // 154 | |
| 647 _Node c_s_implicitYamlKey(int ctx) => transaction(() { | |
| 648 // The indentation parameter is "null" since it's unused in this path | |
| 649 var node = ns_flowYamlNode(null, ctx); | |
| 650 if (!truth(node)) return null; | |
| 651 zeroOrOne(s_separateInLine); | |
| 652 return node; | |
| 653 }); | |
| 654 | |
| 655 // 155 | |
| 656 _Node c_s_implicitJsonKey(int ctx) => null; // TODO(nweiz): implement | |
| 657 | |
| 658 // 156 | |
| 659 _Node ns_flowYamlContent(int indent, int ctx) { | |
| 660 var str = ns_plain(indent, ctx); | |
| 661 if (!truth(str)) return null; | |
| 662 return new _ScalarNode("?", content: str); | |
| 663 } | |
| 664 | |
| 665 // 157 | |
| 666 // TODO(nweiz): implement | |
| 667 _Node ns_flowJsonContent(int indent, int ctx) => null; | |
| 668 | |
| 669 // 158 | |
| 670 _Node ns_flowContent(int indent, int ctx) => or([ | |
| 671 () => ns_flowYamlContent(indent, ctx), | |
| 672 () => ns_flowJsonContent(indent, ctx) | |
| 673 ]); | |
| 674 | |
| 675 // 159 | |
| 676 _Node ns_flowYamlNode(int indent, int ctx) => or([ | |
| 677 c_ns_aliasNode, | |
| 678 () => ns_flowYamlContent(indent, ctx), | |
| 679 () { | |
| 680 var props = c_ns_properties(indent, ctx); | |
| 681 if (!truth(props)) return null; | |
| 682 var node = or([ | |
| 683 () => transaction(() { | |
| 684 if (!s_separate(indent, ctx)) return null; | |
| 685 return ns_flowYamlContent(indent, ctx); | |
| 686 }), | |
| 687 e_scalar | |
| 688 ]); | |
| 689 return addProps(node, props); | |
| 690 } | |
| 691 ]); | |
| 692 | |
| 693 // 161 | |
| 694 _Node ns_flowNode(int indent, int ctx) => or([ | |
| 695 c_ns_aliasNode, | |
| 696 () => ns_flowContent(indent, ctx), | |
| 697 () => transaction(() { | |
| 698 var props = c_ns_properties(indent, ctx); | |
| 699 if (!truth(props)) return null; | |
| 700 var node = or([ | |
| 701 () => transaction(() => s_separate(indent, ctx) ? | |
| 702 ns_flowContent(indent, ctx) : null), | |
| 703 e_scalar]); | |
| 704 return addProps(node, props); | |
| 705 }) | |
| 706 ]); | |
| 707 | |
| 708 // 170 | |
| 709 _Node c_l_literal(int indent) => null; // TODO(nweiz); implement | |
| 710 | |
| 711 // 174 | |
| 712 _Node c_l_folded(int indent) => null; // TODO(nweiz); implement | |
| 713 | |
| 714 // 183 | |
| 715 _SequenceNode l_blockSequence(int indent) => context('sequence', () { | |
| 716 var additionalIndent = countIndentation() - indent; | |
| 717 if (additionalIndent <= 0) return null; | |
| 718 | |
| 719 var content = oneOrMore(() => transaction(() { | |
| 720 if (!s_indent(indent + additionalIndent)) return null; | |
| 721 return c_l_blockSeqEntry(indent + additionalIndent); | |
| 722 })); | |
| 723 if (!truth(content)) return null; | |
| 724 | |
| 725 return new _SequenceNode("?", content); | |
| 726 }); | |
| 727 | |
| 728 // 184 | |
| 729 _Node c_l_blockSeqEntry(int indent) => transaction(() { | |
| 730 if (!c_indicator(C_SEQUENCE_ENTRY)) return null; | |
| 731 if (isNonSpace(peek())) return null; | |
| 732 | |
| 733 return s_l_blockIndented(indent, BLOCK_IN); | |
| 734 }); | |
| 735 | |
| 736 // 185 | |
| 737 _Node s_l_blockIndented(int indent, int ctx) { | |
| 738 var additionalIndent = countIndentation(); | |
| 739 return or([ | |
| 740 () => transaction(() { | |
| 741 if (!s_indent(additionalIndent)) return null; | |
| 742 return or([ | |
| 743 () => ns_l_compactSequence(indent + 1 + additionalIndent), | |
| 744 () => ns_l_compactMapping(indent + 1 + additionalIndent)]); | |
| 745 }), | |
| 746 () => s_l_blockNode(indent, ctx), | |
| 747 () => s_l_comments() ? e_node() : null]); | |
| 748 } | |
| 749 | |
| 750 // 186 | |
| 751 _Node ns_l_compactSequence(int indent) => context('sequence', () { | |
| 752 var first = c_l_blockSeqEntry(indent); | |
| 753 if (!truth(first)) return null; | |
| 754 | |
| 755 var content = zeroOrMore(() => transaction(() { | |
| 756 if (!s_indent(indent)) return null; | |
| 757 return c_l_blockSeqEntry(indent); | |
| 758 })); | |
| 759 content.insertRange(0, 1, first); | |
| 760 | |
| 761 return new _SequenceNode("?", content); | |
| 762 }); | |
| 763 | |
| 764 // 187 | |
| 765 _Node l_blockMapping(int indent) => context('mapping', () { | |
| 766 var additionalIndent = countIndentation() - indent; | |
| 767 if (additionalIndent <= 0) return null; | |
| 768 | |
| 769 var pairs = oneOrMore(() => transaction(() { | |
| 770 if (!s_indent(indent + additionalIndent)) return null; | |
| 771 return ns_l_blockMapEntry(indent + additionalIndent); | |
| 772 })); | |
| 773 if (!truth(pairs)) return null; | |
| 774 | |
| 775 return map(pairs); | |
| 776 }); | |
| 777 | |
| 778 // 188 | |
| 779 _Pair<_Node, _Node> ns_l_blockMapEntry(int indent) => or([ | |
| 780 () => c_l_blockMapExplicitEntry(indent), | |
| 781 () => ns_l_blockMapImplicitEntry(indent) | |
| 782 ]); | |
| 783 | |
| 784 // 189 | |
| 785 // TODO(nweiz): implement | |
| 786 _Pair<_Node, _Node> c_l_blockMapExplicitEntry(int indent) => null; | |
| 787 | |
| 788 // 192 | |
| 789 _Pair<_Node, _Node> ns_l_blockMapImplicitEntry(int indent) => transaction(() { | |
| 790 var key = or([ns_s_blockMapImplicitKey, e_node]); | |
| 791 var value = c_l_blockMapImplicitValue(indent); | |
| 792 return truth(value) ? new _Pair<_Node, _Node>(key, value) : null; | |
| 793 }); | |
| 794 | |
| 795 // 193 | |
| 796 _Node ns_s_blockMapImplicitKey() => context('mapping key', () => or([ | |
| 797 () => c_s_implicitJsonKey(BLOCK_KEY), | |
| 798 () => c_s_implicitYamlKey(BLOCK_KEY) | |
| 799 ])); | |
| 800 | |
| 801 // 194 | |
| 802 _Node c_l_blockMapImplicitValue(int indent) => context('mapping value', () => | |
| 803 transaction(() { | |
| 804 if (!c_indicator(C_MAPPING_VALUE)) return null; | |
| 805 return or([ | |
| 806 () => s_l_blockNode(indent, BLOCK_OUT), | |
| 807 () => s_l_comments() ? e_node() : null | |
| 808 ]); | |
| 809 })); | |
| 810 | |
| 811 // 195 | |
| 812 _Node ns_l_compactMapping(int indent) => context('mapping', () { | |
| 813 var first = ns_l_blockMapEntry(indent); | |
| 814 if (!truth(first)) return null; | |
| 815 | |
| 816 var pairs = zeroOrMore(() => transaction(() { | |
| 817 if (!s_indent(indent)) return null; | |
| 818 return ns_l_blockMapEntry(indent); | |
| 819 })); | |
| 820 pairs.insertRange(0, 1, first); | |
| 821 | |
| 822 return map(pairs); | |
| 823 }); | |
| 824 | |
| 825 // 196 | |
| 826 _Node s_l_blockNode(int indent, int ctx) => or([ | |
| 827 () => s_l_blockInBlock(indent, ctx), | |
| 828 () => s_l_flowInBlock(indent) | |
| 829 ]); | |
| 830 | |
| 831 // 197 | |
| 832 _Node s_l_flowInBlock(int indent) => transaction(() { | |
| 833 if (!s_separate(indent + 1, FLOW_OUT)) return null; | |
| 834 var node = ns_flowNode(indent + 1, FLOW_OUT); | |
| 835 if (!truth(node)) return null; | |
| 836 if (!s_l_comments()) return null; | |
| 837 return node; | |
| 838 }); | |
| 839 | |
| 840 // 198 | |
| 841 _Node s_l_blockInBlock(int indent, int ctx) => or([ | |
| 842 () => s_l_blockScalar(indent, ctx), | |
| 843 () => s_l_blockCollection(indent, ctx) | |
| 844 ]); | |
| 845 | |
| 846 // 199 | |
| 847 _Node s_l_blockScalar(int indent, int ctx) => transaction(() { | |
| 848 if (!s_separate(indent + 1, ctx)) return null; | |
| 849 var props = transaction(() { | |
| 850 var props = c_ns_properties(indent + 1, ctx); | |
| 851 if (!truth(props)) return null; | |
| 852 if (!s_separate(indent + 1, ctx)) return null; | |
| 853 return props; | |
| 854 }); | |
| 855 if (!truth(props)) props = new _Pair<_Tag, String>(null, null); | |
| 856 | |
| 857 var node = or([() => c_l_literal(indent), () => c_l_folded(indent)]); | |
| 858 if (!truth(node)) return null; | |
| 859 return addProps(node, props); | |
| 860 }); | |
| 861 | |
| 862 // 200 | |
| 863 _Node s_l_blockCollection(int indent, int ctx) => transaction(() { | |
| 864 var props = transaction(() { | |
| 865 if (!s_separate(indent + 1, ctx)) return null; | |
| 866 return c_ns_properties(indent + 1, ctx); | |
| 867 }); | |
| 868 if (!truth(props)) props = new _Pair<_Tag, String>(null, null); | |
| 869 | |
| 870 if (!s_l_comments()) return null; | |
| 871 return or([ | |
| 872 () => l_blockSequence(seqSpaces(indent, ctx)), | |
| 873 () => l_blockMapping(indent)]); | |
| 874 }); | |
| 875 | |
| 876 // 201 | |
| 877 int seqSpaces(int indent, int ctx) => ctx == BLOCK_OUT ? indent - 1 : indent; | |
| 878 | |
| 879 // 202 | |
| 880 void l_documentPrefix() { | |
| 881 zeroOrMore(l_comment); | |
| 882 } | |
| 883 | |
| 884 // 203 | |
| 885 bool c_directivesEnd() => rawString("---"); | |
| 886 | |
| 887 // 204 | |
| 888 bool c_documentEnd() => rawString("..."); | |
| 889 | |
| 890 // 205 | |
| 891 bool l_documentSuffix() => transaction(() { | |
| 892 if (!c_documentEnd()) return false; | |
| 893 return s_l_comments(); | |
| 894 }); | |
| 895 | |
| 896 // 206 | |
| 897 bool c_forbidden() { | |
| 898 if (!inBareDocument || !atStartOfLine) return false; | |
| 899 var forbidden = false; | |
| 900 transaction(() { | |
| 901 if (!truth(or([c_directivesEnd, c_documentEnd]))) return; | |
| 902 var char = peek(); | |
| 903 forbidden = isBreak(char) || isSpace(char) || atEndOfFile; | |
| 904 return; | |
| 905 }); | |
| 906 return forbidden; | |
| 907 } | |
| 908 | |
| 909 // 207 | |
| 910 _Node l_bareDocument() { | |
| 911 try { | |
| 912 inBareDocument = true; | |
| 913 return s_l_blockNode(-1, BLOCK_IN); | |
| 914 } finally { | |
| 915 inBareDocument = false; | |
| 916 } | |
| 917 } | |
| 918 | |
| 919 // 208 | |
| 920 _Node l_explicitDocument() { | |
| 921 if (!c_directivesEnd()) return null; | |
| 922 var doc = l_bareDocument(); | |
| 923 if (truth(doc)) return doc; | |
| 924 | |
| 925 doc = e_node(); | |
| 926 s_l_comments(); | |
| 927 return doc; | |
| 928 } | |
| 929 | |
| 930 // 209 | |
| 931 _Node l_directiveDocument() { | |
| 932 if (!truth(oneOrMore(l_directive))) return null; | |
| 933 var doc = l_explicitDocument(); | |
| 934 if (doc != null) return doc; | |
| 935 parseFailed(); | |
| 936 } | |
| 937 | |
| 938 // 210 | |
| 939 _Node l_anyDocument() => | |
| 940 or([l_directiveDocument, l_explicitDocument, l_bareDocument]); | |
| 941 | |
| 942 // 211 | |
| 943 List<_Node> l_yamlStream() { | |
| 944 var docs = []; | |
| 945 zeroOrMore(l_documentPrefix); | |
| 946 var first = zeroOrOne(l_anyDocument); | |
| 947 if (!truth(first)) first = e_node(); | |
| 948 docs.add(first); | |
| 949 | |
| 950 zeroOrMore(() { | |
| 951 var doc; | |
| 952 if (truth(oneOrMore(l_documentSuffix))) { | |
| 953 zeroOrMore(l_documentPrefix); | |
| 954 doc = zeroOrOne(l_anyDocument); | |
| 955 } else { | |
| 956 zeroOrMore(l_documentPrefix); | |
| 957 doc = zeroOrOne(l_explicitDocument); | |
| 958 } | |
| 959 if (truth(doc)) docs.add(doc); | |
| 960 return doc; | |
| 961 }); | |
| 962 | |
| 963 if (!atEndOfFile) parseFailed(); | |
| 964 return docs; | |
| 965 } | |
| 966 } | |
| 967 | |
| 968 class SyntaxError extends YamlException { | |
| 969 final int line; | |
| 970 final int column; | |
| 971 | |
| 972 SyntaxError(this.line, this.column, String msg) : super(msg); | |
| 973 | |
| 974 String toString() => "Syntax error on line $line, column $column: $msg"; | |
| 975 } | |
| 976 | |
| 977 /** A pair of values. */ | |
| 978 class _Pair<E, F> { | |
| 979 E first; | |
| 980 F last; | |
| 981 | |
| 982 _Pair(this.first, this.last); | |
| 983 | |
| 984 String toString() => '($first, $last)'; | |
| 985 } | |
| OLD | NEW |