Skip to content

Commit

Permalink
feat(parse): make retained state info optional
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Apr 16, 2020
1 parent 3bec0db commit a89ee87
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 48 deletions.
2 changes: 1 addition & 1 deletion packages/parse/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ParseContext } from "./context";

export interface ParseScope<T> {
id: string;
state: ParseState<T>;
state: Nullable<ParseState<T>>;
children: Nullable<ParseScope<T>[]>;
result: any;
}
Expand Down
79 changes: 56 additions & 23 deletions packages/parse/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isString } from "@thi.ng/checks";
import type { IReader, ParseScope } from "./api";
import { parseError } from "./error";
import { defStringReader } from "./string-reader";
import { defStringReader } from "./readers/string-reader";

interface ContextOpts {
/**
Expand All @@ -10,49 +11,59 @@ interface ContextOpts {
*/
maxDepth: number;
/**
* True to enable parser debug output.
* True to enable parser debug output. Will emit details of each
* parse scope.
*
* @defaultValue false
*/
debug: boolean;
/**
* True to retain reader state for each AST node. State of root node
* is always available.
*
* @defaultValue false
*/
retain: boolean;
}

export class ParseContext<T> {
maxDepth: number;
debug: boolean;

protected _scopes: ParseScope<T>[];
protected _curr: ParseScope<T>;

protected _maxDepth: number;
protected _debug: boolean;
protected _retain: boolean;

constructor(public reader: IReader<T>, opts?: Partial<ContextOpts>) {
opts = { maxDepth: 32, debug: false, ...opts };
this.maxDepth = opts.maxDepth!;
this.debug = opts.debug!;
opts = { maxDepth: 32, debug: false, retain: false, ...opts };
this._maxDepth = opts.maxDepth!;
this._debug = opts.debug!;
this._retain = opts.retain!;
this._curr = {
id: "root",
state: { p: 0, l: 1, c: 1 },
children: null,
result: null,
};
this._scopes = [this._curr];
reader.isDone(this._curr.state);
reader.isDone(this._curr.state!);
}

start(id: string) {
if (this._scopes.length >= this.maxDepth) {
parseError(this, `recursion limit reached ${this.maxDepth}`);
if (this._scopes.length >= this._maxDepth) {
parseError(this, `recursion limit reached ${this._maxDepth}`);
}
const scopes = this._scopes;
const scope: ParseScope<T> = {
id,
state: { ...scopes[scopes.length - 1].state },
state: { ...scopes[scopes.length - 1].state! },
children: null,
result: null,
};
scopes.push(scope);
if (this.debug) {
if (this._debug) {
console.log(
`${" ".repeat(scopes.length)}start: ${id} (${scope.state.p})`
`${" ".repeat(scopes.length)}start: ${id} (${scope.state!.p})`
);
}
return (this._curr = scope);
Expand All @@ -62,7 +73,7 @@ export class ParseContext<T> {
const scopes = this._scopes;
const child = scopes.pop()!;
this._curr = scopes[scopes.length - 1];
if (this.debug) {
if (this._debug) {
console.log(`${" ".repeat(scopes.length + 1)}discard: ${child.id}`);
}
return false;
Expand All @@ -74,12 +85,16 @@ export class ParseContext<T> {
const parent = scopes[scopes.length - 1];
const cstate = child.state;
const pstate = parent.state;
if (this.debug) {
if (this._debug) {
console.log(
`${" ".repeat(scopes.length + 1)}end: ${child.id} (${cstate.p})`
`${" ".repeat(scopes.length + 1)}end: ${child.id} (${
cstate!.p
})`
);
}
child.state = { p: pstate.p, l: pstate.l, c: pstate.c };
child.state = this._retain
? { p: pstate!.p, l: pstate!.l, c: pstate!.c }
: null;
parent.state = cstate;
const children = parent.children;
children ? children.push(child) : (parent.children = [child]);
Expand All @@ -92,7 +107,9 @@ export class ParseContext<T> {
const cstate = curr.state;
const child: ParseScope<T> = {
id,
state: { p: cstate.p, l: cstate.l, c: cstate.c },
state: this._retain
? { p: cstate!.p, l: cstate!.l, c: cstate!.c }
: null,
children: null,
result,
};
Expand All @@ -110,7 +127,7 @@ export class ParseContext<T> {
}

get done() {
return this._curr.state.done;
return this._curr.state!.done;
}

/**
Expand Down Expand Up @@ -138,10 +155,26 @@ export class ParseContext<T> {
}

/**
* Creates new {@link ParseContext} for given input string and context options.
* Creates new {@link ParseContext} for given input string or reader and
* context options.
*
* @param input -
* @param opts -
*/
export const defContext = (input: string, opts?: Partial<ContextOpts>) =>
new ParseContext<string>(defStringReader(input), opts);
export function defContext(
input: string,
opts?: Partial<ContextOpts>
): ParseContext<string>;
export function defContext<T>(
input: IReader<T>,
opts?: Partial<ContextOpts>
): ParseContext<T>;
export function defContext(
input: string | IReader<any>,
opts?: Partial<ContextOpts>
): ParseContext<any> {
return new ParseContext<string>(
isString(input) ? defStringReader(input) : input,
opts
);
}
2 changes: 1 addition & 1 deletion packages/parse/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import { ParseContext } from "./context";
const ParseError = defError(() => `ParseError`);

export const parseError = (ctx: ParseContext<any>, msg: string): never => {
const info = ctx.reader.format(ctx.scope.state);
const info = ctx.reader.format(ctx.scope.state!);
throw new ParseError(msg + (info ? ` @ ${info}` : ""));
};
10 changes: 5 additions & 5 deletions packages/parse/src/prims/anchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ import type { Parser } from "../api";
export const anchor = <T>(
fn: Fn2<Nullable<T>, Nullable<T>, boolean>
): Parser<T> => (ctx) => {
const state = ctx.state;
const state = ctx.state!;
return fn(state.last, state.done ? null : ctx.reader.read(state));
};

export const inputStart: Parser<any> = (ctx) => ctx.state.last == null;
export const inputStart: Parser<any> = (ctx) => ctx.state!.last == null;

export const inputEnd: Parser<any> = (ctx) =>
ctx.state.done || !ctx.reader.read(ctx.state);
ctx.state!.done || !ctx.reader.read(ctx.state!);

export const lineStart: Parser<string> = (ctx) => {
const l = ctx.state.last;
const l = ctx.state!.last;
return l == null || l === "\n";
};

export const lineEnd: Parser<string> = (ctx) => {
const state = ctx.state;
const state = ctx.state!;
return state.done || ctx.reader.read(state) === "\n";
};
13 changes: 13 additions & 0 deletions packages/parse/src/prims/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,16 @@ import { satisfy } from "./satisfy";

export const range = <T extends NumOrString>(min: T, max: T, id = "lit") =>
satisfy<T>((x) => x >= min && x <= max, id);

/**
* Matches single char in given UTF-16 code range.
*
* @param min
* @param max
* @param id
*/
export const utf16Range = (min: number, max: number, id = "utfLit") =>
satisfy<string>((x) => {
const c = x.charCodeAt(0)!;
return c >= min && c <= max;
}, id);
4 changes: 2 additions & 2 deletions packages/parse/src/prims/satisfy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export const satisfy = <T>(fn: Predicate<T>, id = "lit"): Parser<T> => (
) => {
if (ctx.done) return false;
const reader = ctx.reader;
const r = reader.read(ctx.state);
const r = reader.read(ctx.state!);
if (!fn(r)) {
return false;
}
const scope = ctx.start(id);
reader.next(scope.state);
reader.next(scope.state!);
scope.result = r;
return ctx.end();
};
2 changes: 1 addition & 1 deletion packages/parse/src/prims/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const string = <T>(str: ArrayLike<T>, id = "string"): Parser<T> => (
) => {
if (ctx.done) return false;
const scope = ctx.start(id);
const state = scope.state;
const state = scope.state!;
const reader = ctx.reader;
for (let i = 0, n = str.length; i < n; i++) {
if (state.done) return false;
Expand Down
8 changes: 3 additions & 5 deletions packages/parse/src/xform/print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import { ScopeTransform } from "../api";
export const xfPrint: ScopeTransform<any> = (scope, _, indent = 0) => {
if (!scope) return;
const prefix = indent > 0 ? " ".repeat(indent) : "";
console.log(
`${prefix}${scope.id} (${scope.state.l}:${
scope.state.c
}): ${JSON.stringify(scope.result)}`
);
const state = scope.state;
const info = state ? ` (${state.l}:${state.c})` : "";
console.log(`${prefix}${scope.id}${info}: ${JSON.stringify(scope.result)}`);
if (scope.children) {
for (let c of scope.children) {
xfPrint(c, _, indent + 1);
Expand Down
2 changes: 1 addition & 1 deletion packages/parse/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const check = (
) => {
const ctx = defContext(src);
assert.equal(parser(ctx), res, `src: '${src}'`);
assert.equal(ctx.state.p, pos, `src: '${src}' pos: ${ctx.state.p}`);
assert.equal(ctx.state!.p, pos, `src: '${src}' pos: ${ctx.state!.p}`);
};

describe("parse", () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/parse/test/rpn.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Fn2 } from "@thi.ng/api";
import * as assert from "assert";
import { alt, defContext, INT, oneOf, WS, xform, zeroOrMore } from "../src";
import { alt, defContext, FLOAT, oneOf, WS_0, xform, zeroOrMore } from "../src";

describe("parse", () => {
it("RPN calc", () => {
Expand All @@ -11,7 +11,7 @@ describe("parse", () => {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
};
const value = xform(INT, (scope) => {
const value = xform(FLOAT, (scope) => {
stack.push(scope!.result);
return null;
});
Expand All @@ -21,7 +21,7 @@ describe("parse", () => {
stack.push(ops[scope!.result](a, b));
return null;
});
const program = zeroOrMore(alt([value, op, zeroOrMore(WS)]));
const program = zeroOrMore(alt([value, op, WS_0]));
const ctx = defContext("10 5 3 * + -2 * 10 /");
assert(program(ctx));
assert(ctx.done);
Expand Down
11 changes: 5 additions & 6 deletions packages/parse/test/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
oneOf,
Parser,
seq,
WS,
WS_0,
xform,
zeroOrMore,
} from "../src";
Expand All @@ -21,18 +21,17 @@ const check = (
) => {
const ctx = defContext(src);
assert.equal(parser(ctx), res, `src: '${src}'`);
assert.equal(ctx.state.p, pos, `src: '${src}' pos: ${ctx.state.p}`);
assert.equal(ctx.state!.p, pos, `src: '${src}' pos: ${ctx.state!.p}`);
};

describe("parse", () => {
it("SVG", () => {
const ws = discard(zeroOrMore(WS));
const wsc = discard(zeroOrMore(oneOf(" \n,")));
const point = collect(seq([INT, wsc, INT]));
const move = collect(seq([oneOf("Mm"), ws, point, ws]));
const line = collect(seq([oneOf("Ll"), ws, point, ws]));
const move = collect(seq([oneOf("Mm"), WS_0, point, WS_0]));
const line = collect(seq([oneOf("Ll"), WS_0, point, WS_0]));
const curve = collect(
seq([oneOf("Cc"), ws, point, wsc, point, wsc, point, ws])
seq([oneOf("Cc"), WS_0, point, wsc, point, wsc, point, WS_0])
);
const close = xform(oneOf("Zz"), ($) => (($!.result = [$!.result]), $));
const path = collect(zeroOrMore(alt([move, line, curve, close])));
Expand Down

0 comments on commit a89ee87

Please sign in to comment.