Skip to content

Commit

Permalink
Merge pull request #31 from uzmoi/dev/source
Browse files Browse the repository at this point in the history
Dev/source
  • Loading branch information
uzmoi authored Jan 12, 2024
2 parents cd68339 + 539f4d2 commit 3102dce
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 111 deletions.
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

0 comments on commit 3102dce

Please sign in to comment.