Index: pkg/yaml/lib/src/parser.dart |
diff --git a/pkg/yaml/lib/src/parser.dart b/pkg/yaml/lib/src/parser.dart |
index d1e20ff74db7ed90a930295473206e25e1704437..d203c32e2df90b9af6aae498ed1fe20db630e686 100644 |
--- a/pkg/yaml/lib/src/parser.dart |
+++ b/pkg/yaml/lib/src/parser.dart |
@@ -6,6 +6,8 @@ library yaml.parser; |
import 'dart:collection'; |
+import 'package:string_scanner/string_scanner.dart'; |
+ |
import 'model.dart'; |
import 'yaml_exception.dart'; |
import 'yaml_map.dart'; |
@@ -116,48 +118,28 @@ class Parser { |
static const CHOMPING_KEEP = 1; |
static const CHOMPING_CLIP = 2; |
- /// 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 current (0-based) column in the source string. |
- int _column = 0; |
+ /// The scanner that's used to scan through the document. |
+ final SpanScanner _scanner; |
/// 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 farthest position in the source string that has been parsed |
- /// successfully before backtracking. Used for error reporting. |
- int _farthestPos = 0; |
+ /// The state of the scanner when it was the farthest in the document it's |
+ /// been. |
+ LineScannerState _farthestState; |
/// 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; |
+ final _contextStack = <String>["document"]; |
/// Annotations attached to ranges of the source string that add extra |
/// information to any errors that occur in the annotated range. |
- _RangeMap<String> _errorAnnotations; |
+ final _errorAnnotations = new _RangeMap<String>(); |
/// The buffer containing the string currently being captured. |
StringBuffer _capturedString; |
@@ -168,46 +150,18 @@ class Parser { |
/// Whether the current string capture is being overridden. |
bool _capturingAs = false; |
- Parser(String s) |
- : this._s = s, |
- _len = s.length, |
- _contextStack = <String>["document"], |
- _errorAnnotations = new _RangeMap(); |
- |
- /// 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.codeUnitAt(_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; |
- } |
- _farthestPos = _pos; |
- |
- return char; |
+ Parser(String yaml, String sourceName) |
+ : _scanner = new SpanScanner(yaml, sourceName) { |
+ _farthestState = _scanner.state; |
} |
+ /// Returns the character at the current position, then moves that position |
+ /// forward one character. |
+ int next() => _scanner.readChar(); |
+ |
/// Returns the code unit 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 peekPos = _pos + i; |
- return (peekPos >= _len || peekPos < 0) ? -1 : _s.codeUnitAt(peekPos); |
- } |
+ int peek([int i = 0]) => _scanner.peekChar(i); |
/// The truthiness operator. Returns `false` if [obj] is `null` or `false`, |
/// `true` otherwise. |
@@ -249,11 +203,11 @@ class Parser { |
/// Conceptually, repeats a production any number of times. |
List zeroOrMore(consumer()) { |
var out = []; |
- var oldPos = _pos; |
+ var oldPos = _scanner.position; |
while (true) { |
var el = consumer(); |
- if (!truth(el) || oldPos == _pos) return out; |
- oldPos = _pos; |
+ if (!truth(el) || oldPos == _scanner.position) return out; |
+ oldPos = _scanner.position; |
out.add(el); |
} |
return null; // Unreachable. |
@@ -276,18 +230,15 @@ class Parser { |
/// Calls [consumer] and returns its result, but rolls back the parser state |
/// if [consumer] returns a falsey value. |
transaction(consumer()) { |
- var oldPos = _pos; |
- var oldLine = _line; |
- var oldColumn = _column; |
+ var oldState = _scanner.state; |
var oldCaptureStart = _captureStart; |
String capturedSoFar = _capturedString == null ? null : |
_capturedString.toString(); |
var res = consumer(); |
+ _refreshFarthestState(); |
if (truth(res)) return res; |
- _pos = oldPos; |
- _line = oldLine; |
- _column = oldColumn; |
+ _scanner.state = oldState; |
_captureStart = oldCaptureStart; |
_capturedString = capturedSoFar == null ? null : |
new StringBuffer(capturedSoFar); |
@@ -324,7 +275,7 @@ class Parser { |
// captureString calls may not be nested |
assert(_capturedString == null); |
- _captureStart = _pos; |
+ _captureStart = _scanner.position; |
_capturedString = new StringBuffer(); |
var res = transaction(consumer); |
if (!truth(res)) { |
@@ -353,14 +304,16 @@ class Parser { |
_capturingAs = false; |
if (!truth(res)) return res; |
- _capturedString.write(transformation(_s.substring(_captureStart, _pos))); |
- _captureStart = _pos; |
+ _capturedString.write(transformation( |
+ _scanner.string.substring(_captureStart, _scanner.position))); |
+ _captureStart = _scanner.position; |
return res; |
} |
void flushCapture() { |
- _capturedString.write(_s.substring(_captureStart, _pos)); |
- _captureStart = _pos; |
+ _capturedString.write(_scanner.string.substring( |
+ _captureStart, _scanner.position)); |
+ _captureStart = _scanner.position; |
} |
/// Adds a tag and an anchor to [node], if they're defined. |
@@ -393,22 +346,19 @@ class Parser { |
/// 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 start = _scanner.position; |
var end; |
transaction(() { |
fn(); |
- end = _pos; |
+ end = _scanner.position; |
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. |
- throw new SyntaxError(_line + 1, _column + 1, |
- "$message (in $_farthestContext)."); |
- } |
+ void error(String message) => |
+ _scanner.error("$message (in $_farthestContext)."); |
/// If [result] is falsey, throws an error saying that [expected] was |
/// expected. |
@@ -417,14 +367,24 @@ class Parser { |
error("Expected $expected"); |
} |
- /// Throws an error saying that the parse failed. Uses [_farthestLine], |
- /// [_farthestColumn], and [_farthestContext] to provide additional |
+ /// Throws an error saying that the parse failed. |
+ /// |
+ /// Uses [_farthestState] and [_farthestContext] to provide additional |
/// information. |
parseFailed() { |
var message = "Invalid YAML in $_farthestContext"; |
- var extraError = _errorAnnotations[_farthestPos]; |
+ _refreshFarthestState(); |
+ _scanner.state = _farthestState; |
+ |
+ var extraError = _errorAnnotations[_scanner.position]; |
if (extraError != null) message = "$message ($extraError)"; |
- throw new SyntaxError(_farthestLine + 1, _farthestColumn + 1, "$message."); |
+ _scanner.error("$message."); |
+ } |
+ |
+ /// Update [_farthestState] if the scanner is farther than it's been before. |
+ void _refreshFarthestState() { |
+ if (_scanner.position <= _farthestState.position) return; |
+ _farthestState = _scanner.state; |
} |
/// Returns the number of spaces after the current position. |
@@ -439,15 +399,11 @@ class Parser { |
if (!header.autoDetectIndent) return header.additionalIndent; |
var maxSpaces = 0; |
- var maxSpacesLine = 0; |
var spaces = 0; |
transaction(() { |
do { |
spaces = captureString(() => zeroOrMore(() => consumeChar(SP))).length; |
- if (spaces > maxSpaces) { |
- maxSpaces = spaces; |
- maxSpacesLine = _line; |
- } |
+ if (spaces > maxSpaces) maxSpaces = spaces; |
} while (b_break()); |
return false; |
}); |
@@ -460,19 +416,15 @@ class Parser { |
// It's an error for a leading empty line to be indented more than the first |
// non-empty line. |
if (maxSpaces > spaces) { |
- throw new SyntaxError(maxSpacesLine + 1, maxSpaces, |
- "Leading empty lines may not be indented more than the first " |
- "non-empty line."); |
+ _scanner.error("Leading empty lines may not be indented more than the " |
+ "first non-empty line."); |
} |
return spaces - indent; |
} |
/// Returns whether the current position is at the beginning of a line. |
- bool get atStartOfLine => _column == 0; |
- |
- /// Returns whether the current position is at the end of the input. |
- bool get atEndOfFile => _pos == _len; |
+ bool get atStartOfLine => _scanner.column == 0; |
/// Given an indicator character, returns the type of that indicator (or null |
/// if the indicator isn't found. |
@@ -504,6 +456,7 @@ class Parser { |
// 1 |
bool isPrintable(int char) { |
+ if (char == null) return false; |
return char == TAB || |
char == LF || |
char == CR || |
@@ -515,7 +468,8 @@ class Parser { |
} |
// 2 |
- bool isJson(int char) => char == TAB || (char >= SP && char <= 0x10FFFF); |
+ bool isJson(int char) => char != null && |
+ (char == TAB || (char >= SP && char <= 0x10FFFF)); |
// 22 |
bool c_indicator(int type) => consume((c) => indicatorType(c) == type); |
@@ -558,10 +512,12 @@ class Parser { |
bool isNonSpace(int char) => isNonBreak(char) && !isSpace(char); |
// 35 |
- bool isDecDigit(int char) => char >= NUMBER_0 && char <= NUMBER_9; |
+ bool isDecDigit(int char) => char != null && char >= NUMBER_0 && |
+ char <= NUMBER_9; |
// 36 |
bool isHexDigit(int char) { |
+ if (char == null) return false; |
return isDecDigit(char) || |
(char >= LETTER_A && char <= LETTER_F) || |
(char >= LETTER_CAP_A && char <= LETTER_CAP_F); |
@@ -754,7 +710,7 @@ class Parser { |
} |
// 76 |
- bool b_comment() => atEndOfFile || b_nonContent(); |
+ bool b_comment() => _scanner.isDone || b_nonContent(); |
// 77 |
bool s_b_comment() { |
@@ -1428,7 +1384,7 @@ class Parser { |
// 165 |
bool b_chompedLast(int chomping) { |
- if (atEndOfFile) return true; |
+ if (_scanner.isDone) return true; |
switch (chomping) { |
case CHOMPING_STRIP: |
return b_nonContent(); |
@@ -1810,7 +1766,7 @@ class Parser { |
transaction(() { |
if (!truth(or([c_directivesEnd, c_documentEnd]))) return; |
var char = peek(); |
- forbidden = isBreak(char) || isSpace(char) || atEndOfFile; |
+ forbidden = isBreak(char) || isSpace(char) || _scanner.isDone; |
return; |
}); |
return forbidden; |
@@ -1871,21 +1827,11 @@ class Parser { |
return doc; |
}); |
- if (!atEndOfFile) parseFailed(); |
+ if (!_scanner.isDone) parseFailed(); |
return docs; |
} |
} |
-class SyntaxError extends YamlException { |
- final int _line; |
- final int _column; |
- |
- SyntaxError(this._line, this._column, String msg) : super(msg); |
- |
- String toString() => "Syntax error on line $_line, column $_column: " |
- "${super.toString()}"; |
-} |
- |
/// A pair of values. |
class _Pair<E, F> { |
E first; |