blob: 7a24e3cbaad847ff6cbbe8a7810fc42cd06d3896 [file] [log] [blame]
import 'package:meta/meta.dart';
import '../context/context.dart';
import '../context/failure.dart';
import '../context/result.dart';
import '../context/success.dart';
/// Abstract base class of all parsers that produce a parse result of type [R].
@optionalTypeArgs
abstract class Parser<R> {
Parser();
/// Primitive method doing the actual parsing.
///
/// The method is overridden in concrete subclasses to implement the
/// parser specific logic. The methods takes a parse [context] and
/// returns the resulting context, which is either a [Success] or
/// [Failure] context.
Result<R> parseOn(Context context);
/// Primitive method doing the actual parsing.
///
/// This method is an optimized version of [Parser.parseOn] that is getting
/// its speed advantage by avoiding any unnecessary memory allocations.
///
/// The method is overridden in most concrete subclasses to implement the
/// optimized logic. As an input the method takes a [buffer] and the current
/// [position] in that buffer. It returns a new (positive) position in case
/// of a successful parse, or `-1` in case of a failure.
///
/// Subclasses don't necessarily have to override this method, since it is
/// emulated using its slower brother.
int fastParseOn(String buffer, int position) {
final result = parseOn(Context(buffer, position));
return result.isSuccess ? result.position : -1;
}
/// Returns the parse result of the [input].
///
/// The implementation creates a default parse context on the input and calls
/// the internal parsing logic of the receiving parser.
///
/// For example, `letter().plus().parse('abc')` results in an instance of
/// [Success], where [Context.position] is `3` and [Success.value] is
/// `[a, b, c]`.
///
/// Similarly, `letter().plus().parse('123')` results in an instance of
/// [Failure], where [Context.position] is `0` and [Failure.message] is
/// ['letter expected'].
@nonVirtual
Result<R> parse(String input) => parseOn(Context(input, 0));
/// Returns a shallow copy of the receiver.
///
/// Override this method in all subclasses, return its own type.
Parser<R> copy();
/// Recursively tests for structural equality of two parsers.
///
/// The code automatically deals with recursive parsers and parsers that
/// refer to other parsers. Do not override this method, instead customize
/// [Parser.hasEqualProperties] and [Parser.children].
@nonVirtual
bool isEqualTo(Parser other, [Set<Parser>? seen]) {
if (this == other) {
return true;
}
if (runtimeType != other.runtimeType || !hasEqualProperties(other)) {
return false;
}
seen ??= {};
return !seen.add(this) || hasEqualChildren(other, seen);
}
/// Compare the properties of two parsers.
///
/// Override this method in all subclasses that add new state.
@protected
@mustCallSuper
bool hasEqualProperties(covariant Parser other) => true;
/// Compare the children of two parsers.
///
/// Normally this method does not need to be overridden, as this method works
/// generically on the returned [Parser.children].
@protected
@nonVirtual
bool hasEqualChildren(covariant Parser other, Set<Parser> seen) {
final thisChildren = children, otherChildren = other.children;
if (thisChildren.length != otherChildren.length) {
return false;
}
for (var i = 0; i < thisChildren.length; i++) {
if (!thisChildren[i].isEqualTo(otherChildren[i], seen)) {
return false;
}
}
return true;
}
/// Returns a list of directly referenced parsers.
///
/// For example, `letter().children` returns the empty collection `[]`,
/// because the letter parser is a primitive or leaf parser that does not
/// depend or call any other parser.
///
/// In contrast, `letter().or(digit()).children` returns a collection
/// containing both the `letter()` and `digit()` parser.
///
/// Override this method and [Parser.replace] in all subclasses that
/// reference other parsers.
List<Parser> get children => const [];
/// Changes the receiver by replacing [source] with [target]. Does nothing
/// if [source] does not exist in [Parser.children].
///
/// The following example creates a letter parser and then defines a parser
/// called `example` that accepts one or more letters. Eventually the parser
/// `example` is modified by replacing the `letter` parser with a new
/// parser that accepts a digit. The resulting `example` parser accepts one
/// or more digits.
///
/// final letter = letter();
/// final example = letter.plus();
/// example.replace(letter, digit());
///
/// Override this method and [Parser.children] in all subclasses that
/// reference other parsers.
@mustCallSuper
void replace(Parser source, Parser target) {}
/// Internal helper to capture the generic type [R] of the parse result from
/// extension methods. This makes it possible to wrap the parser without
/// loosing type information.
@internal
@nonVirtual
T captureResultGeneric<T>(T Function<R>(Parser<R> self) callback) =>
callback<R>(this);
}