securemark 0.257.3 → 0.258.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.js +1216 -606
  3. package/markdown.d.ts +1 -12
  4. package/package.json +9 -9
  5. package/src/combinator/control/manipulation/convert.ts +8 -4
  6. package/src/combinator/control/manipulation/scope.ts +10 -2
  7. package/src/combinator/data/parser/context/delimiter.ts +70 -0
  8. package/src/combinator/data/parser/context/memo.ts +30 -0
  9. package/src/combinator/{control/manipulation → data/parser}/context.test.ts +9 -9
  10. package/src/combinator/data/parser/context.ts +161 -0
  11. package/src/combinator/data/parser/sequence.test.ts +1 -1
  12. package/src/combinator/data/parser/sequence.ts +1 -1
  13. package/src/combinator/data/parser/some.test.ts +1 -1
  14. package/src/combinator/data/parser/some.ts +14 -37
  15. package/src/combinator/data/parser/subsequence.test.ts +1 -1
  16. package/src/combinator/data/parser/union.test.ts +1 -1
  17. package/src/combinator/data/parser.ts +7 -47
  18. package/src/combinator.ts +1 -2
  19. package/src/parser/api/bind.ts +5 -5
  20. package/src/parser/api/parse.ts +3 -1
  21. package/src/parser/block/blockquote.ts +1 -1
  22. package/src/parser/block/dlist.ts +4 -10
  23. package/src/parser/block/extension/figure.ts +4 -3
  24. package/src/parser/block/extension/table.ts +2 -2
  25. package/src/parser/block/heading.ts +5 -13
  26. package/src/parser/block/ilist.ts +3 -2
  27. package/src/parser/block/olist.ts +10 -7
  28. package/src/parser/block/paragraph.ts +1 -1
  29. package/src/parser/block/reply/cite.ts +1 -1
  30. package/src/parser/block/reply/quote.ts +1 -1
  31. package/src/parser/block/reply.ts +1 -1
  32. package/src/parser/block/sidefence.ts +1 -1
  33. package/src/parser/block/table.ts +9 -9
  34. package/src/parser/block/ulist.ts +4 -3
  35. package/src/parser/block.ts +1 -1
  36. package/src/parser/context.ts +32 -0
  37. package/src/parser/header.ts +1 -1
  38. package/src/parser/inline/annotation.ts +9 -17
  39. package/src/parser/inline/autolink/email.ts +1 -1
  40. package/src/parser/inline/autolink/url.ts +1 -1
  41. package/src/parser/inline/autolink.ts +5 -3
  42. package/src/parser/inline/bracket.ts +16 -15
  43. package/src/parser/inline/code.ts +1 -1
  44. package/src/parser/inline/comment.ts +4 -3
  45. package/src/parser/inline/deletion.ts +5 -4
  46. package/src/parser/inline/emphasis.ts +5 -4
  47. package/src/parser/inline/emstrong.ts +5 -4
  48. package/src/parser/inline/extension/index.ts +7 -14
  49. package/src/parser/inline/extension/indexee.ts +8 -10
  50. package/src/parser/inline/extension/indexer.ts +4 -3
  51. package/src/parser/inline/extension/label.ts +3 -2
  52. package/src/parser/inline/extension/placeholder.ts +5 -4
  53. package/src/parser/inline/html.ts +5 -4
  54. package/src/parser/inline/htmlentity.ts +1 -1
  55. package/src/parser/inline/insertion.ts +5 -4
  56. package/src/parser/inline/link.ts +11 -20
  57. package/src/parser/inline/mark.ts +5 -4
  58. package/src/parser/inline/math.ts +1 -1
  59. package/src/parser/inline/media.ts +8 -7
  60. package/src/parser/inline/reference.ts +10 -16
  61. package/src/parser/inline/ruby.ts +4 -3
  62. package/src/parser/inline/shortmedia.ts +3 -2
  63. package/src/parser/inline/strong.ts +5 -4
  64. package/src/parser/inline/template.test.ts +1 -1
  65. package/src/parser/inline/template.ts +9 -6
  66. package/src/parser/locale.ts +6 -7
  67. package/src/parser/processor/footnote.ts +5 -3
  68. package/src/parser/source/text.ts +1 -1
  69. package/src/parser/util.ts +0 -220
  70. package/src/parser/visibility.ts +205 -0
  71. package/src/util/info.ts +4 -2
  72. package/src/util/quote.ts +12 -15
  73. package/src/util/toc.ts +14 -17
  74. package/webpack.config.js +1 -0
  75. package/src/combinator/control/manipulation/context.ts +0 -70
  76. 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.257.3",
