Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ Take parsers, return a new parser. **64** exports — [full reference →][api-c
| [`bind`][combinators-bind] | Chain parsers where the second parser depends on the first result. |
| [`braced`][combinators-braced] | Parse content surrounded by braces. |
| [`bracketed`][combinators-bracketed] | Parse content surrounded by brackets. |
| [`chainLeft`][combinators-chainleft] | Chain left-associative operations (right-to-left for same precedence). |
| [`chainLeft`][combinators-chainleft] | Chain left-associative operations: `1-2-3` is evaluated as `(1-2)-3`. |
| [`chainLeft1`][combinators-chainleft1] | Chain left-associative operations (fails on empty input). |
| [`chainRight`][combinators-chainright] | Chain right-associative operations: `2^3^2` is evaluated as `2^(3^2)`. |
| [`chainRight1`][combinators-chainright1] | Chain right-associative operations (fails on empty input). |
Expand All @@ -492,7 +492,7 @@ Take parsers, return a new parser. **64** exports — [full reference →][api-c
| [`foldRight1`][combinators-foldright1] | Fold one or more occurrences from the right into a single value. |
| [`fuse`][combinators-fuse] | Fuse multiple string parsers into a single one. |
| [`guard`][combinators-guard] | Conditionally apply parser based on a condition. |
| [`inner`][combinators-inner] | Extract inner value from surrounded content (like inner of braced). |
| [`inner`][combinators-inner] | Parse three parsers in sequence and keep only the middle result. |
| [`interleaved`][combinators-interleaved] | Parse items separated by separators, keeping both in the result. |
| [`last`][combinators-last] | Extract the last element from a parser result array. |
| [`left`][combinators-left] | Keep only the left result from a sequence. |
Expand Down
8 changes: 4 additions & 4 deletions doc/api/combinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ bracketed(string('hi'))('[hi]'); // { ok: true, value: 'hi', index: 4 }

### `chainLeft`

Chain left-associative operations (right-to-left for same precedence). Zero or more terms: succeeds with `null` when there is no term at all.
Chain left-associative operations: `1-2-3` is evaluated as `(1-2)-3`. Zero or more terms: succeeds with `null` when there is no term at all.

```typescript
chainLeft(digits, operation)('1+2+3'); // { ok: true, value: 6, index: 5 }
Expand Down Expand Up @@ -77,7 +77,7 @@ chainRight1(digits, operation)('1+2+3'); // { ok: true, value: 6, index: 5 }

### `choice`

Try each parser in order, return first success. Alternatives all start from the same offset, so the expectations of those that failed there are unioned into the result. Anything they recorded deeper in the input is already in the parse context.
Try each parser in order, return first success. If all alternatives fail, the failure lists what each one expected at the starting offset. Failures further into the input are already recorded in the parse context.

```typescript
choice(string('hello'), string('world'))('hello'); // { ok: true, value: 'hello', index: 5 }
Expand Down Expand Up @@ -193,7 +193,7 @@ guard(false, string('hello'))('hello'); // { ok: false, index: 0, expected: [] }

### `inner`

Extract inner value from surrounded content (like inner of braced).
Parse three parsers in sequence and keep only the middle result.

```typescript
inner(char('('), string('hi'), char(')'))('(hi)'); // { ok: true, value: 'hi', index: 4 }
Expand Down Expand Up @@ -233,7 +233,7 @@ lexeme(string('hello'))('hello world'); // { ok: true, value: 'hello', index:

### `many`

Zero or more occurrences (never fails). The failure that stopped the loop is kept as a trace, so a later error can still report what the repetition was expecting next.
Zero or more occurrences (never fails). The failure that ends the repetition is still recorded in the parse context, so if parsing fails later, the error message can list what would have continued the repetition.

```typescript
many(char('a'))('aaa'); // { ok: true, value: ['a', 'a', 'a'], index: 3 }
Expand Down
8 changes: 4 additions & 4 deletions doc/api/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ context(); // { furthest: -1, expected: [] }

### `create`

Creates a parser from a parser function. A parser reads `input` from `index` and never slices it. The third argument is the parse context — pass it on to any parser you call, so expectations recorded inside your parser reach the final error message.
Creates a parser from a parser function. A parser receives the full `input` and the `index` to start reading at, and returns the index where it stopped. The third argument is the parse context — pass it on to any parser you call, so expectations recorded inside your parser reach the final error message.

```typescript
create((input, index = 0) => success('parsed', index + 6))('hello world'); // { ok: true, value: 'parsed', index: 6 }
Expand All @@ -50,7 +50,7 @@ format('a=1,b=x', 6, ['digit']); // "1:7 expected digit, found 'x'\n 1 | a=1,b=

### `grammar`

Creates a recursive grammar where rules can reference each other. Rules are resolved lazily through a shared record, so they may refer to one another by name without forward declarations. Left-recursive rules are reported by name rather than overflowing the stack.
Creates a recursive grammar where rules can reference each other. Each rule receives an object holding all rules of the grammar and looks them up only when it runs, so rules may refer to one another by name without forward declarations. Left-recursive rules are reported by name rather than overflowing the stack.

```typescript
type Math = {
Expand Down Expand Up @@ -78,7 +78,7 @@ run(g.expr, '(1+2)'); // 3

### `label`

Replaces the expectations of a parser with a single description. Only applies when the parser failed without consuming input: once a branch has committed, its own deeper error is more useful than the label. The labelled parser runs against an isolated context so that its internals can be suppressed rather than leaking into the message.
Replaces the expectations of a parser with a single description. Only applies when the parser failed without consuming input: once it has consumed input, its own error further along is more useful than the label. The labelled parser runs against a separate context, so the expectations of the parsers inside it do not leak into the message.

```typescript
label(char('x'), 'letter x')(''); // { ok: false, index: 0, expected: ['letter x'] }
Expand Down Expand Up @@ -110,7 +110,7 @@ match(success('hello', 5), { success: (v) => v, failure: () => 'failed' }); // '

### `memoize`

Memoizes a parser so each offset is parsed at most once (packrat caching). Keyed by offset rather than by the remaining input, so entries are cheap and the cache is dropped as soon as a different input is parsed. Each entry also remembers what the parse expected, so a cache hit still contributes to the error message rather than silently weakening it.
Memoizes a parser so each offset is parsed at most once (packrat caching). The cache is keyed by offset and cleared whenever the parser is called with a different input string. Each entry also stores what the parse expected, so a cache hit contributes the same expectations to the error message as a fresh parse would.

```typescript
const memoDigits = memoize(digits);
Expand Down
2 changes: 1 addition & 1 deletion src/combinators/chainLeft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { chainLeft1 } from './chainLeft1';
import { optional } from './optional';

/**
* Chain left-associative operations (right-to-left for same precedence).
* Chain left-associative operations: `1-2-3` is evaluated as `(1-2)-3`.
*
* Zero or more terms: succeeds with `null` when there is no term at all.
*
Expand Down
6 changes: 3 additions & 3 deletions src/combinators/choice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ const NONE: readonly string[] = [];
/**
* Try each parser in order, return first success.
*
* Alternatives all start from the same offset, so the expectations of those
* that failed there are unioned into the result. Anything they recorded deeper
* in the input is already in the parse context.
* If all alternatives fail, the failure lists what each one expected at the
* starting offset. Failures further into the input are already recorded in the
* parse context.
*
* @example
* choice(string('hello'), string('world'))('hello') // { ok: true, value: 'hello', index: 5 }
Expand Down
2 changes: 1 addition & 1 deletion src/combinators/inner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { map } from './map';
import { sequence } from './sequence';

/**
* Extract inner value from surrounded content (like inner of braced).
* Parse three parsers in sequence and keep only the middle result.
*
* @example
* inner(char('('), string('hi'), char(')'))('(hi)') // { ok: true, value: 'hi', index: 4 }
Expand Down
5 changes: 3 additions & 2 deletions src/combinators/many.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { success, type Success } from '../core/success';
/**
* Zero or more occurrences (never fails).
*
* The failure that stopped the loop is kept as a trace, so a later error can
* still report what the repetition was expecting next.
* The failure that ends the repetition is still recorded in the parse context,
* so if parsing fails later, the error message can list what would have
* continued the repetition.
*
* @example
* many(char('a'))('aaa') // { ok: true, value: ['a', 'a', 'a'], index: 3 }
Expand Down
2 changes: 1 addition & 1 deletion src/core/grammar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('grammar', () => {
}

{
const result = expr('1+(2+3)'); // [6, '']
const result = expr('1+(2+3)');
assertSuccess<unknown>(result, 6, 7);
}
});
Expand Down
5 changes: 3 additions & 2 deletions src/core/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ const guardLeftRecursion = <T>(
/**
* Creates a recursive grammar where rules can reference each other.
*
* Rules are resolved lazily through a shared record, so they may refer to one
* another by name without forward declarations. Left-recursive rules are
* Each rule receives an object holding all rules of the grammar and looks them
* up only when it runs, so rules may refer to one another by name without
* forward declarations. Left-recursive rules are
* reported by name rather than overflowing the stack.
*
* @example
Expand Down
8 changes: 4 additions & 4 deletions src/core/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { create } from './parser';
/**
* Replaces the expectations of a parser with a single description.
*
* Only applies when the parser failed without consuming input: once a branch
* has committed, its own deeper error is more useful than the label. The
* labelled parser runs against an isolated context so that its internals can
* be suppressed rather than leaking into the message.
* Only applies when the parser failed without consuming input: once it has
* consumed input, its own error further along is more useful than the label.
* The labelled parser runs against a separate context, so the expectations of
* the parsers inside it do not leak into the message.
*
* @example
* label(char('x'), 'letter x')('') // { ok: false, index: 0, expected: ['letter x'] }
Expand Down
8 changes: 4 additions & 4 deletions src/core/memoize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ type Entry<T> = {
/**
* Memoizes a parser so each offset is parsed at most once (packrat caching).
*
* Keyed by offset rather than by the remaining input, so entries are cheap and
* the cache is dropped as soon as a different input is parsed. Each entry also
* remembers what the parse expected, so a cache hit still contributes to the
* error message rather than silently weakening it.
* The cache is keyed by offset and cleared whenever the parser is called with a
* different input string. Each entry also stores what the parse expected, so a
* cache hit contributes the same expectations to the error message as a fresh
* parse would.
*
* @example
* const memoDigits = memoize(digits);
Expand Down
7 changes: 4 additions & 3 deletions src/core/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export type Parser<T = unknown> = (
/**
* Creates a parser from a parser function.
*
* A parser reads `input` from `index` and never slices it. The third argument
* is the parse context — pass it on to any parser you call, so expectations
* recorded inside your parser reach the final error message.
* A parser receives the full `input` and the `index` to start reading at, and
* returns the index where it stopped. The third argument is the parse
* context — pass it on to any parser you call, so expectations recorded
* inside your parser reach the final error message.
*
* @example
* create((input, index = 0) => success('parsed', index + 6))('hello world') // { ok: true, value: 'parsed', index: 6 }
Expand Down
8 changes: 4 additions & 4 deletions src/core/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ const merge = (
* A parser can succeed without consuming everything — the interesting position
* is then the furthest offset any branch reached, not where the parse stopped.
*
* A result reaching the same offset as the context carries no more authority
* than the context does, so the two expectation sets are merged. Combinators
* that fail without an expectation of their own — `separatedBy1`, `guard`, a
* hand-written `failure(ctx, index)` — would otherwise report nothing at all.
* When the result and the context point at the same offset, their expectations
* are merged. Otherwise combinators that fail without an expectation of their
* own — `separatedBy1`, `guard`, a hand-written `failure(ctx, index)` — would
* report nothing at all.
*/
export const diagnose = <T>(
input: string,
Expand Down
12 changes: 6 additions & 6 deletions test/examples.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('examples from source', () => {
expect(result0).toEqual({ ok: true, value: 6, index: 5 });
});

it('choice: in the input is already in the parse context.', () => {
it('choice: parse context.', () => {
const result0 = choice(string('hello'), string('world'))('hello');
expect(result0).toEqual({ ok: true, value: 'hello', index: 5 });
});
Expand Down Expand Up @@ -162,7 +162,7 @@ describe('examples from source', () => {
expect(result1).toEqual({ ok: false, index: 0, expected: [] });
});

it('inner: Extract inner value from surrounded content (like inner of braced).', () => {
it('inner: Parse three parsers in sequence and keep only the middle result.', () => {
const result0 = inner(char('('), string('hi'), char(')'))('(hi)');
expect(result0).toEqual({ ok: true, value: 'hi', index: 4 });
});
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('examples from source', () => {
expect(result0).toEqual({ ok: true, value: 'hello', index: 8 });
});

it('many: still report what the repetition was expecting next.', () => {
it('many: continued the repetition.', () => {
const result0 = many(char('a'))('aaa');
expect(result0).toEqual({
ok: true,
Expand Down Expand Up @@ -550,7 +550,7 @@ describe('examples from source', () => {
expect(result2).toEqual(3);
});

it('label: be suppressed rather than leaking into the message.', () => {
it('label: the parsers inside it do not leak into the message.', () => {
const result0 = label(char('x'), 'letter x')('');
expect(result0).toEqual({
ok: false,
Expand All @@ -577,7 +577,7 @@ describe('examples from source', () => {
expect(result0).toEqual('hello');
});

it('memoize: error message rather than silently weakening it.', () => {
it('memoize: parse would.', () => {
const memoDigits = memoize(digits);
const result0 = memoDigits('123');
expect(result0).toEqual({ ok: true, value: 123, index: 3 });
Expand All @@ -588,7 +588,7 @@ describe('examples from source', () => {
expect(result0).toEqual(false);
});

it('parser: recorded inside your parser reach the final error message.', () => {
it('parser: inside your parser reach the final error message.', () => {
const result0 = create((input, index = 0) =>
success('parsed', index + 6),
)('hello world');
Expand Down
Loading