Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do #33

Merged
merged 6 commits into from
Jan 25, 2024
Merged

Do #33

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
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_ };
Loading