Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkgs/string_scanner/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
124 changes: 1 addition & 123 deletions pkgs/string_scanner/lib/src/eager_span_scanner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Match> _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);
}
18 changes: 10 additions & 8 deletions pkgs/string_scanner/lib/src/line_scanner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ 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].
mixin LineScannerMixin on StringScanner {
Comment thread
amanmaurya92 marked this conversation as resolved.
/// The scanner's current (zero-based) line number.
int get line => _line;
int _line = 0;
Expand Down Expand Up @@ -119,8 +118,6 @@ class LineScanner extends StringScanner {
}
}

LineScanner(super.string, {super.sourceUrl, super.position});

@override
bool scanChar(int character) {
if (!super.scanChar(character)) return false;
Expand Down Expand Up @@ -191,10 +188,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;
Expand Down