blob: cef6025d2327eb478f10845b645f51449c4b0056 [file] [log] [blame]
import 'package:meta/meta.dart';
import '../core/parser.dart';
import 'reference.dart';
import 'resolve.dart';
/// Helper to conveniently define and build complex, recursive grammars using
/// plain Dart code.
///
/// To create a new grammar definition subclass [GrammarDefinition]. For every
/// production create a new method returning the primitive parser defining it.
/// The method called [start] is supposed to return the start production of the
/// grammar (that can be customized when building the parsers). To refer to
/// another production use [ref0] with the function reference as the argument.
///
/// Consider the following example to parse a list of numbers:
///
/// class ListGrammarDefinition extends GrammarDefinition {
/// Parser start() => ref0(list).end();
/// Parser list() => ref0(element) & char(',') & ref0(list)
/// | ref0(element);
/// Parser element() => digit().plus().flatten();
/// }
///
/// Since this is plain Dart code, common refactorings such as renaming a
/// production updates all references correctly. Also code navigation and code
/// completion works as expected.
///
/// To attach custom production actions you might want to further subclass your
/// grammar definition and override overriding the necessary productions defined
/// in the superclass:
///
/// class ListParserDefinition extends ListGrammarDefinition {
/// Parser element() => super.element().map((value) => int.parse(value));
/// }
///
/// Note that productions can be parametrized. Define such productions with
/// positional arguments, and refer to them using [ref1], [ref2], ... where
/// the number corresponds to the argument count.
///
/// Consider extending the above grammar with a parametrized token production:
///
/// class TokenizedListGrammarDefinition extends GrammarDefinition {
/// Parser start() => ref0(list).end();
/// Parser list() => ref0(element) & ref1(token, char(',')) & ref0(list)
/// | ref0(element);
/// Parser element() => ref1(token, digit().plus());
/// Parser token(Parser parser) => parser.token().trim();
/// }
///
/// To get a runnable parser call the [build] method on the definition. It
/// resolves recursive references and returns an efficient parser that can be
/// further composed. The optional `start` reference specifies a different
/// starting production within the grammar. The optional `arguments`
/// parametrize the start production.
///
/// final parser = new ListParserDefinition().build();
///
/// parser.parse('1'); // [1]
/// parser.parse('1,2,3'); // [1, 2, 3]
///
@optionalTypeArgs
abstract class GrammarDefinition<R> {
const GrammarDefinition();
/// The starting production of this definition.
Parser<R> start();
/// Builds a composite parser from this definition.
///
/// The optional [start] reference specifies a different starting production
/// into the grammar. The optional [arguments] list parametrizes the called
/// production.
///
/// In the upcoming major release all arguments (generic and method arguments)
/// will be removed and the typed [start] production will be returned. Use
/// [buildFrom] to start at another production rule.
@useResult
@optionalTypeArgs
Parser<T> build<T>({
@Deprecated("Use `buildFrom(parser)`") Function? start,
@Deprecated("Use `buildFrom(parser)`") List<Object> arguments = const [],
}) {
if (start != null) {
return resolve(Function.apply(start, arguments));
} else if (arguments.isEmpty) {
return resolve(this.start() as Parser<T>);
} else {
throw StateError('Invalid arguments passed.');
}
}
/// Builds a composite parser starting at the specified function.
@useResult
Parser<T> buildFrom<T>(Parser<T> parser) => resolve(parser);
}