All Projects → petitparser → Dart Petitparser

petitparser / Dart Petitparser

Licence: mit
Dynamic parser combinators in Dart.

Programming Languages

dart
5743 projects
grammar
57 projects

Projects that are alternatives of or similar to Dart Petitparser

Mediawiki
MediaWiki API wrapper in python http://pymediawiki.readthedocs.io/en/latest/
Stars: ✭ 89 (-66.54%)
Mutual labels:  parser-library, parser
Substitution Schedule Parser
Java library for parsing schools' substitution schedules. Supports multiple different systems mainly used in the German-speaking countries, including Untis, svPlan, and DAVINCI
Stars: ✭ 33 (-87.59%)
Mutual labels:  parser-library, parser
Kgt
BNF wrangling and railroad diagrams
Stars: ✭ 312 (+17.29%)
Mutual labels:  parser-library, parser
Tatsu
竜 TatSu generates Python parsers from grammars in a variation of EBNF
Stars: ✭ 198 (-25.56%)
Mutual labels:  parser-library, parser
Pygdbmi
A library to parse gdb mi output and interact with gdb subprocesses
Stars: ✭ 139 (-47.74%)
Mutual labels:  parser-library, parser
Cppcmb
A generic C++17 parser-combinator library with a natural grammar notation.
Stars: ✭ 108 (-59.4%)
Mutual labels:  parser-library, parser
Mercury Parser
📜 Extract meaningful content from the chaos of a web page
Stars: ✭ 4,025 (+1413.16%)
Mutual labels:  parser-library, parser
Java Petitparser
Dynamic parser combinators in Java.
Stars: ✭ 118 (-55.64%)
Mutual labels:  parser-library, parser
Participle
A parser library for Go
Stars: ✭ 2,302 (+765.41%)
Mutual labels:  parser-library, parser
Lark
Lark is a parsing toolkit for Python, built with a focus on ergonomics, performance and modularity.
Stars: ✭ 2,916 (+996.24%)
Mutual labels:  parser-library, parser
metal
A Java library for parsing binary data formats, using declarative descriptions.
Stars: ✭ 13 (-95.11%)
Mutual labels:  parser-library
gab-cmdline
A GABStudios Java library to help with command line parsing.
Stars: ✭ 12 (-95.49%)
Mutual labels:  parser-library
microparsec
⭐ A performant Nim parsing library built for humans.
Stars: ✭ 26 (-90.23%)
Mutual labels:  parser-library
Kuiper
Digital Forensics Investigation Platform
Stars: ✭ 257 (-3.38%)
Mutual labels:  parser
DHTMLParser
D HTML Parser, similar to python BeautifulSoup
Stars: ✭ 17 (-93.61%)
Mutual labels:  parser-library
Covfefe
A parser for nondeterministic context free languages
Stars: ✭ 49 (-81.58%)
Mutual labels:  parser-library
kiuatan
A parser library for Pony.
Stars: ✭ 15 (-94.36%)
Mutual labels:  parser-library
android-pls-parser
A playlist file (*.pls) parser library for Android
Stars: ✭ 19 (-92.86%)
Mutual labels:  parser-library
autumn
A Java parser combinator library written with an unmatched feature set.
Stars: ✭ 112 (-57.89%)
Mutual labels:  parser-library
Node Csv
Full featured CSV parser with simple api and tested against large datasets.
Stars: ✭ 3,068 (+1053.38%)
Mutual labels:  parser

PetitParser for Dart

Pub Package Build Status Coverage Status 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 scannnerless 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.

Installation

Follow the installation instructions on dart packages.

Import the package into your Dart code using:

import 'package:petitparser/petitparser.dart';

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();

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 a sequence of parsers.
    • CharacterParser: This parser accepts a single letter.
    • PossessiveRepeatingParser: This parser accepts zero or more times another parser.
      • ChoiceParser: This parser accepts a single word character.
        • CharacterParser: This parser accepts a single letter.
        • CharacterParser: 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. Both of the parsers below are equivalent to the one above:

final id1 = letter().seq(letter().or(digit()).star());
final id2 = [letter(), [letter(), digit()].toChoiceParser()].toSequenceParser().star();

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 ParserError. Context.isSuccess and Context.isFailure can be used to decide if the parsing was successful.

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 are the simplest. We've already seen a few of those:

  • char('a') (or 'a'.toParser()) parses the character a.
  • string('abc') (or 'abc'.toParser()) parses the string abc.
  • pattern('a-f') (or 'abc'.toParser(isPattern: true)) parsers any character between a and f.
  • 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:

final id = letter() & word().star();

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

  • p1 & p2, p1.seq(p2), or [p1, p2].toSequenceParser() parse p1 followed by p2 (sequence).
  • p1 | p2, p1.or(p2), or [p1, p2].toChoiceParser() parse p1, if that doesn't work parse 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.

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

  • p.map((value) => ...) performs the transformation using the provided callback.
  • p.pick(n) returns the n-th element of the list p returns.
  • p.flatten() creates a string from the consumed input of p.
  • p.token() creates a token from the result of p.
  • p.trim() trims whitespaces before and after p.
  • p.cast<T>() casts the result of p to the type T.

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

final id = (letter() & word().star()).flatten();

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

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

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 into 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 the Expression Builder

Writing such expression parsers is pretty common and can be quite 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 expression builder:

final builder = ExpressionBuilder();

Then we define the operator-groups in descending precedence. The highest precedence are the literal numbers themselves. This time we accept floating-point numbers, not just integers. In the same group we add support for the parenthesis:

builder.group()
  ..primitive(digit()
      .plus()
      .seq(char('.').seq(digit().plus()).optional())
      .flatten()
      .trim()
      .map((a) => num.tryParse(a)))
  ..wrapper(char('(').trim(), char(')').trim(), (String l, num a, String r) => a);

Then come the normal arithmetic operators. Note, that the action blocks 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(), (String op, num a) => -a);

// power is right-associative
builder.group()
  ..right(char('^').trim(), (num a, String op, num b) => math.pow(a, b));

// multiplication and addition are left-associative
builder.group()
  ..left(char('*').trim(), (num a, String op, num b) => a * b)
  ..left(char('/').trim(), (num a, String op, num b) => a / b);
builder.group()
  ..left(char('+').trim(), (num a, String op, num b) => a + b)
  ..left(char('-').trim(), (num a, String op, num b) => a - b);

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

Misc

Examples

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

  • example/lib/dart contains an experimental Dart grammar.
  • example/lib/json contains a complete JSON grammar and parser.
  • example/lib/lisp contains a complete LISP grammar, parser and evaluator.
  • example/lib/prolog contains a basic Prolog grammar, parser and evaluator.
  • example/lib/smalltalk contains a complete Smalltalk grammar.

Furthermore, there are numerous open source projects using PetitParser:

  • badger is an experimental programming language.
  • expression_language is a library for parsing and evaluating expressions.
  • intl_translation provides internationalization and localization support to Dart.
  • pem encodes and decodes textual cryptographic keys.
  • powerconfig is a power config implementation.
  • query implements search queries with support for boolean groups, field scopes, ranges, etc.
  • rythm is a rich featured, high performance template engine.
  • xml is a lightweight library for parsing, traversing, and querying XML documents.

History

PetitParser was originally implemented in Smalltalk. Later on, as a mean 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 practises of the target language.

Implementations

License

The MIT License, see LICENSE.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].