Line data Source code
1 : // Copyright (c) 2014, 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 : import 'package:charcode/ascii.dart'; 6 : 7 : import 'string_scanner.dart'; 8 : 9 : // Note that much of this code is duplicated in eager_span_scanner.dart. 10 : 11 : /// A regular expression matching newlines across platforms. 12 0 : final _newlineRegExp = RegExp(r'\r\n?|\n'); 13 : 14 : /// A subclass of [StringScanner] that tracks line and column information. 15 : class LineScanner extends StringScanner { 16 : /// The scanner's current (zero-based) line number. 17 0 : int get line => _line; 18 : int _line = 0; 19 : 20 : /// The scanner's current (zero-based) column number. 21 0 : int get column => _column; 22 : int _column = 0; 23 : 24 : /// The scanner's state, including line and column information. 25 : /// 26 : /// This can be used to efficiently save and restore the state of the scanner 27 : /// when backtracking. A given [LineScannerState] is only valid for the 28 : /// [LineScanner] that created it. 29 : /// 30 : /// This does not include the scanner's match information. 31 0 : LineScannerState get state => 32 0 : LineScannerState._(this, position, line, column); 33 : 34 : /// Whether the current position is between a CR character and an LF 35 : /// charactet. 36 0 : bool get _betweenCRLF => peekChar(-1) == $cr && peekChar() == $lf; 37 : 38 0 : set state(LineScannerState state) { 39 0 : if (!identical(state._scanner, this)) { 40 0 : throw ArgumentError('The given LineScannerState was not returned by ' 41 : 'this LineScanner.'); 42 : } 43 : 44 0 : super.position = state.position; 45 0 : _line = state.line; 46 0 : _column = state.column; 47 : } 48 : 49 0 : @override 50 : set position(int newPosition) { 51 0 : final oldPosition = position; 52 0 : super.position = newPosition; 53 : 54 0 : if (newPosition > oldPosition) { 55 0 : final newlines = _newlinesIn(string.substring(oldPosition, newPosition)); 56 0 : _line += newlines.length; 57 0 : if (newlines.isEmpty) { 58 0 : _column += newPosition - oldPosition; 59 : } else { 60 0 : _column = newPosition - newlines.last.end; 61 : } 62 : } else { 63 0 : final newlines = _newlinesIn(string.substring(newPosition, oldPosition)); 64 0 : if (_betweenCRLF) newlines.removeLast(); 65 : 66 0 : _line -= newlines.length; 67 0 : if (newlines.isEmpty) { 68 0 : _column -= oldPosition - newPosition; 69 : } else { 70 0 : _column = 71 0 : newPosition - string.lastIndexOf(_newlineRegExp, newPosition) - 1; 72 : } 73 : } 74 : } 75 : 76 0 : LineScanner(String string, {sourceUrl, int? position}) 77 0 : : super(string, sourceUrl: sourceUrl, position: position); 78 : 79 0 : @override 80 : bool scanChar(int character) { 81 0 : if (!super.scanChar(character)) return false; 82 0 : _adjustLineAndColumn(character); 83 : return true; 84 : } 85 : 86 0 : @override 87 : int readChar() { 88 0 : final character = super.readChar(); 89 0 : _adjustLineAndColumn(character); 90 : return character; 91 : } 92 : 93 : /// Adjusts [_line] and [_column] after having consumed [character]. 94 0 : void _adjustLineAndColumn(int character) { 95 0 : if (character == $lf || (character == $cr && peekChar() != $lf)) { 96 0 : _line += 1; 97 0 : _column = 0; 98 : } else { 99 0 : _column += 1; 100 : } 101 : } 102 : 103 0 : @override 104 : bool scan(Pattern pattern) { 105 0 : if (!super.scan(pattern)) return false; 106 : 107 0 : final newlines = _newlinesIn(lastMatch![0]!); 108 0 : _line += newlines.length; 109 0 : if (newlines.isEmpty) { 110 0 : _column += (lastMatch![0])!.length; 111 : } else { 112 0 : _column = (lastMatch![0])!.length - newlines.last.end; 113 : } 114 : 115 : return true; 116 : } 117 : 118 : /// Returns a list of [Match]es describing all the newlines in [text], which 119 : /// is assumed to end at [position]. 120 0 : List<Match> _newlinesIn(String text) { 121 0 : final newlines = _newlineRegExp.allMatches(text).toList(); 122 0 : if (_betweenCRLF) newlines.removeLast(); 123 : return newlines; 124 : } 125 : } 126 : 127 : /// A class representing the state of a [LineScanner]. 128 : class LineScannerState { 129 : /// The [LineScanner] that created this. 130 : final LineScanner _scanner; 131 : 132 : /// The position of the scanner in this state. 133 : final int position; 134 : 135 : /// The zero-based line number of the scanner in this state. 136 : final int line; 137 : 138 : /// The zero-based column number of the scanner in this state. 139 : final int column; 140 : 141 0 : LineScannerState._(this._scanner, this.position, this.line, this.column); 142 : }