Skip to content

Commit

Permalink
Merge pull request #20 from rizzzse/dev/context
Browse files Browse the repository at this point in the history
Dev/context
  • Loading branch information
uzmoi authored Oct 16, 2021
2 parents 3be0326 + a5ad5b7 commit 7eccc5a
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 115 deletions.
10 changes: 5 additions & 5 deletions examples/parser-by-do.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Parser, qo } from "../src";
import { Config, Parser, qo } from "../src";

// For simplicity, the behavior may differ in a few cases.

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

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

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

const right = <T>(left: Parser<unknown>, right: Parser<T>) =>
qo(perform => (perform(left), perform(right)));
Expand Down
24 changes: 12 additions & 12 deletions src/combinator.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { Parser, Parsed } from "./parser";
import { failFrom, margeFail, succUpdate } from "./state";
import { failFrom, margeFail, updateSucc } from "./state";

/**
* Delays variable references until the parser runs.
*/
export const lazy = <T>(getParser: () => Parser<T>): Parser<T> => {
let cache: Parser<T>;
return new Parser(state => {
return new Parser((state, context) => {
if (cache == null) {
cache = getParser();
}
return cache.run(state);
return cache.run(state, context);
});
};

export const notFollowedBy = (parser: Parser<unknown>): Parser<unknown> =>
new Parser(state => {
const newState = parser.run(state);
new Parser((state, context) => {
const newState = parser.run(state, context);
if (newState.succ) {
return failFrom(newState.src, newState.pos);
return failFrom(context, newState.pos);
}
return state;
});
Expand All @@ -35,25 +35,25 @@ export const seq: {
options?: { droppable?: boolean },
): Parser<Partial<Seq<T>>>;
} = (parsers, options) =>
new Parser(state => {
new Parser((state, context) => {
const accum: unknown[] = [];
for (let i = 0; i < parsers.length; i++) {
const newState = parsers[i].run(state);
const newState = parsers[i].run(state, context);
if (!newState.succ) {
if (options?.droppable) break;
return newState;
}
accum.push(newState.val);
state = newState;
}
return succUpdate(state, accum, 0);
return updateSucc(state, accum, 0);
});

export const choice = <T>(parsers: readonly Parser<T>[]): Parser<T> =>
new Parser(state => {
let fail = failFrom(state.src, state.pos);
new Parser((state, context) => {
let fail = failFrom(context, state.pos);
for (let i = 0; i < parsers.length; i++) {
const newState = parsers[i].run(state);
const newState = parsers[i].run(state, context);
if (newState.succ) {
return newState;
}
Expand Down
12 changes: 12 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type Source<T = unknown> =
| (T extends string ? string : string extends T ? string : never)
| ArrayLike<T>;

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

export interface Context {
readonly config: Config;
readonly src: Source;
}
11 changes: 6 additions & 5 deletions src/do.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import type { Config } from "./context";
import { Parser } from "./parser";
import { Failure, succUpdate, Config } from "./state";
import { Failure, updateSucc } from "./state";

const parseFailErrorRef = {};

type Perform = <T>(parser: Parser<T>) => T;

export const qo = <T>(runner: (perform: Perform, config: Config) => T): Parser<T> =>
new Parser(state => {
new Parser((state, context) => {
let fail: Failure;
try {
const value = runner(parser => {
const newState = parser.run(state);
const newState = parser.run(state, context);
if (!newState.succ) {
fail = newState;
throw parseFailErrorRef;
}
return (state = newState).val;
}, state.config);
return succUpdate(state, value, 0);
}, context.config);
return updateSucc(state, value, 0);
} catch (err) {
if (err === parseFailErrorRef && fail!) {
return fail;
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./combinator";
export * from "./context";
export * from "./do";
export * from "./parser";
export * from "./primitive";
Expand Down
67 changes: 32 additions & 35 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,61 @@
import { isArrayLike, clamp, MAX_INT32 } from "emnorst";
import {
Config,
margeFail,
ParseState,
Success,
succInit,
succUpdate,
Source,
} from "./state";
import type { Config, Context, Source } from "./context";
import { margeFail, ParseState, Success, succInit, updateSucc } from "./state";

export type Parsed<T> = T extends Parser<infer U> ? U : never;

type ParseRunner<T, U> = (this: void, state: Success<T>) => ParseState<U>;
type ParseRunner<T, U> = (
this: void,
state: Success<T>,
context: Context,
) => ParseState<U>;

export class Parser<T> {
constructor(readonly run: ParseRunner<unknown, T>) {}
parse(this: Parser<T>, source: Source, config: Config = {}): ParseState<T> {
if (!isArrayLike(source)) {
parse(this: Parser<T>, src: Source, config: Config = {}): ParseState<T> {
if (!isArrayLike(src)) {
throw new TypeError("source is not ArrayLike.");
}
const initState = succInit(source, config);
const finalState = this.run(initState);
const context: Context = { src, config };
const finalState = this.run(succInit, context);
return finalState;
}
map<U>(this: Parser<T>, f: (val: T, config: Config) => U): Parser<U> {
return new Parser(state => {
const newState = this.run(state);
return new Parser((state, context) => {
const newState = this.run(state, context);
return newState.succ
? succUpdate(newState, f(newState.val, newState.config), 0)
? updateSucc(newState, f(newState.val, context.config), 0)
: newState;
});
}
flatMap<U>(this: Parser<T>, f: (val: T, config: Config) => Parser<U>): Parser<U> {
return new Parser(state => {
const newState = this.run(state);
return new Parser((state, context) => {
const newState = this.run(state, context);
return newState.succ
? f(newState.val, newState.config).run(newState)
? f(newState.val, context.config).run(newState, context)
: newState;
});
}
right<U>(this: Parser<unknown>, parser: Parser<U>): Parser<U> {
return new Parser(state => {
const newState = this.run(state);
return newState.succ ? parser.run(newState) : newState;
return new Parser((state, context) => {
const newState = this.run(state, context);
return newState.succ ? parser.run(newState, context) : newState;
});
}
left(this: Parser<T>, parser: Parser<unknown>): Parser<T> {
return new Parser(state => {
const newStateA = this.run(state);
return new Parser((state, context) => {
const newStateA = this.run(state, context);
if (!newStateA.succ) return newStateA;
const newStateB = parser.run(newStateA);
const newStateB = parser.run(newStateA, context);
if (!newStateB.succ) return newStateB;
return succUpdate(newStateB, newStateA.val, 0);
return updateSucc(newStateB, newStateA.val, 0);
});
}
or<U>(this: Parser<T>, parser: Parser<U>): Parser<T | U> {
return new Parser<T | U>(state => {
const newStateA = this.run(state);
return new Parser<T | U>((state, context) => {
const newStateA = this.run(state, context);
if (newStateA.succ) return newStateA;
const newStateB = parser.run(state);
const newStateB = parser.run(state, context);
if (newStateB.succ) return newStateB;
return margeFail(newStateA, newStateB);
});
Expand All @@ -72,17 +69,17 @@ export class Parser<T> {
const clampedMin = clamp(options?.min || 0, 0, MAX_INT32) | 0;
const clampedMax = clamp(options?.max || MAX_INT32, clampedMin, MAX_INT32) | 0;

return new Parser(state => {
let accum: U = init(state.config);
return new Parser((state, context) => {
let accum: U = init(context.config);
for (let i = 0; i < clampedMax; i++) {
const newState = this.run(state);
const newState = this.run(state, context);
if (!newState.succ) {
if (i < clampedMin) return newState;
break;
}
accum = f(accum, (state = newState).val, state.config);
accum = f(accum, (state = newState).val, context.config);
}
return succUpdate(state, accum, 0);
return updateSucc(state, accum, 0);
});
}
many(this: Parser<T>, options?: { min?: number; max?: number }): Parser<T[]> {
Expand Down
41 changes: 21 additions & 20 deletions src/primitive.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import type { Config, Source } from "./context";
import { Parser } from "./parser";
import { failFrom, succUpdate, Source, Config } from "./state";
import { failFrom, updateSucc } from "./state";

/**
* Always succeed with the value of the argument.
*/
export const pure = <T>(value: T): Parser<T> =>
new Parser(state => succUpdate(state, value, 0));
new Parser(state => updateSucc(state, value, 0));

export const fail = (): Parser<never> =>
new Parser(state => failFrom(state.src, state.pos));
new Parser((state, context) => failFrom(context, state.pos));

/**
* end of input
*/
export const EOI = new Parser(state =>
state.pos < state.src.length ? failFrom(state.src, state.pos) : state,
export const EOI = new Parser((state, context) =>
state.pos < context.src.length ? failFrom(context, state.pos) : state,
);

/**
Expand All @@ -23,10 +24,10 @@ export const EOI = new Parser(state =>
* @example any.parse([someValue]).value === someValue;
* @example any.parse([]); // parse fail
*/
export const ANY_EL = new Parser(state =>
state.pos < state.src.length
? succUpdate(state, state.src[state.pos], 1)
: failFrom(state.src, state.pos),
export const ANY_EL = new Parser((state, context) =>
state.pos < context.src.length
? updateSucc(state, context.src[state.pos], 1)
: failFrom(context, state.pos),
);

export const el = <T>(value: T): Parser<T> => satisfy(srcEl => Object.is(srcEl, value));
Expand All @@ -36,25 +37,25 @@ export const satisfy = <T>(
| ((el: unknown, config: Config) => boolean)
| ((el: unknown, config: Config) => el is T),
): Parser<T> =>
new Parser(state => {
new Parser((state, context) => {
let srcEl: unknown;
return state.pos < state.src.length &&
f((srcEl = state.src[state.pos]), state.config)
? succUpdate(state, srcEl, 1)
: failFrom(state.src, state.pos);
return state.pos < context.src.length &&
f((srcEl = context.src[state.pos]), context.config)
? updateSucc(state, srcEl, 1)
: failFrom(context, state.pos);
});

export const literal = <T extends Source>(chunk: T): Parser<T> =>
new Parser<T>(state => {
if (state.pos + chunk.length > state.src.length) {
return failFrom(state.src, state.pos);
new Parser((state, context) => {
if (state.pos + chunk.length > context.src.length) {
return failFrom(context, state.pos);
}
for (let i = 0; i < chunk.length; i++) {
const srcEl = state.src[state.pos + i];
const srcEl = context.src[state.pos + i];
const chunkEl = chunk[i];
if (!Object.is(srcEl, chunkEl)) {
return failFrom(state.src, state.pos + i);
return failFrom(context, state.pos + i);
}
}
return succUpdate(state, chunk, chunk.length);
return updateSucc(state, chunk, chunk.length);
});
Loading

0 comments on commit 7eccc5a

Please sign in to comment.