securemark 0.279.1 → 0.280.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 (41) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +10 -10
  3. package/dist/index.js +116 -98
  4. package/markdown.d.ts +1 -1
  5. package/package.json +1 -1
  6. package/src/combinator/data/parser/context/delimiter.ts +49 -21
  7. package/src/combinator/data/parser/context/memo.ts +6 -6
  8. package/src/combinator/data/parser/context.ts +23 -7
  9. package/src/parser/api/parse.test.ts +13 -13
  10. package/src/parser/block/extension/table.ts +1 -1
  11. package/src/parser/block.ts +2 -2
  12. package/src/parser/inline/annotation.ts +4 -4
  13. package/src/parser/inline/autolink/account.ts +1 -1
  14. package/src/parser/inline/autolink/url.ts +1 -1
  15. package/src/parser/inline/autolink.ts +2 -2
  16. package/src/parser/inline/bracket.test.ts +3 -2
  17. package/src/parser/inline/bracket.ts +8 -9
  18. package/src/parser/inline/deletion.ts +4 -4
  19. package/src/parser/inline/emphasis.ts +4 -4
  20. package/src/parser/inline/emstrong.ts +4 -4
  21. package/src/parser/inline/extension/index.test.ts +6 -0
  22. package/src/parser/inline/extension/index.ts +11 -11
  23. package/src/parser/inline/extension/placeholder.ts +4 -4
  24. package/src/parser/inline/html.test.ts +1 -1
  25. package/src/parser/inline/html.ts +5 -5
  26. package/src/parser/inline/insertion.ts +4 -4
  27. package/src/parser/inline/link.ts +6 -6
  28. package/src/parser/inline/mark.ts +4 -4
  29. package/src/parser/inline/media.ts +4 -4
  30. package/src/parser/inline/reference.ts +3 -3
  31. package/src/parser/inline/remark.test.ts +1 -0
  32. package/src/parser/inline/remark.ts +4 -4
  33. package/src/parser/inline/ruby.ts +2 -2
  34. package/src/parser/inline/strong.ts +4 -4
  35. package/src/parser/inline/template.ts +7 -4
  36. package/src/parser/inline.test.ts +2 -5
  37. package/src/parser/source/str.ts +0 -19
  38. package/src/parser/source/text.test.ts +1 -1
  39. package/src/parser/source/text.ts +3 -3
  40. package/src/parser/source.ts +1 -1
  41. package/src/parser/visibility.ts +3 -1