3
+ "version": "0.258.0",
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.28.0",
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.17.0",
41
+ "eslint": "^8.18.0",
42
42
  "eslint-plugin-redos": "^4.4.1",
43
- "eslint-webpack-plugin": "^3.1.1",
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.0.1",
52
+ "npm-check-updates": "^14.1.1",
53
53
  "semver": "^7.3.7",
54
- "spica": "0.0.570",
55
- "ts-loader": "^9.3.0",
56
- "typed-dom": "^0.0.299",
57
- "typescript": "4.7.3",
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
- return source === ''
10
- ? [[], '']
11
- : parser(source, context);
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
+ context.memo = undefined;
16
18
  const result = parser(src, context);
17
19
  assert(check(src, result));
20
+ context.memo = memo;
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
+ context.memo = undefined;
39
46
  const res2 = parser(src, context);
40
47
  assert(check(src, res2));
48
+ context.memo = memo;
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,30 @@
1
+ import { splice } from 'spica/array';
2
+
3
+ export class Memo {
4
+ private memory: Record<string, readonly [any[], number]>[/* pos */] = [];
5
+ public get length(): number {
6
+ return this.memory.length;
7
+ }
8
+ public get(
9
+ position: number,
10
+ rule: number,
11
+ syntax: number,
12
+ state: number,
13
+ ): readonly [any[], number] | undefined {
14
+ return this.memory[position - 1]?.[`${rule}:${syntax}:${state}`];
15
+ }
16
+ public set(
17
+ position: number,
18
+ rule: number,
19
+ syntax: number,
20
+ state: number,
21
+ nodes: any[],
22
+ offset: number,
23
+ ): void {
24
+ const record = this.memory[position - 1] ??= {};
25
+ record[`${rule}:${syntax}:${state}`] = [nodes, offset];
26
+ }
27
+ public clear(position: number): void {
28
+ splice(this.memory, position, this.memory.length - position);
29
+ }
30
+ }
@@ -1,11 +1,11 @@
1
- import { Parser, Ctx } from '../../data/parser';
2
- import { some } from '../../data/parser/some';
1
+ import { Parser, Ctx } from '../parser';
2
+ import { some } from './some';
3
3
  import { reset, context } from './context';
4
- import { creator } from './resource';
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
- state?: boolean;
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.state!], s.slice(1)]));
40
+ (s, context) => [[context.status!], s.slice(1)]));
41
41
 
42
42
  it('', () => {
43
- const base: Context = { state: true };
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.state === undefined);
47
+ assert(ctx.status === undefined);
48
48
  assert.throws(() => reset(base, parser)('1', ctx));
49
49
  assert(ctx.resources?.budget === 0);
50
- assert(ctx.state === undefined);
50
+ assert(ctx.status === undefined);
51
51
  });
52
52
 
53
53
  });
