petitparser

v7.0.2

A dynamic parser framework to build efficient grammars and parsers quickly.

Архив пакета: https://pubdev.letsnova.ru/api/archives/petitparser/7.0.2.tar.gz

Установкаdart pub add petitparser

README

PetitParser for Dart

Pub Package Build Status Code Coverage GitHub Issues GitHub Forks GitHub Stars GitHub License

Grammars for programming languages are traditionally specified statically. They are hard to compose and reuse due to ambiguities that inevitably arise. PetitParser combines ideas from scannerless parsing, parser combinators, parsing expression grammars (PEG) and packrat parsers to model grammars and parsers as objects that can be reconfigured dynamically.

This library is open source, stable and well tested. Development happens on GitHub. Feel free to report issues or create a pull-request there. General questions are best asked on StackOverflow.

The package is hosted on dart packages. Up-to-date API documentation is created with every release.

Tutorial

Below are step-by-step instructions of how to write your first parser. More elaborate examples (JSON parser, LISP parser and evaluator, Prolog parser and evaluator, etc.) are included in the example repository. Try out the running demos at petitparser.github.io.

Installation

Follow the installation instructions on dart packages.

Import the package into your Dart code using:

import 'package:petitparser/petitparser.dart';
                

It is also possible to more selectively import only certain parts of this library, i.e. package:petitparser/core.dart and package:petitparser/parser.dart for core infrastructure and the basic parsers.

[!IMPORTANT] This library makes extensive use of static extension methods. If you import the library using a library prefix or only selectively show classes you might miss some of the functionality.

Writing a Simple Grammar

Writing grammars with PetitParser is as simple as writing Dart code. For example, the following code creates a parser that can read identifiers (a letter followed by zero or more letter or digits):

final id = letter() & (letter() | digit()).star();  // (0): Parser<List<dynamic>>
                

If you inspect the object id in the debugger, you'll notice that the code above builds a tree of parser objects:

  • SequenceParser: This parser accepts the sequence of its child parsers.
    • SingleCharacterParser: This parser accepts a single letter.
    • PossessiveRepeatingParser: This parser accepts zero or more times its child parsers.
      • ChoiceParser: This parser accepts the first of its succeeding child parsers, or otherwise fails.
        • SingleCharacterParser: This parser accepts a single letter.
        • SingleCharacterParser: This parser accepts a single digit.

The operators & and | are overloaded and create a sequence and a choice parser respectively. In some contexts it might be more convenient to use chained function calls, or the extension methods on lists. All of the following parsers accept the same inputs as the parser above:

final id1 = letter().seq(letter().or(digit()).star());  // (1): Parser<List<dynamic>>
                final id2 = [letter(), [letter(), digit()].toChoiceParser().star()].toSequenceParser();  // (2): Parser<List<Object>>
                final id3 = seq2(letter(), [letter(), digit()].toChoiceParser().star());  // (3): Parser<(String, List<String>)>
                

[!NOTE] The inferred type of the 3 parsers is not equivalent: Due to github.com/dart-lang/language/issues/1557 the inferred type of sequence and choice parsers created with operators (0) or chained function calls (1) is Parser<dynamic>. The parser built from lists (2) provides the most generic type, List<Object> in this example. The last variation (3) is the only one that doesn't lose type information and produces a record (tuple) with two typed elements String and List<String>.

[!IMPORTANT] Parsers read input greedily: if a parser fails, it does not retry. Only ChoiceParser provides backtracking by trying its children in order until one succeeds; if none do, the whole choice fails.

Parsing Some Input

To actually consume an input string we use the method Parser.parse:

final result1 = id.parse('yeah');
                final result2 = id.parse('f12');
                

The method Parser.parse returns a Result, which is either an instance of Success or Failure. In both examples we are successful and can retrieve the resulting value using Success.value:

