Skip to content

Commit

Permalink
Merge pull request #33 from uzmoi/do
Browse files Browse the repository at this point in the history
Do
  • Loading branch information
uzmoi authored Jan 25, 2024
2 parents 6929431 + d0b13b6 commit 2c6bc0b
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 76 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
# Change Log

## [Unreleased]
### Added
- Added alias `do_` for `qo`.
- Added `PerformOptions` type.

### Changed
- Moved option to rollback state if `perform.try` fails to `PerformOptions`.
- Added defaultValue parameter to `perform.try`.

## [0.9.0] - 2024-01-16
### Changed
Expand Down
50 changes: 18 additions & 32 deletions examples/parser-by-do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const and = <T, S>(left: Parser<unknown, S>, right: Parser<T, S>) =>

const skip = <T, S>(left: Parser<T, S>, right: Parser<unknown, S>) =>
qo<T, S>(perform => {
const leftValue = perform(left);
const result = perform(left);
perform(right);
return leftValue;
return result;
});

const between = <T, S>(parser: Parser<T, S>, pre: Parser<unknown, S>, post = pre) =>
Expand All @@ -35,44 +35,30 @@ const between = <T, S>(parser: Parser<T, S>, pre: Parser<unknown, S>, post = pre

const or = <T, U, S>(left: Parser<T, S>, right: Parser<U, S>) =>
qo<T | U, S>(perform => {
const leftResult = perform.try(() => ({
value: perform(left),
}));
return leftResult ? leftResult.value : perform(right);
const [success, result] = perform.try(
[false] as const,
() => [true, perform(left)] as const,
);
return success ? result : perform(right);
});

const option = <T, U, S>(parser: Parser<T, S>, value: U) =>
const option = <T, U, S>(parser: Parser<T, S>, defaultValue: U) =>
qo<T | U, S>(perform => {
const result = perform.try(() => ({
value: perform(parser),
}));
return result ? result.value : value;
return perform.try(defaultValue, () => perform(parser));
});

const seq = <T, S>(
parsers: readonly Parser<T, S>[],
options?: { allowPartial?: boolean },
): Parser<T[], S> =>
const seq = <T, S>(parsers: readonly Parser<T, S>[]): Parser<T[], S> =>
qo(perform => {
const accum: T[] = [];
const fullSeq = () => {
for (const parser of parsers) {
accum.push(perform(parser));
}
};
if (options?.allowPartial) {
perform.try(fullSeq, true);
} else {
fullSeq();
}
return accum;
return parsers.map(parser => perform(parser));
});

const many = <T, S>(parser: Parser<T, S>): Parser<T[], S> =>
qo(perform => {
const xs: T[] = [];
perform.try(() => {
for (;;) xs.push(perform(parser));
}, true);
return xs;
const result: T[] = [];
perform.try(undefined, () => {
for (;;) {
result.push(perform(parser, { allowPartial: true }));
}
});
return result;
});
8 changes: 4 additions & 4 deletions examples/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ const sepBy = <T, S>(
): P.Parser<T[], S> => {
return P.qo(perform => {
const xs: T[] = [];
perform.try(() => {
perform.try(undefined, () => {
for (;;) {
xs.push(perform(parser));
perform(sep);
xs.push(perform(parser, { allowPartial: true }));
perform(sep, { allowPartial: true });
}
}, true);
});
return xs;
});
};
Expand Down
47 changes: 21 additions & 26 deletions src/do.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { describe, expect, test } from "@jest/globals";
import { isInt32 } from "emnorst";
import { qo } from "./do";
import { Parser } from "./parser";
import { ANY_EL, fail, satisfy } from "./primitive";

describe("qo", () => {
Expand All @@ -27,33 +26,29 @@ describe("qo", () => {
expect(parser.parse([20, 5])).toHaveProperty("success", false);
});
test("try", () => {
expect.assertions(2);
qo(perform => {
perform.try(() => {
expect(perform(ANY_EL)).toBe("hoge");
const parser = qo(perform => {
perform.try(undefined, () => {
perform(ANY_EL);
perform(fail());
});
perform(
new Parser(state => {
expect(state).toHaveProperty("i", 0);
return null;
}),
);
}).parse(["hoge"]);
});
expect(parser.parse(["hoge"])).toEqual({
success: true,
index: 0,
value: undefined,
});
});
test("try allowPartialCommit", () => {
expect.assertions(2);
qo(perform => {
perform.try(() => {
expect(perform(ANY_EL)).toBe("hoge");
perform(fail());
}, true);
perform(
new Parser(state => {
expect(state).toHaveProperty("i", 1);
return null;
}),
);
}).parse(["hoge"]);
test("try + allowPartial", () => {
const parser = qo(perform => {
perform.try(undefined, () => {
perform(ANY_EL);
perform(fail(), { allowPartial: true });
});
});
expect(parser.parse(["hoge"])).toEqual({
success: true,
index: 1,
value: undefined,
});
});
});
34 changes: 20 additions & 14 deletions src/do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,49 @@ import type { Config } from "./context";
import { Parser } from "./parser";
import { updateState } from "./state";

const ParseaDoErrorSymbol = /* #__PURE__ */ Symbol();
const doError = /* #__PURE__ */ Symbol("parsea.doError");

export type PerformOptions = {
allowPartial?: boolean;
};

export type Perform<S> = {
<T>(parser: Parser<T, S>): T;
try<T>(runner: () => T, allowPartialCommit?: boolean): T | null;
<T>(parser: Parser<T, S>, options?: PerformOptions): T;
try<T, U = T>(defaultValue: T, runner: () => U): T | U;
};

export const qo = <T, S>(
runner: (perform: Perform<S>, config: Config) => T,
): Parser<T, S> =>
new Parser((state, context) => {
const perform: Perform<S> = parser => {
const perform: Perform<S> = (parser, options) => {
const newState = parser.run(state, context);
if (newState == null) {
throw { [ParseaDoErrorSymbol]: null };
throw { [doError]: options };
}
return (state = newState).v;
};

// !shouldRollbackState
perform.try = (runner, allowPartialCommit) => {
perform.try = (defaultValue, runner) => {
const beforeTryState = state;
try {
return runner();
} catch (err) {
if (!allowPartialCommit) {
state = beforeTryState;
} catch (error) {
if (!has(error, doError)) {
throw error;
}
if (has(err, ParseaDoErrorSymbol)) {
return null;
const options = error[doError] as PerformOptions | undefined;
if (!options?.allowPartial) {
state = beforeTryState;
}
throw err;
return defaultValue;
}
};

return perform.try(() => {
return perform.try(null, () => {
const value = runner(perform, context.cfg);
return updateState(state, value);
});
});

export { qo as do_ };

0 comments on commit 2c6bc0b

Please sign in to comment.