diff --git a/pkgs/string_scanner/CHANGELOG.md b/pkgs/string_scanner/CHANGELOG.md index 076b73114d..4ea4c979e5 100644 --- a/pkgs/string_scanner/CHANGELOG.md +++ b/pkgs/string_scanner/CHANGELOG.md @@ -1,5 +1,8 @@ ## 1.4.2-wip +* Add `LineScannerMixin` to allow custom subclasses of `StringScanner` to share + eager line and column tracking logic. + ## 1.4.1 * Move to `dart-lang/tools` monorepo. diff --git a/pkgs/string_scanner/lib/src/eager_span_scanner.dart b/pkgs/string_scanner/lib/src/eager_span_scanner.dart index 1ccc746aca..d19d7aec2a 100644 --- a/pkgs/string_scanner/lib/src/eager_span_scanner.dart +++ b/pkgs/string_scanner/lib/src/eager_span_scanner.dart @@ -2,132 +2,10 @@ // 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. -import 'charcode.dart'; import 'line_scanner.dart'; import 'span_scanner.dart'; -import 'utils.dart'; - -// TODO(nweiz): Currently this duplicates code in line_scanner.dart. Once -// sdk#23770 is fully complete, we should move the shared code into a mixin. - -/// A regular expression matching newlines across platforms. -final _newlineRegExp = RegExp(r'\r\n?|\n'); /// A [SpanScanner] that tracks the line and column eagerly, like [LineScanner]. -class EagerSpanScanner extends SpanScanner { - @override - int get line => _line; - int _line = 0; - - @override - int get column => _column; - int _column = 0; - - @override - LineScannerState get state => - _EagerSpanScannerState(this, position, line, column); - - bool get _betweenCRLF => peekChar(-1) == $cr && peekChar() == $lf; - - @override - set state(LineScannerState state) { - if (state is! _EagerSpanScannerState || !identical(state._scanner, this)) { - throw ArgumentError('The given LineScannerState was not returned by ' - 'this LineScanner.'); - } - - super.position = state.position; - _line = state.line; - _column = state.column; - } - - @override - set position(int newPosition) { - final oldPosition = position; - super.position = newPosition; - - if (newPosition > oldPosition) { - final newlines = _newlinesIn(string.substring(oldPosition, newPosition)); - _line += newlines.length; - if (newlines.isEmpty) { - _column += newPosition - oldPosition; - } else { - _column = newPosition - newlines.last.end; - } - } else { - final newlines = _newlinesIn(string.substring(newPosition, oldPosition)); - if (_betweenCRLF) newlines.removeLast(); - - _line -= newlines.length; - if (newlines.isEmpty) { - _column -= oldPosition - newPosition; - } else { - _column = - newPosition - string.lastIndexOf(_newlineRegExp, newPosition) - 1; - } - } - } - +class EagerSpanScanner extends SpanScanner with LineScannerMixin { EagerSpanScanner(super.string, {super.sourceUrl, super.position}); - - @override - bool scanChar(int character) { - if (!super.scanChar(character)) return false; - _adjustLineAndColumn(character); - return true; - } - - @override - int readChar() { - final character = super.readChar(); - _adjustLineAndColumn(character); - return character; - } - - /// Adjusts [_line] and [_column] after having consumed [character]. - void _adjustLineAndColumn(int character) { - if (character == $lf || (character == $cr && peekChar() != $lf)) { - _line += 1; - _column = 0; - } else { - _column += inSupplementaryPlane(character) ? 2 : 1; - } - } - - @override - bool scan(Pattern pattern) { - if (!super.scan(pattern)) return false; - final firstMatch = lastMatch![0]!; - - final newlines = _newlinesIn(firstMatch); - _line += newlines.length; - if (newlines.isEmpty) { - _column += firstMatch.length; - } else { - _column = firstMatch.length - newlines.last.end; - } - - return true; - } - - /// Returns a list of [Match]es describing all the newlines in [text], which - /// is assumed to end at [position]. - List _newlinesIn(String text) { - final newlines = _newlineRegExp.allMatches(text).toList(); - if (_betweenCRLF) newlines.removeLast(); - return newlines; - } -} - -/// A class representing the state of an [EagerSpanScanner]. -class _EagerSpanScannerState implements LineScannerState { - final EagerSpanScanner _scanner; - @override - final int position; - @override - final int line; - @override - final int column; - - _EagerSpanScannerState(this._scanner, this.position, this.line, this.column); } diff --git a/pkgs/string_scanner/lib/src/line_scanner.dart b/pkgs/string_scanner/lib/src/line_scanner.dart index 111ac78f0f..23906738e1 100644 --- a/pkgs/string_scanner/lib/src/line_scanner.dart +++ b/pkgs/string_scanner/lib/src/line_scanner.dart @@ -6,10 +6,15 @@ import 'charcode.dart'; import 'string_scanner.dart'; import 'utils.dart'; -// Note that much of this code is duplicated in eager_span_scanner.dart. - -/// A subclass of [StringScanner] that tracks line and column information. -class LineScanner extends StringScanner { +/// A mixin that implements the line- and column-tracking functionality of +/// [LineScanner]. +/// +/// For example: +/// +/// class MyScanner extends StringScanner with LineScannerMixin { +/// MyScanner(super.string); +/// } +mixin LineScannerMixin on StringScanner { /// The scanner's current (zero-based) line number. int get line => _line; int _line = 0; @@ -119,8 +124,6 @@ class LineScanner extends StringScanner { } } - LineScanner(super.string, {super.sourceUrl, super.position}); - @override bool scanChar(int character) { if (!super.scanChar(character)) return false; @@ -191,10 +194,15 @@ class LineScanner extends StringScanner { } } +/// A subclass of [StringScanner] that tracks line and column information. +class LineScanner extends StringScanner with LineScannerMixin { + LineScanner(super.string, {super.sourceUrl, super.position}); +} + /// A class representing the state of a [LineScanner]. class LineScannerState { - /// The [LineScanner] that created this. - final LineScanner _scanner; + /// The [StringScanner] that created this. + final StringScanner _scanner; /// The position of the scanner in this state. final int position;