@@ -0,0 +1,161 @@
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
+ const r = context.rule ?? 0;
69
+ context.rule = r | syntax;
70
+ context.backtrackable ??= ~0;
71
+ context.state ??= 0;
72
+ const p = context.precedence;
73
+ context.precedence = precedence;
74
+ const { resources = { budget: 1, recursion: 1 } } = context;
75
+ if (resources.budget <= 0) throw new Error('Too many creations');
76
+ if (resources.recursion <= 0) throw new Error('Too much recursion');
77
+ --resources.recursion;
78
+ const pos = source.length;
79
+ const cache = context.memo?.get(pos, context.rule, syntax, context.state);
80
+ const result: Result<T> = cache
81
+ ? [cache[0], source.slice(cache[1])]
82
+ : parser!(source, context);
83
+ ++resources.recursion;
84
+ if (result) {
85
+ if (!cache) {
86
+ assert(cost = cost as number);
87
+ resources.budget -= cost;
88
+ }
89
+ if (syntax ) {
90
+ if (r & context.backtrackable) {
91
+ context.memo ??= new Memo();
92
+ cache ?? context.memo.set(pos, context.rule, syntax, context.state, eval(result), source.length - exec(result).length);
93
+ assert.deepStrictEqual(cache && cache, cache && context.memo.get(pos, context.rule, syntax, context.state));
94
+ }
95
+ else if (context.memo?.length! >= pos) {
96
+ assert(!(r & context.backtrackable));
97
+ context.memo!.clear(pos);
98
+ }
99
+ }
100
+ }
101
+ context.precedence = p;
102
+ context.rule = r;
103
+ return result;
104
+ };
105
+ }
106
+
107
+ export function creator<P extends Parser<unknown>>(parser: P): P;
108
+ export function creator<P extends Parser<unknown>>(cost: number, parser: P): P;
109
+ export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
110
+ if (typeof cost === 'function') return creator(1, cost);
111
+ assert(cost >= 0);
112
+ return (source, context) => {
113
+ const { resources = { budget: 1, recursion: 1 } } = context;
114
+ if (resources.budget <= 0) throw new Error('Too many creations');
115
+ if (resources.recursion <= 0) throw new Error('Too much recursion');
116
+ --resources.recursion;
117
+ const result = parser!(source, context);
118
+ ++resources.recursion;
119
+ if (result) {
120
+ resources.budget -= cost;
121
+ }
122
+ return result;
123
+ };
124
+ }
125
+
126
+ export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
127
+ export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
128
+ return (source, context) => {
129
+ const p = context.precedence;
130
+ context.precedence = precedence;
131
+ const result = parser(source, context);
132
+ context.precedence = p;
133
+ return result;
134
+ };
135
+ }
136
+
137
+ export function guard<P extends Parser<unknown>>(f: (context: Context<P>) => boolean | number, parser: P): P;
138
+ export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T>): Parser<T> {
139
+ return (source, context) =>
140
+ f(context)
141
+ ? parser(source, context)
142
+ : undefined;
143
+ }
144
+
145
+ export function state<P extends Parser<unknown>>(state: number, parser: P): P;
146
+ export function state<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
147
+ export function state<T>(state: number, positive: boolean | Parser<T>, parser?: Parser<T>): Parser<T> {
148
+ if (typeof positive === 'function') {
149
+ parser = positive;
150
+ positive = true;
151
+ }
152
+ return (source, context) => {
153
+ const s = context.state ?? 0;
154
+ context.state = positive
155
+ ? s | state
156
+ : s & ~state;
157
+ const result = parser!(source, context);
158
+ context.state = s;
159
+ return result;
160
+ };
161
+ }
@@ -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'
@@ -11,7 +11,7 @@ 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)) break;
14
+ if (context.delimiters?.match(rest, context.precedence)) return;
15
15
  const result = parsers[i](rest, context);
16
16
  assert(check(rest, result));
17
17
  if (!result) return;
