dart-parsing

Composable, reusable, and extensible parsers for Dart.

dart-parsing is a small parsing library built around a minimal core abstraction. It lets you build expressive parser pipelines through composition, instead of relying on large regular expressions or complex, stateful parsing code.

The main goal is simple: write parsers that look like the grammar they implement.


Why dart-parsing?

Common parsing approaches usually fall into one of these categories:

  • Regular expressions — compact, but hard to read, debug, and extend
  • Hand-written parsers — flexible, but often verbose and error-prone
  • Parser generators — powerful, but heavyweight and not always idiomatic in Dart

dart-parsing focuses on small building blocks that can be composed into larger parsers in a way that stays readable and maintainable.

Key ideas:

  • Parsers are easy to combine
  • Small parsers can be reused across different grammars
  • New behavior comes from composition, not modifying the core
  • The resulting code should describe what is being parsed, not how to move through characters

Quick example

Here is a small parser that reads a latitude like:

15.832373° S
final northSouthSign = OneOf([
  StringLiteral("N").map((_) => 1),
  StringLiteral("S").map((_) => -1),
]);

final lat = DoubleParser()
    .skip(StringLiteral("°"))
    .skip(StringLiteral(" "))
    .take(northSouthSign)
    .map((value, sign) => value * sign);

Parsers can then be combined to form larger structures in a way that mirrors the input grammar.

A full example parsing multiple cities and coordinates can be found in EXAMPLE.md.


Performance

Although the library focuses on readability and composability, performance is a first-class concern in the implementation.

Parsers operate directly on the input string using codeUnits comparisons rather than creating intermediate substrings. This keeps parsing close to a simple indexed scan over a String, which is both predictable and efficient in Dart.

Some of the key performance characteristics:

  • Character-level parsing
    Parsers compare integer code units instead of allocating temporary String objects.

  • Minimal heap allocations
    Most combinators pass indices and lightweight results rather than copying data.

  • No regex engine overhead
    Parsing relies on direct control flow instead of a general-purpose regex engine.

  • Composability without abstraction penalty
    Even though parsers are built from small units, execution remains close to a linear scan.


Benchmark

A small benchmark was created to compare different strategies for parsing the same coordinate format:

15.832373° S, 47.987751° W

Each strategy parsed a large input string (~2,699,999 characters) 10 times, using the default configuration from the benchmark_harness package.

Results

Approach Total time (µs) for 10 parses
Code unit–based parser 41,267 µs
MutableStringSlice parser 40,595 µs
Regex-based parser 40,884 µs

That corresponds to roughly ~4 ms per full parse of a ~2.7 MB input string.

These results show that composable parsers can remain in the same performance class as regex-based approaches while providing significantly better structure and readability.


When to use this

dart-parsing works well when:

  • You want readable parsing logic written in pure Dart
  • You are parsing structured text formats (configs, coordinates, small DSLs, logs, etc.)
  • Regular expressions are becoming difficult to maintain
  • You want a strongly-typed parsing pipeline

Status

This project is still evolving. APIs may change as new composition patterns and use cases appear.

Feedback, issues, and contributions are welcome.


License

MIT License

Libraries

dart_parsing
extra
fp