Chromium Code Reviews| Index: lib/yaml/parser.dart |
| diff --git a/lib/yaml/parser.dart b/lib/yaml/parser.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..97726d21985148b9f142d022fb4f72b976bbcdbb |
| --- /dev/null |
| +++ b/lib/yaml/parser.dart |
| @@ -0,0 +1,959 @@ |
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +/** |
| + * Translates a string of characters into a YAML serialization tree. |
| + * |
| + * This parser is designed to closely follow the spec. All productions in the |
| + * spec are numbered, and the corresponding methods in the parser have the same |
| + * numbers. This is certainly not the most efficient way of parsing YAML, but it |
| + * is the easiest to write and read in the context of the spec. |
| + * |
| + * Methods corresponding to productions are also named as in the spec, |
| + * translating the name of the method (although not the annotation characters) |
| + * into camel-case for dart style.. For example, the spec has a production named |
| + * `nb-ns-plain-in-line`, and the method implementing it is named |
| + * `_nb_ns_plainInLine`. The exception to that rule is methods that just |
| + * recognize character classes; these are named `_is*`. |
| + */ |
| +class _Parser { |
| + static final TAB = 0x9; |
| + static final LF = 0xA; |
| + static final CR = 0xD; |
| + static final SP = 0x20; |
| + static final TILDE = 0x7E; |
| + static final NEL = 0x85; |
| + static final HYPHEN = 0x2D; |
| + static final QUESTION_MARK = 0x3F; |
| + static final COLON = 0x3A; |
| + static final COMMA = 0x2C; |
| + static final LEFT_BRACKET = 0x5B; |
| + static final RIGHT_BRACKET = 0x5D; |
| + static final LEFT_BRACE = 0x7B; |
| + static final RIGHT_BRACE = 0x7D; |
| + static final HASH = 0x23; |
| + static final AMPERSAND = 0x26; |
| + static final ASTERISK = 0x2A; |
| + static final EXCLAMATION = 0x21; |
| + static final VERTICAL_BAR = 0x7C; |
| + static final GREATER_THAN = 0x3E; |
| + static final SINGLE_QUOTE = 0x27; |
| + static final DOUBLE_QUOTE = 0x22; |
| + static final PERCENT = 0x25; |
| + static final AT = 0x40; |
| + static final GRAVE_ACCENT = 0x60; |
| + |
| + static final NULL = 0x0; |
| + static final BELL = 0x7; |
| + static final BACKSPACE = 0x8; |
| + static final VERTICAL_TAB = 0xB; |
| + static final FORM_FEED = 0xC; |
| + static final ESCAPE = 0x1B; |
| + static final BACKSLASH = 0x5C; |
| + static final NBSP = 0xA0; |
| + static final LINE_SEPARATOR = 0x2028; |
| + static final PARAGRAPH_SEPARATOR = 0x2029; |
| + |
| + 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.
|
| + static final _C_MAPPING_KEY = 5; |
| + static final _C_MAPPING_VALUE = 6; |
| + static final _C_COLLECT_ENTRY = 7; |
| + static final _C_SEQUENCE_START = 8; |
| + static final _C_SEQUENCE_END = 9; |
| + static final _C_MAPPING_START = 10; |
| + static final _C_MAPPING_END = 11; |
| + static final _C_COMMENT = 12; |
| + static final _C_ANCHOR = 13; |
| + static final _C_ALIAS = 14; |
| + static final _C_TAG = 15; |
| + static final _C_LITERAL = 16; |
| + static final _C_FOLDED = 17; |
| + static final _C_SINGLE_QUOTE = 18; |
| + static final _C_DOUBLE_QUOTE = 19; |
| + static final _C_DIRECTIVE = 20; |
| + static final _C_RESERVED = 21; |
| + |
| + static final BLOCK_OUT = 0; |
| + static final BLOCK_IN = 1; |
| + static final FLOW_OUT = 2; |
| + static final FLOW_IN = 3; |
| + static final BLOCK_KEY = 4; |
| + static final FLOW_KEY = 5; |
| + |
| + /** The source string being parsed. */ |
| + final String _s; |
| + |
| + /** The current position in the source string. */ |
| + int _pos = 0; |
| + |
| + /** The length of the string being parsed. */ |
| + final int _len; |
| + |
| + /** The current (0-based) line in the source string. */ |
| + int _line = 0; |
| + |
| + /** The 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.
|
| + int _column = 0; |
| + |
| + /** |
| + * Whether we're parsing a bare document (that is, one that doesn't begin with |
| + * `---`). Bare documents don't allow `%` immediately following newlines. |
| + */ |
| + bool _inBareDocument = false; |
| + |
| + /** |
| + * The line number of the farthest position that has been parsed successfully |
| + * before backtracking. Used for error reporting. |
| + */ |
| + int _farthestLine = 0; |
| + |
| + /** |
| + * The column number of the farthest position that has been parsed |
| + * successfully before backtracking. Used for error reporting. |
| + */ |
| + int _farthestColumn = 0; |
| + |
| + /** |
| + * The name of the context of the farthest position that has been parsed |
| + * successfully before backtracking. Used for error reporting. |
| + */ |
| + String _farthestContext = "document"; |
| + |
| + /** A stack of the names of parse contexts. Used for error reporting. */ |
| + List<String> _contextStack; |
| + |
| + _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.
|
| + _contextStack = <String>["document"]; |
| + |
| + /** |
| + * Return the character at the current position, then move that position |
| + * forward one character. Also updates the current line and column numbers. |
| + */ |
| + int _next() { |
| + if (_pos == _len) return -1; |
| + var char = _s.charCodeAt(_pos++); |
| + if (_isBreak(char)) { |
| + _line++; |
| + _column = 0; |
| + } else { |
| + _column++; |
| + } |
| + |
| + if (_farthestLine < _line) { |
| + _farthestLine = _line; |
| + _farthestColumn = _column; |
| + _farthestContext = _contextStack.last(); |
| + } else if (_farthestLine == _line && _farthestColumn < _column) { |
| + _farthestColumn = _column; |
| + _farthestContext = _contextStack.last(); |
| + } |
| + |
| + return char; |
| + } |
| + |
| + /** |
| + * Returns the character at the current position, or the character [i] |
| + * characters after the current position. |
| + * |
| + * Returns -1 if this would return a character after the end or before the |
| + * beginning of the input string. |
| + */ |
| + int _peek([int i = 0]) { |
| + var pos = _pos + i; |
| + return (pos >= _len || pos < 0) ? -1 : _s.charCodeAt(pos); |
| + } |
| + |
| + /** |
| + * 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.
|
| + * otherwise. |
| + */ |
| + bool _(obj) => obj != null && obj != false; |
| + |
| + /** |
| + * Consumes the current character if it matches [matcher]. Returns the result |
| + * of [matcher]. |
| + */ |
| + bool _consume(bool matcher(int)) { |
| + if (matcher(_peek())) { |
| + _next(); |
| + return true; |
| + } |
| + return false; |
| + } |
| + |
| + /** |
| + * Calls [consumer] until it returns a falsey value. Returns a list of all |
| + * truthy return values of [consumer], or null if it didn't consume anything. |
| + * |
| + * Conceptually, repeats a production one or more times. |
| + */ |
| + List _oneOrMore(consumer()) { |
| + var first = consumer(); |
| + if (!_(first)) return null; |
| + var out = [first]; |
| + while (true) { |
| + var el = consumer(); |
| + if (!_(el)) return out; |
| + out.add(el); |
| + } |
| + } |
| + |
| + /** |
| + * Calls [consumer] until it returns a falsey value. Returns a list of all |
| + * truthy return values of [consumer], or the empty list if it didn't consume |
| + * anything. |
| + * |
| + * Conceptually, repeats a production any number of times. |
| + */ |
| + List _zeroOrMore(consumer()) { |
| + var out = []; |
| + var pos = _pos; |
| + while (true) { |
| + var el = consumer(); |
| + if (!_(el) || pos == _pos) return out; |
| + pos = _pos; |
| + out.add(el); |
| + } |
| + } |
| + |
| + /** |
| + * 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.
|
| + * production is intended to be optional. |
| + */ |
| + _zeroOrOne(consumer()) => consumer(); |
| + |
| + /** |
| + * Calls each function in [consumers] until one returns a truthy value, then |
| + * returns that. |
| + */ |
| + _or(List<Function> consumers) { |
| + for (var c in consumers) { |
| + var res = c(); |
| + if (_(res)) return res; |
| + } |
| + return null; |
| + } |
| + |
| + /** |
| + * Calls [consumer] and returns its result, but rolls back the parser state if |
| + * [consumer] returns a falsey value. |
| + */ |
| + _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
|
| + int pos = _pos, line = _line, column = _column; |
| + var res = consumer(); |
| + if (_(res)) return res; |
| + |
| + _pos = pos; |
| + _line = line; |
| + _column = column; |
| + return res; |
| + } |
| + |
| + /** |
| + * Consumes [n] characters matching [matcher], or none if there isn't a |
| + * complete match. The first argument to [matcher] is the character code, the |
| + * second is the index (from 0 to [n] - 1). |
| + * |
| + * Returns whether or not the characters were consumed. |
| + */ |
| + bool _nAtOnce(int n, bool matcher(int, 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.
|
| + for (int i = 0; i < n; i++) { |
| + if (!_consume((c) => matcher(c, i))) return false; |
| + } |
| + return true; |
| + }); |
| + |
| + /** |
| + * Consumes the exact characters in [str], or nothing. |
| + * |
| + * Returns whether or not the string was consumed. |
| + */ |
| + bool _rawString(String str) => |
| + _nAtOnce(str.length, (c, i) => str.charCodeAt(i) == c); |
| + |
| + /** |
| + * Consumes and returns a string of characters matching [matcher], or null if |
| + * there are no such characters. |
| + */ |
| + String _stringOf(bool matcher(int)) => |
| + _captureString(_oneOrMore(() => _consume(matcher))); |
| + |
| + /** |
| + * Calls [consumer] and returns the string that was consumed while doing so, |
| + * or null if [consumer] returned a falsey value. Automatically wraps |
| + * [consumer] in `_group`. |
| + */ |
| + String _captureString(consumer()) { |
| + int start = _pos; |
| + var res = _group(consumer); |
| + if (!_(res)) return null; |
| + return _s.substring(start, _pos); |
| + } |
| + |
| + /** |
| + * Adds a tag and an anchor to [node], if they're defined. [props] should be a |
| + * two-element list where the first element is a Tag or null and the second is |
| + * a String or null. |
| + */ |
| + _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.
|
| + if (_(props[0])) node.tag = props[0]; |
| + if (_(props[1])) node.anchor = props[1]; |
| + return node; |
| + } |
| + |
| + /** Creates a MappingNode from [pairs]. */ |
| + _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.
|
| + var content = new Map<_Node, _Node>(); |
| + 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
|
| + return new _MappingNode("?", content); |
| + } |
| + |
| + /** Runs [fn] in a context named [name]. Used for error reporting. */ |
| + _context(String name, fn()) { |
| + try { |
| + _contextStack.add(name); |
| + return fn(); |
| + } finally { |
| + var popped = _contextStack.removeLast(); |
| + assert(popped == name); |
| + } |
| + } |
| + |
| + /** Throws an error with additional context information. */ |
| + _error(String message) { |
| + // Line and column should be one-based |
|
Bob Nystrom
2012/04/20 20:28:55
"."
nweiz
2012/04/23 23:06:33
Done.
|
| + throw new SyntaxError(_line + 1, _column + 1, |
| + "$message (in $_farthestContext)"); |
| + } |
| + |
| + /** |
| + * If [result] is falsey, throws an error saying that [expected] was |
| + * expected. |
| + */ |
| + _expect(result, String expected) { |
| + if (_(result)) return result; |
| + _error("expected $expected"); |
| + } |
| + |
| + /** |
| + * 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
|
| + * `_farthestColumn`, and `_farthestContext` to provide additional |
| + * information. |
| + */ |
| + _parseFailed() { |
| + throw new SyntaxError(_farthestLine + 1, _farthestColumn + 1, |
| + "invalid YAML in $_farthestContext"); |
| + } |
| + |
| + /** Returns the number of spaces after the current position. */ |
| + 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.
|
| + var i = 0; |
| + while (_peek(i) == SP) i++; |
| + return i; |
| + } |
| + |
| + /** Returns whether the current position is at the beginning of a line. */ |
| + 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.
|
| + var char = _peek(-1); |
| + return char == -1 || _isBreak(char); |
| + } |
| + |
| + /** Returns whether the current position is at the end of the input. */ |
| + bool get _atEndOfFile() => _pos == _len; |
| + |
| + /** |
| + * Given an indicator character, returns the type of that indicator (or null |
| + * if the indicator isn't found. |
| + */ |
| + int _indicatorType(int char) { |
| + switch (char) { |
| + case HYPHEN: return _C_SEQUENCE_ENTRY; |
| + case QUESTION_MARK: return _C_MAPPING_KEY; |
| + case COLON: return _C_MAPPING_VALUE; |
| + case COMMA: return _C_COLLECT_ENTRY; |
| + case LEFT_BRACKET: return _C_SEQUENCE_START; |
| + case RIGHT_BRACKET: return _C_SEQUENCE_END; |
| + case LEFT_BRACE: return _C_MAPPING_START; |
| + case RIGHT_BRACE: return _C_MAPPING_END; |
| + case HASH: return _C_COMMENT; |
| + case AMPERSAND: return _C_ANCHOR; |
| + case ASTERISK: return _C_ALIAS; |
| + case EXCLAMATION: return _C_TAG; |
| + case VERTICAL_BAR: return _C_LITERAL; |
| + case GREATER_THAN: return _C_FOLDED; |
| + case SINGLE_QUOTE: return _C_SINGLE_QUOTE; |
| + case DOUBLE_QUOTE: return _C_DOUBLE_QUOTE; |
| + case PERCENT: return _C_DIRECTIVE; |
| + case AT: |
| + case GRAVE_ACCENT: |
| + return _C_RESERVED; |
| + default: return null; |
| + } |
| + } |
| + |
| + // 1 |
| + bool _isPrintable(int char) { |
| + return char == TAB || char == LF || char == CR || |
| + (char >= SP && char <= TILDE) || char == NEL || |
| + (char >= 0xA0 && char <= 0xD7FF) || (char >= 0xE000 && char <= 0xFFFD) || |
| + (char >= 0x10000 && char <= 0x10FFFF); |
|
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.
|
| + } |
| + |
| + // 22 |
| + bool _c_indicator(int type) => _consume((c) => _indicatorType(c) == type); |
| + |
| + // 23 |
| + bool _isFlowIndicator(int char) { |
| + var indicator = _indicatorType(char); |
| + return indicator == _C_COLLECT_ENTRY || indicator == _C_SEQUENCE_START || |
| + indicator == _C_SEQUENCE_END || indicator == _C_MAPPING_START || |
| + indicator == _C_MAPPING_END; |
| + } |
| + |
| + // 26 |
| + bool _isBreak(int char) => char == LF || char == CR; |
| + |
| + // 27 |
| + bool _isNonBreak(int char) => _isPrintable(char) && !_isBreak(char); |
| + |
| + // 30 |
| + bool _b_non_content() => _consume(_isBreak); |
| + |
| + // 33 |
| + bool _isSpace(int char) => char == SP || char == TAB; |
| + |
| + // 34 |
| + bool _isNonSpace(int char) => _isNonBreak(char) && !_isSpace(char); |
| + |
| + // 63 |
| + bool _s_indent(int n) => _nAtOnce(n, (c, i) => c == SP); |
| + |
| + // 66 |
| + bool _s_separateInLine() => _group(() => |
| + _(_oneOrMore(() => _consume(_isSpace))) || _atStartOfLine); |
| + |
| + // 69 |
| + bool _s_flowLinePrefix(int n) { |
| + if (!_s_indent(n)) return false; |
| + _zeroOrOne(_s_separateInLine); |
| + return true; |
| + } |
| + |
| + // 74 |
| + 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?
|
| + |
| + // 75 |
| + bool _c_nb_commentText() { |
| + if (!_c_indicator(_C_COMMENT)) return false; |
| + _zeroOrMore(() => _consume(_isNonBreak)); |
| + return true; |
| + } |
| + |
| + // 76 |
| + bool _b_comment() => _atEndOfFile || _b_non_content(); |
| + |
| + // 77 |
| + bool _s_b_comment() { |
| + if (_s_separateInLine()) { |
| + _zeroOrOne(_c_nb_commentText); |
| + } |
| + return _b_comment(); |
| + } |
| + |
| + // 78 |
| + bool _l_comment() => _group(() { |
| + if (!_s_separateInLine()) return false; |
| + _zeroOrOne(_c_nb_commentText); |
| + return _b_comment(); |
| + }); |
| + |
| + // 79 |
| + bool _s_l_comments() { |
| + if (!_s_b_comment() && !_atStartOfLine) return false; |
| + _zeroOrMore(_l_comment); |
| + return true; |
| + } |
| + |
| + // 80 |
| + bool _s_separate(int n, int c) { |
| + switch (c) { |
| + case BLOCK_OUT: |
| + case BLOCK_IN: |
| + case FLOW_OUT: |
| + case FLOW_IN: |
| + return _s_separateLines(n); |
| + case BLOCK_KEY: |
| + case FLOW_KEY: |
| + return _s_separateInLine(); |
| + } |
|
Bob Nystrom
2012/04/20 20:28:55
Switches without defaults feel strange to me, espe
nweiz
2012/04/23 23:06:33
Done.
|
| + } |
| + |
| + // 81 |
| + bool _s_separateLines(int n) { |
| + 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
|
| + _s_separateInLine(); |
| + } |
| + |
| + // 82 |
| + bool _l_directive() => false; // TODO(nweiz): implement |
| + |
| + // 96 |
| + 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
|
| + var tag, anchor; |
| + tag = _c_ns_tagProperty(); |
| + if (_(tag)) { |
| + anchor = _group(() { |
| + if (!_s_separate(n, c)) return null; |
| + return _c_ns_anchorProperty(); |
| + }); |
| + return [tag, anchor]; |
| + } |
| + |
| + anchor = _c_ns_anchorProperty(); |
| + if (_(anchor)) { |
| + tag = _group(() { |
| + if (!_s_separate(n, c)) return null; |
| + return _c_ns_tagProperty(); |
| + }); |
| + return [tag, anchor]; |
| + } |
| + |
| + return null; |
| + } |
| + |
| + // 97 |
| + _Tag _c_ns_tagProperty() => null; // TODO(nweiz): implement |
| + |
| + // 101 |
| + String _c_ns_anchorProperty() => null; // TODO(nweiz): implement |
| + |
| + // 102 |
| + bool _isAnchorChar(int char) => _isNonSpace(char) && !_isFlowIndicator(char); |
| + |
| + // 103 |
| + String _ns_anchorName() => _captureString(_isAnchorChar); |
| + |
| + // 104 |
| + _Node _c_ns_aliasNode() { |
| + if (!_c_indicator(_C_ALIAS)) return null; |
| + var name = _expect(_ns_anchorName(), 'anchor name'); |
| + return new _AliasNode(name); |
| + } |
| + |
| + // 105 |
| + _ScalarNode _e_scalar() => new _ScalarNode("?", content: ""); |
| + |
| + // 106 |
| + _ScalarNode _e_node() => _e_scalar(); |
| + |
| + // 126 |
| + bool _ns_plainFirst(int c) { |
| + var char = _peek(); |
| + var indicator = _indicatorType(char); |
| + if (indicator == _C_RESERVED) { |
| + _error("reserved indicators can't start a plain scalar"); |
| + } |
| + var match = (_isNonSpace(char) && indicator == null) || |
| + ((indicator == _C_MAPPING_KEY || |
| + indicator == _C_MAPPING_VALUE || |
| + indicator == _C_SEQUENCE_ENTRY) && |
| + _isPlainSafe(c, _peek(1))); |
| + |
| + if (match) _next(); |
| + return match; |
| + } |
| + |
| + // 127 |
| + bool _isPlainSafe(int c, int char) { |
| + switch (c) { |
| + case FLOW_OUT: |
| + case BLOCK_KEY: |
| + // 128 |
| + return _isNonSpace(char); |
| + case FLOW_IN: |
| + case FLOW_KEY: |
| + // 129 |
| + return _isNonSpace(char) && !_isFlowIndicator(char); |
| + } |
| + } |
| + |
| + // 130 |
| + bool _ns_plainChar(int c) { |
| + var char = _peek(); |
| + var indicator = _indicatorType(char); |
| + 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.
|
| + indicator != _C_COMMENT) || |
| + (_isNonSpace(_peek(-1)) && indicator == _C_COMMENT) || |
| + (indicator == _C_MAPPING_VALUE && _isPlainSafe(c, _peek(1))); |
| + |
| + if (match) _next(); |
| + return match; |
| + } |
| + |
| + // 131 |
| + String _ns_plain(int n, int c) => _context('plain scalar', () { |
| + switch (c) { |
| + case FLOW_OUT: |
| + case FLOW_IN: |
| + return _ns_plainMultiLine(n, c); |
| + case BLOCK_KEY: |
| + case FLOW_KEY: |
| + return _ns_plainOneLine(c); |
| + } |
| + }); |
| + |
| + // 132 |
| + void _nb_ns_plainInLine(int c) { |
| + _zeroOrMore(() => _group(() { |
| + _zeroOrMore(() => _consume(_isSpace)); |
| + return _ns_plainChar(c); |
| + })); |
| + } |
| + |
| + // 133 |
| + String _ns_plainOneLine(int c) => _captureString(() { |
| + if (_c_forbidden()) return false; |
| + if (!_ns_plainFirst(c)) return false; |
| + _nb_ns_plainInLine(c); |
| + return true; |
| + }); |
| + |
| + // 134 |
| + bool _s_ns_plainNextLine(int n, int c) => _group(() { |
| + if (_c_forbidden()) return false; |
| + if (!_s_flowFolded(n)) return false; |
| + if (!_ns_plainChar(c)) return false; |
| + _nb_ns_plainInLine(c); |
| + return true; |
| + }); |
| + |
| + // 135 |
| + String _ns_plainMultiLine(int n, int c) => _captureString(() { |
| + if (!_(_ns_plainOneLine(c))) return false; |
| + _zeroOrMore(() => _s_ns_plainNextLine(n, c)); |
| + return true; |
| + }); |
| + |
| + // 154 |
| + _Node _c_s_implicitYamlKey(int c) => _group(() { |
| + /* Indentation parameter is unused in this path */ |
|
Bob Nystrom
2012/04/20 20:28:55
//
nweiz
2012/04/23 23:06:33
Done.
|
| + 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.
|
| + if (!_(node)) return null; |
| + _zeroOrOne(_s_separateInLine); |
| + return node; |
| + }); |
| + |
| + // 155 |
| + _Node _c_s_implicitJsonKey(int c) => null; // TODO(nweiz): implement |
| + |
| + // 156 |
| + _Node _ns_flowYamlContent(int n, int c) { |
| + var str = _ns_plain(n, c); |
| + if (!_(str)) return null; |
| + return new _ScalarNode("?", content: str); |
| + } |
| + |
| + // 157 |
| + _Node _ns_flowJsonContent(int n, int c) => null; // TODO(nweiz): implement |
| + |
| + // 158 |
| + _Node _ns_flowContent(int n, int c) => _or([ |
| + () => _ns_flowYamlContent(n, c), |
| + () => _ns_flowJsonContent(n, c) |
| + ]); |
| + |
| + // 159 |
| + _Node _ns_flowYamlNode(int n, int c) => _or([ |
| + _c_ns_aliasNode, |
| + () => _ns_flowYamlContent(n, c), |
| + () { |
| + var props = _c_ns_properties(n, c); |
| + if (!_(props)) return null; |
| + var node = _or([ |
| + () => _group(() { |
| + if (!_s_separate(n, c)) return null; |
| + return _ns_flowYamlContent(n, c); |
| + }), |
| + _e_scalar |
| + ]); |
| + return _addProps(node, props); |
| + } |
| + ]); |
| + |
| + // 161 |
| + _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
|
| + _c_ns_aliasNode, |
| + () => _ns_flowContent(n, c), |
| + () => _group(() { |
| + var props = _c_ns_properties(n, c); |
| + if (!_(props)) return null; |
| + var node = _or([ |
| + () => _group(() => _s_separate(n, c) ? _ns_flowContent(n, c) : null), |
| + _e_scalar]); |
| + return _addProps(node, props); |
| + }) |
| + ]); |
| + |
| + // 170 |
| + _Node _c_l_literal(int n) => null; // TODO(nweiz); implement |
| + |
| + // 174 |
| + _Node _c_l_folded(int n) => null; // TODO(nweiz); implement |
| + |
| + // 183 |
| + _SequenceNode _l_blockSequence(int n) => _context('sequence', () { |
| + var m = _detectIndentation() - n; |
| + if (m <= 0) return null; |
| + |
| + var content = _oneOrMore(() => _group(() { |
| + if (!_s_indent(n + m)) return null; |
| + return _c_l_blockSeqEntry(n + m); |
| + })); |
| + if (!_(content)) return null; |
| + |
| + return new _SequenceNode("?", content); |
| + }); |
| + |
| + // 184 |
| + _Node _c_l_blockSeqEntry(int n) => _group(() { |
| + if (!_c_indicator(_C_SEQUENCE_ENTRY)) return null; |
| + if (_isNonSpace(_peek())) return null; |
| + |
| + return _s_l_blockIndented(n, BLOCK_IN); |
| + }); |
| + |
| + // 185 |
| + _Node _s_l_blockIndented(int n, int c) { |
| + var m = _detectIndentation(); |
| + 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.
|
| + if (!_s_indent(m)) return null; |
| + return _or([ |
| + () => _ns_l_compactSequence(n+1+m), |
| + () => _ns_l_compactMapping(n+1+m)]); |
| + }), |
| + () => _s_l_blockNode(n, c), |
| + () => _s_l_comments() ? _e_node() : null]); |
| + } |
| + |
| + // 186 |
| + _Node _ns_l_compactSequence(int n) => _context('sequence', () { |
| + var first = _c_l_blockSeqEntry(n); |
| + if (!_(first)) return null; |
| + |
| + var content = _zeroOrMore(() => _group(() { |
| + if (!_s_indent(n)) return null; |
| + return _c_l_blockSeqEntry(n); |
| + })); |
| + content.insertRange(0, 1, first); |
| + |
| + return new _SequenceNode("?", content); |
| + }); |
| + |
| + // 187 |
| + _Node _l_blockMapping(int n) => _context('mapping', () { |
| + var m = _detectIndentation() - n; |
| + if (m <= 0) return null; |
| + |
| + var pairs = _oneOrMore(() => _group(() { |
| + if (!_s_indent(n + m)) return null; |
| + return _ns_l_blockMapEntry(n + m); |
| + })); |
| + if (!_(pairs)) return null; |
| + |
| + return _map(pairs); |
| + }); |
| + |
| + // 188 |
| + List<_Node> _ns_l_blockMapEntry(int n) => _or([ |
| + () => _c_l_blockMapExplicitEntry(n), |
| + () => _ns_l_blockMapImplicitEntry(n) |
| + ]); |
| + |
| + // 189 |
| + List<_Node> _c_l_blockMapExplicitEntry(int n) => null; // TODO(nweiz): implement |
| + |
| + // 192 |
| + List<_Node> _ns_l_blockMapImplicitEntry(int n) => _group(() { |
| + var key = _or([_ns_s_blockMapImplicitKey, _e_node]); |
| + var value = _c_l_blockMapImplicitValue(n); |
| + return _(value) ? [key, value] : null; |
| + }); |
| + |
| + // 193 |
| + _Node _ns_s_blockMapImplicitKey() => _context('mapping key', () => _or([ |
| + () => _c_s_implicitJsonKey(BLOCK_KEY), |
| + () => _c_s_implicitYamlKey(BLOCK_KEY) |
| + ])); |
| + |
| + // 194 |
| + _Node _c_l_blockMapImplicitValue(int n) => _context('mapping value', () => |
| + _group(() { |
| + if (!_c_indicator(_C_MAPPING_VALUE)) return null; |
| + return _or([ |
| + () => _s_l_blockNode(n, BLOCK_OUT), |
| + () => _s_l_comments() ? _e_node() : null |
| + ]); |
| + })); |
| + |
| + // 195 |
| + _Node _ns_l_compactMapping(int n) => _context('mapping', () { |
| + var first = _ns_l_blockMapEntry(n); |
| + if (!_(first)) return null; |
| + |
| + var pairs = _zeroOrMore(() => _group(() { |
| + if (!_s_indent(n)) return null; |
| + return _ns_l_blockMapEntry(n); |
| + })); |
| + pairs.insertRange(0, 1, first); |
| + |
| + return _map(pairs); |
| + }); |
| + |
| + // 196 |
| + _Node _s_l_blockNode(int n, int c) => |
| + _or([() => _s_l_blockInBlock(n, c), () => _s_l_flowInBlock(n)]); |
| + |
| + // 197 |
| + _Node _s_l_flowInBlock(int n) => _group(() { |
| + 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.
|
| + var node = _ns_flowNode(n+1, FLOW_OUT); |
| + if (!_(node)) return null; |
| + if (!_s_l_comments()) return null; |
| + return node; |
| + }); |
| + |
| + // 198 |
| + _Node _s_l_blockInBlock(int n, int c) => |
| + _or([() => _s_l_blockScalar(n, c), () => _s_l_blockCollection(n, c)]); |
| + |
| + // 199 |
| + _Node _s_l_blockScalar(int n, int c) => _group(() { |
| + if (!_s_separate(n+1, c)) return null; |
| + var props = _group(() { |
| + var props = _c_ns_properties(n+1, c); |
| + if (!_(props)) return null; |
| + if (!_s_separate(n+1, c)) return null; |
| + return props; |
| + }); |
| + if (!_(props)) props = [null, null]; |
| + |
| + var node = _or([() => _c_l_literal(n), () => _c_l_folded(n)]); |
| + if (!_(node)) return null; |
| + return _addProps(node, props); |
| + }); |
| + |
| + // 200 |
| + _Node _s_l_blockCollection(int n, int c) => _group(() { |
| + var props = _group(() { |
| + if (!_s_separate(n+1, c)) return null; |
| + return _c_ns_properties(n+1, c); |
| + }); |
| + if (!_(props)) props = [null, null]; |
| + |
| + if (!_s_l_comments()) return null; |
| + return _or([ |
| + () => _l_blockSequence(_seqSpaces(n, c)), |
| + () => _l_blockMapping(n)]); |
| + }); |
| + |
| + // 201 |
| + int _seqSpaces(int n, int c) => c == BLOCK_OUT ? n - 1 : n; |
| + |
| + // 202 |
| + void _l_documentPrefix() { |
| + _zeroOrMore(_l_comment); |
| + } |
| + |
| + // 203 |
| + bool _c_directivesEnd() => _rawString("---"); |
| + |
| + // 204 |
| + bool _c_documentEnd() => _rawString("..."); |
| + |
| + // 205 |
| + bool _l_documentSuffix() => _group(() { |
| + if (!_c_documentEnd()) return false; |
| + return _s_l_comments(); |
| + }); |
| + |
| + // 206 |
| + bool _c_forbidden() { |
| + if (!_inBareDocument || !_atStartOfLine) return false; |
| + var forbidden = false; |
| + _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
|
| + if (!_or([_c_directivesEnd, _c_documentEnd])) return; |
| + var char = _peek(); |
| + forbidden = _isBreak(char) || _isWhite(char) || _atEndOfFile; |
| + return; |
| + }); |
| + return forbidden; |
| + } |
| + |
| + // 207 |
| + _Node _l_bareDocument() { |
| + try { |
| + _inBareDocument = true; |
| + return _s_l_blockNode(-1, BLOCK_IN); |
| + } finally { |
| + _inBareDocument = false; |
| + } |
| + } |
| + |
| + // 208 |
| + _Node _l_explicitDocument() { |
| + if (!_c_directivesEnd()) return null; |
| + var doc = _l_bareDocument(); |
| + if (_(doc)) return doc; |
| + |
| + doc = _e_node(); |
| + _s_l_comments(); |
| + return doc; |
| + } |
| + |
| + // 209 |
| + _Node _l_directiveDocument() { |
| + if (!_(_oneOrMore(_l_directive))) return null; |
| + var doc = _l_explicitDocument(); |
| + if (doc != null) return doc; |
| + _parseFailed(); |
| + } |
| + |
| + // 210 |
| + _Node _l_anyDocument() => |
| + _or([_l_directiveDocument, _l_explicitDocument, _l_bareDocument]); |
| + |
| + // 211 |
| + List<_Node> _l_yamlStream() { |
| + var docs = []; |
| + _zeroOrMore(_l_documentPrefix); |
| + var first = _zeroOrOne(_l_anyDocument); |
| + if (!_(first)) first = _e_node(); |
| + docs.add(first); |
| + |
| + _zeroOrMore(() { |
| + var doc; |
| + if (_(_oneOrMore(_l_documentSuffix))) { |
| + _zeroOrMore(_l_documentPrefix); |
| + doc = _zeroOrOne(_l_anyDocument); |
| + } else { |
| + _zeroOrMore(_l_documentPrefix); |
| + doc = _zeroOrOne(_l_explicitDocument); |
| + } |
| + if (_(doc)) docs.add(doc); |
| + return doc; |
| + }); |
| + |
| + if (!_atEndOfFile) _parseFailed(); |
| + return docs; |
| + } |
| +} |
| + |
| +class SyntaxError extends Error { |
| + final int _line; |
| + final int _column; |
| + |
| + SyntaxError(int this._line, int this._column, String _msg) : super(_msg); |
| + |
| + String toString() => "Syntax error on line $_line, column $_column: $_msg"; |
| +} |