# incparse

**Zero-copy incremental string parsing with atomic rollback.**

`incparse` provides a lightweight, composable toolkit for parsing
`&str` buffers incrementally. Each parser receives a mutable position cursor and either advances it on success or leaves it untouched on failure — guaranteeing atomic, rollback-aware composition.

## Features

- **Zero-copy** — All parsers return `&str` slices into the original buffer.
- **Atomic rollback** — The [
  `atomic`] combinator saves the cursor before invoking a closure and only commits the new position on success. Every built-in parser uses it internally.
- **Composable** — Combine small parsers with [`string_sequence`], [`option`], [`obj_list`], or the [`Parse`] trait.
- **Tuple parsing** — `(T1, T2, ...)` up to 8-tuples implement [`Parse`] when their elements do.
- **Unicode aware** — Identifiers support Unicode alphabetic characters; whitespace uses Rust's Unicode definition.

## Usage

Add this to your `Cargo.toml`:

```toml
[dependencies]
incparse = "1"
```

## Quick Start

```rust
use incparse::parse::*;

let mut pos = 0;
let name = identifier("hello world", & mut pos) ?;
assert_eq!(name, "hello");
assert_eq!(pos, 5);
```

## API Overview

### Core trait — [`Parse`]

The trait that drives incremental parsing:

```rust
pub trait Parse: Sized {
    fn parse_at(buffer: &str, pos: &mut usize) -> Result<Self, ParseErr>;
    fn parse(buffer: &str) -> Result<Self, ParseErr>;
}
```

Implement it on your own types:

```rust
#[derive(PartialEq)]
struct Point(i32, i32);

impl Parse for Point {
    fn parse_at(buffer: &str, pos: &mut usize) -> Result<Self, ParseErr> {
        token("(", buffer, pos)?;
        let x: i32 = digits(buffer, pos)?.parse().unwrap();
        token(",", buffer, pos)?;
        let y: i32 = digits(buffer, pos)?.parse().unwrap();
        token(")", buffer, pos)?;
        Ok(Point(x, y))
    }
}

let pt = Point::parse("(42, 99)") ?;
assert_eq!(pt, Point(42, 99));
```

### Combinators

| Function / Combinator                  | Description                                                                    |
|----------------------------------------|--------------------------------------------------------------------------------|
| [`atomic(pos, f)`]                     | Save cursor, run `f`, commit only on success                                   |
| [`string_sequence(&[fns], buf, pos)`]  | Run an array of parsers sequentially, atomically                               |
| [`option(&[strings], buf, pos)`]       | Match one of several literal strings                                           |
| [`obj_list(pre, sep, post, buf, pos)`] | Parse a delimited list of `Parse` objects                                      |
| [`seq2..seq8(buf, pos)`]               | Parses 2-8 elements in one transaction (alternative syntax for parsing tuples) |

### Built-in Parsers

| Function                                                         | Returns | Skips whitespace | Matches                                                   |
|------------------------------------------------------------------|---------|------------------|-----------------------------------------------------------|
| [`name(buf, pos)`]                                               | `&str`  | Yes              | Unicode alphabetic start + alphanumeric / `_`             |
| [`identifier(buf, pos)`]                                         | `&str`  | Yes              | Unicode alphabetic start + alphanumeric / `-` `$` `.` `_` |
| [`generic_name(type_name, continuation_char_matcher, buf, pos)`] | `&str`  | Yes              | Unicode alphabetic start + <continuation_char_matcher>    |
| [`token(tok, buf, pos)`]                                         | `&str`  | Yes              | Exact literal string                                      |
| [`digits(buf, pos)`]                                             | `&str`  | Yes              | One or more ASCII digits                                  |
| [`digits_direct(buf, pos)`]                                      | `&str`  | **No**           | One or more ASCII digits                                  |
| [`skip_whitespaces(buf, pos)`]                                   | `()`    | —                | Advances past Unicode whitespace                          |

### Tuple Parsing

Tuples of elements that implement [`Parse`] also implement [`Parse`]:

```rust
let (a, b, c): (Id1, Id2, C) = <(Id1, Id2, C)>::parse("Foo Bar$ /")?;
assert_eq!(a.0, "Foo");
assert_eq!(b.0, "Bar$");
assert_eq!(c.0, "/");
```

Supported: tuples with 2..8 elements.

### Error Handling

[`ParseErr`] carries an error message and an optional context snapshot (up to 20 characters of the remaining input):

```rust
let err = ParseErr::new("unexpected token".into(), "hello world", & 6);
assert!(err.to_string().contains("hello"));
```

Use [`ParseErr::msg`] for errors without a context snapshot.

## License

MIT
