Chromium Code Reviews| Index: utils/pub/yaml/parser.dart |
| diff --git a/utils/pub/yaml/parser.dart b/utils/pub/yaml/parser.dart |
| index 7f4875ebfcdcbe4e291b030aee2ed6451bea71f1..bc721c3e0a6ef8b6bee7cda2e1a76f7b426dfceb 100644 |
| --- a/utils/pub/yaml/parser.dart |
| +++ b/utils/pub/yaml/parser.dart |
| @@ -144,6 +144,12 @@ class _Parser { |
| int farthestColumn = 0; |
| /** |
| + * The farthest position in the source string that has been parsed |
| + * successfully before backtracking. Used for error reporting. |
| + */ |
| + int farthestPos = 0; |
| + |
| + /** |
| * The name of the context of the farthest position that has been parsed |
| * successfully before backtracking. Used for error reporting. |
| */ |
| @@ -153,6 +159,12 @@ class _Parser { |
| List<String> contextStack; |
| /** |
| + * Annotations attached to ranges of the source string that add extra |
| + * information to any errors that occur in the annotated range. |
| + */ |
| + _RangeMap<String> errorAnnotations; |
| + |
| + /** |
| * The buffer containing the string currently being captured. |
| */ |
| StringBuffer capturedString; |
| @@ -170,7 +182,8 @@ class _Parser { |
| _Parser(String s) |
| : this.s = s, |
| len = s.length, |
| - contextStack = <String>["document"]; |
| + contextStack = <String>["document"], |
| + errorAnnotations = new _RangeMap(); |
| /** |
| * Return the character at the current position, then move that position |
| @@ -194,6 +207,7 @@ class _Parser { |
| farthestColumn = column; |
| farthestContext = contextStack.last(); |
| } |
| + farthestPos = pos; |
| return char; |
| } |
| @@ -416,6 +430,22 @@ class _Parser { |
| } |
| } |
| + /** |
| + * Adds [message] as extra information to any errors that occur between the |
| + * current position and the position of the cursor after running [fn]. The |
| + * cursor is reset after [fn] is run. |
| + */ |
| + annotateError(String message, fn()) { |
| + var start = pos; |
| + var end; |
| + transaction(() { |
| + fn(); |
| + end = pos; |
| + return false; |
| + }); |
| + errorAnnotations[new _Range(start, end)] = message; |
| + } |
| + |
| /** Throws an error with additional context information. */ |
| error(String message) { |
| // Line and column should be one-based. |
| @@ -437,8 +467,10 @@ class _Parser { |
| * [farthestColumn], and [farthestContext] to provide additional information. |
| */ |
| parseFailed() { |
| - throw new SyntaxError(farthestLine + 1, farthestColumn + 1, |
| - "invalid YAML in $farthestContext"); |
| + var message = "invalid YAML in $farthestContext"; |
| + var extraError = errorAnnotations[farthestPos]; |
| + if (extraError != null) message = "$message ($extraError)"; |
| + throw new SyntaxError(farthestLine + 1, farthestColumn + 1, message); |
| } |
| /** Returns the number of spaces after the current position. */ |
| @@ -674,12 +706,28 @@ class _Parser { |
| })); |
| // 63 |
| - bool s_indent(int indent) => nAtOnce(indent, (c, i) => c == SP); |
| + bool s_indent(int indent) { |
| + var result = nAtOnce(indent, (c, i) => c == SP); |
| + if (peek() == TAB) { |
| + annotateError("\\t is not allowed as indentation in YAML", |
|
Bob Nystrom
2012/08/30 16:43:51
I would use "tab characters" instead of the escape
nweiz
2012/08/30 19:19:02
Done.
|
| + () => zeroOrMore(() => consume(isSpace))); |
| + } |
| + return result; |
| + } |
| // 64 |
| bool s_indentLessThan(int indent) { |
| for (int i = 0; i < indent - 1; i++) { |
| - if (!consumeChar(SP)) break; |
| + if (!consumeChar(SP)) { |
| + if (peek() == TAB) { |
| + annotateError("\\t is not allowed as indentation in YAML", () { |
|
Bob Nystrom
2012/08/30 16:43:51
Ditto.
nweiz
2012/08/30 19:19:02
Done.
|
| + for (; i < indent - 1; i++) { |
| + if (!consume(isSpace)) break; |
| + } |
| + }); |
| + } |
| + break; |
| + } |
| } |
| return true; |
| } |
| @@ -1901,3 +1949,50 @@ class _BlockHeader { |
| bool get autoDetectIndent => additionalIndent == null; |
| } |
| + |
| +/** |
| + * A range of characters in the YAML document, from [start] to [end] (inclusive). |
| + */ |
| +class _Range { |
| + /** The first character in the range. */ |
| + final int start; |
| + |
| + /** The last character in the range. */ |
| + final int end; |
| + |
| + _Range(this.start, this.end); |
| + |
| + /** Returns whether or not [pos] lies within this range. */ |
| + bool contains(int pos) => pos >= start && pos <= end; |
| +} |
| + |
| +/** |
| + * A map that associates [E] values with [_Range]s. It's efficient to create new |
| + * associations, but finding the value associated with a position is more |
| + * expensive. |
| + */ |
| +class _RangeMap<E> { |
| + /** The ranges and their associated elements. */ |
| + final List<_Pair<_Range, E>> contents; |
| + |
| + _RangeMap() : this.contents = <_Pair<_Range, E>>[]; |
| + |
| + /** |
| + * Returns the value associated with the range in which [pos] lies, or null if |
| + * there is no such range. If there's more than one such range, the most |
| + * recently set one is used. |
| + */ |
| + E operator[](int pos) { |
| + // Iterate backwards through contents so the more recent range takes |
| + // precedence. TODO(nweiz): clean this up when issue 2804 is fixed. |
| + for (var i = contents.length - 1; i >= 0; i--) { |
| + var pair = contents[i]; |
| + if (pair.first.contains(pos)) return pair.last; |
| + } |
| + return null; |
| + } |
| + |
| + /** Associates [value] with [range]. */ |
| + operator[]=(_Range range, E value) => |
| + contents.add(new _Pair<_Range, E>(range, value)); |
| +} |