blob: a631c87b64eabbd54b80970c5f7672298baf26c0 [file] [log] [blame]
import 'dart:math' as math;
import 'package:meta/meta.dart';
import '../matcher/matches.dart';
import '../parser/action/token.dart';
import '../parser/misc/newline.dart';
import 'parser.dart';
/// A token represents a parsed part of the input stream.
///
/// The token holds the resulting value of the input, the input buffer,
/// and the start and stop position in the input buffer. It provides many
/// convenience methods to access the state of the token.
@immutable
class Token<T> {
/// Constructs a token from the parsed value, the input buffer, and the
/// start and stop position in the input buffer.
const Token(this.value, this.buffer, this.start, this.stop);
/// The parsed value of the token.
final T value;
/// The parsed buffer of the token.
final String buffer;
/// The start position of the token in the buffer.
final int start;
/// The stop position of the token in the buffer.
final int stop;
/// The consumed input of the token.
String get input => buffer.substring(start, stop);
/// The length of the token.
int get length => stop - start;
/// The line number of the token.
int get line => Token.lineAndColumnOf(buffer, start)[0];
/// The column number of this token.
int get column => Token.lineAndColumnOf(buffer, start)[1];
/// Converts the value of the token.
Token<R> map<R>(R Function(T value) mapper) =>
Token(mapper(value), buffer, start, stop);
@override
String toString() => 'Token[${positionString(buffer, start)}]: $value';
@override
bool operator ==(Object other) =>
other is Token &&
value == other.value &&
start == other.start &&
stop == other.stop;
@override
int get hashCode => value.hashCode + start.hashCode + stop.hashCode;
/// Combines multiple token into a single token with the list of its values.
static Token<List<T>> join<T>(Iterable<Token<T>> token) {
final iterator = token.iterator;
if (!iterator.moveNext()) {
throw ArgumentError.value(token, 'token', 'Require at least one token');
}
final value = <T>[iterator.current.value];
final buffer = iterator.current.buffer;
var start = iterator.current.start;
var stop = iterator.current.stop;
while (iterator.moveNext()) {
if (buffer != iterator.current.buffer) {
throw ArgumentError.value(
token, 'token', 'Token do not use same buffer');
}
value.add(iterator.current.value);
start = math.min(start, iterator.current.start);
stop = math.max(stop, iterator.current.stop);
}
return Token(value, buffer, start, stop);
}
/// Returns a parser that detects newlines platform independently.
static Parser<String> newlineParser() => _newlineParser;
static final Parser<String> _newlineParser = newline();
/// Converts the [position] index in a [buffer] to a line and column tuple.
static List<int> lineAndColumnOf(String buffer, int position) {
var line = 1, offset = 0;
for (final token in newlineParser().token().allMatches(buffer)) {
if (position < token.stop) {
return [line, position - offset + 1];
}
line++;
offset = token.stop;
}
return [line, position - offset + 1];
}
/// Returns a human readable string representing the [position] index in a
/// [buffer].
static String positionString(String buffer, int position) {
final lineAndColumn = lineAndColumnOf(buffer, position);
return '${lineAndColumn[0]}:${lineAndColumn[1]}';
}
}