Skip to content

Commit

Permalink
feat(parse): add/rename/reorg parsers, xforms, ctx
Browse files Browse the repository at this point in the history
- add dlit(), dstring()
- add fail()
- rename lift() => pass(), Lift<T> => PassValue<T>
- rename merge()/xfMerge() => join()/xfJoin()
- add hoist()/xfHoist()
- migrate xform syntax sugars to /xform
- add indent() util for ParseContext & print()
  • Loading branch information
postspectacular committed Apr 17, 2020
1 parent 8ca5c7f commit ee537f4
Show file tree
Hide file tree
Showing 24 changed files with 166 additions and 96 deletions.
31 changes: 19 additions & 12 deletions packages/parse/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Purely functional parser combinators & AST generation for generic inputs.
yarn add @thi.ng/parse
```

Package sizes (gzipped, pre-treeshake): ESM: 2.39 KB / CJS: 2.60 KB / UMD: 2.48 KB
Package sizes (gzipped, pre-treeshake): ESM: 2.47 KB / CJS: 2.69 KB / UMD: 2.55 KB

## Dependencies

Expand Down Expand Up @@ -94,47 +94,54 @@ Source:

- `anchor`
- `always`
- `fail`
- `inputStart` / `inputEnd`
- `lift`
- `lineStart` / `lineEnd`
- `lit`
- `lit` / `dlit`
- `noneOf`
- `oneOf`
- `pass`
- `range`
- `satisfy`
- `string`
- `string` / `dstring`

### Combinators

Source:
[/combinators](https://github.com/thi-ng/umbrella/tree/feature/parse/packages/parse/src/combinators)

- `alt`
- `check`
- `discard`
- `expect`
- `maybe`
- `not`
- `oneOrMore` / `zeroOrMore`
- `repeat`
- `seq`
- `xform`

### Transformers

Source:
[/xform](https://github.com/thi-ng/umbrella/tree/feature/parse/packages/parse/src/xform)

Syntax sugars for `xform(parser, fn)`:

- `xform`
- `check`
- `collect`
- `discard`
- `expect`
- `merge`
- `hoist`
- `join`
- `print`

Source:
[/xform](https://github.com/thi-ng/umbrella/tree/feature/parse/packages/parse/src/xform)
Actual transforms:

- `comp` - scope transform composition
- `xfCollect`
- `xfFloat`
- `xfHoist`
- `xfInt`
- `xfMerge`
- `xfJoin`
- `xfPrint`

### SVG path parser example

Expand Down
2 changes: 1 addition & 1 deletion packages/parse/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface IReader<T> {

export type Parser<T> = Fn<ParseContext<T>, boolean>;

export type Lift<T> = T | Fn0<T>;
export type PassValue<T> = T | Fn0<T>;

export type ScopeTransform<T> = (
scope: Nullable<ParseScope<T>>,
Expand Down
5 changes: 0 additions & 5 deletions packages/parse/src/combinators/collect.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/parse/src/combinators/maybe.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Lift, Parser } from "../api";
import { lift } from "../prims/lift";
import type { Parser, PassValue } from "../api";
import { pass } from "../prims/pass";

export const maybe = <T, R = any>(
parser: Parser<T>,
fn?: Lift<R>,
result?: PassValue<R>,
id = "maybe"
): Parser<T> => (ctx) => parser(ctx) || lift(fn, id)(ctx);
): Parser<T> => (ctx) => parser(ctx) || pass(result, id)(ctx);
5 changes: 0 additions & 5 deletions packages/parse/src/combinators/merge.ts

This file was deleted.

10 changes: 5 additions & 5 deletions packages/parse/src/combinators/not.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { isFunction } from "@thi.ng/checks";
import type { Lift, Parser } from "../api";
import type { Parser, PassValue } from "../api";

export const not = <T, R = any>(
parser: Parser<T>,
lift?: Lift<R>,
result?: PassValue<R>,
id = "not"
): Parser<T> => (ctx) => {
if (ctx.done) return false;
const scope = ctx.start(id);
if (parser(ctx)) {
ctx.discard();
return false;
return ctx.discard();
}
scope.result = lift != null ? (isFunction(lift) ? lift() : lift) : null;
scope.result =
result != null ? (isFunction(result) ? result() : result) : null;
return ctx.end();
};
5 changes: 0 additions & 5 deletions packages/parse/src/combinators/print.ts

This file was deleted.

20 changes: 10 additions & 10 deletions packages/parse/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { isString, isArrayLike } from "@thi.ng/checks";
import type { IReader, ParseScope } from "./api";
import { isArrayLike, isString } from "@thi.ng/checks";
import type { IReader, ParseScope, ParseState } from "./api";
import { parseError } from "./error";
import { defStringReader } from "./readers/string-reader";
import { defArrayReader } from "./readers/array-reader";
import { defStringReader } from "./readers/string-reader";
import { indent } from "./utils";

interface ContextOpts {
/**
Expand Down Expand Up @@ -64,7 +65,7 @@ export class ParseContext<T> {
scopes.push(scope);
if (this._debug) {
console.log(
`${" ".repeat(scopes.length)}start: ${id} (${scope.state!.p})`
`${indent(scopes.length)}start: ${id} (${scope.state!.p})`
);
}
return (this._curr = scope);
Expand All @@ -75,7 +76,7 @@ export class ParseContext<T> {
const child = scopes.pop()!;
this._curr = scopes[scopes.length - 1];
if (this._debug) {
console.log(`${" ".repeat(scopes.length + 1)}discard: ${child.id}`);
console.log(`${indent(scopes.length + 1)}discard: ${child.id}`);
}
return false;
}
Expand All @@ -85,16 +86,15 @@ export class ParseContext<T> {
const child = scopes.pop()!;
const parent = scopes[scopes.length - 1];
const cstate = child.state;
const pstate = parent.state;
let pstate: ParseState<T>;
if (this._debug) {
console.log(
`${" ".repeat(scopes.length + 1)}end: ${child.id} (${
cstate!.p
})`
`${indent(scopes.length + 1)}end: ${child.id} (${cstate!.p})`
);
}
child.state = this._retain
? { p: pstate!.p, l: pstate!.l, c: pstate!.c }
? ((pstate = parent.state!),
{ p: pstate.p, l: pstate.l, c: pstate.c })
: null;
parent.state = cstate;
const children = parent.children;
Expand Down
9 changes: 4 additions & 5 deletions packages/parse/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ export * from "./error";

export * from "./combinators/alt";
export * from "./combinators/check";
export * from "./combinators/collect";
export * from "./combinators/discard";
export * from "./combinators/expect";
export * from "./combinators/maybe";
export * from "./combinators/merge";
export * from "./combinators/not";
export * from "./combinators/print";
export * from "./combinators/repeat";
export * from "./combinators/seq";
export * from "./combinators/xform";
Expand All @@ -22,10 +19,11 @@ export * from "./presets/whitespace";

export * from "./prims/always";
export * from "./prims/anchor";
export * from "./prims/lift";
export * from "./prims/fail";
export * from "./prims/lit";
export * from "./prims/none-of";
export * from "./prims/one-of";
export * from "./prims/pass";
export * from "./prims/range";
export * from "./prims/satisfy";
export * from "./prims/string";
Expand All @@ -35,6 +33,7 @@ export * from "./readers/string-reader";

export * from "./xform/collect";
export * from "./xform/comp";
export * from "./xform/hoist";
export * from "./xform/join";
export * from "./xform/number";
export * from "./xform/merge";
export * from "./xform/print";
4 changes: 4 additions & 0 deletions packages/parse/src/prims/fail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { Parser } from "../api";
import { parseError } from "../error";

export const fail = (msg: string): Parser<any> => (ctx) => parseError(ctx, msg);
6 changes: 0 additions & 6 deletions packages/parse/src/prims/lift.ts

This file was deleted.

14 changes: 14 additions & 0 deletions packages/parse/src/prims/lit.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import { discard } from "../combinators/discard";
import { satisfy } from "./satisfy";

/**
* Matches single char/value `c`.
*
* @param c
* @param id
*/
export const lit = <T>(c: T, id = "lit") => satisfy<T>((x) => x === c, id);

/**
* Discarded literal. Same as {@link lit}, but result will be discarded.
*
* @param c
*/
export const dlit = <T>(c: T) => discard(lit(c));
8 changes: 8 additions & 0 deletions packages/parse/src/prims/pass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { isFunction } from "@thi.ng/checks";
import type { PassValue, Parser } from "../api";

export const pass = <R = any>(
result: PassValue<R>,
id = "lift"
): Parser<any> => (ctx) =>
ctx.addChild(id, isFunction(result) ? result() : result);
5 changes: 1 addition & 4 deletions packages/parse/src/prims/satisfy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,5 @@ export const satisfy = <T>(fn: Predicate<T>, id = "lit"): Parser<T> => (
) => {
if (ctx.done) return false;
const r = ctx.reader.read(ctx.state!);
if (!fn(r)) {
return false;
}
return ctx.addChild(id, r, true);
return fn(r) ? ctx.addChild(id, r, true) : false;
};
3 changes: 3 additions & 0 deletions packages/parse/src/prims/string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Parser } from "../api";
import { discard } from "../combinators/discard";

export const string = <T>(str: ArrayLike<T>, id = "string"): Parser<T> => (
ctx
Expand All @@ -18,3 +19,5 @@ export const string = <T>(str: ArrayLike<T>, id = "string"): Parser<T> => (
scope.result = str;
return ctx.end();
};

export const dstring = <T>(str: ArrayLike<T>) => discard(string(str));
11 changes: 11 additions & 0 deletions packages/parse/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const cache: string[] = [];

/**
* Memoized indentation.
*
* @param x
*
* @internal
*/
export const indent = (x: number) =>
x > 0 ? cache[x] || (cache[x] = " ".repeat(x)) : "";
10 changes: 9 additions & 1 deletion packages/parse/src/xform/collect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { ScopeTransform } from "../api";
import { Parser, ScopeTransform } from "../api";
import { xform } from "../combinators/xform";

export const xfCollect: ScopeTransform<any> = (scope) => {
scope!.result = scope!.children!.map((c) => c.result);
scope!.children = null;
return scope;
};

/**
* Syntax sugar for `xform(parser, xfCollect)`.
*
* @param parser
*/
export const collect = <T>(parser: Parser<T>) => xform(parser, xfCollect);
8 changes: 7 additions & 1 deletion packages/parse/src/xform/comp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { ScopeTransform } from "../api";

export const xfComp = <T>(...xs: ScopeTransform<T>[]): ScopeTransform<T> => {
/**
* Takes any number of {@link ScopeTransform}s and composes them into
* new xform w/ left to right order of execution.
*
* @param xs
*/
export const comp = <T>(...xs: ScopeTransform<T>[]): ScopeTransform<T> => {
const [a, b, c, d] = xs;
switch (xs.length) {
case 0:
Expand Down
10 changes: 10 additions & 0 deletions packages/parse/src/xform/hoist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Parser, ScopeTransform } from "../api";
import { xform } from "../combinators/xform";

export const xfHoist: ScopeTransform<any> = (scope) => {
scope!.result = scope!.children![0].result;
scope!.children = null;
return scope;
};

export const hoist = <T>(parser: Parser<T>) => xform(parser, xfHoist);
22 changes: 22 additions & 0 deletions packages/parse/src/xform/join.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Nullable } from "@thi.ng/api";
import type { Parser, ParseScope } from "../api";
import { xform } from "../combinators/xform";

export const xfJoin = <T>(scope: Nullable<ParseScope<T>>) => {
if (!scope || !scope.children) return null;
const res = [];
for (let c of scope.children) {
xfJoin(c);
if (c.result) res.push(c.result);
}
scope.result = res.join("");
scope.children = null;
return scope;
};

/**
* Syntax sugar for `xform(parser, xfJoin)`.
*
* @param parser
*/
export const join = <T>(parser: Parser<T>) => xform(parser, xfJoin);
14 changes: 0 additions & 14 deletions packages/parse/src/xform/merge.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/parse/src/xform/number.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ScopeTransform } from "../api";
import { xfMerge } from "./merge";
import { xfJoin } from "./join";

export const xfInt = (radix = 10): ScopeTransform<string> => (scope) => {
scope!.result = parseInt(xfMerge(scope)!.result, radix);
scope!.result = parseInt(xfJoin(scope)!.result, radix);
return scope;
};

export const xfFloat: ScopeTransform<string> = (scope) => {
scope!.result = parseFloat(xfMerge(scope)!.result);
scope!.result = parseFloat(xfJoin(scope)!.result);
return scope;
};
Loading

0 comments on commit ee537f4

Please sign in to comment.