Chromium Code Reviews| 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; | |
|
Bob Nystrom
2012/04/20 20:28:55
Why make these private?
nweiz
2012/04/23 23:06:33
Changed as part of the great de-underscoring.
| |
| 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 curent (0-based) column in the source string. */ | |
|
Bob Nystrom
2012/04/20 20:28:55
"current"
nweiz
2012/04/23 23:06:33
Done.
| |
| 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) : _s = s, _len = s.length, | |
|
Bob Nystrom
2012/04/20 20:28:55
I would format like:
_Parser(...)
: _s = s,
nweiz
2012/04/23 23:06:33
Done.
| |
| 127 _contextStack = <String>["document"]; | |
| 128 | |
| 129 /** | |
| 130 * Return the character at the current position, then move that position | |
| 131 * forward one character. Also updates the current line and column numbers. | |
| 132 */ | |
| 133 int _next() { | |
| 134 if (_pos == _len) return -1; | |
| 135 var char = _s.charCodeAt(_pos++); | |
| 136 if (_isBreak(char)) { | |
| 137 _line++; | |
| 138 _column = 0; | |
| 139 } else { | |
| 140 _column++; | |
| 141 } | |
| 142 | |
| 143 if (_farthestLine < _line) { | |
| 144 _farthestLine = _line; | |
| 145 _farthestColumn = _column; | |
| 146 _farthestContext = _contextStack.last(); | |
| 147 } else if (_farthestLine == _line && _farthestColumn < _column) { | |
| 148 _farthestColumn = _column; | |
| 149 _farthestContext = _contextStack.last(); | |
| 150 } | |
| 151 | |
| 152 return char; | |
| 153 } | |
| 154 | |
| 155 /** | |
| 156 * Returns the character at the current position, or the character [i] | |
| 157 * characters after the current position. | |
| 158 * | |
| 159 * Returns -1 if this would return a character after the end or before the | |
| 160 * beginning of the input string. | |
| 161 */ | |
| 162 int _peek([int i = 0]) { | |
| 163 var pos = _pos + i; | |
| 164 return (pos >= _len || pos < 0) ? -1 : _s.charCodeAt(pos); | |
| 165 } | |
| 166 | |
| 167 /** | |
| 168 * The truthiness operator. Returns false if [obj] is null or false, true | |
|
Bob Nystrom
2012/04/20 20:28:55
If you want to be fancy, consider code-formatting
nweiz
2012/04/23 23:06:33
Done.
| |
| 169 * otherwise. | |
| 170 */ | |
| 171 bool _(obj) => obj != null && obj != false; | |
| 172 | |
| 173 /** | |
| 174 * Consumes the current character if it matches [matcher]. Returns the result | |
| 175 * of [matcher]. | |
| 176 */ | |
| 177 bool _consume(bool matcher(int)) { | |
| 178 if (matcher(_peek())) { | |
| 179 _next(); | |
| 180 return true; | |
| 181 } | |
| 182 return false; | |
| 183 } | |
| 184 | |
| 185 /** | |
| 186 * Calls [consumer] until it returns a falsey value. Returns a list of all | |
| 187 * truthy return values of [consumer], or null if it didn't consume anything. | |
| 188 * | |
| 189 * Conceptually, repeats a production one or more times. | |
| 190 */ | |
| 191 List _oneOrMore(consumer()) { | |
| 192 var first = consumer(); | |
| 193 if (!_(first)) return null; | |
| 194 var out = [first]; | |
| 195 while (true) { | |
| 196 var el = consumer(); | |
| 197 if (!_(el)) return out; | |
| 198 out.add(el); | |
| 199 } | |
| 200 } | |
| 201 | |
| 202 /** | |
| 203 * Calls [consumer] until it returns a falsey value. Returns a list of all | |
| 204 * truthy return values of [consumer], or the empty list if it didn't consume | |
| 205 * anything. | |
| 206 * | |
| 207 * Conceptually, repeats a production any number of times. | |
| 208 */ | |
| 209 List _zeroOrMore(consumer()) { | |
| 210 var out = []; | |
| 211 var pos = _pos; | |
| 212 while (true) { | |
| 213 var el = consumer(); | |
| 214 if (!_(el) || pos == _pos) return out; | |
| 215 pos = _pos; | |
| 216 out.add(el); | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 /** | |
| 221 * Just calls [consumer] and returns its result. Used to make it explicit that a | |
|
Bob Nystrom
2012/04/20 20:28:55
Long line.
nweiz
2012/04/23 23:06:33
Done.
| |
| 222 * production is intended to be optional. | |
| 223 */ | |
| 224 _zeroOrOne(consumer()) => consumer(); | |
| 225 | |
| 226 /** | |
| 227 * Calls each function in [consumers] until one returns a truthy value, then | |
| 228 * returns that. | |
| 229 */ | |
| 230 _or(List<Function> consumers) { | |
| 231 for (var c in consumers) { | |
| 232 var res = c(); | |
| 233 if (_(res)) return res; | |
| 234 } | |
| 235 return null; | |
| 236 } | |
| 237 | |
| 238 /** | |
| 239 * Calls [consumer] and returns its result, but rolls back the parser state if | |
| 240 * [consumer] returns a falsey value. | |
| 241 */ | |
| 242 _group(consumer()) { | |
|
Bob Nystrom
2012/04/20 20:28:55
This name isn't clear to me. Maybe "attempt"?
nweiz
2012/04/23 23:06:33
Changed to "transaction"; slightly longer, but muc
| |
| 243 int pos = _pos, line = _line, column = _column; | |
| 244 var res = consumer(); | |
| 245 if (_(res)) return res; | |
| 246 | |
| 247 _pos = pos; | |
| 248 _line = line; | |
| 249 _column = column; | |
| 250 return res; | |
| 251 } | |
| 252 | |
| 253 /** | |
| 254 * Consumes [n] characters matching [matcher], or none if there isn't a | |
| 255 * complete match. The first argument to [matcher] is the character code, the | |
| 256 * second is the index (from 0 to [n] - 1). | |
| 257 * | |
| 258 * Returns whether or not the characters were consumed. | |
| 259 */ | |
| 260 bool _nAtOnce(int n, bool matcher(int, int)) => _group(() { | |
|
Bob Nystrom
2012/04/20 20:28:55
That doesn't do what you think it does. It declare
nweiz
2012/04/23 23:06:33
Fixed.
| |
| 261 for (int i = 0; i < n; i++) { | |
| 262 if (!_consume((c) => matcher(c, i))) return false; | |
| 263 } | |
| 264 return true; | |
| 265 }); | |
| 266 | |
| 267 /** | |
| 268 * Consumes the exact characters in [str], or nothing. | |
| 269 * | |
| 270 * Returns whether or not the string was consumed. | |
| 271 */ | |
| 272 bool _rawString(String str) => | |
| 273 _nAtOnce(str.length, (c, i) => str.charCodeAt(i) == c); | |
| 274 | |
| 275 /** | |
| 276 * Consumes and returns a string of characters matching [matcher], or null if | |
| 277 * there are no such characters. | |
| 278 */ | |
| 279 String _stringOf(bool matcher(int)) => | |
| 280 _captureString(_oneOrMore(() => _consume(matcher))); | |
| 281 | |
| 282 /** | |
| 283 * Calls [consumer] and returns the string that was consumed while doing so, | |
| 284 * or null if [consumer] returned a falsey value. Automatically wraps | |
| 285 * [consumer] in `_group`. | |
| 286 */ | |
| 287 String _captureString(consumer()) { | |
| 288 int start = _pos; | |
| 289 var res = _group(consumer); | |
| 290 if (!_(res)) return null; | |
| 291 return _s.substring(start, _pos); | |
| 292 } | |
| 293 | |
| 294 /** | |
| 295 * Adds a tag and an anchor to [node], if they're defined. [props] should be a | |
| 296 * two-element list where the first element is a Tag or null and the second is | |
| 297 * a String or null. | |
| 298 */ | |
| 299 _Node _addProps(_Node node, List props) { | |
|
Bob Nystrom
2012/04/20 20:28:55
Using a List here is kinda gross. As tedious as it
nweiz
2012/04/23 23:06:33
Done.
| |
| 300 if (_(props[0])) node.tag = props[0]; | |
| 301 if (_(props[1])) node.anchor = props[1]; | |
| 302 return node; | |
| 303 } | |
| 304 | |
| 305 /** Creates a MappingNode from [pairs]. */ | |
| 306 _MappingNode _map(List<List<_Node>> pairs) { | |
|
Bob Nystrom
2012/04/20 20:28:55
A Pair<A, B> class would help here. The code that
nweiz
2012/04/23 23:06:33
Done.
| |
| 307 var content = new Map<_Node, _Node>(); | |
| 308 pairs.forEach((pair) { content[pair[0]] = pair[1]; }); | |
|
Bob Nystrom
2012/04/20 20:28:55
=>?
nweiz
2012/04/23 23:06:33
Doesn't work with "=", because it's a statement.
Bob Nystrom
2012/04/24 00:50:36
? Assignment is an expression.
nweiz
2012/04/25 00:26:29
Huh, I could have sworn this didn't work last time
| |
| 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 | |
|
Bob Nystrom
2012/04/20 20:28:55
"."
nweiz
2012/04/23 23:06:33
Done.
| |
| 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 (_(result)) return result; | |
| 336 _error("expected $expected"); | |
| 337 } | |
| 338 | |
| 339 /** | |
| 340 * Throws an error saying that the parse failed. Uses `_farthestLine`, | |
|
Bob Nystrom
2012/04/20 20:28:55
[] instead of ``.
nweiz
2012/04/23 23:06:33
Done. I didn't remember that [] worked for members
| |
| 341 * `_farthestColumn`, and `_farthestContext` to provide additional | |
| 342 * information. | |
| 343 */ | |
| 344 _parseFailed() { | |
| 345 throw new SyntaxError(_farthestLine + 1, _farthestColumn + 1, | |
| 346 "invalid YAML in $_farthestContext"); | |
| 347 } | |
| 348 | |
| 349 /** Returns the number of spaces after the current position. */ | |
| 350 int _detectIndentation() { | |
|
Bob Nystrom
2012/04/20 20:28:55
"countIndentation"? I would expect "detect" to ret
nweiz
2012/04/23 23:06:33
Done.
| |
| 351 var i = 0; | |
| 352 while (_peek(i) == SP) i++; | |
| 353 return i; | |
| 354 } | |
| 355 | |
| 356 /** Returns whether the current position is at the beginning of a line. */ | |
| 357 bool get _atStartOfLine() { | |
|
Bob Nystrom
2012/04/20 20:28:55
=> _column == 0
?
nweiz
2012/04/23 23:06:33
Good idea. I wrote this before I added _column.
| |
| 358 var char = _peek(-1); | |
| 359 return char == -1 || _isBreak(char); | |
| 360 } | |
| 361 | |
| 362 /** Returns whether the current position is at the end of the input. */ | |
| 363 bool get _atEndOfFile() => _pos == _len; | |
| 364 | |
| 365 /** | |
| 366 * Given an indicator character, returns the type of that indicator (or null | |
| 367 * if the indicator isn't found. | |
| 368 */ | |
| 369 int _indicatorType(int char) { | |
| 370 switch (char) { | |
| 371 case HYPHEN: return _C_SEQUENCE_ENTRY; | |
| 372 case QUESTION_MARK: return _C_MAPPING_KEY; | |
| 373 case COLON: return _C_MAPPING_VALUE; | |
| 374 case COMMA: return _C_COLLECT_ENTRY; | |
| 375 case LEFT_BRACKET: return _C_SEQUENCE_START; | |
| 376 case RIGHT_BRACKET: return _C_SEQUENCE_END; | |
| 377 case LEFT_BRACE: return _C_MAPPING_START; | |
| 378 case RIGHT_BRACE: return _C_MAPPING_END; | |
| 379 case HASH: return _C_COMMENT; | |
| 380 case AMPERSAND: return _C_ANCHOR; | |
| 381 case ASTERISK: return _C_ALIAS; | |
| 382 case EXCLAMATION: return _C_TAG; | |
| 383 case VERTICAL_BAR: return _C_LITERAL; | |
| 384 case GREATER_THAN: return _C_FOLDED; | |
| 385 case SINGLE_QUOTE: return _C_SINGLE_QUOTE; | |
| 386 case DOUBLE_QUOTE: return _C_DOUBLE_QUOTE; | |
| 387 case PERCENT: return _C_DIRECTIVE; | |
| 388 case AT: | |
| 389 case GRAVE_ACCENT: | |
| 390 return _C_RESERVED; | |
| 391 default: return null; | |
| 392 } | |
| 393 } | |
| 394 | |
| 395 // 1 | |
| 396 bool _isPrintable(int char) { | |
| 397 return char == TAB || char == LF || char == CR || | |
| 398 (char >= SP && char <= TILDE) || char == NEL || | |
| 399 (char >= 0xA0 && char <= 0xD7FF) || (char >= 0xE000 && char <= 0xFFFD) || | |
| 400 (char >= 0x10000 && char <= 0x10FFFF); | |
|
Bob Nystrom
2012/04/20 20:28:55
Style nit, but I would probably do one clause per
nweiz
2012/04/23 23:06:33
Done.
| |
| 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 || indicator == _C_SEQUENCE_START || | |
| 410 indicator == _C_SEQUENCE_END || indicator == _C_MAPPING_START || | |
| 411 indicator == _C_MAPPING_END; | |
| 412 } | |
| 413 | |
| 414 // 26 | |
| 415 bool _isBreak(int char) => char == LF || char == CR; | |
| 416 | |
| 417 // 27 | |
| 418 bool _isNonBreak(int char) => _isPrintable(char) && !_isBreak(char); | |
| 419 | |
| 420 // 30 | |
| 421 bool _b_non_content() => _consume(_isBreak); | |
| 422 | |
| 423 // 33 | |
| 424 bool _isSpace(int char) => char == SP || char == TAB; | |
| 425 | |
| 426 // 34 | |
| 427 bool _isNonSpace(int char) => _isNonBreak(char) && !_isSpace(char); | |
| 428 | |
| 429 // 63 | |
| 430 bool _s_indent(int n) => _nAtOnce(n, (c, i) => c == SP); | |
| 431 | |
| 432 // 66 | |
| 433 bool _s_separateInLine() => _group(() => | |
| 434 _(_oneOrMore(() => _consume(_isSpace))) || _atStartOfLine); | |
| 435 | |
| 436 // 69 | |
| 437 bool _s_flowLinePrefix(int n) { | |
| 438 if (!_s_indent(n)) return false; | |
| 439 _zeroOrOne(_s_separateInLine); | |
| 440 return true; | |
| 441 } | |
| 442 | |
| 443 // 74 | |
| 444 bool _s_flowFolded(int n) => false; // TODO(nweiz): implement | |
|
Bob Nystrom
2012/04/20 20:28:55
Throw UnsupportedOperationException instead. Here
nweiz
2012/04/23 23:06:33
That would break parsing of supported structures c
Bob Nystrom
2012/04/24 00:50:36
Oh, it's relying on transaction to backtrack here?
| |
| 445 | |
| 446 // 75 | |
| 447 bool _c_nb_commentText() { | |
| 448 if (!_c_indicator(_C_COMMENT)) return false; | |
| 449 _zeroOrMore(() => _consume(_isNonBreak)); | |
| 450 return true; | |
| 451 } | |
| 452 | |
| 453 // 76 | |
| 454 bool _b_comment() => _atEndOfFile || _b_non_content(); | |
| 455 | |
| 456 // 77 | |
| 457 bool _s_b_comment() { | |
| 458 if (_s_separateInLine()) { | |
| 459 _zeroOrOne(_c_nb_commentText); | |
| 460 } | |
| 461 return _b_comment(); | |
| 462 } | |
| 463 | |
| 464 // 78 | |
| 465 bool _l_comment() => _group(() { | |
| 466 if (!_s_separateInLine()) return false; | |
| 467 _zeroOrOne(_c_nb_commentText); | |
| 468 return _b_comment(); | |
| 469 }); | |
| 470 | |
| 471 // 79 | |
| 472 bool _s_l_comments() { | |
| 473 if (!_s_b_comment() && !_atStartOfLine) return false; | |
| 474 _zeroOrMore(_l_comment); | |
| 475 return true; | |
| 476 } | |
| 477 | |
| 478 // 80 | |
| 479 bool _s_separate(int n, int c) { | |
| 480 switch (c) { | |
| 481 case BLOCK_OUT: | |
| 482 case BLOCK_IN: | |
| 483 case FLOW_OUT: | |
| 484 case FLOW_IN: | |
| 485 return _s_separateLines(n); | |
| 486 case BLOCK_KEY: | |
| 487 case FLOW_KEY: | |
| 488 return _s_separateInLine(); | |
| 489 } | |
|
Bob Nystrom
2012/04/20 20:28:55
Switches without defaults feel strange to me, espe
nweiz
2012/04/23 23:06:33
Done.
| |
| 490 } | |
| 491 | |
| 492 // 81 | |
| 493 bool _s_separateLines(int n) { | |
| 494 return _group(() => _s_l_comments() && _s_flowLinePrefix(n)) || | |
|
Bob Nystrom
2012/04/20 20:28:55
=> _group(() ... )
nweiz
2012/04/23 23:06:33
I can't get this formatted so that everything fits
| |
| 495 _s_separateInLine(); | |
| 496 } | |
| 497 | |
| 498 // 82 | |
| 499 bool _l_directive() => false; // TODO(nweiz): implement | |
| 500 | |
| 501 // 96 | |
| 502 List _c_ns_properties(int n, int c) { | |
|
Bob Nystrom
2012/04/20 20:28:55
"n" isn't a helpful name. "indent"?
nweiz
2012/04/23 23:06:33
Changed n -> indent and c -> ctx (not "context" to
| |
| 503 var tag, anchor; | |
| 504 tag = _c_ns_tagProperty(); | |
| 505 if (_(tag)) { | |
| 506 anchor = _group(() { | |
| 507 if (!_s_separate(n, c)) return null; | |
| 508 return _c_ns_anchorProperty(); | |
| 509 }); | |
| 510 return [tag, anchor]; | |
| 511 } | |
| 512 | |
| 513 anchor = _c_ns_anchorProperty(); | |
| 514 if (_(anchor)) { | |
| 515 tag = _group(() { | |
| 516 if (!_s_separate(n, c)) return null; | |
| 517 return _c_ns_tagProperty(); | |
| 518 }); | |
| 519 return [tag, anchor]; | |
| 520 } | |
| 521 | |
| 522 return null; | |
| 523 } | |
| 524 | |
| 525 // 97 | |
| 526 _Tag _c_ns_tagProperty() => null; // TODO(nweiz): implement | |
| 527 | |
| 528 // 101 | |
| 529 String _c_ns_anchorProperty() => null; // TODO(nweiz): implement | |
| 530 | |
| 531 // 102 | |
| 532 bool _isAnchorChar(int char) => _isNonSpace(char) && !_isFlowIndicator(char); | |
| 533 | |
| 534 // 103 | |
| 535 String _ns_anchorName() => _captureString(_isAnchorChar); | |
| 536 | |
| 537 // 104 | |
| 538 _Node _c_ns_aliasNode() { | |
| 539 if (!_c_indicator(_C_ALIAS)) return null; | |
| 540 var name = _expect(_ns_anchorName(), 'anchor name'); | |
| 541 return new _AliasNode(name); | |
| 542 } | |
| 543 | |
| 544 // 105 | |
| 545 _ScalarNode _e_scalar() => new _ScalarNode("?", content: ""); | |
| 546 | |
| 547 // 106 | |
| 548 _ScalarNode _e_node() => _e_scalar(); | |
| 549 | |
| 550 // 126 | |
| 551 bool _ns_plainFirst(int c) { | |
| 552 var char = _peek(); | |
| 553 var indicator = _indicatorType(char); | |
| 554 if (indicator == _C_RESERVED) { | |
| 555 _error("reserved indicators can't start a plain scalar"); | |
| 556 } | |
| 557 var match = (_isNonSpace(char) && indicator == null) || | |
| 558 ((indicator == _C_MAPPING_KEY || | |
| 559 indicator == _C_MAPPING_VALUE || | |
| 560 indicator == _C_SEQUENCE_ENTRY) && | |
| 561 _isPlainSafe(c, _peek(1))); | |
| 562 | |
| 563 if (match) _next(); | |
| 564 return match; | |
| 565 } | |
| 566 | |
| 567 // 127 | |
| 568 bool _isPlainSafe(int c, int char) { | |
| 569 switch (c) { | |
| 570 case FLOW_OUT: | |
| 571 case BLOCK_KEY: | |
| 572 // 128 | |
| 573 return _isNonSpace(char); | |
| 574 case FLOW_IN: | |
| 575 case FLOW_KEY: | |
| 576 // 129 | |
| 577 return _isNonSpace(char) && !_isFlowIndicator(char); | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 // 130 | |
| 582 bool _ns_plainChar(int c) { | |
| 583 var char = _peek(); | |
| 584 var indicator = _indicatorType(char); | |
| 585 var match = (_isPlainSafe(c, char) && indicator != _C_MAPPING_VALUE && | |
|
Bob Nystrom
2012/04/20 20:28:55
This is kind of hard to understand. Maybe break it
nweiz
2012/04/23 23:06:33
Done.
| |
| 586 indicator != _C_COMMENT) || | |
| 587 (_isNonSpace(_peek(-1)) && indicator == _C_COMMENT) || | |
| 588 (indicator == _C_MAPPING_VALUE && _isPlainSafe(c, _peek(1))); | |
| 589 | |
| 590 if (match) _next(); | |
| 591 return match; | |
| 592 } | |
| 593 | |
| 594 // 131 | |
| 595 String _ns_plain(int n, int c) => _context('plain scalar', () { | |
| 596 switch (c) { | |
| 597 case FLOW_OUT: | |
| 598 case FLOW_IN: | |
| 599 return _ns_plainMultiLine(n, c); | |
| 600 case BLOCK_KEY: | |
| 601 case FLOW_KEY: | |
| 602 return _ns_plainOneLine(c); | |
| 603 } | |
| 604 }); | |
| 605 | |
| 606 // 132 | |
| 607 void _nb_ns_plainInLine(int c) { | |
| 608 _zeroOrMore(() => _group(() { | |
| 609 _zeroOrMore(() => _consume(_isSpace)); | |
| 610 return _ns_plainChar(c); | |
| 611 })); | |
| 612 } | |
| 613 | |
| 614 // 133 | |
| 615 String _ns_plainOneLine(int c) => _captureString(() { | |
| 616 if (_c_forbidden()) return false; | |
| 617 if (!_ns_plainFirst(c)) return false; | |
| 618 _nb_ns_plainInLine(c); | |
| 619 return true; | |
| 620 }); | |
| 621 | |
| 622 // 134 | |
| 623 bool _s_ns_plainNextLine(int n, int c) => _group(() { | |
| 624 if (_c_forbidden()) return false; | |
| 625 if (!_s_flowFolded(n)) return false; | |
| 626 if (!_ns_plainChar(c)) return false; | |
| 627 _nb_ns_plainInLine(c); | |
| 628 return true; | |
| 629 }); | |
| 630 | |
| 631 // 135 | |
| 632 String _ns_plainMultiLine(int n, int c) => _captureString(() { | |
| 633 if (!_(_ns_plainOneLine(c))) return false; | |
| 634 _zeroOrMore(() => _s_ns_plainNextLine(n, c)); | |
| 635 return true; | |
| 636 }); | |
| 637 | |
| 638 // 154 | |
| 639 _Node _c_s_implicitYamlKey(int c) => _group(() { | |
| 640 /* Indentation parameter is unused in this path */ | |
|
Bob Nystrom
2012/04/20 20:28:55
//
nweiz
2012/04/23 23:06:33
Done.
| |
| 641 var node = _ns_flowYamlNode(-10, c); | |
|
Bob Nystrom
2012/04/20 20:28:55
What is -10 here?
nweiz
2012/04/23 23:06:33
A value that's never referenced. Would null be bet
Bob Nystrom
2012/04/24 00:50:36
Yeah, plus a comment explaining that.
nweiz
2012/04/25 00:26:29
Done.
| |
| 642 if (!_(node)) return null; | |
| 643 _zeroOrOne(_s_separateInLine); | |
| 644 return node; | |
| 645 }); | |
| 646 | |
| 647 // 155 | |
| 648 _Node _c_s_implicitJsonKey(int c) => null; // TODO(nweiz): implement | |
| 649 | |
| 650 // 156 | |
| 651 _Node _ns_flowYamlContent(int n, int c) { | |
| 652 var str = _ns_plain(n, c); | |
| 653 if (!_(str)) return null; | |
| 654 return new _ScalarNode("?", content: str); | |
| 655 } | |
| 656 | |
| 657 // 157 | |
| 658 _Node _ns_flowJsonContent(int n, int c) => null; // TODO(nweiz): implement | |
| 659 | |
| 660 // 158 | |
| 661 _Node _ns_flowContent(int n, int c) => _or([ | |
| 662 () => _ns_flowYamlContent(n, c), | |
| 663 () => _ns_flowJsonContent(n, c) | |
| 664 ]); | |
| 665 | |
| 666 // 159 | |
| 667 _Node _ns_flowYamlNode(int n, int c) => _or([ | |
| 668 _c_ns_aliasNode, | |
| 669 () => _ns_flowYamlContent(n, c), | |
| 670 () { | |
| 671 var props = _c_ns_properties(n, c); | |
| 672 if (!_(props)) return null; | |
| 673 var node = _or([ | |
| 674 () => _group(() { | |
| 675 if (!_s_separate(n, c)) return null; | |
| 676 return _ns_flowYamlContent(n, c); | |
| 677 }), | |
| 678 _e_scalar | |
| 679 ]); | |
| 680 return _addProps(node, props); | |
| 681 } | |
| 682 ]); | |
| 683 | |
| 684 // 161 | |
| 685 _Node _ns_flowNode(int n, int c) => _or([ | |
|
Bob Nystrom
2012/04/20 20:28:55
I'm OK with => for _group() method bodies which ju
nweiz
2012/04/23 23:06:33
I disagree. I can see how _or looks weird -- a lis
| |
| 686 _c_ns_aliasNode, | |
| 687 () => _ns_flowContent(n, c), | |
| 688 () => _group(() { | |
| 689 var props = _c_ns_properties(n, c); | |
| 690 if (!_(props)) return null; | |
| 691 var node = _or([ | |
| 692 () => _group(() => _s_separate(n, c) ? _ns_flowContent(n, c) : null), | |
| 693 _e_scalar]); | |
| 694 return _addProps(node, props); | |
| 695 }) | |
| 696 ]); | |
| 697 | |
| 698 // 170 | |
| 699 _Node _c_l_literal(int n) => null; // TODO(nweiz); implement | |
| 700 | |
| 701 // 174 | |
| 702 _Node _c_l_folded(int n) => null; // TODO(nweiz); implement | |
| 703 | |
| 704 // 183 | |
| 705 _SequenceNode _l_blockSequence(int n) => _context('sequence', () { | |
| 706 var m = _detectIndentation() - n; | |
| 707 if (m <= 0) return null; | |
| 708 | |
| 709 var content = _oneOrMore(() => _group(() { | |
| 710 if (!_s_indent(n + m)) return null; | |
| 711 return _c_l_blockSeqEntry(n + m); | |
| 712 })); | |
| 713 if (!_(content)) return null; | |
| 714 | |
| 715 return new _SequenceNode("?", content); | |
| 716 }); | |
| 717 | |
| 718 // 184 | |
| 719 _Node _c_l_blockSeqEntry(int n) => _group(() { | |
| 720 if (!_c_indicator(_C_SEQUENCE_ENTRY)) return null; | |
| 721 if (_isNonSpace(_peek())) return null; | |
| 722 | |
| 723 return _s_l_blockIndented(n, BLOCK_IN); | |
| 724 }); | |
| 725 | |
| 726 // 185 | |
| 727 _Node _s_l_blockIndented(int n, int c) { | |
| 728 var m = _detectIndentation(); | |
| 729 return _or([() => _group(() { | |
|
Bob Nystrom
2012/04/20 20:28:55
Wow this is hard to parse. Move () => _group... on
nweiz
2012/04/23 23:06:33
Done.
| |
| 730 if (!_s_indent(m)) return null; | |
| 731 return _or([ | |
| 732 () => _ns_l_compactSequence(n+1+m), | |
| 733 () => _ns_l_compactMapping(n+1+m)]); | |
| 734 }), | |
| 735 () => _s_l_blockNode(n, c), | |
| 736 () => _s_l_comments() ? _e_node() : null]); | |
| 737 } | |
| 738 | |
| 739 // 186 | |
| 740 _Node _ns_l_compactSequence(int n) => _context('sequence', () { | |
| 741 var first = _c_l_blockSeqEntry(n); | |
| 742 if (!_(first)) return null; | |
| 743 | |
| 744 var content = _zeroOrMore(() => _group(() { | |
| 745 if (!_s_indent(n)) return null; | |
| 746 return _c_l_blockSeqEntry(n); | |
| 747 })); | |
| 748 content.insertRange(0, 1, first); | |
| 749 | |
| 750 return new _SequenceNode("?", content); | |
| 751 }); | |
| 752 | |
| 753 // 187 | |
| 754 _Node _l_blockMapping(int n) => _context('mapping', () { | |
| 755 var m = _detectIndentation() - n; | |
| 756 if (m <= 0) return null; | |
| 757 | |
| 758 var pairs = _oneOrMore(() => _group(() { | |
| 759 if (!_s_indent(n + m)) return null; | |
| 760 return _ns_l_blockMapEntry(n + m); | |
| 761 })); | |
| 762 if (!_(pairs)) return null; | |
| 763 | |
| 764 return _map(pairs); | |
| 765 }); | |
| 766 | |
| 767 // 188 | |
| 768 List<_Node> _ns_l_blockMapEntry(int n) => _or([ | |
| 769 () => _c_l_blockMapExplicitEntry(n), | |
| 770 () => _ns_l_blockMapImplicitEntry(n) | |
| 771 ]); | |
| 772 | |
| 773 // 189 | |
| 774 List<_Node> _c_l_blockMapExplicitEntry(int n) => null; // TODO(nweiz): impleme nt | |
| 775 | |
| 776 // 192 | |
| 777 List<_Node> _ns_l_blockMapImplicitEntry(int n) => _group(() { | |
| 778 var key = _or([_ns_s_blockMapImplicitKey, _e_node]); | |
| 779 var value = _c_l_blockMapImplicitValue(n); | |
| 780 return _(value) ? [key, value] : null; | |
| 781 }); | |
| 782 | |
| 783 // 193 | |
| 784 _Node _ns_s_blockMapImplicitKey() => _context('mapping key', () => _or([ | |
| 785 () => _c_s_implicitJsonKey(BLOCK_KEY), | |
| 786 () => _c_s_implicitYamlKey(BLOCK_KEY) | |
| 787 ])); | |
| 788 | |
| 789 // 194 | |
| 790 _Node _c_l_blockMapImplicitValue(int n) => _context('mapping value', () => | |
| 791 _group(() { | |
| 792 if (!_c_indicator(_C_MAPPING_VALUE)) return null; | |
| 793 return _or([ | |
| 794 () => _s_l_blockNode(n, BLOCK_OUT), | |
| 795 () => _s_l_comments() ? _e_node() : null | |
| 796 ]); | |
| 797 })); | |
| 798 | |
| 799 // 195 | |
| 800 _Node _ns_l_compactMapping(int n) => _context('mapping', () { | |
| 801 var first = _ns_l_blockMapEntry(n); | |
| 802 if (!_(first)) return null; | |
| 803 | |
| 804 var pairs = _zeroOrMore(() => _group(() { | |
| 805 if (!_s_indent(n)) return null; | |
| 806 return _ns_l_blockMapEntry(n); | |
| 807 })); | |
| 808 pairs.insertRange(0, 1, first); | |
| 809 | |
| 810 return _map(pairs); | |
| 811 }); | |
| 812 | |
| 813 // 196 | |
| 814 _Node _s_l_blockNode(int n, int c) => | |
| 815 _or([() => _s_l_blockInBlock(n, c), () => _s_l_flowInBlock(n)]); | |
| 816 | |
| 817 // 197 | |
| 818 _Node _s_l_flowInBlock(int n) => _group(() { | |
| 819 if (!_s_separate(n+1, FLOW_OUT)) return null; | |
|
Bob Nystrom
2012/04/20 20:28:55
Spaces around + here and elsewhere.
nweiz
2012/04/23 23:06:33
Done.
| |
| 820 var node = _ns_flowNode(n+1, FLOW_OUT); | |
| 821 if (!_(node)) return null; | |
| 822 if (!_s_l_comments()) return null; | |
| 823 return node; | |
| 824 }); | |
| 825 | |
| 826 // 198 | |
| 827 _Node _s_l_blockInBlock(int n, int c) => | |
| 828 _or([() => _s_l_blockScalar(n, c), () => _s_l_blockCollection(n, c)]); | |
| 829 | |
| 830 // 199 | |
| 831 _Node _s_l_blockScalar(int n, int c) => _group(() { | |
| 832 if (!_s_separate(n+1, c)) return null; | |
| 833 var props = _group(() { | |
| 834 var props = _c_ns_properties(n+1, c); | |
| 835 if (!_(props)) return null; | |
| 836 if (!_s_separate(n+1, c)) return null; | |
| 837 return props; | |
| 838 }); | |
| 839 if (!_(props)) props = [null, null]; | |
| 840 | |
| 841 var node = _or([() => _c_l_literal(n), () => _c_l_folded(n)]); | |
| 842 if (!_(node)) return null; | |
| 843 return _addProps(node, props); | |
| 844 }); | |
| 845 | |
| 846 // 200 | |
| 847 _Node _s_l_blockCollection(int n, int c) => _group(() { | |
| 848 var props = _group(() { | |
| 849 if (!_s_separate(n+1, c)) return null; | |
| 850 return _c_ns_properties(n+1, c); | |
| 851 }); | |
| 852 if (!_(props)) props = [null, null]; | |
| 853 | |
| 854 if (!_s_l_comments()) return null; | |
| 855 return _or([ | |
| 856 () => _l_blockSequence(_seqSpaces(n, c)), | |
| 857 () => _l_blockMapping(n)]); | |
| 858 }); | |
| 859 | |
| 860 // 201 | |
| 861 int _seqSpaces(int n, int c) => c == BLOCK_OUT ? n - 1 : n; | |
| 862 | |
| 863 // 202 | |
| 864 void _l_documentPrefix() { | |
| 865 _zeroOrMore(_l_comment); | |
| 866 } | |
| 867 | |
| 868 // 203 | |
| 869 bool _c_directivesEnd() => _rawString("---"); | |
| 870 | |
| 871 // 204 | |
| 872 bool _c_documentEnd() => _rawString("..."); | |
| 873 | |
| 874 // 205 | |
| 875 bool _l_documentSuffix() => _group(() { | |
| 876 if (!_c_documentEnd()) return false; | |
| 877 return _s_l_comments(); | |
| 878 }); | |
| 879 | |
| 880 // 206 | |
| 881 bool _c_forbidden() { | |
| 882 if (!_inBareDocument || !_atStartOfLine) return false; | |
| 883 var forbidden = false; | |
| 884 _group(() { | |
|
Bob Nystrom
2012/04/20 20:28:55
Couldn't you do:
return _group(() { ... });
and
nweiz
2012/04/23 23:06:33
No; that would move the cursor forward if it retur
| |
| 885 if (!_or([_c_directivesEnd, _c_documentEnd])) return; | |
| 886 var char = _peek(); | |
| 887 forbidden = _isBreak(char) || _isWhite(char) || _atEndOfFile; | |
| 888 return; | |
| 889 }); | |
| 890 return forbidden; | |
| 891 } | |
| 892 | |
| 893 // 207 | |
| 894 _Node _l_bareDocument() { | |
| 895 try { | |
| 896 _inBareDocument = true; | |
| 897 return _s_l_blockNode(-1, BLOCK_IN); | |
| 898 } finally { | |
| 899 _inBareDocument = false; | |
| 900 } | |
| 901 } | |
| 902 | |
| 903 // 208 | |
| 904 _Node _l_explicitDocument() { | |
| 905 if (!_c_directivesEnd()) return null; | |
| 906 var doc = _l_bareDocument(); | |
| 907 if (_(doc)) return doc; | |
| 908 | |
| 909 doc = _e_node(); | |
| 910 _s_l_comments(); | |
| 911 return doc; | |
| 912 } | |
| 913 | |
| 914 // 209 | |
| 915 _Node _l_directiveDocument() { | |
| 916 if (!_(_oneOrMore(_l_directive))) return null; | |
| 917 var doc = _l_explicitDocument(); | |
| 918 if (doc != null) return doc; | |
| 919 _parseFailed(); | |
| 920 } | |
| 921 | |
| 922 // 210 | |
| 923 _Node _l_anyDocument() => | |
| 924 _or([_l_directiveDocument, _l_explicitDocument, _l_bareDocument]); | |
| 925 | |
| 926 // 211 | |
| 927 List<_Node> _l_yamlStream() { | |
| 928 var docs = []; | |
| 929 _zeroOrMore(_l_documentPrefix); | |
| 930 var first = _zeroOrOne(_l_anyDocument); | |
| 931 if (!_(first)) first = _e_node(); | |
| 932 docs.add(first); | |
| 933 | |
| 934 _zeroOrMore(() { | |
| 935 var doc; | |
| 936 if (_(_oneOrMore(_l_documentSuffix))) { | |
| 937 _zeroOrMore(_l_documentPrefix); | |
| 938 doc = _zeroOrOne(_l_anyDocument); | |
| 939 } else { | |
| 940 _zeroOrMore(_l_documentPrefix); | |
| 941 doc = _zeroOrOne(_l_explicitDocument); | |
| 942 } | |
| 943 if (_(doc)) docs.add(doc); | |
| 944 return doc; | |
| 945 }); | |
| 946 | |
| 947 if (!_atEndOfFile) _parseFailed(); | |
| 948 return docs; | |
| 949 } | |
| 950 } | |
| 951 | |
| 952 class SyntaxError extends Error { | |
| 953 final int _line; | |
| 954 final int _column; | |
| 955 | |
| 956 SyntaxError(int this._line, int this._column, String _msg) : super(_msg); | |
| 957 | |
| 958 String toString() => "Syntax error on line $_line, column $_column: $_msg"; | |
| 959 } | |
| OLD | NEW |