blob: 739918cbb3e6c3f69c78788b8383c8835586e635 [file] [log] [blame]
/**
* This package contains the core library of PetitParser, a dynamic parser
* combinator framework.
*
* # Writing a Simple Grammar
*
* Writing grammars with PetitParser is simple as writing Dart code. For
* example, to write a grammar that can parse identifiers that start with
* a letter followed by zero or more letter or digits is defined as follows:
*
* Parser id = letter().seq(letter().or(digit()).star());
*
* If you look at the object `id` in the debugger, you'll notice that the
* code above builds a tree of parser objects:
*
* - Sequence: This parser accepts a sequence of parsers.
* - - Predicate: This parser accepts a single letter.
* - - Repeater: This parser accepts zero or more times another parser.
* - - - Choice: This parser accepts a single word character.
* - - - - Predicate: This parser accepts a single letter.
* - - - - Predicate: This parser accepts a single digit.
*
* # Parsing Some Input
*
* To actually parse a [String] (or [List]) we can use the method
* [Parser.parse]:
*
* Result id1 = id.parse('yeah');
* Result id2 = id.parse('f12');
*
* The method [Parser.parse] returns a parse [Result], which is either an
* instance of [Success] or [Failure]. In both examples above we are
* successful and can retrieve the parse result using [Success.value]:
*
* print(id1.value); // ['y', ['e', 'a', 'h']]
* print(id2.value); // ['f', ['1', '2']]
*
* While it seems odd to get these nested arrays with characters as a return
* value, this is the default decomposition of the input into a parse tree.
* We'll see in a while how that can be customized.
*
* If we try to parse something invalid we get an instance of [Failure] as
* an answer and we can retrieve a descriptive error message using
* [Failure.message]:
*
* Result id3 = id.parse('123');
* print(id3.message); // 'letter expected'
* print(id3.position); // 0
*
* Trying to retrieve the parse result by calling [Failure.value] would throw
* the exception [UnsupportedError]. [Context.isSuccess] and [Context.isFailure]
* can be used to decide if the parse was successful.
*
* If you are only interested if a given string matches or not you can use the
* helper method [Parser.accept]:
*
* print(id.accept('foo')); // true
* print(id.accept('123')); // false
*
* # Different Kinds of Parsers
*
* PetitParser provide a large set of ready-made parser that you can compose
* to consume and transform arbitrarily complex languages. The terminal parsers
* are the most simple ones. We've already seen a few of those:
*
* * `char('a')` parses the character *a*.
* * `string('abc')` parses the string *abc*.
* * `any()` parses any character.
* * `digit()` parses any digit from *0* to *9*.
* * `letter()` parses any letter from *a* to *z* and *A* to *Z*.
* * `word()` parses any letter or digit.
*
* So instead of using the letter and digit predicate, we could have written
* our identifier parser like this:
*
* var id = letter().seq(word().star());
*
* The next set of parsers are used to combine other parsers together:
*
* * `p1.seq(p2)` and `p1 & p2` parse *p1* followed by *p2* (sequence).
* * `p1.or(p2)` and `p1 | p2` parse *p1*, if that doesn't work parses *p2* (ordered choice).
* * `p.star()` parses *p* zero or more times.
* * `p.plus()` parses *p* one or more times.
* * `p.optional()` parses *p*, if possible.
* * `p.and()` parses *p*, but does not consume its input.
* * `p.not()` parses *p* and succeed when p fails, but does not consume its input.
* * `p.end()` parses *p* and succeed at the end of the input.
*
* To attach an action or transformation to a parser we can use the following
* methods:
*
* * `p.map((value) => ...)` performs the transformation given the function.
* * `p.pick(n)` returns the *n*-th element of the list *p* returns.
* * `p.flatten()` creates a string from the result of *p*.
* * `p.token()` creates a token from the result of *p*.
* * `p.trim()` trims whitespaces before and after *p*.
*
* To return a string of the parsed identifier, we can modify our parser like
* this:
*
* var id = letter().seq(word().star()).flatten();
*
* To conveniently find all matches in a given input string you can use
* [Parser.matchesSkipping]:
*
* var matches = id.matchesSkipping('foo 123 bar4');
* print(matches); // ['foo', 'bar4']
*
* These are the basic elements to build parsers. There are a few more well
* documented and tested factory methods in the [Parser] class. If you want
* browse their documentation and tests.
*
* # Writing a More Complicated Grammar
*
* Now we are able to write a more complicated grammar for evaluating simple
* arithmetic expressions. Within a file we start with the grammar for a
* number (actually an integer):
*
* var number = digit().plus().flatten().trim().map(int.parse);
*
* Then we define the productions for addition and multiplication in order of
* precedence. Note that we instantiate the productions with undefined parsers
* upfront, because they recursively refer to each other. Later on we can
* resolve this recursion by setting their reference:
*
* var term = undefined();
* var prod = undefined();
* var prim = undefined();
*
* term.set(prod.seq(char('+').trim()).seq(term).map((values) {
* return values[0] + values[2];
* }).or(prod));
* prod.set(prim.seq(char('*').trim()).seq(prod).map((values) {
* return values[0] * values[2];
* }).or(prim));
* prim.set(char('(').trim().seq(term).seq(char(')'.trim())).map((values) {
* return values[1];
* }).or(number));
*
* To make sure that our parser consumes all input we wrap it with the `end()`
* parser into the start production:
*
* var start = term.end();
*
* That's it, now we can test our parser and evaluator:
*
* print(start.parse('1 + 2 * 3').value); // 7
* print(start.parse('(1 + 2) * 3').value); // 9
*
* As an exercise we could extend the parser to also accept negative numbers
* and floating point numbers, not only integers. Furthermore it would be
* useful to support subtraction and division as well. All these features
* can be added with a few lines of PetitParser code.
*/
library petitparser;
part 'src/core/actions.dart';
part 'src/core/characters.dart';
part 'src/core/combinators.dart';
part 'src/core/composite.dart';
part 'src/core/context.dart';
part 'src/core/definition.dart';
part 'src/core/expression.dart';
part 'src/core/parser.dart';
part 'src/core/parsers.dart';
part 'src/core/predicates.dart';
part 'src/core/repeaters.dart';
part 'src/core/token.dart';