package/markdown.d.ts CHANGED
@@ -784,8 +784,8 @@ export namespace MarkdownParser {
784
784
  // [#index|signature]
785
785
  Inline<'extension/index'>,
786
786
  Parser<HTMLAnchorElement, Context, [
787
- IndexParser.SignatureParser,
788
787
  InlineParser,
788
+ IndexParser.SignatureParser,
789
789
  ]> {
790
790
  }
791
791
  export namespace IndexParser {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.279.1",
3
+ "version": "0.280.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",
@@ -1,5 +1,12 @@
1
1
  import { memoize, reduce } from 'spica/memoize';
2
2
 
3
+ interface Delimiter {
4
+ readonly index: number;
5
+ readonly signature: string;
6
+ readonly matcher: (source: string) => boolean | undefined;
7
+ readonly precedence: number;
8
+ }
9
+
3
10
  export class Delimiters {
4
11
  public static signature(pattern: string | RegExp | undefined): string {
5
12
  switch (typeof pattern) {
@@ -23,43 +30,64 @@ export class Delimiters {
23
30
  }
24
31
  },
25
32
  this.signature);
26
- private readonly matchers: [number, string, number, (source: string) => boolean | undefined][] = [];
27
- private readonly registry: Record<string, boolean> = {};
28
- private length = 0;
33
+ private readonly registry = memoize<(signature: string) => Delimiter[]>(() => []);
34
+ private readonly delimiters: Delimiter[] = [];
35
+ private readonly order: number[] = [];
29
36
  public push(
30
- ...delimiters: readonly {
37
+ ...delims: readonly {
31
38
  readonly signature: string;
32
39
  readonly matcher: (source: string) => boolean | undefined;
33
40
  readonly precedence?: number;
34
41
  }[]
35
42
  ): 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
+ const { registry, delimiters, order } = this;
44
+ for (let i = 0; i < delims.length; ++i) {
45
+ const { signature, matcher, precedence = 1 } = delims[i];
46
+ const stack = registry(signature);
47
+ const index = stack[0]?.index ?? delimiters.length;
48
+ if (stack.length === 0 || precedence > delimiters[index].precedence) {
49
+ const delimiter: Delimiter = {
50
+ index,
51
+ signature,
52
+ matcher,
53
+ precedence,
54
+ };
55
+ delimiters[index] = delimiter;
56
+ stack.push(delimiter);
57
+ order.push(index);
58
+ }
59
+ else {
60
+ order.push(-1);
43
61
  }
44
- ++this.length;
45
62
  }
46
63
  }
47
64
  public pop(count = 1): void {
48
65
  assert(count > 0);
66
+ const { registry, delimiters, order } = this;
49
67
  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;
68
+ assert(this.order.length > 0);
69
+ const index = order.pop()!;
70
+ if (index === -1) continue;
71
+ const stack = registry(delimiters[index].signature);
72
+ assert(stack.length > 0);
73
+ if (stack.length === 1) {
74
+ assert(index === delimiters.length - 1);
75
+ assert(stack[0] === delimiters.at(-1));
76
+ stack.pop();
77
+ delimiters.pop();
78
+ }
79
+ else {
80
+ stack.pop();
81
+ delimiters[index] = stack.at(-1)!;
54
82
  }
55
83
  }
56
84
  }
57
85
  public match(source: string, precedence = 1): boolean {
58
- const { matchers } = this;
59
- for (let i = 0; i < matchers.length; ++i) {
60
- const matcher = matchers[i];
61
- if (precedence >= matcher[2]) continue;
62
- switch (matcher[3](source)) {
86
+ const { delimiters } = this;
87
+ for (let i = 0; i < delimiters.length; ++i) {
88
+ const delimiter = delimiters[i];
89
+ if (precedence >= delimiter.precedence) continue;
90
+ switch (delimiter.matcher(source)) {
63
91
  case true:
64
92
  return true;
65
93
  case false:
@@ -3,7 +3,7 @@ export class Memo {
3
3
  this.targets = targets;
4
4
  }
5
5
  public readonly targets: number;
6
- private readonly memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
6
+ private readonly memory: Record<number, Record<number, readonly [any[], number] | readonly []>>[/* pos */] = [];
7
7
  public get length(): number {
8
8
  return this.memory.length;
9
9
  }
@@ -12,8 +12,8 @@ export class Memo {
12
12
  syntax: number,
13
13
  state: number,
14
14
  ): readonly [any[], number] | readonly [] | undefined {
15
- //console.log('get', position, syntax, state, this.memory[position - 1]?.[`${syntax}:${state}`]);;
16
- const cache = this.memory[position - 1]?.[`${syntax}:${state}`];
15
+ //console.log('get', position, syntax, state, this.memory[position - 1]?.[syntax]?.[state]);
16
+ const cache = this.memory[position - 1]?.[syntax]?.[state];
17
17
  return cache?.length === 2
18
18
  ? [cache[0].slice(), cache[1]]
19
19
  : cache;
@@ -26,11 +26,11 @@ export class Memo {
26
26
  offset: number,
27
27
  ): void {
28
28
  const record = this.memory[position - 1] ??= {};
29
- assert(!record[`${syntax}:${state}`]);
30
- record[`${syntax}:${state}`] = nodes
29
+ assert(!record[syntax]?.[state]);
30
+ (record[syntax] ??= {})[state] = nodes
31
31
  ? [nodes.slice(), offset]
32
32
  : [];
33
- //console.log('set', position, syntax, state, record[`${syntax}:${state}`]);
33
+ //console.log('set', position, syntax, state, record[syntax]?.[state]);
34
34
  }
35
35
  public clear(position: number): void {
36
36
  const memory = this.memory;
@@ -66,9 +66,9 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: read
66
66
  return result;
67
67
  }
68
68
 
69
- export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, state: number, parser: P): P;
70
- export function syntax<T>(syntax: number, prec: number, cost: number, state: number, parser?: Parser<T>): Parser<T> {
71
- return creation(cost, precedence(prec, ({ source, context }) => {
69
+ export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, state: number, parser: P): P;
70
+ export function syntax<T>(syntax: number, prec: number, state: number, parser?: Parser<T>): Parser<T> {
71
+ return precedence(prec, ({ source, context }) => {
72
72
  if (source === '') return;
73
73
  const memo = context.memo ??= new Memo();
74
74
  context.offset ??= 0;
@@ -91,7 +91,7 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
91
91
  }
92
92
  context.state = stateOuter;
93
93
  return result;
94
- }));
94
+ });
95
95
  }
96
96
 
97
97
  export function creation<P extends Parser<unknown>>(parser: P): P;
@@ -100,11 +100,13 @@ export function creation<P extends Parser<unknown>>(cost: number, recursion: boo
100
100
  export function creation(cost: number | Parser<unknown>, recursion?: boolean | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
101
101
  if (typeof cost === 'function') return creation(1, true, cost);
102
102
  if (typeof recursion === 'function') return creation(cost, true, recursion);
103
- assert(cost > 0);
103
+ assert(cost >= 0);
104
+ assert(recursion !== undefined);
104
105
  return ({ source, context }) => {
105
- const resources = context.resources ?? { clock: 1, recursion: 1 };
106
+ assert([recursion = recursion!]);
107
+ const resources = context.resources ?? { clock: cost || 1, recursion: 1 };
106
108
  if (resources.clock <= 0) throw new Error('Too many creations');
107
- if (resources.recursion <= 0) throw new Error('Too much recursion');
109
+ if (resources.recursion < +recursion) throw new Error('Too much recursion');
108
110
  recursion && --resources.recursion;
109
111
  const result = parser!({ source, context });
110
112
  recursion && ++resources.recursion;
@@ -171,3 +173,17 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
171
173
  return result;
172
174
  };
173
175
  }
176
+
177
+ //export function log<P extends Parser<unknown>>(log: number, parser: P, cond?: (ns: readonly Tree<P>[]) => boolean): P;
178
+ //export function log<T>(log: number, parser: Parser<T>, cond: (ns: readonly T[]) => boolean = () => true): Parser<T> {
179
+ // assert(log);
180
+ // return ({ source, context }) => {
181
+ // const l = context.log ?? 0;
182
+ // context.log = 0;
183
+ // const result = parser!({ source, context });
184
+ // context.log = result && cond(eval(result))
185
+ // ? l | log
186
+ // : l;
187
+ // return result;
188
+ // };
189
+ //}
@@ -298,17 +298,17 @@ describe('Unit: parser/api/parse', () => {
298
298
 
299
299
  it('recursion', () => {
300
300
  assert.deepStrictEqual(
301
- [...parse('{'.repeat(20)).children].map(el => el.outerHTML),
302
- [`<p>${'{'.repeat(20)}</p>`]);
301
+ [...parse('{'.repeat(21)).children].map(el => el.outerHTML),
302
+ [`<p>${'{'.repeat(21)}</p>`]);
303
303
  assert.deepStrictEqual(
304
- [...parse('{'.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
304
+ [...parse('{'.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
305
305
  [
306
306
  '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
307
- `<pre class="error" translate="no">${'{'.repeat(21)}</pre>`,
307
+ `<pre class="error" translate="no">${'{'.repeat(22)}</pre>`,
308
308
  ]);
309
309
  assert.deepStrictEqual(
310
- [...parse('('.repeat(20)).children].map(el => el.outerHTML),
311
- [`<p>${'('.repeat(20)}</p>`]);
310
+ [...parse('('.repeat(21)).children].map(el => el.outerHTML),
311
+ [`<p>${'('.repeat(21)}</p>`]);
312
312
  assert.deepStrictEqual(
313
313
  [...parse('('.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
314
314
  [
@@ -316,8 +316,8 @@ describe('Unit: parser/api/parse', () => {
316
316
  `<pre class="error" translate="no">${'('.repeat(22)}</pre>`,
317
317
  ]);
318
318
  assert.deepStrictEqual(
319
- [...parse('['.repeat(20)).children].map(el => el.outerHTML),
320
- [`<p>${'['.repeat(20)}</p>`]);
319
+ [...parse('['.repeat(21)).children].map(el => el.outerHTML),
320
+ [`<p>${'['.repeat(21)}</p>`]);
321
321
  assert.deepStrictEqual(
322
322
  [...parse('['.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
323
323
  [
@@ -325,8 +325,8 @@ describe('Unit: parser/api/parse', () => {
325
325
  `<pre class="error" translate="no">${'['.repeat(22)}</pre>`,
326
326
  ]);
327
327
  assert.deepStrictEqual(
328
- [...parse('['.repeat(17) + '\na').children].map(el => el.outerHTML),
329
- [`<p>${'['.repeat(17)}<br>a</p>`]);
328
+ [...parse('['.repeat(20) + '\na').children].map(el => el.outerHTML),
329
+ [`<p>${'['.repeat(20)}<br>a</p>`]);
330
330
  });
331
331
 
332
332
  if (!navigator.userAgent.includes('Chrome')) return;
@@ -335,15 +335,15 @@ describe('Unit: parser/api/parse', () => {
335
335
  this.timeout(5000);
336
336
  // 実測500ms程度
337
337
  assert.deepStrictEqual(
338
- [...parse('.'.repeat(50000)).children].map(el => el.outerHTML),
339
- [`<p>${'.'.repeat(50000)}</p>`]);
338
+ [...parse('.'.repeat(20000)).children].map(el => el.outerHTML),
339
+ [`<p>${'.'.repeat(20000)}</p>`]);
340
340
  });
341
341
 
342
342
  it('creation error', function () {
343
343
  this.timeout(5000);
344
344
  // 実測500ms程度
345
345
  assert.deepStrictEqual(
346
- [...parse('.'.repeat(50001)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
346
+ [...parse('.'.repeat(20001)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
347
347
  [
348
348
  '<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
349
349
  `<pre class="error" translate="no">${'.'.repeat(1000).slice(0, 997)}...</pre>`,
@@ -119,7 +119,7 @@ const dataline: CellParser.DatalineParser = creation(1, false, line(
119
119
  convert(source => `: ${source}`, data),
120
120
  ]))));
121
121
 
122
- function attributes(source: string) {
122
+ function attributes(source: string): Record<string, string | undefined> {
123
123
  let [, rowspan = undefined, colspan = undefined, highlight = undefined, extension = undefined] =
124
124
  source.match(/^[#:](?:(\d+)?:(\d+)?)?(?:(!+)([+]?))?$/) ?? [];
125
125
  assert(rowspan?.[0] !== '0');
@@ -36,8 +36,8 @@ export import MediaBlockParser = BlockParser.MediaBlockParser;
36
36
  export import ReplyParser = BlockParser.ReplyParser;
37
37
  export import ParagraphParser = BlockParser.ParagraphParser;
38
38
 
39
- export const block: BlockParser = creation(1, false, error(
40
- reset({ resources: { clock: 50 * 1000, recursion: 20 } },
39
+ export const block: BlockParser = creation(0, false, error(
40
+ reset({ resources: { clock: 20000, recursion: 20 + 1 } },
41
41
  union([
42
42
  emptyline,
43
43
  pagebreak,
@@ -1,15 +1,15 @@
1
1
  import { AnnotationParser } from '../inline';
2
- import { union, some, syntax, constraint, surround, lazy } from '../../combinator';
2
+ import { union, some, syntax, creation, constraint, surround, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { Syntax, State } from '../context';
5
5
  import { trimBlankStart, trimNodeEnd } from '../visibility';
6
6
  import { html, defrag } from 'typed-dom/dom';
7
7
 
8
- export const annotation: AnnotationParser = lazy(() => surround(
8
+ export const annotation: AnnotationParser = lazy(() => creation(surround(
9
9
  '((',
10
10
  constraint(State.annotation, false,
11
- syntax(Syntax.none, 6, 1, State.annotation | State.media,
11
+ syntax(Syntax.none, 6, State.annotation | State.media,
12
12
  trimBlankStart(some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])))),
13
13
  '))',
14
14
  false,
15
- ([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNodeEnd(defrag(ns)))])], rest]));
15
+ ([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNodeEnd(defrag(ns)))])], rest])));
@@ -1,5 +1,5 @@
1
1
  import { AutolinkParser } from '../../inline';
2
- import { union, constraint, tails, rewrite, open, convert, fmap, lazy } from '../../../combinator';
2
+ import { union, tails, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
3
  import { unsafelink } from '../link';
4
4
  import { str } from '../../source';
5
5
  import { State } from '../../context';
@@ -28,5 +28,5 @@ const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creation(prec
28
28
  surround('(', some(union([bracket, unescsource]), ')'), ')', true),
29
29
  surround('[', some(union([bracket, unescsource]), ']'), ']', true),
30
30
  surround('{', some(union([bracket, unescsource]), '}'), '}', true),
31
- surround('"', precedence(8, some(unescsource, '"')), '"', true),
31
+ surround('"', precedence(3, some(unescsource, '"')), '"', true),
32
32
  ]))));
@@ -12,9 +12,9 @@ import { Syntax, State } from '../context';
12
12
  import { stringify } from '../util';
13
13
 
14
14
  export const autolink: AutolinkParser = lazy(() =>
15
- validate(/^(?:[@#>0-9a-z\r\n]|\S[#>])/iu,
15
+ validate(/^(?:[@#>0-9a-z]|\S[#>]|[\r\n]!?https?:\/\/)/iu,
16
16
  constraint(State.autolink, false,
17
- syntax(Syntax.autolink, 1, 1, ~State.shortcut,
17
+ syntax(Syntax.autolink, 1, ~State.shortcut,
18
18
  union([
19
19
  some(union([lineurl])),
20
20
  fmap(some(union([
@@ -48,6 +48,9 @@ describe('Unit: parser/inline/bracket', () => {
48
48
  assert.deepStrictEqual(inspect(parser('(A)')), [['(', 'A', ')'], '']);
49
49
  assert.deepStrictEqual(inspect(parser('(A,B)')), [['(', 'A,B', ')'], '']);
50
50
  assert.deepStrictEqual(inspect(parser('(A、B)')), [['(', 'A、B', ')'], '']);
51
+ assert.deepStrictEqual(inspect(parser('(<bdi>a\\\nb</bdi>)')), [['<span class="paren">(<bdi>a<br>b</bdi>)</span>'], '']);
52
+ assert.deepStrictEqual(inspect(parser('([% a\\\nb %])')), [['<span class="paren">(<span class="remark"><input type="checkbox"><span>[% a<br>b %]</span></span>)</span>'], '']);
53
+ assert.deepStrictEqual(inspect(parser('({{\\\n}})')), [['<span class="paren">(<span class="template">{{\\\n}}</span>)</span>'], '']);
51
54
  });
52
55
 
53
56
  it('[', () => {
@@ -77,8 +80,6 @@ describe('Unit: parser/inline/bracket', () => {
77
80
  assert.deepStrictEqual(inspect(parser('"(")"')), [['"', '(', '"'], ')"']);
78
81
  assert.deepStrictEqual(inspect(parser('"(("')), [['"', '(', '(', '"'], '']);
79
82
  assert.deepStrictEqual(inspect(parser('"(\\")"')), [['"', '<span class="paren">(")</span>', '"'], '']);
80
- assert.deepStrictEqual(inspect(parser('"(\n)"')), [['"', '<span class="paren">(<br>)</span>', '"'], '']);
81
- assert.deepStrictEqual(inspect(parser('"(\\\n)"')), [['"', '<span class="paren">(<br>)</span>', '"'], '']);
82
83
  });
83
84
 
84
85
  });
@@ -1,5 +1,5 @@
1
1
  import { BracketParser } from '../inline';
2
- import { union, some, syntax, surround, lazy } from '../../combinator';
2
+ import { union, some, syntax, creation, surround, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { str } from '../source';
5
5
  import { Syntax, State } from '../context';
@@ -9,22 +9,21 @@ import { html, defrag } from 'typed-dom/dom';
9
9
  const index = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*/;
10
10
 
11
11
  export const bracket: BracketParser = lazy(() => union([
12
- surround(str('('), syntax(Syntax.none, 2, 1, State.none, str(index)), str(')')),
13
- surround(str('('), syntax(Syntax.bracket, 2, 1, State.none, some(inline, ')', [[')', 2]])), str(')'), true,
12
+ surround(str('('), creation(syntax(Syntax.none, 2, State.none, str(index))), str(')')),
13
+ surround(str('('), creation(syntax(Syntax.bracket, 2, State.none, some(inline, ')', [[/^\\?\n/, 3], [')', 2]]))), str(')'), true,
14
14
  ([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
15
15
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
16
- surround(str('('), syntax(Syntax.none, 2, 1, State.none, str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0))))), str(')')),
17
- surround(str('('), syntax(Syntax.bracket, 2, 1, State.none, some(inline, ')', [[')', 2]])), str(')'), true,
16
+ surround(str('('), creation(syntax(Syntax.none, 2, State.none, str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0)))))), str(')')),
17
+ surround(str('('), creation(syntax(Syntax.bracket, 2, State.none, some(inline, ')', [[/^\\?\n/, 3], [')', 2]]))), str(')'), true,
18
18
  ([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
19
19
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
20
- surround(str('['), syntax(Syntax.bracket, 2, 1, State.none, some(inline, ']', [[']', 2]])), str(']'), true,
20
+ surround(str('['), creation(syntax(Syntax.bracket, 2, State.none, some(inline, ']', [[/^\\?\n/, 3], [']', 2]]))), str(']'), true,
21
21
  undefined,
22
22
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
23
- surround(str('{'), syntax(Syntax.bracket, 2, 1, State.none, some(inline, '}', [['}', 2]])), str('}'), true,
23
+ surround(str('{'), creation(syntax(Syntax.bracket, 2, State.none, some(inline, '}', [[/^\\?\n/, 3], ['}', 2]]))), str('}'), true,
24
24
  undefined,
25
25
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
26
- // Control media blinking in editing rather than control confusion of pairs of quote marks.
27
- surround(str('"'), syntax(Syntax.none, 3, 1, State.none, some(inline, '"', [['"', 3]])), str('"'), true,
26
+ surround(str('"'), creation(syntax(Syntax.none, 3, State.none, some(inline, '"', [[/^\\?\n/, 4], ['"', 3]]))), str('"'), true,
28
27
  undefined,
29
28
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
30
29
  ]));
@@ -1,5 +1,5 @@
1
1
  import { DeletionParser } from '../inline';
2
- import { union, some, syntax, surround, open, lazy } from '../../combinator';
2
+ import { union, some, syntax, creation, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { str } from '../source';
5
5
  import { Syntax, State } from '../context';
@@ -7,13 +7,13 @@ import { blankWith } from '../visibility';
7
7
  import { unshift } from 'spica/array';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
 
10
- export const deletion: DeletionParser = lazy(() => surround(
10
+ export const deletion: DeletionParser = lazy(() => creation(surround(
11
11
  str('~~', '~'),
12
- syntax(Syntax.none, 1, 1, State.none,
12
+ syntax(Syntax.none, 1, State.none,
13
13
  some(union([
14
14
  some(inline, blankWith('\n', '~~')),
15
15
  open('\n', some(inline, '~'), true),
16
16
  ]))),
17
17
  str('~~'), false,
18
18
  ([, bs], rest) => [[html('del', defrag(bs))], rest],
19
- ([as, bs], rest) => [unshift(as, bs), rest]));
19
+ ([as, bs], rest) => [unshift(as, bs), rest])));
@@ -1,5 +1,5 @@
1
1
  import { EmphasisParser } from '../inline';
2
- import { union, some, syntax, surround, open, lazy } from '../../combinator';
2
+ import { union, some, syntax, creation, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { emstrong } from './emstrong';
5
5
  import { strong } from './strong';
@@ -9,9 +9,9 @@ import { startTight, blankWith } from '../visibility';
9
9
  import { unshift } from 'spica/array';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
 
12
- export const emphasis: EmphasisParser = lazy(() => surround(
12
+ export const emphasis: EmphasisParser = lazy(() => creation(surround(
13
13
  str('*', '*'),
14
- syntax(Syntax.none, 1, 1, State.none,
14
+ syntax(Syntax.none, 1, State.none,
15
15
  startTight(some(union([
16
16
  strong,
17
17
  some(inline, blankWith('*'), [[/^\\?\n/, 9]]),
@@ -23,4 +23,4 @@ export const emphasis: EmphasisParser = lazy(() => surround(
23
23
  ])))),
24
24
  str('*'), false,
25
25
  ([, bs], rest) => [[html('em', defrag(bs))], rest],
26
- ([as, bs], rest) => [unshift(as, bs), rest]));
26
+ ([as, bs], rest) => [unshift(as, bs), rest])));
@@ -1,6 +1,6 @@
1
1
  import { EmStrongParser, EmphasisParser, StrongParser } from '../inline';
2
2
  import { Result, IntermediateParser } from '../../combinator/data/parser';
3
- import { union, syntax, some, surround, open, lazy, bind } from '../../combinator';
3
+ import { union, syntax, creation, some, surround, open, lazy, bind } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { strong } from './strong';
6
6
  import { emphasis } from './emphasis';
@@ -27,9 +27,9 @@ const subemphasis: IntermediateParser<EmphasisParser> = lazy(() => some(union([
27
27
  ])),
28
28
  ])));
29
29
 
30
- export const emstrong: EmStrongParser = lazy(() => surround(
30
+ export const emstrong: EmStrongParser = lazy(() => creation(surround(
31
31
  str('***'),
32
- syntax(Syntax.none, 1, 1, State.none,
32
+ syntax(Syntax.none, 1, State.none,
33
33
  startTight(some(union([
34
34
  some(inline, blankWith('*'), [[/^\\?\n/, 9]]),
35
35
  open(some(inline, '*', [[/^\\?\n/, 9]]), inline),
@@ -59,4 +59,4 @@ export const emstrong: EmStrongParser = lazy(() => surround(
59
59
  }
60
60
  assert(false);
61
61
  },
62
- ([as, bs], rest) => [unshift(as, bs), rest]));
62
+ ([as, bs], rest) => [unshift(as, bs), rest])));
@@ -20,6 +20,12 @@ describe('Unit: parser/inline/extension/index', () => {
20
20
  assert.deepStrictEqual(inspect(parser('[#\\]')), undefined);
21
21
  assert.deepStrictEqual(inspect(parser('[#a')), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('[#*a\nb*]')), undefined);
23
+ assert.deepStrictEqual(inspect(parser('[#(a\nb)]')), undefined);
24
+ assert.deepStrictEqual(inspect(parser('[#"a\nb"]')), undefined);
25
+ assert.deepStrictEqual(inspect(parser('[#<bdi>a\nb</bdi>]')), undefined);
26
+ assert.deepStrictEqual(inspect(parser('[#[% a\nb %]]')), undefined);
27
+ assert.deepStrictEqual(inspect(parser('[#{{ a\nb }}]')), undefined);
28
+ assert.deepStrictEqual(inspect(parser('[#({{ a\nb }})]')), undefined);
23
29
  assert.deepStrictEqual(inspect(parser('[#a\n|b]')), undefined);
24
30
  assert.deepStrictEqual(inspect(parser('[#a|\n]')), undefined);
25
31
  assert.deepStrictEqual(inspect(parser('[#a|\\\n]')), undefined);
@@ -1,23 +1,23 @@
1
1
  import { ExtensionParser } from '../../inline';
2
- import { union, some, syntax, creation, precedence, constraint, validate, surround, open, lazy, fmap } from '../../../combinator';
2
+ import { union, inits, some, syntax, creation, precedence, constraint, validate, surround, open, lazy, fmap } from '../../../combinator';
3
3
  import { inline } from '../../inline';
4
4
  import { indexee, identity } from './indexee';
5
- import { txt, str, stropt } from '../../source';
5
+ import { txt, str } from '../../source';
6
6
  import { Syntax, State } from '../../context';
7
7
  import { startTight, trimNodeEnd } from '../../visibility';
8
8
  import { html, define, defrag } from 'typed-dom/dom';
9
9
 
10
10
  import IndexParser = ExtensionParser.IndexParser;
11
11
 
12
- export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surround(
12
+ export const index: IndexParser = lazy(() => validate('[#', creation(fmap(indexee(surround(
13
13
  '[#',
14
14
  constraint(State.index, false,
15
- syntax(Syntax.index, 2, 1, State.linkers | State.media,
15
+ syntax(Syntax.index, 2, State.linkers | State.media,
16
16
  startTight(
17
- open(stropt('|'), some(union([
18
- signature,
17
+ some(inits([
19
18
  inline,
20
- ]), ']', [[/^\\?\n/, 9], [']', 2]]), true)))),
19
+ signature,
20
+ ]), ']', [[/^\\?\n/, 9], [']', 2]])))),
21
21
  ']',
22
22
  false,
23
23
  ([, ns], rest) => [[html('a', trimNodeEnd(defrag(ns)))], rest])),
@@ -28,18 +28,18 @@ export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surroun
28
28
  class: 'index',
29
29
  href: el.id ? `#${el.id}` : undefined,
30
30
  }),
31
- ])));
31
+ ]))));
32
32
 
33
- export const signature: IndexParser.SignatureParser = lazy(() => creation(fmap(open(
33
+ export const signature: IndexParser.SignatureParser = lazy(() => validate('|', creation(fmap(open(
34
34
  '|',
35
35
  startTight(some(union([bracket, txt]), ']'))),
36
36
  ns => [
37
37
  html('span', { class: 'indexer', 'data-index': identity(undefined, ns.join(''))!.slice(7) }),
38
- ])));
38
+ ]))));
39
39
 
40
40
  const bracket: IndexParser.SignatureParser.BracketParser = lazy(() => creation(union([
41
41
  surround(str('('), some(union([bracket, txt]), ')'), str(')'), true),
42
42
  surround(str('['), some(union([bracket, txt]), ']'), str(']'), true),
43
43
  surround(str('{'), some(union([bracket, txt]), '}'), str('}'), true),
44
- surround(str('"'), precedence(8, some(txt, '"')), str('"'), true),
44
+ surround(str('"'), precedence(3, some(txt, '"')), str('"'), true),
45
45
  ])));
@@ -1,5 +1,5 @@
1
1
  import { ExtensionParser } from '../../inline';
2
- import { union, some, syntax, validate, surround, lazy } from '../../../combinator';
2
+ import { union, some, syntax, creation, validate, surround, lazy } from '../../../combinator';
3
3
  import { inline } from '../../inline';
4
4
  import { str } from '../../source';
5
5
  import { Syntax, State } from '../../context';
@@ -11,9 +11,9 @@ import { html, defrag } from 'typed-dom/dom';
11
11
 
12
12
  // All syntax surrounded by square brackets shouldn't contain line breaks.
13
13
 
14
- export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate('[', surround(
14
+ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate('[', creation(surround(
15
15
  str(/^\[[:^|]/),
16
- syntax(Syntax.placeholder, 2, 1, State.none,
16
+ syntax(Syntax.placeholder, 2, State.none,
17
17
  startTight(some(union([inline]), ']', [[']', 2]]))),
18
18
  str(']'), false,
19
19
  ([, bs], rest) => [[
@@ -24,4 +24,4 @@ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validat
24
24
  'data-invalid-message': `Invalid start symbol or linebreak`,
25
25
  }, defrag(bs)),
26
26
  ], rest],
27
- ([as, bs], rest) => [unshift(as, bs), rest])));
27
+ ([as, bs], rest) => [unshift(as, bs), rest]))));
@@ -75,7 +75,7 @@ describe('Unit: parser/inline/html', () => {
75
75
  });
76
76
 
77
77
  it('nest', () => {
78
- assert.deepStrictEqual(inspect(parser('<bdi>[% </bdi>')), [['<bdi>[% </bdi>'], '']);
78
+ assert.deepStrictEqual(inspect(parser('<bdi>[% </bdi>')), [['<span class="invalid">&lt;bdi&gt;[% &lt;/bdi&gt;</span>'], '']);
79
79
  assert.deepStrictEqual(inspect(parser('<bdi><bdi>a</bdi></bdi>')), [['<bdi><bdi>a</bdi></bdi>'], '']);
80
80
  assert.deepStrictEqual(inspect(parser('<bdi>a<bdi>b</bdi>c</bdi>')), [['<bdi>a<bdi>b</bdi>c</bdi>'], '']);
81
81
  assert.deepStrictEqual(inspect(parser('<bdi>`a`</bdi>')), [['<bdi><code data-src="`a`">a</code></bdi>'], '']);
@@ -1,5 +1,5 @@
1
1
  import { HTMLParser } from '../inline';
2
- import { union, subsequence, some, syntax, validate, focus, surround, open, match, lazy } from '../../combinator';
2
+ import { union, subsequence, some, syntax, creation, validate, focus, surround, open, match, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { str } from '../source';
5
5
  import { Syntax, State } from '../context';
@@ -18,7 +18,7 @@ const attrspecs = {
18
18
  Object.setPrototypeOf(attrspecs, null);
19
19
  Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
20
20
 
21
- export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^\S\n]|>)/i, syntax(Syntax.none, 5, 1, State.none, union([
21
+ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^\S\n]|>)/i, creation(syntax(Syntax.none, 4, State.none, union([
22
22
  focus(
23
23
  /^<wbr[^\S\n]*>/i,
24
24
  () => [[h('wbr')], '']),
@@ -35,7 +35,7 @@ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^
35
35
  str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true),
36
36
  subsequence([
37
37
  focus(/^[^\S\n]*\n/, some(inline)),
38
- some(open(/^\n?/, some(inline, blankWith('\n', `</${tag}>`), [[blankWith('\n', `</${tag}>`), 5]]), true)),
38
+ some(open(/^\n?/, some(inline, blankWith('\n', `</${tag}>`), [[blankWith('\n', `</${tag}>`), 4]]), true)),
39
39
  ]),
40
40
  str(`</${tag}>`), true,
41
41
  ([as, bs = [], cs], rest) =>
@@ -50,7 +50,7 @@ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^
50
50
  str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true),
51
51
  subsequence([
52
52
  focus(/^[^\S\n]*\n/, some(inline)),
53
- some(inline, `</${tag}>`, [[`</${tag}>`, 5]]),
53
+ some(inline, `</${tag}>`, [[`</${tag}>`, 4]]),
54
54
  ]),
55
55
  str(`</${tag}>`), true,
56
56
  ([as, bs = [], cs], rest) =>
@@ -59,7 +59,7 @@ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^
59
59
  [[elem(tag, as, bs, [])], rest]),
60
60
  ([, tag]) => tag,
61
61
  new Clock(10000))),
62
- ])))));
62
+ ]))))));
63
63
 
64
64
  export const attribute: HTMLParser.AttributeParser = union([
65
65
  str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="[^"\n]*")?(?=[^\S\n]|>)/i),