@@ -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, Delimiters, eval, exec, check } from '../parser';
3
- import { memoize, reduce } from 'spica/memoize';
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
- const signature = (pattern: string | RegExp | undefined): string => {
9
- switch (typeof pattern) {
10
- case 'undefined':
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([until].concat(deeps.map(o => o[0])).every(d => d instanceof RegExp ? !d.flags.match(/[gmy]/) && d.source.startsWith('^') : true));
36
- const match = matcher(until);
37
- const delimiters = deeps.map(([delimiter, precedence]) => ({
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 (delimiters.length > 0) {
23
+ if (delims.length > 0) {
47
24
  context.delimiters ??= new Delimiters();
48
- context.delimiters.push(...delimiters);
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 (delimiters.length > 0) {
64
- context.delimiters!.pop(delimiters.length);
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,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'
@@ -1,3 +1,6 @@
1
+ import { Delimiters } from './parser/context/delimiter';
2
+ import { Memo } from './parser/context/memo';
3
+
1
4
  export type Parser<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
2
5
  = (source: string, context: C) => Result<T, C, D>;
3
6
  export type Result<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
@@ -11,6 +14,10 @@ export interface Ctx {
11
14
  };
12
15
  precedence?: number;
13
16
  delimiters?: Delimiters;
17
+ state?: number;
18
+ rule?: number;
19
+ backtrackable?: number;
20
+ memo?: Memo;
14
21
  }
15
22
  export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
16
23
  export type SubParsers<P extends Parser<unknown>> = P extends Parser<unknown, Ctx, infer D> ? D : never;
@@ -20,53 +27,6 @@ export type IntermediateParser<P extends Parser<unknown>> = Parser<SubTree<P>, C
20
27
  type ExtractSubTree<D extends Parser<unknown>[]> = ExtractSubParser<D> extends infer T ? T extends Parser<infer U> ? U : never : never;
21
28
  type ExtractSubParser<D extends Parser<unknown>[]> = D extends (infer P)[] ? P extends Parser<unknown> ? P : never : never;
22
29
 
23
- export class Delimiters {
24
- private readonly matchers: [number, string, number, (source: string) => boolean | undefined][] = [];
25
- private readonly registry: Record<string, boolean> = {};
26
- private length = 0;
27
- public push(
28
- ...delimiters: readonly {
29
- readonly signature: string;
30
- readonly matcher: (source: string) => boolean | undefined;
31
- readonly precedence?: number;
32
- }[]
33
- ): void {
34
- for (let i = 0; i < delimiters.length; ++i) {
35
- const delimiter = delimiters[i];
36
- assert(this.length >= this.matchers.length);
37
- const { signature, matcher, precedence = 1 } = delimiter;
38
- if (!this.registry[signature]) {
39
- this.matchers.unshift([this.length, signature, precedence, matcher]);
40
- this.registry[signature] = true;
41
- }
42
- ++this.length;
43
- }
44
- }
45
- public pop(count = 1): void {
46
- assert(count > 0);
47
- for (let i = 0; i < count; ++i) {
48
- assert(this.matchers.length > 0);
49
- assert(this.length >= this.matchers.length);
50
- if (--this.length === this.matchers[0][0]) {
51
- this.registry[this.matchers.shift()![1]] = false;
52
- }
53
- }
54
- }
55
- public match(source: string, precedence = 1): boolean {
56
- const { matchers } = this;
57
- for (let i = 0; i < matchers.length; ++i) {
58
- switch (matchers[i][3](source)) {
59
- case true:
60
- if (precedence < matchers[i][2]) return true;
61
- continue;
62
- case false:
63
- return false;
64
- }
65
- }
66
- return false;
67
- }
68
- }
69
-
70
30
  export { eval_ as eval };
71
31
  function eval_<T>(result: NonNullable<Result<T>>, default_?: T[]): T[];
72
32
  function eval_<T>(result: Result<T>, default_: T[]): T[];
package/src/combinator.ts CHANGED
@@ -4,14 +4,13 @@ export * from './combinator/data/parser/tails';
4
4
  export * from './combinator/data/parser/sequence';
5
5
  export * from './combinator/data/parser/subsequence';
6
6
  export * from './combinator/data/parser/some';
7
+ export * from './combinator/data/parser/context';
7
8
  export * from './combinator/control/constraint/block';
8
9
  export * from './combinator/control/constraint/line';
9
10
  export * from './combinator/control/constraint/contract';
10
11
  export * from './combinator/control/manipulation/fence';
11
12
  export * from './combinator/control/manipulation/indent';
12
13
  export * from './combinator/control/manipulation/scope';
13
- export * from './combinator/control/manipulation/context';
14
- export * from './combinator/control/manipulation/resource';
15
14
  export * from './combinator/control/manipulation/surround';
16
15
  export * from './combinator/control/manipulation/match';
17
16
  export * from './combinator/control/manipulation/convert';