print(result1.value);                   // ['y', ['e', 'a', 'h']]
                print(result2.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 and we can retrieve a descriptive error message using Failure.message:

final result3 = id.parse('123');
                print(result3.message);                 // 'letter expected'
                print(result3.position);                // 0
                

Trying to retrieve result by calling Failure.value would throw the exception ParserException. Pattern matching can be used to decide if the parse result was a success or a failure:

switch (id.parse(input)) {
                  case Success(value: final value):
                    print('Success: $value');
                  case Failure(message: final message, position: final position):
                    print('Failure at $position: $message');
                }
                

If you are only interested if a given string is valid you can use the helper method Parser.accept:

print(id.accept('foo'));                // true
                print(id.accept('123'));                // false
                

Different Kinds of Parsers

PetitParser provides a large set of ready-made parser that you can compose to consume and transform arbitrarily complex languages.

Terminal Parsers

Terminal parsers are the simplest. We've already seen a few of those:

By default all parsers use an automatically generated descriptive error message, match case-sensitive, and work on 16-bit UTF-16 code units. To change this default behavior use the named arguments (where appropriate):

  • message: 'expected a special character' to define a custom error message,
  • ignoreCase: true to accept both lower- and uppercase variations, and
  • unicode: true to decode surrogate pairs and read Unicode code-points.

Combinator Parsers

The next set of parsers are used to combine other parsers together:

The following parsers repeat another parser a configured amount of times, and produce a list of parsed results. Check the documentation for other repeaters that are lazy or greedy, or that can handle separators.

A variation of the parsers above is the optional operator, it produces the value of p or null.

  • p.optional() parses p and returns its result, otherwise returns null.
  • p.optionalWith(v) parses p and returns its result, otherwise returns the argument v.

More complicated combinators that can come in handy at times are:

  • p.and() parses p, but does not consume its input.
  • p.not() parses p and succeeds when p fails, but does not consume its input.
  • p.end() parses p and succeeds at the end of the input.

Transforming Parsers

The last type of parsers are actions or transformations we can use as follows:

[!TIP] Various other parsers for more specific use-cases are available, browse the subclasses and extensions of the Parser class.

To return a string of the parsed identifier, we can modify our parser like this:

final id = (letter() & pattern('a-zA-Z0-9').star()).flatten();
                

To conveniently find all matches in a given input string you can use Parser.allMatches:

final matches = id.allMatches('foo 123 bar4');
                print(matches);                         // ['foo', 'bar4']
                

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 an integer:

final 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:

final term = undefined();
                final prod = undefined();
                final prim = undefined();
                
                final add = (prod & char('+').trim() & term)
                    .map((values) => values[0] + values[2]);
                term.set(add | prod);
                
                final mul = (prim & char('*').trim() & prod)
                    .map((values) => values[0] * values[2]);
                prod.set(mul | prim);
                
                final parens = (char('(').trim() & term & char(')').trim())
                    .map((values) => values[1]);
                final number = digit().plus().flatten().trim().map(int.parse);
                prim.set(parens | number);
                

To make sure our parser consumes all input we wrap it with the end() parser in the start production:

final parser = term.end();
                

That's it, now we can test our parser and evaluator:

parser.parse('1 + 2 * 3');              // 7
                parser.parse('(1 + 2) * 3');            // 9
                

Using Parser References

Defining and reusing complex grammars can be cumbersome, particularly if the grammar is large and recursive (such as the example above). PetitParser provides building blocks to conveniently define and build complex grammars with possibly hundreds of productions.

To create a new grammar definition subclass GrammarDefinition. In our case we call the class ExpressionDefinition. 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. To refer to a production defined in the same definition use ref(Function) with the function reference as the argument.

class ExpressionDefinition extends GrammarDefinition {
                  Parser start() => ref(term).end();
                
                  Parser term() => ref(add) | ref(prod);
                  Parser add() => ref(prod) & char('+').trim() & ref(term);
                
                  Parser prod() => ref(mul) | ref(prim);
                  Parser mul() => ref(prim) & char('*').trim() & ref(prod);
                
                  Parser prim() => ref(parens) | ref(number);
                  Parser parens() => char('(').trim() & ref(term) & char(')').trim();
                
                  Parser number() => digit().plus().flatten().trim();
                }
                

To create a parser with all the references correctly resolved call build().

final definition = ExpressionDefinition();
                final parser = definition.build();
                parser.parse('1 + 2 * 3');              // ['1', '+', ['2', '+', '3']]
                

Again, since this is plain Dart, common code refactorings such as renaming a production updates all references correctly. Also code navigation and code completion works as expected.

[!TIP] The use of ref(Function) is not limited to subclasses of GrammarDefinition, it can be called from anywhere in Dart. To build the resulting parser use resolve(Parser) on the root node of the grammar.

[!TIP] The function ref takes positional arguments to parametrize the created parser, if the referenced function takes arguments. While ref supports an arbitrary amount of arguments, it can neither infer nor check return or argument types at compile time. The variations ref0, ref1, ref2, ... solve this problem, but require you to specify the number of arguments.

To attach custom production actions you might want to further subclass your grammar definition and override the necessary productions defined in the superclass:

class EvaluatorDefinition extends ExpressionDefinition {
                  Parser add() => super.add().map((values) => values[0] + values[2]);
                  Parser mul() => super.mul().map((values) => values[0] * values[2]);
                  Parser parens() => super.parens().castList<num>().pick(1);
                  Parser number() => super.number().map((value) => int.parse(value));
                }
                

Similarly, build the evaluator parser like so:

final definition = EvaluatorDefinition();
                final parser = definition.build();
                parser.parse('1 + 2 * 3');              // 7
                

[!TIP] Subclassing of definitions only works well, if you keep your parsers dynamic like in the example above (Parser or Parser<dynamic>). While this might increase reusability of your parser definitions, it might also increase your code size and come with extra run-time cost.

To use just a part of the parser you can specify the start production when building. For example, to reuse the number parser one would write:

final definition = EvaluatorDefinition();
                final parser = definition.build(start: definition.number);
                parser.parse('42');                     // 42
                

Check out the documentation for more examples.

Using the Expression Builder

Writing such expression parsers is pretty common and can be tricky to get right. To simplify things, PetitParser comes with a builder that can help you to define such grammars easily. It supports the definition of operator precedence; and prefix, postfix, left- and right-associative operators.

The following code creates the empty ExpressionBuilder producing values of type num:

final builder = ExpressionBuilder<num>();
                

Every ExpressionBuilder needs to define at least one primitive type to parse. In this example these are the literal numbers. This time we accept floating-point numbers, not just integers. The mapping function converts the string input into an actual number.

builder.primitive(digit()
                    .plus()
                    .seq(char('.').seq(digit().plus()).optional())
                    .flatten()
                    .trim()
                    .map(num.parse));
                

Then we define the operator-groups in descending precedence. The highest precedence have parentheses. The mapping function receives both the opening parenthesis, the value, and the closing parenthesis as arguments:

builder.group().wrapper(
                    char('(').trim(), char(')').trim(), (left, value, right) => value);
                

Then come the normal arithmetic operators. We are using cascade notation to define multiple operators on the same precedence-group. The mapping functions receive both, the terms and the parsed operator in the order they appear in the parsed input:

// Negation is a prefix operator.
                builder.group().prefix(char('-').trim(), (operator, value) => -value);
                
                // Power is right-associative.
                builder.group().right(
                    char('^').trim(), (left, operator, right) => math.pow(left, right));
                
                // Multiplication and addition are left-associative, multiplication has
                // higher priority than addition.
                builder.group()
                  ..left(char('*').trim(), (left, operator, right) => left * right)
                  ..left(char('/').trim(), (left, operator, right) => left / right);
                builder.group()
                  ..left(char('+').trim(), (left, operator, right) => left + right)
                  ..left(char('-').trim(), (left, operator, right) => left - right);
                

Finally, we can build the parser:

final parser = builder.build().end();
                

After executing the above code we get an efficient parser that correctly evaluates expressions like:

parser.parse('-8');                     // -8
                parser.parse('1+2*3');                  // 7
                parser.parse('1*2+3');                  // 5
                parser.parse('8/4/2');                  // 1
                parser.parse('2^2^3');                  // 256
                

Check out the documentation for more examples.

Testing your Grammars

Real world grammars are typically large and complicated. PetitParser's architecture allows one to break down a grammar into manageable pieces, and develop and test each part individually before assembling the complete system.

Start the development and testing of a new grammar at the leaves (or tokens): write the parsers that read numbers, strings, and variables first; then continue with the expressions that can be built from these literals; and finally conclude with control structures, classes and other overarching constructs. At each step add tests and assert that the individual parsers behave as desired, so that you can be sure they also work when composing them to a larger grammar later.

Accessing and testing individual productions is simple: If you organize your grammar in your own code, make sure to expose parts of the grammar individually. If you use a GrammarDefinition, you can build individual productions using the buildFrom method. For example, to test the number production of the EvaluatorDefinition from above you would write:

test('number parsing', () {
                  final definition = EvaluatorDefinition();
                  final parser = definition.buildFrom(definition.number);
                  expect(parser.parse('42').value, 42);
                });
                

Additionally, PetitParser provides a Linter that comes with a collection of predefined rules that can help you find common bugs or inefficient constructs in your code. Among other things, the analyzer detects infinite loops, unreachable parsers, repeated parsers, and unresolved parsers. For an up-to-date list of all available rules check the implementation at linter_rules.dart.

To run the linter as part of your tests include the package petitparser/reflection.dart, call the linter function with the starting parser of your grammar, and assert that there are no findings. With the EvaluatorDefinition from above one would write:

test('detect common problems', () {
                  final definition = EvaluatorDefinition();
                  final parser = definition.build();
                  expect(linter(parser), isEmpty);
                });
                

To exclude certain rules from being reported you can exclude certain rules, i.e. linter(parser, excludedRules: {'Nested choice'}).

Check out the extensive test suites of PetitParser and PetitParser Examples for examples on testing.

Debugging your Grammars

Sometimes parsers might not behave the way you expect them to. The first step should always be to come up with a small reproducible example. If this doesn't already solve the problem, PetitParser comes with a set of built-in tools that can help you understand what is going on.

The function trace(Parser) transforms your grammar so that each parser prints its activation and results:

final parser = letter() & word().star();
                trace(parser).parse('f1');
                

The above snippet produces the following output:

SequenceParser<dynamic>
                  SingleCharacterParser[letter expected]
                  Success<String>[1:2]: f
                  PossessiveRepeatingParser<String>[0..*]
                    SingleCharacterParser[letter or digit expected]
                    Success<String>[1:3]: 1
                    SingleCharacterParser[letter or digit expected]
                    Failure[1:3]: letter or digit expected
                  Success<List<String>>[1:3]: [1]
                Success<List<dynamic>>[1:3]: [f, [1]]
                

Indentation signifies the activation of a parser object. Reverse indentation signifies the returning of a parse result either with a success or failure context.

Similarly, the function profile(Parser) produces a table of activation counts and run-time tallies of each parser. And, progress(Parser) visualizes how the parsers process (and possibly backtrack) through your input. Both tools can help to understand and optimize the performance characteristics of your parsers.

Misc

petitparser.github.io contains up-to-date information about PetitParser and ports to other languages.

Examples

The package comes with a large collection of example grammars and language experiments ready to explore:

  • CSV contains a simple Comma-separated values (CSV) parser.
  • Dart contains an experimental Dart grammar.
  • JSON contains a complete JSON grammar and parser.
  • Lisp contains a complete LISP grammar, parser and evaluator.
  • Math contains an mathematical expression parser and evaluator.
  • Pascal contains an experimental Pascal grammar.
  • Prolog contains a basic Prolog grammar, parser and evaluator.
  • Smalltalk contains a complete Smalltalk grammar.
  • Uri contains a simple URI parser.

Furthermore, there are numerous open source projects using PetitParser:

  • apollovm, a simple VM that can parse, run and generate basic Dart and Java8 code.
  • equations is an equation solving library.
  • expression_language is a library for parsing and evaluating expressions.
  • expressions is a library to parse and evaluate simple expressions.
  • json_path is an implementation of JSONPath expressions.
  • pem encodes and decodes textual cryptographic keys.
  • puppeteer is a library to automate the Chrome browser.
  • query implements search queries with support for boolean groups, field scopes, ranges, etc.
  • xml is a lightweight library for parsing, traversing, and querying XML documents.

History

PetitParser was originally implemented in Smalltalk. Later on, as a means to learn these languages, I reimplemented PetitParser in Java and Dart. The implementations are very similar in their API and the supported features. If possible, the implementations adopt best practices of the target language.

License

The MIT License, see LICENSE.

История изменений

Changelog

7.0.2

  • Numerous fixes and improvements to documentation and examples.
  • Add a constant parser Parser.constant(Object value) that can be used to return a constant value.
  • Add a linter rule to detect duplicate parsers.

7.0.1

  • Dart and Flutter 3.9 compatibility.
  • Minor optimization to repeating character parsers.
  • Assertion on looping trim parser.

7.0.0

  • Dart 3.8 requirement.
  • Support for unicode character parsing in all relevant character parsers.
  • Most character parser constructors now uniformly support the following named arguments (breaking-change):
    • message: to customize the default error message;
    • ignoreCase: to accept lower- and upper-case variations; and
    • unicode: to decode surrogate pairs and instead of UTF-16 only.
  • For consistency and better flexibility in the future, replaced optional arguments to a named ones in various other constructors: Parser.flatten({String message}), Parser.not({String message}), Parser.neg({String message}), Parser.end({String message}), Parser.starString([String? message]), Parser.plusString({String? message}), Parser.timesString(int count, {String? message}), Parser.repeatString(int min, int max, {String? message}), failure({String message}), newline({String message}), undefined({String message}) (breaking-change).
  • Cleanup, simplifications, and optimizations to the codebase.
  • Removal of long deprecated code.

6.1.0

  • Improve documentation and fix broken links.
  • Prevent infinite recursion in repeating parsers.
  • Improve performance of case-insensitive string matching and the permutation parser.
  • Make the loopback variable in the expression builder public (thanks to joranmulderij).
  • Fix various bugs in equality testing of character predicates (thanks to North101).

6.0.0

  • Dart 3.0 requirement.
  • Use Dart Records for typed sequences:
    • Add convenience converter: (char('a'), char('b')).toSequenceParser()
    • And extension methods to emulate the old Sequence classes, deprecate old accessors.
  • Make Result a sealed class to be able to pattern match Success and Failure.
    • Removed the unused generic type of Failure, which is of type Result<Never> now.
    • Deprecated isSuccess and isFailure, instead use the more efficient is Success and is Failure operators.
  • Reintroduce hasSideEffect in MapParser and consider callbacks to be side-effect free by default.
  • The above changes give typical parser speed improvements between 10% and 30%.

5.4.0

  • Dart 2.19 requirement, enabled strict casts and type inference.
  • Introduce repeating character parser starString, plusString, timesString and repeatString for extra fast reading of strings.
  • Renamed AnyParser to AnyCharacterParser, and CharacterParser to SingleCharacterParser for consistency.
  • Add support for optional expression groups in the ExpressionBuilder.
  • Optimize, cleanup, and improve code and documentation.
  • Add optimize to in-place optimize parser graphs.

5.3.0

  • Maintenance release deprecating some old code in anticipation of the upcoming major release.
  • Deprecate the old way of defining primitive parsers and move the functionality directly to ExpressionBuilder.
  • Deprecate GrammarDefinition.build(Function, List<Object?>), use buildFrom(Parser) for a strongly typed parser instead.
  • Replace various uses of exception throwing with assertions, which yields code the compiler can optimize better.

5.2.0

  • Add @useResult to parser constructors to avoid bugs when using the old parser instance.
  • Add a linter rule to detect unoptimized flatten parsers.

5.1.0

  • Dart 2.18 requirement.
  • Add seq2, seq3, ... combinator functions returning strongly typed sequences of Sequence2<R1, R2>, Sequence3<R1, R2, R3>, ...
  • Add Parser.starSeparated, Parser.plusSeparated, Parser.timesSeparated, and Parser.repeatSeparated returning SeparatedList with the strongly typed elements and separators. Deprecate the dynamically typed Parser.separatedBy.
  • Add Parser.matchesAll that creates a lazy iterable over the (overlapping or non-overlapping) successful parse results. Deprecate matches and matchesSkipping.
  • Add a native platform independent newline parser.
  • Add a section on debugging to the tutorial.
  • Remove the deprecated ref0, ref1, ... instance methods, these methods are globally defined since 4.2.0.
  • Make GrammarDefinition and GrammarDefinition.start() optionally typed.

5.0.0

  • Dart 2.16 requirement.
  • Moved PetitParser examples to a separate Git repository: https://github.com/petitparser/dart-petitparser-examples.
  • Add a skip helper that silently consumes input before and/or after another parser.
  • Make the ExpressionBuilder<T> statically typed. This requires existing code to specify the desired result type, and provide all reduction actions.
  • Deprecate hasSideEffect in MapParser by considering all callbacks to have side-effects, the benefit of the added complications is negligible.
  • Add charIgnoringCase, and provide better standard error messages for character parsers.
  • Add initial support for indentation based grammars.

4.4.0

  • Dart 2.15 requirement.
  • Add a PatternParser that allows to use any Dart Pattern as a parser.
  • Greatly improve the test coverage of all code to 98%.

4.3.0

  • Dart 2.14 requirement.
  • Add a labeled parser, that allows to add a debug label to the parser graph.
  • Extract Predicate<T> and Callback<T> function types to shared file.
  • Change debug functions to named arguments, and generate output events with first class objects instead of strings.
  • Various improvements to the Analyzer:
    • Compute all deeply referenced children.
    • Compute all paths or the shortest path between parsers.
  • Fix inaccuracies in character parser documentation and tutorial.
  • Add more grammar linter rules that detect common bugs.

4.2.0

  • Dart 2.13 requirement.
  • ref0, ref1, ref2, ... is now also usable outside of GrammarDefinition.
    • Use resolve to inline all the referenced parsers, which now also works with SettableParser.
    • Deprecated removeSettables, that is superseded by the more powerful resolve operation.
  • Add the possibility to join multiple Token and transform their values.
  • Add Analyzer to compute nullability, as well as first-, follow-, and cycle-sets of parsers.
  • Add a linter that performs a series of checks on grammar graphs.
  • Expand the tutorial with a section on testing.

4.1.0

  • Add the option to select the failure join strategy on ChoiceParser parsers:
    • selectLast is the default strategy, reporting the failure of the last parser tried.
    • selectFarthest reports the parser failure the farthest down in the input string, preferring later failures to earlier ones.
    • selectFarthestJoined is the same as above, but joins error messages that happen at the same position.
  • Properly type all delegate parsers in choice, sequence, repeat, action, ...
    • Fix typing in transformParser and its users (debug tools, optimizers). To fix type your transformation function.
    • Fix typing of GrammarDefinition and reference parsers. To take advantage replace uses of ref with ref0, ref1, ...
    • Deprecate GrammarParser, a no longer needed wrapper around GrammarDefinition. Call build() on the definition to get the parser.
  • Improve documentation and add a tutorial section on GrammarDefinition.

4.0.0

  • Dart 2.12 requirement and null-safety.
  • Success.message throws an UnsupportedError exception, instead of returning null.
  • DelegateParser has been made abstract to avoid a concrete class in-between abstract classes.
  • Parser.delegate() has been removed, use Parser.settable() as an equivalent replacement.
  • Parser.optional() is now returning Parser<T?>, to provide a non-null default value use Parser.optionalWith(T value).
  • Parser.not() is now returning the failure Parser<Failure> as success value, instead of null.
  • epsilon() is now returning Parser<void>, to provide a non-null default value use epsilonWith(T value).
  • Removed const constructor from Parser hierarchy, as most parsers are inherently mutable and having some constant makes things inconsistent and more complicated than necessary.

3.1.0

  • Fix missing type information on eof and failure parser.
  • Optimize character predicates by using lookup tables.
  • Improvements to documentation and examples.

3.0.0

  • Dart 2.7 compatibility and requirement (extension methods).
  • New features:
    • String.toParser() enables creating efficient string and character parsers more easily.
    • Iterable.toChoiceParser() and Iterable.toSequenceParser() enables creating parsers from collections more easily.
    • Parser.callCC(Function) enables capturing a parse continuation more easily.
  • Restructure the internal code to be more modular:
    • The Parser class now only defines a few core methods, everything else is an extension method.
    • As long as you continue to import package:petitparser/petitparser.dart none of the changes should affect existing code.
    • Parser implementations have been moved to package:petitparser/parser.dart.
    • Helpers to parse and extract data has been moved to package:petitparser/matcher.dart.
    • The expression builder has been moved to package:petitparser/expression.dart.
    • The grammar builder has been moved topackage:petitparser/definition.dart.
  • Breaking changes:
    • Parser is no longer a Pattern, but can be converted to one with toPattern.
    • anyIn has been removed in favor of the already existing and equivalent anyOf parser.
    • pick and permute are defined on Parser<List>, thus they won't be available on the more generic Parser<dynamic> any longer. Prefix the operators with a castList operator.

2.5.0

  • Made ParserError a FormatException to follow typical Dart exception style.

2.4.0

  • Dart 2.4 compatibility and requirement.
  • More tight typing, more strict linter rules.
  • Documentation improvements.

2.3.0

  • Dart 2.3 compatibility and requirement.
  • The expression builder supports building expression with parenthesis.
  • Improved the documentation on greedy and lazy parsers.
  • Add a prolog parser and interpreter example.
  • Numerous optimizations and improvements.

2.2.0

  • Dart 2.2 compatibility and requirement.
  • Parser implements the Pattern interface.
  • Add an example of the expression builder to the tutorial.
  • Introduce a fast-parse mode that avoids unnecessary memory allocations during parsing.

2.1.0

  • Rename ParserError to ParserException, and make it an Exception.
  • Simplify the EndOfInputParser and the ListParser.
  • Add a PositionParser that produces the current input position.
  • Constructor assertions across the stack.

2.0.0

  • Make parsers fully typed, where it makes sense.
    • In most cases this should have no effect on existing code, but sometimes can point out actual bugs.
    • In rare cases, it might be necessary to insert cast<R> or castList<R> at the appropriate places.
  • Move examples into their own example package.

1.8.0

  • Drop Dart 1.0 compatibility.

1.7.6

  • More Dart 2 strong mode fixes.

1.7.5

  • Dart 2.0 strong mode compatibility.
  • Removed deprecated code, and empty beta package.
  • Reformatted all code using dartfmt.

1.7.0

  • Dart 2.0 compatibility.
  • Fixed numerous analyzer warnings.
  • Generate better default error messages.
  • Moved example grammars to examples.

1.6.1

  • Fix bug with duplicated package name.
  • Update documentation.

1.6.0

  • Migrate to micro libraries.
  • Move Smalltalk, Json, Dart and Lisp grammars to examples.

1.5.5

  • Strict typing fixes.

1.5.4

  • Fix analyzer warnings.
  • Fix package dependencies.

1.5.3

  • Dev compiler support.

1.5.2

  • Enable strong mode.

1.5.1

  • Improve the Dart parser and add more tests.

1.5.0

  • Update documentation to match the style guide.
  • Change library names.
  • Add optimizations and tests for the Dart language grammar.
  • Improve comments.
  • Better error-handling and primitives for Lisp command line app.
  • Fix unicode parsing in the JSON parser.
  • Add browser back to dev_dependencies.

1.4.3

  • Restore the CompositeParser class.
  • Add more references to open source projects using PetitParser.

1.4.2

  • Integrate the tutorial into the README.
  • Improve formatting of README code blocks.

1.4.1

  • Improve test coverage.
  • Bump minimum SDK to 1.8.0.
  • Remove deprecated CompositeParser class.

1.4.0

  • Migrate from unittest to test.
  • Setup Travis.
  • Allow for const GrammarDefinitions.
  • Fix typo in docs.
  • Clean up the JSON grammar.
  • Format the benchmarks.

1.3.7

  • Cleanup dependencies:
    • browser is now >=0.10.0 <0.11.0.
    • unittest is now >=0.11.0 <0.12.0.
    • Remove explicit dependency on matcher package.
  • Make the JSON parser twice as fast.
  • Reformat tests.

1.3.6

  • Add a benchmark for JSON native vs PetitParser.

1.3.5

  • Change hasEqualProperties to gracefully handle parsers of inconsistent types.

1.3.4

  • Format source code.
  • Add missing documentation.

1.3.3

  • Performance optimizations