securemark 0.257.2 → 0.258.1
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 +12 -0
- package/dist/index.js +1238 -618
- package/markdown.d.ts +1 -12
- package/package.json +9 -9
- package/src/combinator/control/manipulation/convert.ts +8 -4
- package/src/combinator/control/manipulation/scope.ts +10 -2
- package/src/combinator/data/parser/context/delimiter.ts +70 -0
- package/src/combinator/data/parser/context/memo.ts +34 -0
- package/src/combinator/{control/manipulation → data/parser}/context.test.ts +9 -9
- package/src/combinator/data/parser/context.ts +158 -0
- package/src/combinator/data/parser/inits.ts +4 -3
- package/src/combinator/data/parser/sequence.test.ts +1 -1
- package/src/combinator/data/parser/sequence.ts +4 -3
- package/src/combinator/data/parser/some.test.ts +1 -1
- package/src/combinator/data/parser/some.ts +14 -37
- package/src/combinator/data/parser/subsequence.test.ts +1 -1
- package/src/combinator/data/parser/subsequence.ts +3 -3
- package/src/combinator/data/parser/tails.ts +3 -3
- package/src/combinator/data/parser/union.test.ts +1 -1
- package/src/combinator/data/parser.ts +6 -47
- package/src/combinator.ts +1 -2
- package/src/parser/api/bind.ts +5 -5
- package/src/parser/api/parse.test.ts +11 -8
- package/src/parser/api/parse.ts +3 -1
- package/src/parser/block/blockquote.ts +1 -1
- package/src/parser/block/dlist.ts +4 -10
- package/src/parser/block/extension/figure.ts +4 -3
- package/src/parser/block/extension/table.ts +2 -2
- package/src/parser/block/heading.ts +5 -13
- package/src/parser/block/ilist.ts +3 -2
- package/src/parser/block/olist.ts +10 -7
- package/src/parser/block/paragraph.ts +1 -1
- package/src/parser/block/reply/cite.ts +1 -1
- package/src/parser/block/reply/quote.ts +1 -1
- package/src/parser/block/reply.ts +1 -1
- package/src/parser/block/sidefence.ts +1 -1
- package/src/parser/block/table.test.ts +5 -0
- package/src/parser/block/table.ts +14 -13
- package/src/parser/block/ulist.ts +4 -3
- package/src/parser/block.ts +1 -1
- package/src/parser/context.ts +32 -0
- package/src/parser/header.ts +1 -1
- package/src/parser/inline/annotation.test.ts +5 -5
- package/src/parser/inline/annotation.ts +9 -17
- package/src/parser/inline/autolink/email.ts +1 -1
- package/src/parser/inline/autolink/url.ts +1 -1
- package/src/parser/inline/autolink.ts +5 -3
- package/src/parser/inline/bracket.ts +16 -15
- package/src/parser/inline/code.ts +1 -1
- package/src/parser/inline/comment.ts +4 -3
- package/src/parser/inline/deletion.ts +5 -4
- package/src/parser/inline/emphasis.ts +5 -4
- package/src/parser/inline/emstrong.ts +5 -4
- package/src/parser/inline/extension/index.ts +7 -14
- package/src/parser/inline/extension/indexee.ts +8 -10
- package/src/parser/inline/extension/indexer.ts +4 -3
- package/src/parser/inline/extension/label.ts +3 -2
- package/src/parser/inline/extension/placeholder.ts +5 -4
- package/src/parser/inline/html.ts +5 -4
- package/src/parser/inline/htmlentity.ts +1 -1
- package/src/parser/inline/insertion.ts +5 -4
- package/src/parser/inline/link.test.ts +2 -1
- package/src/parser/inline/link.ts +21 -27
- package/src/parser/inline/mark.ts +5 -4
- package/src/parser/inline/math.ts +1 -1
- package/src/parser/inline/media.test.ts +1 -0
- package/src/parser/inline/media.ts +8 -7
- package/src/parser/inline/reference.test.ts +5 -5
- package/src/parser/inline/reference.ts +10 -16
- package/src/parser/inline/ruby.test.ts +1 -0
- package/src/parser/inline/ruby.ts +4 -3
- package/src/parser/inline/shortmedia.ts +3 -2
- package/src/parser/inline/strong.ts +5 -4
- package/src/parser/inline/template.test.ts +1 -1
- package/src/parser/inline/template.ts +9 -6
- package/src/parser/inline.test.ts +2 -1
- package/src/parser/locale.ts +6 -7
- package/src/parser/processor/footnote.ts +5 -3
- package/src/parser/source/text.ts +1 -1
- package/src/parser/util.ts +0 -220
- package/src/parser/visibility.ts +205 -0
- package/src/util/info.ts +4 -2
- package/src/util/quote.ts +12 -15
- package/src/util/toc.ts +14 -17
- package/webpack.config.js +1 -0
- package/src/combinator/control/manipulation/context.ts +0 -70
- package/src/combinator/control/manipulation/resource.ts +0 -54
package/markdown.d.ts
CHANGED
|
@@ -17,17 +17,6 @@ export namespace MarkdownParser {
|
|
|
17
17
|
readonly url?: URL;
|
|
18
18
|
readonly id?: string;
|
|
19
19
|
readonly header?: boolean;
|
|
20
|
-
readonly syntax?: {
|
|
21
|
-
readonly inline?: {
|
|
22
|
-
readonly annotation?: boolean;
|
|
23
|
-
readonly reference?: boolean;
|
|
24
|
-
readonly index?: boolean;
|
|
25
|
-
readonly label?: boolean;
|
|
26
|
-
readonly link?: boolean;
|
|
27
|
-
readonly media?: boolean;
|
|
28
|
-
readonly autolink?: boolean;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
20
|
readonly caches?: {
|
|
32
21
|
readonly code?: Dict<string, HTMLElement>;
|
|
33
22
|
readonly math?: Dict<string, HTMLElement>;
|
|
@@ -700,7 +689,7 @@ export namespace MarkdownParser {
|
|
|
700
689
|
export interface TemplateParser extends
|
|
701
690
|
// {{abc}}
|
|
702
691
|
Inline<'template'>,
|
|
703
|
-
Parser<HTMLSpanElement, Context, [
|
|
692
|
+
Parser<HTMLSpanElement | string, Context, [
|
|
704
693
|
TemplateParser.BracketParser,
|
|
705
694
|
SourceParser.EscapableSourceParser,
|
|
706
695
|
]> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securemark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.258.1",
|
|
4
4
|
"description": "Secure markdown renderer working on browsers for user input data.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/falsandtru/securemark",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"@types/mocha": "9.1.1",
|
|
35
35
|
"@types/power-assert": "1.5.8",
|
|
36
36
|
"@types/prismjs": "1.26.0",
|
|
37
|
-
"@typescript-eslint/parser": "^5.
|
|
37
|
+
"@typescript-eslint/parser": "^5.29.0",
|
|
38
38
|
"babel-loader": "^8.2.5",
|
|
39
39
|
"babel-plugin-unassert": "^3.2.0",
|
|
40
40
|
"concurrently": "^7.2.2",
|
|
41
|
-
"eslint": "^8.
|
|
41
|
+
"eslint": "^8.18.0",
|
|
42
42
|
"eslint-plugin-redos": "^4.4.1",
|
|
43
|
-
"eslint-webpack-plugin": "^3.
|
|
43
|
+
"eslint-webpack-plugin": "^3.2.0",
|
|
44
44
|
"glob": "^8.0.3",
|
|
45
45
|
"karma": "^6.4.0",
|
|
46
46
|
"karma-chrome-launcher": "^3.1.1",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"karma-mocha": "^2.0.1",
|
|
50
50
|
"karma-power-assert": "^1.0.0",
|
|
51
51
|
"mocha": "^10.0.0",
|
|
52
|
-
"npm-check-updates": "^14.
|
|
52
|
+
"npm-check-updates": "^14.1.1",
|
|
53
53
|
"semver": "^7.3.7",
|
|
54
|
-
"spica": "0.0.
|
|
55
|
-
"ts-loader": "^9.3.
|
|
56
|
-
"typed-dom": "^0.0.
|
|
57
|
-
"typescript": "4.7.
|
|
54
|
+
"spica": "0.0.573",
|
|
55
|
+
"ts-loader": "^9.3.1",
|
|
56
|
+
"typed-dom": "^0.0.301",
|
|
57
|
+
"typescript": "4.7.4",
|
|
58
58
|
"webpack": "^5.73.0",
|
|
59
59
|
"webpack-cli": "^4.10.0",
|
|
60
60
|
"webpack-merge": "^5.8.0"
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { undefined } from 'spica/global';
|
|
1
2
|
import { Parser } from '../../data/parser';
|
|
2
3
|
|
|
3
4
|
export function convert<P extends Parser<unknown>>(conv: (source: string) => string, parser: P): P;
|
|
4
5
|
export function convert<T>(conv: (source: string) => string, parser: Parser<T>): Parser<T> {
|
|
5
6
|
assert(parser);
|
|
6
|
-
return (source, context) => {
|
|
7
|
+
return (source, context = {}) => {
|
|
7
8
|
if (source === '') return;
|
|
8
9
|
source = conv(source);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
if (source === '') return [[], ''];
|
|
11
|
+
const memo = context.memo;
|
|
12
|
+
context.memo = undefined;
|
|
13
|
+
const result = parser(source, context);
|
|
14
|
+
context.memo = memo;
|
|
15
|
+
return result;
|
|
12
16
|
};
|
|
13
17
|
}
|
|
@@ -8,13 +8,16 @@ export function focus<T>(scope: string | RegExp, parser: Parser<T>): Parser<T> {
|
|
|
8
8
|
const match: (source: string) => string = typeof scope === 'string'
|
|
9
9
|
? source => source.slice(0, scope.length) === scope ? scope : ''
|
|
10
10
|
: source => source.match(scope)?.[0] ?? '';
|
|
11
|
-
return (source, context) => {
|
|
11
|
+
return (source, context = {}) => {
|
|
12
12
|
if (source === '') return;
|
|
13
13
|
const src = match(source);
|
|
14
14
|
assert(source.startsWith(src));
|
|
15
15
|
if (src === '') return;
|
|
16
|
+
const memo = context.memo;
|
|
17
|
+
memo && (memo.offset = source.length - src.length);
|
|
16
18
|
const result = parser(src, context);
|
|
17
19
|
assert(check(src, result));
|
|
20
|
+
memo && (memo.offset = source.length + src.length);
|
|
18
21
|
if (!result) return;
|
|
19
22
|
assert(exec(result).length < src.length);
|
|
20
23
|
return exec(result).length < src.length
|
|
@@ -28,16 +31,21 @@ export function rewrite<P extends Parser<unknown>>(scope: Parser<unknown, Contex
|
|
|
28
31
|
export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T> {
|
|
29
32
|
assert(scope);
|
|
30
33
|
assert(parser);
|
|
31
|
-
return (source, context) => {
|
|
34
|
+
return (source, context = {}) => {
|
|
32
35
|
if (source === '') return;
|
|
36
|
+
const memo = context.memo;
|
|
37
|
+
context.memo = undefined;
|
|
33
38
|
const res1 = scope(source, context);
|
|
34
39
|
assert(check(source, res1));
|
|
40
|
+
context.memo = memo;
|
|
35
41
|
if (!res1 || exec(res1).length >= source.length) return;
|
|
36
42
|
const src = source.slice(0, source.length - exec(res1).length);
|
|
37
43
|
assert(src !== '');
|
|
38
44
|
assert(source.startsWith(src));
|
|
45
|
+
memo && (memo.offset = source.length - src.length);
|
|
39
46
|
const res2 = parser(src, context);
|
|
40
47
|
assert(check(src, res2));
|
|
48
|
+
memo && (memo.offset = source.length + src.length);
|
|
41
49
|
if (!res2) return;
|
|
42
50
|
assert(exec(res2) === '');
|
|
43
51
|
return exec(res2).length < src.length
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { memoize, reduce } from 'spica/memoize';
|
|
2
|
+
|
|
3
|
+
export class Delimiters {
|
|
4
|
+
public static signature(pattern: string | RegExp | undefined): string {
|
|
5
|
+
switch (typeof pattern) {
|
|
6
|
+
case 'undefined':
|
|
7
|
+
return `undefined`;
|
|
8
|
+
case 'string':
|
|
9
|
+
return `s:${pattern}`;
|
|
10
|
+
case 'object':
|
|
11
|
+
return `r/${pattern.source}/${pattern.flags}`;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
public static matcher = memoize(
|
|
15
|
+
(pattern: string | RegExp | undefined): (source: string) => true | undefined => {
|
|
16
|
+
switch (typeof pattern) {
|
|
17
|
+
case 'undefined':
|
|
18
|
+
return () => undefined;
|
|
19
|
+
case 'string':
|
|
20
|
+
return source => source.slice(0, pattern.length) === pattern || undefined;
|
|
21
|
+
case 'object':
|
|
22
|
+
return reduce(source => pattern.test(source) || undefined);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
this.signature);
|
|
26
|
+
private readonly matchers: [number, string, number, (source: string) => boolean | undefined][] = [];
|
|
27
|
+
private readonly registry: Record<string, boolean> = {};
|
|
28
|
+
private length = 0;
|
|
29
|
+
public push(
|
|
30
|
+
...delimiters: readonly {
|
|
31
|
+
readonly signature: string;
|
|
32
|
+
readonly matcher: (source: string) => boolean | undefined;
|
|
33
|
+
readonly precedence?: number;
|
|
34
|
+
}[]
|
|
35
|
+
): void {
|
|
36
|
+
for (let i = 0; i < delimiters.length; ++i) {
|
|
37
|
+
const delimiter = delimiters[i];
|
|
38
|
+
assert(this.length >= this.matchers.length);
|
|
39
|
+
const { signature, matcher, precedence = 1 } = delimiter;
|
|
40
|
+
if (!this.registry[signature]) {
|
|
41
|
+
this.matchers.unshift([this.length, signature, precedence, matcher]);
|
|
42
|
+
this.registry[signature] = true;
|
|
43
|
+
}
|
|
44
|
+
++this.length;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
public pop(count = 1): void {
|
|
48
|
+
assert(count > 0);
|
|
49
|
+
for (let i = 0; i < count; ++i) {
|
|
50
|
+
assert(this.matchers.length > 0);
|
|
51
|
+
assert(this.length >= this.matchers.length);
|
|
52
|
+
if (--this.length === this.matchers[0][0]) {
|
|
53
|
+
this.registry[this.matchers.shift()![1]] = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
public match(source: string, precedence = 1): boolean {
|
|
58
|
+
const { matchers } = this;
|
|
59
|
+
for (let i = 0; i < matchers.length; ++i) {
|
|
60
|
+
switch (matchers[i][3](source)) {
|
|
61
|
+
case true:
|
|
62
|
+
if (precedence < matchers[i][2]) return true;
|
|
63
|
+
continue;
|
|
64
|
+
case false:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class Memo {
|
|
2
|
+
private memory: Record<string, readonly [any[], number]>[/* pos */] = [];
|
|
3
|
+
public get length(): number {
|
|
4
|
+
return this.memory.length;
|
|
5
|
+
}
|
|
6
|
+
public offset = 0;
|
|
7
|
+
public get(
|
|
8
|
+
position: number,
|
|
9
|
+
syntax: number,
|
|
10
|
+
state: number,
|
|
11
|
+
): readonly [any[], number] | undefined {
|
|
12
|
+
//console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
|
|
13
|
+
return this.memory[position + this.offset - 1]?.[`${syntax}:${state}`];
|
|
14
|
+
}
|
|
15
|
+
public set(
|
|
16
|
+
position: number,
|
|
17
|
+
syntax: number,
|
|
18
|
+
state: number,
|
|
19
|
+
nodes: any[],
|
|
20
|
+
offset: number,
|
|
21
|
+
): void {
|
|
22
|
+
const record = this.memory[position + this.offset - 1] ??= {};
|
|
23
|
+
assert(!record[`${syntax}:${state}`]);
|
|
24
|
+
record[`${syntax}:${state}`] = [nodes.slice(), offset];
|
|
25
|
+
//console.log('set', position + this.offset, syntax, state);
|
|
26
|
+
}
|
|
27
|
+
public clear(position: number): void {
|
|
28
|
+
const memory = this.memory;
|
|
29
|
+
for (let i = position + this.offset, len = memory.length; i < len; ++i) {
|
|
30
|
+
memory.pop();
|
|
31
|
+
}
|
|
32
|
+
//console.log(position);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Parser, Ctx } from '
|
|
2
|
-
import { some } from '
|
|
1
|
+
import { Parser, Ctx } from '../parser';
|
|
2
|
+
import { some } from './some';
|
|
3
3
|
import { reset, context } from './context';
|
|
4
|
-
import { creator } from './
|
|
4
|
+
import { creator } from './context';
|
|
5
5
|
|
|
6
|
-
describe('Unit: combinator/context', () => {
|
|
6
|
+
describe('Unit: combinator/data/parser/context', () => {
|
|
7
7
|
interface Context extends Ctx {
|
|
8
|
-
|
|
8
|
+
status?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
describe('reset', () => {
|
|
@@ -37,17 +37,17 @@ describe('Unit: combinator/context', () => {
|
|
|
37
37
|
|
|
38
38
|
describe('context', () => {
|
|
39
39
|
const parser: Parser<boolean, Context> = some(creator(
|
|
40
|
-
(s, context) => [[context.
|
|
40
|
+
(s, context) => [[context.status!], s.slice(1)]));
|
|
41
41
|
|
|
42
42
|
it('', () => {
|
|
43
|
-
const base: Context = {
|
|
43
|
+
const base: Context = { status: true };
|
|
44
44
|
const ctx: Context = { resources: { budget: 3, recursion: 1 } };
|
|
45
45
|
assert.deepStrictEqual(context(base, parser)('123', ctx), [[true, true, true], '']);
|
|
46
46
|
assert(ctx.resources?.budget === 0);
|
|
47
|
-
assert(ctx.
|
|
47
|
+
assert(ctx.status === undefined);
|
|
48
48
|
assert.throws(() => reset(base, parser)('1', ctx));
|
|
49
49
|
assert(ctx.resources?.budget === 0);
|
|
50
|
-
assert(ctx.
|
|
50
|
+
assert(ctx.status === undefined);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { undefined, Array, Object } from 'spica/global';
|
|
2
|
+
import { hasOwnProperty, ObjectCreate } from 'spica/alias';
|
|
3
|
+
import { Parser, Result, Ctx, Context, eval, exec, Tree } from '../../data/parser';
|
|
4
|
+
import { Memo } from './context/memo';
|
|
5
|
+
|
|
6
|
+
export function reset<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
|
|
7
|
+
export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
8
|
+
assert(Object.getPrototypeOf(base) === Object.prototype);
|
|
9
|
+
assert(Object.freeze(base));
|
|
10
|
+
const changes = Object.entries(base);
|
|
11
|
+
const values = Array(changes.length);
|
|
12
|
+
return (source, context) =>
|
|
13
|
+
apply(parser, source, ObjectCreate(context), changes, values);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function context<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
|
|
17
|
+
export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
18
|
+
assert(Object.getPrototypeOf(base) === Object.prototype);
|
|
19
|
+
assert(Object.freeze(base));
|
|
20
|
+
const changes = Object.entries(base);
|
|
21
|
+
const values = Array(changes.length);
|
|
22
|
+
return (source, context) =>
|
|
23
|
+
apply(parser, source, context, changes, values);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: [string, any][], values: any[]): Result<Tree<P>>;
|
|
27
|
+
function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [string, any][], values: any[]): Result<T> {
|
|
28
|
+
if (context) for (let i = 0; i < changes.length; ++i) {
|
|
29
|
+
const change = changes[i];
|
|
30
|
+
const prop = change[0];
|
|
31
|
+
switch (prop) {
|
|
32
|
+
case 'resources':
|
|
33
|
+
assert(typeof change[1] === 'object');
|
|
34
|
+
assert(context[prop] || !(prop in context));
|
|
35
|
+
if (prop in context && !hasOwnProperty(context, prop)) break;
|
|
36
|
+
// @ts-expect-error
|
|
37
|
+
context[prop] = ObjectCreate(change[1]);
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
values[i] = context[prop];
|
|
41
|
+
context[prop] = change[1];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const result = parser(source, context);
|
|
45
|
+
if (context) for (let i = 0; i < changes.length; ++i) {
|
|
46
|
+
const change = changes[i];
|
|
47
|
+
const prop = change[0];
|
|
48
|
+
switch (prop) {
|
|
49
|
+
case 'resources':
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
context[prop] = values[i];
|
|
53
|
+
values[i] = undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, parser: P): P;
|
|
60
|
+
export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, parser: P): P;
|
|
61
|
+
export function syntax<T>(syntax: number, precedence: number, cost: number | Parser<T>, parser?: Parser<T>): Parser<T> {
|
|
62
|
+
if (typeof cost === 'function') {
|
|
63
|
+
parser = cost;
|
|
64
|
+
cost = 1;
|
|
65
|
+
}
|
|
66
|
+
return (source, context) => {
|
|
67
|
+
if (source === '') return;
|
|
68
|
+
context.backtrackable ??= ~0;
|
|
69
|
+
const state = context.state ??= 0;
|
|
70
|
+
const p = context.precedence;
|
|
71
|
+
context.precedence = precedence;
|
|
72
|
+
const { resources = { budget: 1, recursion: 1 } } = context;
|
|
73
|
+
if (resources.budget <= 0) throw new Error('Too many creations');
|
|
74
|
+
if (resources.recursion <= 0) throw new Error('Too much recursion');
|
|
75
|
+
--resources.recursion;
|
|
76
|
+
const pos = source.length;
|
|
77
|
+
const cache = syntax && context.memo?.get(pos, syntax, state);
|
|
78
|
+
const result: Result<T> = cache
|
|
79
|
+
? [cache[0], source.slice(cache[1])]
|
|
80
|
+
: parser!(source, context);
|
|
81
|
+
++resources.recursion;
|
|
82
|
+
if (result) {
|
|
83
|
+
if (!cache) {
|
|
84
|
+
assert(cost = cost as number);
|
|
85
|
+
resources.budget -= cost;
|
|
86
|
+
}
|
|
87
|
+
if (syntax) {
|
|
88
|
+
if (state & context.backtrackable) {
|
|
89
|
+
context.memo ??= new Memo();
|
|
90
|
+
cache ?? context.memo.set(pos, syntax, state, eval(result), source.length - exec(result).length);
|
|
91
|
+
assert.deepStrictEqual(cache && cache, cache && context.memo.get(pos, syntax, state));
|
|
92
|
+
}
|
|
93
|
+
else if (context.memo?.length! >= pos) {
|
|
94
|
+
assert(!(state & context.backtrackable));
|
|
95
|
+
context.memo!.clear(pos);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
context.precedence = p;
|
|
100
|
+
return result;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function creator<P extends Parser<unknown>>(parser: P): P;
|
|
105
|
+
export function creator<P extends Parser<unknown>>(cost: number, parser: P): P;
|
|
106
|
+
export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
|
|
107
|
+
if (typeof cost === 'function') return creator(1, cost);
|
|
108
|
+
assert(cost >= 0);
|
|
109
|
+
return (source, context) => {
|
|
110
|
+
const { resources = { budget: 1, recursion: 1 } } = context;
|
|
111
|
+
if (resources.budget <= 0) throw new Error('Too many creations');
|
|
112
|
+
if (resources.recursion <= 0) throw new Error('Too much recursion');
|
|
113
|
+
--resources.recursion;
|
|
114
|
+
const result = parser!(source, context);
|
|
115
|
+
++resources.recursion;
|
|
116
|
+
if (result) {
|
|
117
|
+
resources.budget -= cost;
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
|
|
124
|
+
export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
|
|
125
|
+
return (source, context) => {
|
|
126
|
+
const p = context.precedence;
|
|
127
|
+
context.precedence = precedence;
|
|
128
|
+
const result = parser(source, context);
|
|
129
|
+
context.precedence = p;
|
|
130
|
+
return result;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function guard<P extends Parser<unknown>>(f: (context: Context<P>) => boolean | number, parser: P): P;
|
|
135
|
+
export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T>): Parser<T> {
|
|
136
|
+
return (source, context) =>
|
|
137
|
+
f(context)
|
|
138
|
+
? parser(source, context)
|
|
139
|
+
: undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function state<P extends Parser<unknown>>(state: number, parser: P): P;
|
|
143
|
+
export function state<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
|
|
144
|
+
export function state<T>(state: number, positive: boolean | Parser<T>, parser?: Parser<T>): Parser<T> {
|
|
145
|
+
if (typeof positive === 'function') {
|
|
146
|
+
parser = positive;
|
|
147
|
+
positive = true;
|
|
148
|
+
}
|
|
149
|
+
return (source, context) => {
|
|
150
|
+
const s = context.state ?? 0;
|
|
151
|
+
context.state = positive
|
|
152
|
+
? s | state
|
|
153
|
+
: s & ~state;
|
|
154
|
+
const result = parser!(source, context);
|
|
155
|
+
context.state = s;
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -2,8 +2,8 @@ import { undefined } from 'spica/global';
|
|
|
2
2
|
import { Parser, Ctx, Tree, Context, SubParsers, SubTree, eval, exec, check } from '../parser';
|
|
3
3
|
import { push } from 'spica/array';
|
|
4
4
|
|
|
5
|
-
export function inits<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
-
export function inits<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
|
|
5
|
+
export function inits<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
+
export function inits<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
|
|
7
7
|
assert(parsers.every(f => f));
|
|
8
8
|
if (parsers.length === 1) return parsers[0];
|
|
9
9
|
return (source, context) => {
|
|
@@ -11,14 +11,15 @@ export function inits<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
|
|
|
11
11
|
let nodes: T[] | undefined;
|
|
12
12
|
for (let i = 0, len = parsers.length; i < len; ++i) {
|
|
13
13
|
if (rest === '') break;
|
|
14
|
+
if (context.delimiters?.match(rest, context.precedence)) break;
|
|
14
15
|
const result = parsers[i](rest, context);
|
|
15
16
|
assert(check(rest, result));
|
|
16
17
|
if (!result) break;
|
|
17
|
-
assert(!context?.delimiters?.match(rest, context.precedence));
|
|
18
18
|
nodes = nodes
|
|
19
19
|
? push(nodes, eval(result))
|
|
20
20
|
: eval(result);
|
|
21
21
|
rest = exec(result);
|
|
22
|
+
if (resume?.(eval(result), exec(result)) === false) break;
|
|
22
23
|
}
|
|
23
24
|
assert(rest.length <= source.length);
|
|
24
25
|
return nodes && rest.length < source.length
|
|
@@ -2,7 +2,7 @@ import { Parser } from '../parser';
|
|
|
2
2
|
import { sequence } from './sequence';
|
|
3
3
|
import { inspect } from '../../../debug.test';
|
|
4
4
|
|
|
5
|
-
describe('Unit: combinator/sequence', () => {
|
|
5
|
+
describe('Unit: combinator/data/parser/sequence', () => {
|
|
6
6
|
describe('sequence', () => {
|
|
7
7
|
const a: Parser<string, never> = source => {
|
|
8
8
|
return source && source[0] === 'a'
|
|
@@ -2,8 +2,8 @@ import { undefined } from 'spica/global';
|
|
|
2
2
|
import { Parser, Ctx, Tree, Context, SubParsers, SubTree, eval, exec, check } from '../parser';
|
|
3
3
|
import { push } from 'spica/array';
|
|
4
4
|
|
|
5
|
-
export function sequence<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
-
export function sequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
|
|
5
|
+
export function sequence<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
+
export function sequence<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
|
|
7
7
|
assert(parsers.every(f => f));
|
|
8
8
|
if (parsers.length === 1) return parsers[0];
|
|
9
9
|
return (source, context) => {
|
|
@@ -11,14 +11,15 @@ export function sequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D
|
|
|
11
11
|
let nodes: T[] | undefined;
|
|
12
12
|
for (let i = 0, len = parsers.length; i < len; ++i) {
|
|
13
13
|
if (rest === '') return;
|
|
14
|
+
if (context.delimiters?.match(rest, context.precedence)) return;
|
|
14
15
|
const result = parsers[i](rest, context);
|
|
15
16
|
assert(check(rest, result));
|
|
16
17
|
if (!result) return;
|
|
17
|
-
assert(!context?.delimiters?.match(rest, context.precedence));
|
|
18
18
|
nodes = nodes
|
|
19
19
|
? push(nodes, eval(result))
|
|
20
20
|
: eval(result);
|
|
21
21
|
rest = exec(result);
|
|
22
|
+
if (resume?.(eval(result), exec(result)) === false) return;
|
|
22
23
|
}
|
|
23
24
|
assert(rest.length <= source.length);
|
|
24
25
|
return nodes && rest.length < source.length
|
|
@@ -3,7 +3,7 @@ import { union } from './union';
|
|
|
3
3
|
import { some } from './some';
|
|
4
4
|
import { inspect } from '../../../debug.test';
|
|
5
5
|
|
|
6
|
-
describe('Unit: combinator/some', () => {
|
|
6
|
+
describe('Unit: combinator/data/parser/some', () => {
|
|
7
7
|
describe('some', () => {
|
|
8
8
|
const a: Parser<string, never> = source => {
|
|
9
9
|
return source && source[0] === 'a'
|
|
@@ -1,51 +1,28 @@
|
|
|
1
1
|
import { undefined } from 'spica/global';
|
|
2
|
-
import { Parser,
|
|
3
|
-
import {
|
|
2
|
+
import { Parser, eval, exec, check } from '../parser';
|
|
3
|
+
import { Delimiters } from './context/delimiter';
|
|
4
4
|
import { push } from 'spica/array';
|
|
5
5
|
|
|
6
6
|
type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return signature('');
|
|
12
|
-
case 'string':
|
|
13
|
-
return `s:${pattern}`;
|
|
14
|
-
case 'object':
|
|
15
|
-
return `r/${pattern.source}/${pattern.flags}`;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
const matcher = memoize(
|
|
19
|
-
(pattern: string | RegExp | undefined): (source: string) => true | undefined => {
|
|
20
|
-
switch (typeof pattern) {
|
|
21
|
-
case 'undefined':
|
|
22
|
-
return () => undefined;
|
|
23
|
-
case 'string':
|
|
24
|
-
return source => source.slice(0, pattern.length) === pattern || undefined;
|
|
25
|
-
case 'object':
|
|
26
|
-
return reduce(source => pattern.test(source) || undefined);
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
signature);
|
|
30
|
-
|
|
31
|
-
export function some<P extends Parser<unknown>>(parser: P, until?: string | RegExp | number, deeps?: readonly DelimiterOption[], limit?: number): P;
|
|
32
|
-
export function some<T>(parser: Parser<T>, until?: string | RegExp | number, deeps: readonly DelimiterOption[] = [], limit = -1): Parser<T> {
|
|
33
|
-
if (typeof until === 'number') return some(parser, undefined, deeps, until);
|
|
8
|
+
export function some<P extends Parser<unknown>>(parser: P, end?: string | RegExp | number, delimiters?: readonly DelimiterOption[], limit?: number): P;
|
|
9
|
+
export function some<T>(parser: Parser<T>, end?: string | RegExp | number, delimiters: readonly DelimiterOption[] = [], limit = -1): Parser<T> {
|
|
10
|
+
if (typeof end === 'number') return some(parser, undefined, delimiters, end);
|
|
34
11
|
assert(parser);
|
|
35
|
-
assert([
|
|
36
|
-
const match = matcher(
|
|
37
|
-
const
|
|
38
|
-
signature: signature(delimiter),
|
|
39
|
-
matcher: matcher(delimiter),
|
|
12
|
+
assert([end].concat(delimiters.map(o => o[0])).every(d => d instanceof RegExp ? !d.flags.match(/[gmy]/) && d.source.startsWith('^') : true));
|
|
13
|
+
const match = Delimiters.matcher(end);
|
|
14
|
+
const delims = delimiters.map(([delimiter, precedence]) => ({
|
|
15
|
+
signature: Delimiters.signature(delimiter),
|
|
16
|
+
matcher: Delimiters.matcher(delimiter),
|
|
40
17
|
precedence,
|
|
41
18
|
}));
|
|
42
19
|
return (source, context) => {
|
|
43
20
|
if (source === '') return;
|
|
44
21
|
let rest = source;
|
|
45
22
|
let nodes: T[] | undefined;
|
|
46
|
-
if (
|
|
23
|
+
if (delims.length > 0) {
|
|
47
24
|
context.delimiters ??= new Delimiters();
|
|
48
|
-
context.delimiters.push(...
|
|
25
|
+
context.delimiters.push(...delims);
|
|
49
26
|
}
|
|
50
27
|
while (true) {
|
|
51
28
|
if (rest === '') break;
|
|
@@ -60,8 +37,8 @@ export function some<T>(parser: Parser<T>, until?: string | RegExp | number, dee
|
|
|
60
37
|
rest = exec(result);
|
|
61
38
|
if (limit >= 0 && source.length - rest.length > limit) break;
|
|
62
39
|
}
|
|
63
|
-
if (
|
|
64
|
-
context.delimiters!.pop(
|
|
40
|
+
if (delims.length > 0) {
|
|
41
|
+
context.delimiters!.pop(delims.length);
|
|
65
42
|
}
|
|
66
43
|
assert(rest.length <= source.length);
|
|
67
44
|
return nodes && rest.length < source.length
|
|
@@ -2,7 +2,7 @@ import { Parser } from '../parser';
|
|
|
2
2
|
import { subsequence } from './subsequence';
|
|
3
3
|
import { inspect } from '../../../debug.test';
|
|
4
4
|
|
|
5
|
-
describe('Unit: combinator/subsequence', () => {
|
|
5
|
+
describe('Unit: combinator/data/parser/subsequence', () => {
|
|
6
6
|
describe('subsequence', () => {
|
|
7
7
|
const a: Parser<string, never> = source => {
|
|
8
8
|
return source && source[0] === 'a'
|
|
@@ -2,12 +2,12 @@ import { Parser, Ctx, Tree, Context, SubParsers, SubTree } from '../parser';
|
|
|
2
2
|
import { union } from './union';
|
|
3
3
|
import { inits } from './inits';
|
|
4
4
|
|
|
5
|
-
export function subsequence<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
-
export function subsequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
|
|
5
|
+
export function subsequence<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
+
export function subsequence<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
|
|
7
7
|
assert(parsers.every(f => f));
|
|
8
8
|
return union(
|
|
9
9
|
parsers.map((_, i) =>
|
|
10
10
|
i + 1 < parsers.length
|
|
11
|
-
? inits([parsers[i], subsequence(parsers.slice(i + 1))])
|
|
11
|
+
? inits([parsers[i], subsequence(parsers.slice(i + 1), resume)], resume)
|
|
12
12
|
: parsers[i]) as D);
|
|
13
13
|
}
|
|
@@ -2,7 +2,7 @@ import { Parser, Ctx, Tree, Context, SubParsers, SubTree } from '../parser';
|
|
|
2
2
|
import { union } from './union';
|
|
3
3
|
import { sequence } from './sequence';
|
|
4
4
|
|
|
5
|
-
export function tails<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
-
export function tails<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
|
|
7
|
-
return union(parsers.map((_, i) => sequence(parsers.slice(i))) as D);
|
|
5
|
+
export function tails<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
|
|
6
|
+
export function tails<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
|
|
7
|
+
return union(parsers.map((_, i) => sequence(parsers.slice(i), resume)) as D);
|
|
8
8
|
}
|
|
@@ -2,7 +2,7 @@ import { Parser } from '../parser';
|
|
|
2
2
|
import { union } from './union';
|
|
3
3
|
import { inspect } from '../../../debug.test';
|
|
4
4
|
|
|
5
|
-
describe('Unit: combinator/union', () => {
|
|
5
|
+
describe('Unit: combinator/data/parser/union', () => {
|
|
6
6
|
describe('union', () => {
|
|
7
7
|
const a: Parser<string, never> = source => {
|
|
8
8
|
return source && source[0] === 'a'
|