@thi.ng/parse 2.4.63 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/api.d.ts +35 -3
- package/combinators/check.js +4 -4
- package/combinators/dynamic.d.ts +1 -1
- package/combinators/dynamic.js +2 -1
- package/context.d.ts +1 -2
- package/context.js +8 -14
- package/package.json +4 -4
- package/prims/anchor.js +6 -10
- package/prims/lit.js +2 -2
- package/prims/none-of.js +1 -1
- package/prims/one-of.js +1 -1
- package/prims/satisfy.js +1 -2
- package/prims/string.js +2 -4
- package/readers/array-reader.d.ts +2 -0
- package/readers/array-reader.js +3 -1
- package/readers/string-reader.d.ts +2 -0
- package/readers/string-reader.js +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-01-
|
|
3
|
+
- **Last updated**: 2025-01-17T14:10:58Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,30 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [2.5.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/parse@2.5.0) (2025-01-17)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- update `DynamicParser`, add `IDeref` support ([cf0d51c](https://github.com/thi-ng/umbrella/commit/cf0d51c))
|
|
17
|
+
|
|
18
|
+
#### ♻️ Refactoring
|
|
19
|
+
|
|
20
|
+
- remove `ParseState.last`, update `IReader` & impls ([20fc5cf](https://github.com/thi-ng/umbrella/commit/20fc5cf))
|
|
21
|
+
- remove `ParseState.last` to lower RAM usage
|
|
22
|
+
- add `IReader.prev()` to obtain previous char, add docs
|
|
23
|
+
- update reader impls
|
|
24
|
+
- update anchor parsers
|
|
25
|
+
- update tests
|
|
26
|
+
- minor internal updates ([ef97aee](https://github.com/thi-ng/umbrella/commit/ef97aee))
|
|
27
|
+
- update `ParseContext.start()`
|
|
28
|
+
- update `check()` combinator impl
|
|
29
|
+
|
|
30
|
+
### [2.4.64](https://github.com/thi-ng/umbrella/tree/@thi.ng/parse@2.4.64) (2025-01-14)
|
|
31
|
+
|
|
32
|
+
#### ♻️ Refactoring
|
|
33
|
+
|
|
34
|
+
- various minor updates ([42ce3f6](https://github.com/thi-ng/umbrella/commit/42ce3f6))
|
|
35
|
+
|
|
12
36
|
### [2.4.52](https://github.com/thi-ng/umbrella/tree/@thi.ng/parse@2.4.52) (2024-08-29)
|
|
13
37
|
|
|
14
38
|
#### ⏱ Performance improvements
|
package/api.d.ts
CHANGED
|
@@ -1,16 +1,48 @@
|
|
|
1
|
-
import type { Fn, Fn0, IObjectOf, Nullable } from "@thi.ng/api";
|
|
1
|
+
import type { Fn, Fn0, IDeref, IObjectOf, Maybe, Nullable } from "@thi.ng/api";
|
|
2
2
|
import type { ParseContext, ParseScope, ParseState } from "./context.js";
|
|
3
3
|
export interface IReader<T> {
|
|
4
|
+
/**
|
|
5
|
+
* Returns the char/value at the current read position. No bounds checking
|
|
6
|
+
* done, assumes reader is not yet {@link IReader.isDone}.
|
|
7
|
+
*
|
|
8
|
+
* @param state
|
|
9
|
+
*/
|
|
4
10
|
read(state: ParseState<T>): T;
|
|
11
|
+
/**
|
|
12
|
+
* Returns the char/value at the previous read position (if any).
|
|
13
|
+
*
|
|
14
|
+
* @param state
|
|
15
|
+
*/
|
|
16
|
+
prev(state: ParseState<T>): Maybe<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Updates the reader's read position.
|
|
19
|
+
*
|
|
20
|
+
* @param state
|
|
21
|
+
*/
|
|
5
22
|
next(state: ParseState<T>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Returns true if the reader already consumed all chars/values.
|
|
25
|
+
*
|
|
26
|
+
* @param state
|
|
27
|
+
*/
|
|
6
28
|
isDone(state: ParseState<T>): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Returns a string formatted version of the reader's position.
|
|
31
|
+
*
|
|
32
|
+
* @param state
|
|
33
|
+
*/
|
|
7
34
|
format(state: ParseState<T>): string;
|
|
8
35
|
}
|
|
9
36
|
export type Parser<T> = Fn<ParseContext<T>, boolean>;
|
|
10
37
|
export type LitParser<T> = Parser<T> & {
|
|
11
38
|
__lit: true;
|
|
12
39
|
};
|
|
13
|
-
|
|
40
|
+
/**
|
|
41
|
+
* A {@link Parser} wrapper, whose actual implementation can (and must!) be
|
|
42
|
+
* defined dynamically via the exposed `.set()` function and which can be
|
|
43
|
+
* retrieved via `.deref()`.
|
|
44
|
+
*/
|
|
45
|
+
export type DynamicParser<T> = Parser<T> & IDeref<Maybe<Parser<T>>> & {
|
|
14
46
|
set: Fn<Parser<T>, void>;
|
|
15
47
|
};
|
|
16
48
|
export type PassValue<T> = T | Fn0<T>;
|
|
@@ -43,7 +75,7 @@ export interface ContextOpts {
|
|
|
43
75
|
* Max recursion depth failsafe. Parsing will terminate once this limit is
|
|
44
76
|
* reached.
|
|
45
77
|
*
|
|
46
|
-
* @
|
|
78
|
+
* @defaultValue 64
|
|
47
79
|
*/
|
|
48
80
|
maxDepth: number;
|
|
49
81
|
/**
|
package/combinators/check.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { parseError } from "../error.js";
|
|
2
2
|
import { xform } from "./xform.js";
|
|
3
|
-
const check = (parser, pred, msg = "check failed") => xform(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const check = (parser, pred, msg = "check failed") => xform(
|
|
4
|
+
parser,
|
|
5
|
+
(scope, ctx) => pred(scope) ? scope : parseError(ctx, msg)
|
|
6
|
+
);
|
|
7
7
|
export {
|
|
8
8
|
check
|
|
9
9
|
};
|
package/combinators/dynamic.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { DynamicParser } from "../api.js";
|
|
|
4
4
|
* later stage via calling `.set()`. The parser always fails until set, after
|
|
5
5
|
* which it then delegates to the chosen impl.
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
7
|
+
* @example
|
|
8
8
|
* ```ts tangle:../../export/dynamic.ts
|
|
9
9
|
* import { defContext, dynamic,lit } from "@thi.ng/parse";
|
|
10
10
|
*
|
package/combinators/dynamic.js
CHANGED
package/context.d.ts
CHANGED
|
@@ -5,8 +5,7 @@ export declare class ParseState<T> implements ICopy<ParseState<T>> {
|
|
|
5
5
|
l: number;
|
|
6
6
|
c: number;
|
|
7
7
|
done?: boolean | undefined;
|
|
8
|
-
|
|
9
|
-
constructor(p: number, l: number, c: number, done?: boolean | undefined, last?: T | undefined);
|
|
8
|
+
constructor(p: number, l: number, c: number, done?: boolean | undefined);
|
|
10
9
|
copy(): ParseState<T>;
|
|
11
10
|
}
|
|
12
11
|
export declare class ParseScope<T> implements ICopy<ParseScope<T>> {
|
package/context.js
CHANGED
|
@@ -5,15 +5,14 @@ import { defArrayReader } from "./readers/array-reader.js";
|
|
|
5
5
|
import { defStringReader } from "./readers/string-reader.js";
|
|
6
6
|
import { __indent } from "./utils.js";
|
|
7
7
|
class ParseState {
|
|
8
|
-
constructor(p, l, c, done
|
|
8
|
+
constructor(p, l, c, done) {
|
|
9
9
|
this.p = p;
|
|
10
10
|
this.l = l;
|
|
11
11
|
this.c = c;
|
|
12
12
|
this.done = done;
|
|
13
|
-
this.last = last;
|
|
14
13
|
}
|
|
15
14
|
copy() {
|
|
16
|
-
return new ParseState(this.p, this.l, this.c, this.done
|
|
15
|
+
return new ParseState(this.p, this.l, this.c, this.done);
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
class ParseScope {
|
|
@@ -57,15 +56,12 @@ class ParseContext {
|
|
|
57
56
|
}
|
|
58
57
|
start(id) {
|
|
59
58
|
const { _scopes: scopes, _maxDepth } = this;
|
|
60
|
-
|
|
59
|
+
const num = scopes.length;
|
|
60
|
+
if (num >= _maxDepth) {
|
|
61
61
|
parseError(this, `recursion limit reached ${_maxDepth}`);
|
|
62
62
|
}
|
|
63
|
-
const scope = new ParseScope(
|
|
64
|
-
|
|
65
|
-
scopes[scopes.length - 1].state.copy()
|
|
66
|
-
);
|
|
67
|
-
scopes.push(scope);
|
|
68
|
-
this._peakDepth = Math.max(this._peakDepth, scopes.length);
|
|
63
|
+
const scope = new ParseScope(id, scopes[num - 1].state.copy());
|
|
64
|
+
this._peakDepth = Math.max(this._peakDepth, scopes.push(scope));
|
|
69
65
|
this._debug && console.log(
|
|
70
66
|
`${__indent(scopes.length)}start: ${id} (${scope.state.p})`
|
|
71
67
|
);
|
|
@@ -88,8 +84,7 @@ class ParseContext {
|
|
|
88
84
|
);
|
|
89
85
|
child.state = this._retain ? parent.state.copy() : null;
|
|
90
86
|
parent.state = cstate;
|
|
91
|
-
|
|
92
|
-
children ? children.push(child) : parent.children = [child];
|
|
87
|
+
parent.children?.push(child) ?? (parent.children = [child]);
|
|
93
88
|
this._curr = parent;
|
|
94
89
|
return true;
|
|
95
90
|
}
|
|
@@ -104,8 +99,7 @@ class ParseContext {
|
|
|
104
99
|
this._debug && console.log(
|
|
105
100
|
`${__indent(this._scopes.length + 1)}addChild: ${id} (${curr.state.p})`
|
|
106
101
|
);
|
|
107
|
-
|
|
108
|
-
children ? children.push(child) : curr.children = [child];
|
|
102
|
+
curr.children?.push(child) ?? (curr.children = [child]);
|
|
109
103
|
if (newState !== false) {
|
|
110
104
|
newState === true ? this.reader.next(curr.state) : this._curr.state = newState;
|
|
111
105
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/parse",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Purely functional parser combinators & AST generation for generic inputs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@thi.ng/api": "^8.11.16",
|
|
44
|
-
"@thi.ng/checks": "^3.6.
|
|
45
|
-
"@thi.ng/defmulti": "^3.0.
|
|
44
|
+
"@thi.ng/checks": "^3.6.19",
|
|
45
|
+
"@thi.ng/defmulti": "^3.0.55",
|
|
46
46
|
"@thi.ng/errors": "^2.5.22",
|
|
47
47
|
"@thi.ng/strings": "^3.9.0"
|
|
48
48
|
},
|
|
@@ -246,5 +246,5 @@
|
|
|
246
246
|
],
|
|
247
247
|
"year": 2020
|
|
248
248
|
},
|
|
249
|
-
"gitHead": "
|
|
249
|
+
"gitHead": "d888087b36b086fd8c3e7dc98d35857266f78942\n"
|
|
250
250
|
}
|
package/prims/anchor.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { ALPHA_NUM } from "@thi.ng/strings/groups";
|
|
2
|
-
const anchor = (fn) => (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
const inputStart = (ctx) => ctx.state.last == null;
|
|
7
|
-
const inputEnd = (ctx) => ctx.state.done || ctx.reader.read(ctx.state) === void 0;
|
|
2
|
+
const anchor = (fn) => ({ reader, state }) => fn(reader.prev(state), state.done ? null : reader.read(state));
|
|
3
|
+
const inputStart = (ctx) => ctx.reader.prev(ctx.state) == null;
|
|
4
|
+
const inputEnd = ({ reader, state }) => state.done || reader.read(state) === void 0;
|
|
8
5
|
const lineStart = (ctx) => {
|
|
9
|
-
const l = ctx.state
|
|
6
|
+
const l = ctx.reader.prev(ctx.state);
|
|
10
7
|
return l == null || l === "\n" || l === "\r";
|
|
11
8
|
};
|
|
12
|
-
const lineEnd = (
|
|
13
|
-
const state = ctx.state;
|
|
9
|
+
const lineEnd = ({ reader, state }) => {
|
|
14
10
|
let c;
|
|
15
|
-
return state.done || (c =
|
|
11
|
+
return state.done || (c = reader.read(state)) === "\n" || c === "\r";
|
|
16
12
|
};
|
|
17
13
|
const wordBoundaryP = (prev, next) => {
|
|
18
14
|
return prev ? next ? ALPHA_NUM[prev] && !ALPHA_NUM[next] : ALPHA_NUM[prev] : next ? ALPHA_NUM[next] : false;
|
package/prims/lit.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { satisfyD, satisfy } from "./satisfy.js";
|
|
2
2
|
const litP = (c) => (x) => x === c;
|
|
3
|
-
const lit = (c, id = "lit") => satisfy(
|
|
4
|
-
const litD = (c) => satisfyD(
|
|
3
|
+
const lit = (c, id = "lit") => satisfy((x) => x === c, id);
|
|
4
|
+
const litD = (c) => satisfyD((x) => x === c);
|
|
5
5
|
export {
|
|
6
6
|
lit,
|
|
7
7
|
litD,
|
package/prims/none-of.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
|
|
2
2
|
import { isSet } from "@thi.ng/checks/is-set";
|
|
3
3
|
import { satisfy, satisfyD } from "./satisfy.js";
|
|
4
|
-
const noneOfP = (opts) => isSet(opts) ? (x) => !opts.has(x) : isPlainObject(opts) ? (x) => !opts[x] : (x) => opts.
|
|
4
|
+
const noneOfP = (opts) => isSet(opts) ? (x) => !opts.has(x) : isPlainObject(opts) ? (x) => !opts[x] : (x) => !opts.includes(x);
|
|
5
5
|
function noneOf(opts, id = "noneOf") {
|
|
6
6
|
return satisfy(noneOfP(opts), id);
|
|
7
7
|
}
|
package/prims/one-of.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
|
|
2
2
|
import { isSet } from "@thi.ng/checks/is-set";
|
|
3
3
|
import { satisfy, satisfyD } from "./satisfy.js";
|
|
4
|
-
const oneOfP = (opts) => isSet(opts) ? (x) => opts.has(x) : isPlainObject(opts) ? (x) => opts[x] : (x) => opts.
|
|
4
|
+
const oneOfP = (opts) => isSet(opts) ? (x) => opts.has(x) : isPlainObject(opts) ? (x) => opts[x] : (x) => opts.includes(x);
|
|
5
5
|
function oneOf(opts, id = "oneOf") {
|
|
6
6
|
return satisfy(oneOfP(opts), id);
|
|
7
7
|
}
|
package/prims/satisfy.js
CHANGED
|
@@ -5,8 +5,7 @@ const satisfy = (pred, id = "satisfy") => (ctx) => {
|
|
|
5
5
|
};
|
|
6
6
|
const satisfyD = (pred) => (ctx) => {
|
|
7
7
|
if (ctx.done) return false;
|
|
8
|
-
const state = ctx
|
|
9
|
-
const reader = ctx.reader;
|
|
8
|
+
const { reader, state } = ctx;
|
|
10
9
|
return pred(reader.read(state)) ? (reader.next(state), true) : false;
|
|
11
10
|
};
|
|
12
11
|
export {
|
package/prims/string.js
CHANGED
|
@@ -5,8 +5,7 @@ const string = (str, id = "string") => (ctx) => {
|
|
|
5
5
|
const reader = ctx.reader;
|
|
6
6
|
for (let i = 0, n = str.length; i < n; i++) {
|
|
7
7
|
if (state.done) return false;
|
|
8
|
-
|
|
9
|
-
if (r !== str[i]) {
|
|
8
|
+
if (reader.read(state) !== str[i]) {
|
|
10
9
|
return ctx.discard();
|
|
11
10
|
}
|
|
12
11
|
reader.next(state);
|
|
@@ -20,8 +19,7 @@ const stringD = (str) => (ctx) => {
|
|
|
20
19
|
const reader = ctx.reader;
|
|
21
20
|
for (let i = 0, n = str.length; i < n; i++) {
|
|
22
21
|
if (state.done) return false;
|
|
23
|
-
|
|
24
|
-
if (r !== str[i]) {
|
|
22
|
+
if (reader.read(state) !== str[i]) {
|
|
25
23
|
return false;
|
|
26
24
|
}
|
|
27
25
|
reader.next(state);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Maybe } from "@thi.ng/api";
|
|
1
2
|
import type { IReader } from "../api.js";
|
|
2
3
|
import type { ParseState } from "../context.js";
|
|
3
4
|
export declare class ArrayReader<T> implements IReader<T> {
|
|
4
5
|
protected _src: ArrayLike<T>;
|
|
5
6
|
constructor(_src: ArrayLike<T>);
|
|
6
7
|
read(state: ParseState<T>): T;
|
|
8
|
+
prev(state: ParseState<T>): Maybe<T>;
|
|
7
9
|
next(state: ParseState<T>): void;
|
|
8
10
|
isDone(state: ParseState<T>): boolean;
|
|
9
11
|
format(state: ParseState<T>): string;
|
package/readers/array-reader.js
CHANGED
|
@@ -5,9 +5,11 @@ class ArrayReader {
|
|
|
5
5
|
read(state) {
|
|
6
6
|
return this._src[state.p];
|
|
7
7
|
}
|
|
8
|
+
prev(state) {
|
|
9
|
+
return this._src[state.p - 1];
|
|
10
|
+
}
|
|
8
11
|
next(state) {
|
|
9
12
|
if (state.done) return;
|
|
10
|
-
state.last = this._src[state.p];
|
|
11
13
|
state.done = ++state.p >= this._src.length;
|
|
12
14
|
}
|
|
13
15
|
isDone(state) {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { Maybe } from "@thi.ng/api";
|
|
1
2
|
import type { IReader } from "../api.js";
|
|
2
3
|
import type { ParseState } from "../context.js";
|
|
3
4
|
export declare class StringReader implements IReader<string> {
|
|
4
5
|
protected _src: string;
|
|
5
6
|
constructor(_src: string);
|
|
6
7
|
read(state: ParseState<string>): string;
|
|
8
|
+
prev(state: ParseState<string>): Maybe<string>;
|
|
7
9
|
next(state: ParseState<string>): void;
|
|
8
10
|
isDone(state: ParseState<string>): boolean;
|
|
9
11
|
format(state: ParseState<string>): string;
|
package/readers/string-reader.js
CHANGED
|
@@ -5,10 +5,12 @@ class StringReader {
|
|
|
5
5
|
read(state) {
|
|
6
6
|
return this._src[state.p];
|
|
7
7
|
}
|
|
8
|
+
prev(state) {
|
|
9
|
+
return this._src[state.p - 1];
|
|
10
|
+
}
|
|
8
11
|
next(state) {
|
|
9
12
|
if (state.done) return;
|
|
10
|
-
|
|
11
|
-
if (state.last === "\n") {
|
|
13
|
+
if (this._src[state.p] === "\n") {
|
|
12
14
|
state.l++;
|
|
13
15
|
state.c = 1;
|
|
14
16
|
} else {
|