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

Dev/source #31

Merged
merged 2 commits into from
Jan 12, 2024
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
6 changes: 3 additions & 3 deletions examples/json.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { JsonValue } from "emnorst";
import { EOI, choice, el, lazy, literal, many, qo, regex, type Parser } from "../src";

const sepBy = <T>(parser: Parser<T>, sep: Parser) =>
qo(perform => {
const sepBy = <T>(parser: Parser<T, string>, sep: Parser<unknown, string>) =>
qo<T[], string>(perform => {
const head = perform(parser);
const rest = perform(sep.then(parser).apply(many));
return [head, ...rest];
Expand All @@ -12,7 +12,7 @@ const sepBy = <T>(parser: Parser<T>, sep: Parser) =>

const ws = regex(/[ \n\r\t]*/);

const jsonValue: Parser<JsonValue> = lazy(() =>
const jsonValue: Parser<JsonValue, string> = lazy(() =>
choice([
object,
array,
Expand Down
38 changes: 20 additions & 18 deletions examples/parser-by-do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,52 @@ import { qo, type Config, type Parser } from "../src";

const pure = <T>(value: T) => qo(() => value);

const map = <T, U>(parser: Parser<T>, f: (value: T, config: Config) => U) =>
qo((perform, config) => f(perform(parser), config));
const map = <T, U, S>(parser: Parser<T, S>, f: (value: T, config: Config) => U) =>
qo<U, S>((perform, config) => f(perform(parser), config));

const flatMap = <T, U>(parser: Parser<T>, f: (value: T, config: Config) => Parser<U>) =>
qo((perform, config) => perform(f(perform(parser), config)));
const flatMap = <T, U, S>(
parser: Parser<T, S>,
f: (value: T, config: Config) => Parser<U, S>,
) => qo<U, S>((perform, config) => perform(f(perform(parser), config)));

const and = <T>(left: Parser, right: Parser<T>) =>
qo(perform => (perform(left), perform(right)));
const and = <T, S>(left: Parser<unknown, S>, right: Parser<T, S>) =>
qo<T, S>(perform => (perform(left), perform(right)));

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

const between = <T>(parser: Parser<T>, pre: Parser, post = pre): Parser<T> =>
qo(perform => {
const between = <T, S>(parser: Parser<T, S>, pre: Parser<unknown, S>, post = pre) =>
qo<T, S>(perform => {
perform(pre);
const value = perform(parser);
perform(post);
return value;
});

const or = <T, U>(left: Parser<T>, right: Parser<U>) =>
qo(perform => {
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 option = <T, U>(parser: Parser<T>, value: U) =>
qo(perform => {
const option = <T, U, S>(parser: Parser<T, S>, value: U) =>
qo<T | U, S>(perform => {
const result = perform.try(() => ({
value: perform(parser),
}));
return result ? result.value : value;
});

const seq = <T>(
parsers: readonly Parser<T>[],
const seq = <T, S>(
parsers: readonly Parser<T, S>[],
options?: { allowPartial?: boolean },
): Parser<T[]> =>
): Parser<T[], S> =>
qo(perform => {
const accum: T[] = [];
const fullSeq = () => {
Expand All @@ -63,7 +65,7 @@ const seq = <T>(
return accum;
});

const many = <T>(parser: Parser<T>): Parser<T[]> =>
const many = <T, S>(parser: Parser<T, S>): Parser<T[], S> =>
qo(perform => {
const xs: T[] = [];
perform.try(() => {
Expand Down
27 changes: 20 additions & 7 deletions examples/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ export type Expr =
| { type: "Call"; callee: Expr; arguments: readonly Expr[] }
| { type: "Property"; target: Expr; name: string };

export const expr: P.Parser<Expr> = P.lazy(() =>
export const expr: P.Parser<Expr, string> = P.lazy(() =>
P.choice([Bool, Number, String, Tuple, Block, If, Ident]).between(ws).flatMap(tail),
);

const sepBy = <T>(parser: P.Parser<T>, sep: P.Parser): P.Parser<T[]> => {
const sepBy = <T, S>(
parser: P.Parser<T, S>,
sep: P.Parser<unknown, S>,
): P.Parser<T[], S> => {
return P.qo(perform => {
const xs: T[] = [];
perform.try(() => {
Expand All @@ -30,7 +33,7 @@ const sepBy = <T>(parser: P.Parser<T>, sep: P.Parser): P.Parser<T[]> => {

const ws = P.regex(/\s*/);

const keyword = (keyword: string): P.Parser => {
const keyword = (keyword: string): P.Parser<unknown, string> => {
return P.literal(keyword).then(P.notFollowedBy(P.regex(/\w/)));
};

Expand Down Expand Up @@ -78,7 +81,14 @@ const Break = keyword("break").return<Stat>({ type: "Break" }).skip(ws);

const Expr = expr.map<Stat>(expr => ({ type: "Expr", expr }));

export const stat: P.Parser<Stat> = P.choice([Let, DefFn, Return, While, Break, Expr])
export const stat: P.Parser<Stat, string> = P.choice([
Let,
DefFn,
Return,
While,
Break,
Expr,
])
.skip(P.el(";"))
.between(ws);

Expand All @@ -89,7 +99,7 @@ const Bool = P.choice([

const digit = P.oneOf("0123456789");
const digits = digit.apply(
P.manyAccum<string, string>,
P.manyAccum<string, string, string>,
(accum, digit) => accum + digit,
() => "",
{ min: 1 },
Expand Down Expand Up @@ -140,8 +150,11 @@ const If = P.seq([
const tail = (expr: Expr) =>
P.choice([Call, Property])
.skip(ws)
.apply(P.many)
.map(tails => tails.reduce((expr, tail) => tail(expr), expr));
.apply(
P.manyAccum<(callee: Expr) => Expr, Expr, string>,
(expr, tail) => tail(expr),
() => expr,
);

const Call = Tuple.map<(callee: Expr) => Expr>(({ elements }) => callee => ({
type: "Call",
Expand Down
34 changes: 17 additions & 17 deletions src/combinator.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { MAX_INT32, clamp } from "emnorst";
import type { Config } from ".";
import { Parser, type Parsed } from "./parser";
import { Parser, type Parsed, type Source } from "./parser";
import { updateState, type ParseState } from "./state";

/**
* Delays variable references until the parser runs.
*/
export const lazy = <T>(getParser: () => Parser<T>): Parser<T> => {
let parser: Parser<T>;
export const lazy = <T, S>(getParser: () => Parser<T, S>): Parser<T, S> => {
let parser: Parser<T, S>;
return new Parser((state, context) => {
if (parser == null) {
parser = getParser();
Expand All @@ -16,7 +16,7 @@ export const lazy = <T>(getParser: () => Parser<T>): Parser<T> => {
});
};

export const notFollowedBy = (parser: Parser): Parser =>
export const notFollowedBy = <S>(parser: Parser<unknown, S>): Parser<unknown, S> =>
new Parser((state, context) => {
const newState = parser.run(state, context);
if (newState == null) {
Expand All @@ -26,7 +26,7 @@ export const notFollowedBy = (parser: Parser): Parser =>
return null;
});

export const lookAhead = <T>(parser: Parser<T>): Parser<T> =>
export const lookAhead = <T, S = unknown>(parser: Parser<T, S>): Parser<T, S> =>
new Parser((state, context) => {
const newState = parser.run(state, context);
return newState && updateState(state, newState.v);
Expand All @@ -40,16 +40,16 @@ export const seq: {
<T extends readonly Parser[] | []>(
parsers: T,
options?: { allowPartial?: false },
): Parser<Seq<T>>;
): Parser<Seq<T>, Source<T[number]>>;
<T extends readonly Parser[] | []>(
parsers: T,
options: { allowPartial: boolean },
): Parser<Partial<Seq<T>>>;
): Parser<Partial<Seq<T>>, Source<T[number]>>;
} = (parsers, options) =>
new Parser((state, context) => {
const values: unknown[] = [];
for (const parser of parsers) {
const newState = parser.run(state, context);
const newState = parser.run(state, context as never);
if (newState == null) {
if (options?.allowPartial) break;
return null;
Expand All @@ -59,25 +59,25 @@ export const seq: {
return updateState(state, values);
});

type Choice<T extends readonly Parser[]> = Parser<Parsed<T[number]>>;
type Choice<T extends readonly Parser[]> = Parser<Parsed<T[number]>, Source<T[number]>>;

export const choice = <T extends readonly Parser[] | []>(parsers: T): Choice<T> =>
new Parser((state, context) => {
for (const parser of parsers) {
const newState = parser.run(state, context);
const newState = parser.run(state, context as never);
if (newState != null) {
return newState as ParseState<Parsed<T[number]>>;
}
}
return null;
});

export const manyAccum = <T, U>(
parser: Parser<T>,
export const manyAccum = <T, U, S>(
parser: Parser<T, S>,
f: (accum: U, cur: T, config: Config) => U | void,
init: (config: Config) => U,
options?: { min?: number; max?: number },
): Parser<U> => {
): Parser<U, S> => {
const clampedMin = clamp(options?.min || 0, 0, MAX_INT32) | 0;
const clampedMax = clamp(options?.max || MAX_INT32, clampedMin, MAX_INT32) | 0;

Expand All @@ -95,11 +95,11 @@ export const manyAccum = <T, U>(
});
};

export const many = <T>(
parser: Parser<T>,
export const many = <T, S>(
parser: Parser<T, S>,
options?: { min?: number; max?: number },
): Parser<T[]> => {
return manyAccum<T, T[]>(
): Parser<T[], S> => {
return manyAccum<T, T[], S>(
parser,
(array, value) => {
array.push(value);
Expand Down
8 changes: 2 additions & 6 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { isArrayLike } from "emnorst";
import type { ParseError } from "./error";

export type Source<T = unknown> =
| (T extends string ? string : string extends T ? string : never)
| ArrayLike<T>;

export interface Config {
readonly [key: string]: unknown;
}

export class Context {
constructor(readonly src: Source, readonly cfg: Config) {
export class Context<out T = unknown> {
constructor(readonly src: ArrayLike<T>, readonly cfg: Config) {
if (!isArrayLike(src)) {
throw new TypeError("source is not ArrayLike.");
}
Expand Down
12 changes: 7 additions & 5 deletions src/do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { updateState } from "./state";

const ParseaDoErrorSymbol = /* #__PURE__ */ Symbol();

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

export const qo = <T>(runner: (perform: Perform, config: Config) => T): Parser<T> =>
export const qo = <T, S>(
runner: (perform: Perform<S>, config: Config) => T,
): Parser<T, S> =>
new Parser((state, context) => {
const perform: Perform = parser => {
const perform: Perform<S> = parser => {
const newState = parser.run(state, context);
if (newState == null) {
throw { [ParseaDoErrorSymbol]: null };
Expand Down
6 changes: 2 additions & 4 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { Source } from "./context";

export type ParseError =
| { type: "Expected"; value: Source }
| { type: "Expected"; value: ArrayLike<unknown> }
| { type: "Label"; name: string; length: number };

export const expected = (value: Source): ParseError => ({
export const expected = (value: ArrayLike<unknown>): ParseError => ({
type: "Expected",
value,
});
Expand Down
Loading