securemark 0.298.5 → 0.298.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.298.5",
3
+ "version": "0.298.7",
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",
@@ -161,13 +161,20 @@ export function matcher(pattern: string | RegExp, advance: boolean, after?: Pars
161
161
  const count = typeof pattern === 'object'
162
162
  ? /[^^\\*+][*+]|{\d+,}/.test(pattern.source)
163
163
  : false;
164
+ let sid = 0, pos = 0, index = -1;
164
165
  switch (typeof pattern) {
165
166
  case 'string':
166
167
  if (pattern === '') return () => new List([new Node(pattern)]);
167
168
  return input => {
168
169
  const context = input;
169
- const { source, position } = context;
170
- if (!source.startsWith(pattern, position)) return;
170
+ const { SID, source, position } = context;
171
+ const hit = SID === sid && position === pos;
172
+ index = hit
173
+ ? index
174
+ : source.startsWith(pattern, position) ? position : -1;
175
+ sid = SID;
176
+ pos = position;
177
+ if (index === -1) return;
171
178
  if (advance) {
172
179
  context.position += pattern.length;
173
180
  }
@@ -180,13 +187,19 @@ export function matcher(pattern: string | RegExp, advance: boolean, after?: Pars
180
187
  assert(pattern.sticky);
181
188
  return input => {
182
189
  const context = input;
183
- const { source, position } = context;
190
+ const { SID, source, position } = context;
191
+ const hit = SID === sid && position === pos;
184
192
  pattern.lastIndex = position;
185
- if (!pattern.test(source)) return;
186
- const src = source.slice(position, pattern.lastIndex);
187
- count && consume(src.length, context);
193
+ index = hit
194
+ ? index
195
+ : pattern.test(source) ? pattern.lastIndex : -1;
196
+ sid = SID;
197
+ pos = position;
198
+ if (index === -1) return;
199
+ const src = source.slice(position, index);
200
+ count && !hit && consume(src.length, context);
188
201
  if (advance) {
189
- context.position += src.length;
202
+ context.position = index;
190
203
  }
191
204
  const next = after?.(input);
192
205
  return after
@@ -201,13 +214,20 @@ export function tester(pattern: string | RegExp, advance: boolean, after?: Parse
201
214
  const count = typeof pattern === 'object'
202
215
  ? /[^^\\*+][*+]|{\d+,}/.test(pattern.source)
203
216
  : false;
217
+ let sid = 0, pos = 0, index = -1;
204
218
  switch (typeof pattern) {
205
219
  case 'string':
206
220
  if (pattern === '') return () => new List();
207
221
  return input => {
208
222
  const context = input;
209
- const { source, position } = context;
210
- if (!source.startsWith(pattern, position)) return;
223
+ const { SID, source, position } = context;
224
+ const hit = SID === sid && position === pos;
225
+ index = hit
226
+ ? index
227
+ : source.startsWith(pattern, position) ? position : -1;
228
+ sid = SID;
229
+ pos = position;
230
+ if (index === -1) return;
211
231
  if (advance) {
212
232
  context.position += pattern.length;
213
233
  }
@@ -218,13 +238,19 @@ export function tester(pattern: string | RegExp, advance: boolean, after?: Parse
218
238
  assert(pattern.sticky);
219
239
  return input => {
220
240
  const context = input;
221
- const { source, position } = context;
241
+ const { SID, source, position } = context;
242
+ const hit = SID === sid && position === pos;
222
243
  pattern.lastIndex = position;
223
- if (!pattern.test(source)) return;
224
- const len = pattern.lastIndex - position;
225
- count && consume(len, context);
244
+ index = hit
245
+ ? index
246
+ : pattern.test(source) ? pattern.lastIndex : -1;
247
+ sid = SID;
248
+ pos = position;
249
+ if (index === -1) return;
250
+ const len = index - position;
251
+ count && !hit && consume(len, context);
226
252
  if (advance) {
227
- context.position += len;
253
+ context.position = index;
228
254
  }
229
255
  if (after && after(input) === undefined) return;
230
256
  return new List();
@@ -122,13 +122,15 @@ export function recursions(rs: readonly number[], parser: Parser): Parser {
122
122
  assert(recursions.length > 0);
123
123
  for (const recursion of rs) {
124
124
  const rec = min(recursion, recursions.length - 1);
125
- if (rec >= 0 && recursions[rec] < 1) throw new Error('Too much recursion');
126
- rec >= 0 && --recursions[rec];
125
+ if (rec === -1) continue;
126
+ if (recursions[rec] < 1) throw new Error('Too much recursion');
127
+ --recursions[rec];
127
128
  }
128
129
  const result = parser(input);
129
130
  for (const recursion of rs) {
130
131
  const rec = min(recursion, recursions.length - 1);
131
- rec >= 0 && ++recursions[rec];
132
+ if (rec === -1) continue;
133
+ ++recursions[rec];
132
134
  }
133
135
  return result;
134
136
  };
@@ -38,8 +38,10 @@ export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp
38
38
  for (const len = source.length; context.position < len;) {
39
39
  if (match(input)) break;
40
40
  if (context.delimiters.test(input)) break;
41
+ const pos = context.position;
41
42
  const result = parser(input);
42
43
  if (result === undefined) break;
44
+ if (context.position === pos) break;
43
45
  nodes = nodes?.import(result) ?? result;
44
46
  if (limit >= 0 && context.position - position > limit) break;
45
47
  }
@@ -10,12 +10,9 @@ export function union<N, D extends Parser<N>[]>(parsers: D): Parser<N, Context,
10
10
  return parsers[0];
11
11
  default:
12
12
  return eval([
13
- '((',
14
- parsers.map((_, i) => `parser${i},`).join(''),
15
- ') =>',
13
+ '(', parsers.map((_, i) => `parser${i},`).join(''), ') =>',
16
14
  'input =>',
17
15
  parsers.map((_, i) => `|| parser${i}(input)`).join('').slice(2),
18
- ')',
19
16
  ].join(''))(...parsers);
20
17
  }
21
18
  }
@@ -26,6 +26,10 @@ export class Node<N> implements List.Node {
26
26
  public next?: this = undefined;
27
27
  public prev?: this = undefined;
28
28
  }
29
+ let SID = 0;
30
+ function sid(): number {
31
+ return SID = ++SID >>> 0 || 1;
32
+ }
29
33
  export class Context {
30
34
  constructor(
31
35
  {
@@ -54,6 +58,7 @@ export class Context {
54
58
  this.offset = offset ?? 0;
55
59
  this.backtracks = backtracks ?? {};
56
60
  }
61
+ public SID: number = sid();
57
62
  public source: string;
58
63
  public position: number;
59
64
  public segment: number;
@@ -110,6 +115,7 @@ export const enum Segment {
110
115
  }
111
116
 
112
117
  export function input<C extends Context>(source: string, context: C): Input<C> {
118
+ context.SID = sid();
113
119
  context.source = source;
114
120
  context.position = 0;
115
121
  return context;
@@ -118,6 +124,7 @@ export function input<C extends Context>(source: string, context: C): Input<C> {
118
124
  export function subinput<C extends Context>(source: string, context: C): Input<C> {
119
125
  return {
120
126
  ...context,
127
+ SID: sid(),
121
128
  source,
122
129
  position: 0,
123
130
  offset: 0,
@@ -297,15 +297,6 @@ describe('Unit: parser/api/parse', () => {
297
297
  });
298
298
 
299
299
  it('recursion', () => {
300
- //assert.deepStrictEqual(
301
- // [...parse(`${'{ '.repeat(20)}0`).children].map(el => el.tagName),
302
- // ['P']);
303
- //assert.deepStrictEqual(
304
- // [...parse(`${'{ '.repeat(21)}0`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
305
- // [
306
- // '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
307
- // `<pre class="error" translate="no">${'{ '.repeat(21)}0</pre>`,
308
- // ]);
309
300
  assert.deepStrictEqual(
310
301
  [...parse(`${'['.repeat(20)}0`).children].map(el => el.outerHTML),
311
302
  [`<p>${'['.repeat(20)}0</p>`]);
@@ -327,24 +318,12 @@ describe('Unit: parser/api/parse', () => {
327
318
  assert.deepStrictEqual(
328
319
  [...parse(`${'(('.repeat(3)}0${'))'.repeat(3)}`).children].map(el => el.tagName),
329
320
  ['H1', 'PRE']);
330
- assert.deepStrictEqual(
331
- [...parse(`${'(('.repeat(2)}!${'))'.repeat(2)}`).children].map(el => el.tagName),
332
- ['P', 'OL']);
333
- assert.deepStrictEqual(
334
- [...parse(`${'(('.repeat(3)}!${'))'.repeat(3)}`).children].map(el => el.tagName),
335
- ['H1', 'PRE']);
336
321
  assert.deepStrictEqual(
337
322
  [...parse(`(${'(('.repeat(2)}0${'))'.repeat(2)}`).children].map(el => el.tagName),
338
323
  ['P', 'OL']);
339
324
  assert.deepStrictEqual(
340
325
  [...parse(`(${'(('.repeat(3)}0${'))'.repeat(3)}`).children].map(el => el.tagName),
341
326
  ['H1', 'PRE']);
342
- assert.deepStrictEqual(
343
- [...parse(`(${'(('.repeat(2)}!${'))'.repeat(2)}`).children].map(el => el.tagName),
344
- ['P', 'OL']);
345
- assert.deepStrictEqual(
346
- [...parse(`(${'(('.repeat(3)}!${'))'.repeat(3)}`).children].map(el => el.tagName),
347
- ['H1', 'PRE']);
348
327
  assert.deepStrictEqual(
349
328
  [...parse(`${'(('.repeat(2)}0${'))'.repeat(2)}${'(('.repeat(2)}0${'))'.repeat(2)}`).children].map(el => el.tagName),
350
329
  ['P', 'OL']);
@@ -357,6 +336,9 @@ describe('Unit: parser/api/parse', () => {
357
336
  assert.deepStrictEqual(
358
337
  [...parse(`${'(('.repeat(2)}0${'))'.repeat(2)}${'(('.repeat(9)}0${'))'.repeat(3)}`).children].map(el => el.tagName),
359
338
  ['H1', 'PRE']);
339
+ assert.deepStrictEqual(
340
+ [...parse(`${'(('.repeat(3)}0))((1))))))`).children].map(el => el.tagName),
341
+ ['H1', 'PRE']);
360
342
  });
361
343
 
362
344
  it('recovery', () => {
@@ -28,7 +28,6 @@ export class Context extends Ctx {
28
28
  20 || Recursion.blockquote,
29
29
  40 || Recursion.listitem,
30
30
  20 || Recursion.inline,
31
- 20 || Recursion.annotation,
32
31
  20 || Recursion.bracket,
33
32
  20 || Recursion.terminal,
34
33
  ],
@@ -116,7 +115,6 @@ export const enum Recursion {
116
115
  blockquote,
117
116
  listitem,
118
117
  inline,
119
- annotation,
120
118
  bracket,
121
119
  terminal,
122
120
  }
@@ -10,18 +10,15 @@ describe('Unit: parser/inline/annotation', () => {
10
10
 
11
11
  it('invalid', () => {
12
12
  assert.deepStrictEqual(inspect(parser, input('', new Context())), undefined);
13
- assert.deepStrictEqual(inspect(parser, input('(', new Context())), undefined);
14
- assert.deepStrictEqual(inspect(parser, input('()', new Context())), undefined);
15
- assert.deepStrictEqual(inspect(parser, input('((', new Context())), undefined);
13
+ assert.deepStrictEqual(inspect(parser, input('(', new Context())), [['<span class="paren">(</span>'], '']);
14
+ assert.deepStrictEqual(inspect(parser, input('()', new Context())), [ [ '<span class="paren">()</span>' ], '' ]);
15
+ assert.deepStrictEqual(inspect(parser, input('((', new Context())), [ [ '<span class="paren">(<span class="paren">(</span></span>' ], '' ]);
16
16
  assert.deepStrictEqual(inspect(parser, input('(())', new Context())), [['<span class="paren">(<span class="paren">()</span>)</span>'], '']);
17
17
  assert.deepStrictEqual(inspect(parser, input('(()))', new Context())), [['<span class="paren">(<span class="paren">()</span>)</span>'], ')']);
18
18
  assert.deepStrictEqual(inspect(parser, input('(("))', new Context())), [['<span class="paren">(<span class="paren">("))</span></span>'], '']);
19
19
  assert.deepStrictEqual(inspect(parser, input('((a', new Context())), [['<span class="paren">(<span class="paren">(a</span></span>'], '']);
20
- assert.deepStrictEqual(inspect(parser, input('((!', new Context())), [['<span class="paren">(<span class="paren">(!</span></span>'], '']);
21
20
  assert.deepStrictEqual(inspect(parser, input('((a)', new Context())), [['<span class="paren">(<span class="paren">(a)</span></span>'], '']);
22
- assert.deepStrictEqual(inspect(parser, input('((!)', new Context())), [['<span class="paren">(<span class="paren">(!)</span></span>'], '']);
23
21
  assert.deepStrictEqual(inspect(parser, input('((a)b)', new Context())), [['<span class="paren">(<span class="paren">(a)</span>b)</span>'], '']);
24
- assert.deepStrictEqual(inspect(parser, input('((!)b)', new Context())), [['<span class="paren">(<span class="paren">(!)</span>b)</span>'], '']);
25
22
  assert.deepStrictEqual(inspect(parser, input('(([))', new Context())), [['<span class="paren">(<span class="paren">([))</span></span>'], '']);
26
23
  assert.deepStrictEqual(inspect(parser, input('(([%))', new Context())), [['<span class="paren">(<span class="paren">([%))</span></span>'], '']);
27
24
  assert.deepStrictEqual(inspect(parser, input('(( ))', new Context())), undefined);
@@ -39,11 +36,10 @@ describe('Unit: parser/inline/annotation', () => {
39
36
  assert.deepStrictEqual(inspect(parser, input('((*a\nb*))', new Context())), [['<span class="bracket">(<span class="bracket">(<em>a<br>b</em>)</span>)</span>'], '']);
40
37
  assert.deepStrictEqual(inspect(parser, input('((\\))', new Context())), [['<span class="paren">(<span class="paren">())</span></span>'], '']);
41
38
  assert.deepStrictEqual(inspect(parser, input('(((a))', new Context())), [['<span class="paren">(<sup class="annotation"><span>a</span></sup></span>'], '']);
42
- assert.deepStrictEqual(inspect(parser, input('(((!))', new Context())), [['<span class="paren">(<sup class="annotation"><span>!</span></sup></span>'], '']);
43
- assert.deepStrictEqual(inspect(parser, input('(((*a*))', new Context())), [['<span class="paren">(<sup class="annotation"><span><em>a</em></span></sup></span>'], '']);
39
+ assert.deepStrictEqual(inspect(parser, input('((((a)))', new Context())), [['<span class="paren">(<sup class="annotation"><span><span class="paren">(a)</span></span></sup></span>'], '']);
44
40
  assert.deepStrictEqual(inspect(parser, input('(((((a))))', new Context())), [['<span class="paren">(<sup class="annotation"><span><sup class="annotation"><span>a</span></sup></span></sup></span>'], '']);
45
- assert.deepStrictEqual(inspect(parser, input('(((((!))))', new Context())), [['<span class="paren">(<sup class="annotation"><span><sup class="annotation"><span>!</span></sup></span></sup></span>'], '']);
46
- assert.deepStrictEqual(inspect(parser, input('(((((*a*))))', new Context())), [['<span class="paren">(<sup class="annotation"><span><sup class="annotation"><span><em>a</em></span></sup></span></sup></span>'], '']);
41
+ assert.deepStrictEqual(inspect(parser, input('((((((a)))b)))', new Context())), [['<sup class="annotation"><span><span class="paren">(<sup class="annotation"><span><span class="paren">(a)</span></span></sup>b)</span></span></sup>'], '']);
42
+ assert.deepStrictEqual(inspect(parser, input('(((((((a)))b)))', new Context())), [['<span class="paren">(<sup class="annotation"><span><span class="paren">(<sup class="annotation"><span><span class="paren">(a)</span></span></sup>b)</span></span></sup></span>'], '']);
47
43
  assert.deepStrictEqual(inspect(parser, input(' ((a))', new Context())), undefined);
48
44
  });
49
45
 
@@ -1,9 +1,10 @@
1
1
  import { AnnotationParser } from '../inline';
2
- import { State, Recursion } from '../context';
2
+ import { State, Recursion, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { union, some, recursions, precedence, constraint, surround, open, lazy } from '../../combinator';
4
+ import { union, some, precedence, constraint, surround, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
6
  import { bracketname } from './bracket';
7
+ import { repeat } from '../repeat';
7
8
  import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
8
9
  import { unwrap } from '../util';
9
10
  import { html, defrag } from 'typed-dom/dom';
@@ -23,118 +24,35 @@ import { html, defrag } from 'typed-dom/dom';
23
24
  // 修正する必要があるためほぼ完全な二重処理が必要になり三重以上の注釈という不適切な使用のために
24
25
  // 常に非常に非効率な処理を行い常時低速化するより三重以上の注釈を禁止して効率性を維持するのが妥当である。
25
26
  const MAX_DEPTH = 20;
26
- export const annotation: AnnotationParser = lazy(() => constraint(State.annotation, surround(
27
- open('((', beforeNonblank),
28
- precedence(1, recursions([Recursion.annotation, Recursion.inline, Recursion.bracket, Recursion.bracket],
29
- some(union([inline]), ')', [[')', 1]]))),
30
- '))',
31
- false, [],
32
- ([, ns], context) => {
33
- const { linebreak, recursion, resources } = context;
34
- if (linebreak !== 0) {
35
- ns.unshift(new Node('('));
36
- ns.push(new Node(')'));
37
- return new List([
38
- new Node(html('span',
39
- { class: bracketname(context, 1, 1) },
40
- ['(', html('span', { class: bracketname(context, 2, 2) }, defrag(unwrap(ns))), ')']))
41
- ]);
42
- }
43
- const depth = MAX_DEPTH - (resources?.recursions[Recursion.annotation] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
44
- recursion.add(depth);
45
- return new List([
46
- new Node(html('sup', { class: 'annotation' }, [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))
47
- ]);
48
- },
49
- ([, bs], context) => {
50
- const { source, position, linebreak, recursion, resources } = context;
51
- const depth = MAX_DEPTH - (resources?.recursions[Recursion.annotation] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
52
- if (linebreak === 0 && bs && bs.length === 1 && source[position] === ')' && typeof bs.head?.value === 'object') {
53
- const { className } = bs.head.value;
54
- if (className === 'paren' || className === 'bracket') {
55
- const { firstChild, lastChild } = bs.head.value;
56
- assert(firstChild instanceof Text);
57
- if (firstChild!.nodeValue!.length === 1) {
58
- firstChild!.remove();
59
- }
60
- else {
61
- firstChild!.nodeValue = firstChild!.nodeValue!.slice(1);
62
- }
63
- assert(lastChild instanceof Text);
64
- if (lastChild!.nodeValue!.length === 1) {
65
- lastChild!.remove();
66
- }
67
- else {
68
- lastChild!.nodeValue = lastChild!.nodeValue!.slice(0, -1);
69
- }
70
- context.position += 1;
71
- recursion.add(depth);
72
- return new List([
73
- new Node(html('span',
74
- { class: 'paren' },
75
- ['(', html('sup', { class: 'annotation' }, [html('span', bs.head.value.childNodes)])]))
76
- ]);
77
- }
78
- if (className === 'annotation' && deepunwrap(bs)) {
79
- context.position += 1;
80
- recursion.add(depth);
27
+ export const annotation: AnnotationParser = lazy(() => constraint(State.annotation,
28
+ repeat('(', beforeNonblank, ')', [Recursion.bracket], precedence(1, surround(
29
+ '',
30
+ some(union([inline]), ')', [[')', 1]]),
31
+ ')',
32
+ false, [],
33
+ ([, bs], { buffer }) => buffer.import(bs),
34
+ ([, bs], { buffer }) => bs && buffer.import(bs).push(new Node(Command.Cancel)) && buffer)),
35
+ (nodes, context, lead, follow) => {
36
+ const { linebreak, recursion, resources } = context;
37
+ if (linebreak !== 0 || nodes.length === 0 || lead === 0 || follow % 2 === 0) {
38
+ nodes.unshift(new Node('('));
39
+ nodes.push(new Node(')'));
81
40
  return new List([
82
- new Node(html('span',
83
- { class: 'paren' },
84
- ['(', html('sup', { class: 'annotation' }, [html('span', [bs.head.value])])]))
41
+ new Node(html('span', { class: bracketname(context, 1, 1) }, defrag(unwrap(nodes))))
85
42
  ]);
86
43
  }
87
- }
88
- bs ??= new List();
89
- bs.unshift(new Node('('));
90
- if (source[context.position] === ')') {
91
- bs.push(new Node(')'));
92
- context.position += 1;
93
- context.range += 1;
94
- }
95
- bs = new List([
96
- new Node(html('span',
97
- { class: bracketname(context, 2, context.position - position) },
98
- defrag(unwrap(bs))))
99
- ]);
100
- bs.unshift(new Node('('));
101
- const cs = parser(context);
102
- if (source[context.position] === ')') {
103
- cs && bs.import(cs);
104
- bs.push(new Node(')'));
44
+ recursion.add(MAX_DEPTH - (resources?.recursions[Recursion.bracket] ?? resources?.recursions.at(-1) ?? MAX_DEPTH));
105
45
  context.position += 1;
106
- context.range += 1;
107
- }
108
- return new List([new Node(html('span',
109
- { class: bracketname(context, 1, context.position - position) },
110
- defrag(unwrap(bs))))]);
111
- })));
112
-
113
- const parser = lazy(() => precedence(1, some(inline, ')', [[')', 1]])));
114
-
115
- function deepunwrap(list: List<Node<string | HTMLElement>>): boolean {
116
- let bottom = list.head!.value as HTMLElement;
117
- for (; bottom;) {
118
- const el = bottom.firstChild!.firstChild;
119
- if (el !== el?.parentNode?.lastChild) break;
120
- if (el instanceof HTMLElement === false) break;
121
- if (el?.className !== 'annotation') break;
122
- bottom = el;
123
- }
124
- const el = bottom.firstChild!.firstChild;
125
- if (el instanceof Element === false) return false;
126
- if (el === el?.parentNode?.lastChild) {
127
- const { className, firstChild, lastChild } = el;
128
- if (className === 'paren' || className === 'bracket') {
129
- firstChild!.nodeValue!.length === 1
130
- ? firstChild!.remove()
131
- : firstChild!.nodeValue = firstChild!.nodeValue!.slice(1);
132
- lastChild!.nodeValue!.length === 1
133
- ? lastChild!.remove()
134
- : lastChild!.nodeValue = lastChild!.nodeValue!.slice(0, -1);
135
- el.replaceWith(...el.childNodes);
136
- return true;
137
- }
138
- }
139
- return false;
140
- }
46
+ return new List([
47
+ new Node(html('sup', { class: 'annotation' }, [html('span', defrag(unwrap(trimBlankNodeEnd(nodes))))]))
48
+ ]);
49
+ },
50
+ (nodes, context, prefix, postfix) => {
51
+ assert(postfix === 0);
52
+ for (let i = 0; i < prefix; ++i) {
53
+ nodes.unshift(new Node('('));
54
+ nodes = new List([new Node(html('span', { class: bracketname(context, 1, 0) }, defrag(unwrap(nodes))))]);
55
+ context.range += 1;
56
+ }
57
+ return nodes;
58
+ })));
@@ -83,6 +83,7 @@ describe('Unit: parser/inline/autolink/url', () => {
83
83
  assert.deepStrictEqual(inspect(parser, input(' http://host>', new Context())), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '>']);
84
84
  assert.deepStrictEqual(inspect(parser, input(' http://host(', new Context())), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '(']);
85
85
  assert.deepStrictEqual(inspect(parser, input(' http://host)', new Context())), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ')']);
86
+ assert.deepStrictEqual(inspect(parser, input(` http://host${'+'.repeat(5e5)}`, new Context())), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '+'.repeat(5e5)]);
86
87
  });
87
88
 
88
89
  it('trailing entities', () => {
@@ -1,14 +1,15 @@
1
1
  import { DeletionParser } from '../inline';
2
2
  import { Recursion, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { union, some, recursion, precedence, surround, open, lazy } from '../../combinator';
4
+ import { union, some, precedence, surround, open, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
+ import { repeat } from '../repeat';
6
7
  import { blankWith } from '../visibility';
7
- import { unwrap, repeat } from '../util';
8
+ import { unwrap } from '../util';
8
9
  import { html, defrag } from 'typed-dom/dom';
9
10
 
10
11
  export const deletion: DeletionParser = lazy(() =>
11
- precedence(0, repeat('~~', '', recursion(Recursion.inline, surround(
12
+ repeat('~~', '', '~~', [Recursion.inline], precedence(0, surround(
12
13
  '',
13
14
  some(union([
14
15
  some(inline, blankWith('\n', '~~')),
@@ -18,4 +19,4 @@ export const deletion: DeletionParser = lazy(() =>
18
19
  false, [],
19
20
  ([, bs], { buffer }) => buffer.import(bs),
20
21
  ([, bs], { buffer }) => bs && buffer.import(bs).push(new Node(Command.Cancel)) && buffer)),
21
- nodes => new List([new Node(html('del', defrag(unwrap(nodes))))]))));
22
+ nodes => new List([new Node(html('del', defrag(unwrap(nodes))))])));
@@ -1,13 +1,14 @@
1
1
  import { EmStrongParser, EmphasisParser, StrongParser } from '../inline';
2
2
  import { Recursion, Command } from '../context';
3
3
  import { Parser, Result, List, Node } from '../../combinator/data/parser';
4
- import { union, some, recursion, precedence, surround, lazy, bind } from '../../combinator';
4
+ import { union, some, precedence, surround, lazy, bind } from '../../combinator';
5
5
  import { inline } from '../inline';
6
6
  import { strong } from './strong';
7
7
  import { emphasis } from './emphasis';
8
8
  import { strs } from '../source';
9
+ import { repeat } from '../repeat';
9
10
  import { beforeNonblank, afterNonblank } from '../visibility';
10
- import { unwrap, repeat } from '../util';
11
+ import { unwrap } from '../util';
11
12
  import { html, defrag } from 'typed-dom/dom';
12
13
 
13
14
  const substrong: Parser.IntermediateParser<StrongParser> = lazy(() => some(union([
@@ -23,7 +24,7 @@ const subemphasis: Parser.IntermediateParser<EmphasisParser> = lazy(() => some(u
23
24
  // 可能な限り早く閉じるよう解析しなければならない。
24
25
  // このため終端記号の後ろを見て終端を中止し同じ構文を再帰的に適用してはならない。
25
26
  export const emstrong: EmStrongParser = lazy(() =>
26
- precedence(0, repeat('***', beforeNonblank, recursion(Recursion.inline, surround(
27
+ repeat('***', beforeNonblank, '***', [Recursion.inline], precedence(0, surround(
27
28
  '',
28
29
  some(union([some(inline, '*', afterNonblank)])),
29
30
  strs('*', 1, 3),
@@ -80,6 +81,7 @@ export const emstrong: EmStrongParser = lazy(() =>
80
81
  nodes => new List([new Node(html('em', [html('strong', defrag(unwrap(nodes)))]))]),
81
82
  (nodes, context, prefix, postfix, state) => {
82
83
  context.position += postfix;
84
+ context.range += postfix;
83
85
  assert(postfix < 3);
84
86
  if (state) {
85
87
  switch (postfix) {
@@ -96,6 +98,7 @@ export const emstrong: EmStrongParser = lazy(() =>
96
98
  }
97
99
  prefix -= postfix;
98
100
  postfix -= postfix;
101
+ context.range += postfix;
99
102
  switch (prefix) {
100
103
  case 0:
101
104
  break;
@@ -114,6 +117,7 @@ export const emstrong: EmStrongParser = lazy(() =>
114
117
  })
115
118
  (context) ?? prepend('*', nodes);
116
119
  prefix -= 1;
120
+ context.range += 1;
117
121
  break;
118
122
  case 2:
119
123
  nodes = bind<StrongParser>(
@@ -130,14 +134,17 @@ export const emstrong: EmStrongParser = lazy(() =>
130
134
  })
131
135
  (context) ?? prepend('**', nodes);
132
136
  prefix -= 2;
137
+ context.range += 2;
133
138
  break;
134
139
  }
135
140
  }
136
141
  if (prefix > postfix) {
137
142
  nodes = prepend('*'.repeat(prefix - postfix), nodes);
143
+ prefix = 0;
144
+ context.range += prefix - postfix;
138
145
  }
139
146
  return nodes;
140
- })));
147
+ }));
141
148
 
142
149
  function prepend<N>(prefix: string, nodes: List<Node<N>>): List<Node<N>> {
143
150
  if (typeof nodes.head?.value === 'string') {
@@ -15,7 +15,7 @@ import { html } from 'typed-dom/dom';
15
15
  export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => surround(
16
16
  // ^はabbrで使用済みだが^:などのようにして分離使用可能
17
17
  str(/\[[:^|]/y, beforeNonblank),
18
- precedence(1, recursion(Recursion.inline,
18
+ precedence(1, recursion(Recursion.bracket,
19
19
  some(union([inline]), ']', [[']', 1]]))),
20
20
  str(']'),
21
21
  false,
@@ -1,14 +1,15 @@
1
1
  import { InsertionParser } from '../inline';
2
2
  import { Recursion, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { union, some, recursion, precedence, surround, open, lazy } from '../../combinator';
4
+ import { union, some, precedence, surround, open, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
+ import { repeat } from '../repeat';
6
7
  import { blankWith } from '../visibility';
7
- import { unwrap, repeat } from '../util';
8
+ import { unwrap } from '../util';
8
9
  import { html, defrag } from 'typed-dom/dom';
9
10
 
10
11
  export const insertion: InsertionParser = lazy(() =>
11
- precedence(0, repeat('++', '', recursion(Recursion.inline, surround(
12
+ repeat('++', '', '++', [Recursion.inline], precedence(0, surround(
12
13
  '',
13
14
  some(union([
14
15
  some(inline, blankWith('\n', '++')),
@@ -18,4 +19,4 @@ export const insertion: InsertionParser = lazy(() =>
18
19
  false, [],
19
20
  ([, bs], { buffer }) => buffer.import(bs),
20
21
  ([, bs], { buffer }) => bs && buffer.import(bs).push(new Node(Command.Cancel)) && buffer)),
21
- nodes => new List([new Node(html('ins', defrag(unwrap(nodes))))]))));
22
+ nodes => new List([new Node(html('ins', defrag(unwrap(nodes))))])));
@@ -1,21 +1,22 @@
1
1
  import { ItalicParser } from '../inline';
2
2
  import { Recursion, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { union, some, recursion, precedence, surround, lazy } from '../../combinator';
4
+ import { union, some, precedence, surround, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
+ import { repeat } from '../repeat';
6
7
  import { beforeNonblank, afterNonblank } from '../visibility';
7
- import { unwrap, repeat } from '../util';
8
+ import { unwrap } from '../util';
8
9
  import { html, defrag } from 'typed-dom/dom';
9
10
 
10
11
  // 可読性のため実際にはオブリーク体を指定する。
11
12
  // 斜体は単語に使うとかえって見づらく読み飛ばしやすくなるため使わないべきであり
12
13
  // ある程度の長さのある文に使うのが望ましい。
13
14
  export const italic: ItalicParser = lazy(() =>
14
- precedence(0, repeat('///', beforeNonblank, recursion(Recursion.inline, surround(
15
+ repeat('///', beforeNonblank, '///', [Recursion.inline], precedence(0, surround(
15
16
  '',
16
17
  some(union([inline]), '///', afterNonblank),
17
18
  '///',
18
19
  false, [],
19
20
  ([, bs], { buffer }) => buffer.import(bs),
20
21
  ([, bs], { buffer }) => bs && buffer.import(bs).push(new Node(Command.Cancel)) && buffer)),
21
- nodes => new List([new Node(html('i', defrag(unwrap(nodes))))]))));
22
+ nodes => new List([new Node(html('i', defrag(unwrap(nodes))))])));