securemark 0.289.4 → 0.289.6

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.
@@ -7,7 +7,6 @@ export function surround<P extends Parser<unknown>, S = string>(
7
7
  f?: (rss: [S[], SubNode<P>[], S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
8
8
  g?: (rss: [S[], SubNode<P>[], string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
9
9
  backtracks?: readonly number[],
10
- backtrackstate?: number,
11
10
  ): P;
12
11
  export function surround<P extends Parser<unknown>, S = string>(
13
12
  opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>,
@@ -15,7 +14,6 @@ export function surround<P extends Parser<unknown>, S = string>(
15
14
  f?: (rss: [S[], SubNode<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
16
15
  g?: (rss: [S[], SubNode<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
17
16
  backtracks?: readonly number[],
18
- backtrackstate?: number,
19
17
  ): P;
20
18
  export function surround<P extends Parser<unknown>, S = string>(
21
19
  opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
@@ -23,7 +21,6 @@ export function surround<P extends Parser<unknown>, S = string>(
23
21
  f?: (rss: [S[], Node<P>[], S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
24
22
  g?: (rss: [S[], Node<P>[], string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
25
23
  backtracks?: readonly number[],
26
- backtrackstate?: number,
27
24
  ): P;
28
25
  export function surround<P extends Parser<unknown>, S = string>(
29
26
  opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
@@ -31,7 +28,6 @@ export function surround<P extends Parser<unknown>, S = string>(
31
28
  f?: (rss: [S[], Node<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
32
29
  g?: (rss: [S[], Node<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
33
30
  backtracks?: readonly number[],
34
- backtrackstate?: number,
35
31
  ): P;
36
32
  export function surround<N>(
37
33
  opener: string | RegExp | Parser<N>, parser: Parser<N>, closer: string | RegExp | Parser<N>,
@@ -39,7 +35,6 @@ export function surround<N>(
39
35
  f?: (rss: [N[], N[], N[]], rest: string, context: Ctx) => Result<N>,
40
36
  g?: (rss: [N[], N[], string], rest: string, context: Ctx) => Result<N>,
41
37
  backtracks: readonly number[] = [],
42
- backtrackstate: number = 0,
43
38
  ): Parser<N> {
44
39
  switch (typeof opener) {
45
40
  case 'string':
@@ -51,7 +46,6 @@ export function surround<N>(
51
46
  case 'object':
52
47
  closer = match(closer);
53
48
  }
54
- const statesize = 2;
55
49
  return ({ source, context }) => {
56
50
  const sme_ = source;
57
51
  if (sme_ === '') return;
@@ -62,41 +56,18 @@ export function surround<N>(
62
56
  if (resultS === undefined) return void revert(context, linebreak);
63
57
  const nodesS = eval(resultS);
64
58
  const me_ = exec(resultS);
65
- for (const backtrack of backtracks) {
66
- if (backtrack & 1) {
67
- const { backtracks = {}, backtrack: state = 0, offset = 0 } = context;
68
- for (let i = 0; i < source.length - me_.length; ++i) {
69
- if (source[i] !== source[0]) break;
70
- const pos = source.length - i + offset - 1;
71
- assert(pos >= 0);
72
- if (!(pos in backtracks)) continue;
73
- const shift = backtrack >>> statesize & state >>> statesize ? state & (1 << statesize) - 1 : 0;
74
- if (backtracks[pos] & 1 << size(backtrack >>> statesize) + shift) return void revert(context, linebreak);
75
- }
76
- }
77
- }
78
- const { backtrack = 0 } = context;
79
- context.backtrack = backtrack | backtrackstate;
59
+ if (getBacktrack(context, backtracks, sme_, sme_.length - me_.length)) return void revert(context, linebreak);
80
60
  const resultM = me_ !== '' ? parser({ source: me_, context }) : undefined;
81
61
  assert(check(me_, resultM));
82
- context.backtrack = backtrack;
83
62
  const nodesM = eval(resultM);
84
63
  const e_ = exec(resultM, me_);
85
- if (!nodesM && !optional) return void revert(context, linebreak);
86
- const resultE = closer({ source: e_, context });
64
+ const resultE = nodesM || optional ? closer({ source: e_, context }) : undefined;
87
65
  assert(check(e_, resultE, false));
88
66
  const nodesE = eval(resultE);
89
67
  const rest = exec(resultE, e_);
68
+ nodesE || setBacktrack(context, backtracks, sme_.length);
69
+ if (!nodesM && !optional) return void revert(context, linebreak);
90
70
  if (rest.length === sme_.length) return void revert(context, linebreak);
91
- for (const backtrack of backtracks) {
92
- if (backtrack & 2 && nodesE === undefined) {
93
- const { backtracks = {}, backtrack: state = 0, offset = 0 } = context;
94
- const pos = source.length + offset - 1;
95
- assert(pos >= 0);
96
- const shift = backtrack >>> statesize & state >>> statesize ? state & (1 << statesize) - 1 : 0;
97
- backtracks[pos] |= 1 << size(backtrack >>> statesize) + shift;
98
- }
99
- }
100
71
  context.recent = [
101
72
  sme_.slice(0, sme_.length - me_.length),
102
73
  me_.slice(0, me_.length - e_.length),
@@ -118,13 +89,72 @@ export function surround<N>(
118
89
  return result;
119
90
  };
120
91
  }
121
- export function open<P extends Parser<unknown>>(opener: string | RegExp | Parser<Node<P>, Context<P>>, parser: P, optional?: boolean): P;
122
- export function open<N>(opener: string | RegExp | Parser<N>, parser: Parser<N>, optional = false): Parser<N> {
123
- return surround(opener, parser, '', optional);
92
+ export function open<P extends Parser<unknown>>(
93
+ opener: string | RegExp | Parser<Node<P>, Context<P>>,
94
+ parser: P,
95
+ optional?: boolean,
96
+ backtracks?: readonly number[],
97
+ ): P;
98
+ export function open<N>(
99
+ opener: string | RegExp | Parser<N>,
100
+ parser: Parser<N>,
101
+ optional?: boolean,
102
+ backtracks?: readonly number[],
103
+ ): Parser<N> {
104
+ return surround(opener, parser, '', optional, undefined, undefined, backtracks);
105
+ }
106
+ export function close<P extends Parser<unknown>>(
107
+ parser: P,
108
+ closer: string | RegExp | Parser<Node<P>, Context<P>>,
109
+ optional?: boolean,
110
+ backtracks?: readonly number[],
111
+ ): P;
112
+ export function close<N>(
113
+ parser: Parser<N>,
114
+ closer: string | RegExp | Parser<N>,
115
+ optional?: boolean,
116
+ backtracks?: readonly number[],
117
+ ): Parser<N> {
118
+ return surround('', parser, closer, optional, undefined, undefined, backtracks);
119
+ }
120
+
121
+ const statesize = 2;
122
+ export function getBacktrack(
123
+ context: Ctx,
124
+ backtracks: readonly number[],
125
+ source: string,
126
+ length: number,
127
+ ): boolean {
128
+ for (const backtrack of backtracks) {
129
+ if (backtrack & 1) {
130
+ const { backtracks = {}, offset = 0 } = context;
131
+ for (let i = 0; i < length; ++i) {
132
+ if (source[i] !== source[0]) break;
133
+ const pos = source.length - i + offset - 1;
134
+ assert(pos >= 0);
135
+ if (!(pos in backtracks)) continue;
136
+ if (backtracks[pos] & 1 << size(backtrack >>> statesize)) return true;
137
+ }
138
+ }
139
+ }
140
+ return false;
124
141
  }
125
- export function close<P extends Parser<unknown>>(parser: P, closer: string | RegExp | Parser<Node<P>, Context<P>>, optional?: boolean): P;
126
- export function close<N>(parser: Parser<N>, closer: string | RegExp | Parser<N>, optional: boolean = false): Parser<N> {
127
- return surround('', parser, closer, optional);
142
+ export function setBacktrack(
143
+ context: Ctx,
144
+ backtracks: readonly number[],
145
+ position: number,
146
+ length: number = 1,
147
+ ): void {
148
+ for (const backtrack of backtracks) {
149
+ if (backtrack & 2) {
150
+ const { backtracks = {}, offset = 0 } = context;
151
+ for (let i = 0; i < length; ++i) {
152
+ const pos = position - i + offset - 1;
153
+ assert(pos >= 0);
154
+ backtracks[pos] |= 1 << size(backtrack >>> statesize);
155
+ }
156
+ }
157
+ }
128
158
  }
129
159
 
130
160
  function match(pattern: string | RegExp): (input: Input) => [never[], string] | undefined {
@@ -12,6 +12,7 @@ export function bind<N, U>(parser: Parser<N>, f: (nodes: N[], rest: string, cont
12
12
  const res1 = parser(input);
13
13
  assert(check(source, res1));
14
14
  if (res1 === undefined) return;
15
+ context.recent = [source.slice(0, source.length - exec(res1).length)];
15
16
  const res2 = f(eval(res1), exec(res1), context);
16
17
  assert(check(source, res2));
17
18
  assert(check(exec(res1), res2, false));
@@ -21,7 +21,6 @@ export interface Ctx {
21
21
  state?: number;
22
22
  depth?: number;
23
23
  backtracks?: Record<number, number>;
24
- backtrack?: number;
25
24
  linebreak?: number;
26
25
  recent?: string[];
27
26
  }
@@ -350,20 +350,21 @@ describe('Unit: parser/api/parse', () => {
350
350
 
351
351
  it('backtrack', function () {
352
352
  this.timeout(5000);
353
- const str = `${'.'.repeat(5 + 0)}{{(([[[http://${'['.repeat(13)}${'.'.repeat(8323)}`;
353
+ // 8n = template + link + annotation/reference + link + code + url + ruby + text
354
+ const source = `${'.'.repeat(1 + 0)}{{(([[[\`http://[${'.'.repeat(12493)}`;
354
355
  assert.deepStrictEqual(
355
- [...parse(str).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
356
- [`<p>${str}</p>`]);
356
+ [...parse(source).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
357
+ [`<p>${source}</p>`]);
357
358
  });
358
359
 
359
360
  it('backtrack error', function () {
360
361
  this.timeout(5000);
361
- const str = `${'.'.repeat(5 + 1)}{{(([[[http://${'['.repeat(13)}${'.'.repeat(8323)}`;
362
+ const source = `${'.'.repeat(1 + 1)}{{(([[[\`http://[${'.'.repeat(12493)}`;
362
363
  assert.deepStrictEqual(
363
- [...parse(str).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
364
+ [...parse(source).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
364
365
  [
365
366
  '<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
366
- `<pre class="error" translate="no">${str.slice(0, 1000 - 3)}...</pre>`,
367
+ `<pre class="error" translate="no">${source.slice(0, 1000 - 3)}...</pre>`,
367
368
  ]);
368
369
  });
369
370
 
@@ -28,18 +28,12 @@ export const enum Recursion {
28
28
  }
29
29
 
30
30
  export const enum Backtrack {
31
- link = 1 << 7,
32
- ruby = 1 << 6,
33
- linedoublebracket = 1 << 5,
34
- linebracket = 1 << 4,
31
+ link = 1 << 6,
32
+ ruby = 1 << 5,
33
+ doublebracket = 1 << 4,
35
34
  bracket = 1 << 3,
36
- lineescbracket = 1 << 2,
37
- lineunescbracket = 0 << 2,
38
- }
39
- // バックトラックを削減するため括弧派生構文を改行禁止し
40
- // 括弧派生構文内のバックトラック状態を統一する。
41
- export const enum BacktrackState {
42
- nobreak = 1,
35
+ escbracket = 1 << 2,
36
+ autolink = 0 << 2,
43
37
  }
44
38
 
45
39
  export const enum Command {
@@ -1,5 +1,5 @@
1
1
  import { AnnotationParser } from '../inline';
2
- import { State, Backtrack, BacktrackState } from '../context';
2
+ import { State, Backtrack } from '../context';
3
3
  import { union, some, precedence, state, constraint, surround, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
@@ -8,13 +8,13 @@ import { html, defrag } from 'typed-dom/dom';
8
8
  export const annotation: AnnotationParser = lazy(() => constraint(State.annotation, false, surround(
9
9
  '((',
10
10
  precedence(1, state(State.annotation | State.media,
11
- trimBlankStart(some(union([inline]), ')', [['\n', 9], [')', 1]])))),
11
+ trimBlankStart(some(union([inline]), ')', [[')', 1]])))),
12
12
  '))',
13
13
  false,
14
- ([, ns], rest) =>
14
+ ([, ns], rest, context) =>
15
+ context.linebreak === undefined &&
15
16
  trimBlankNodeEnd(ns).length > 0
16
17
  ? [[html('sup', { class: 'annotation' }, [html('span', defrag(ns))])], rest]
17
18
  : undefined,
18
19
  undefined,
19
- [3 | Backtrack.linedoublebracket, 1 | Backtrack.linebracket],
20
- Backtrack.bracket | BacktrackState.nobreak)));
20
+ [3 | Backtrack.doublebracket, 1 | Backtrack.bracket])));
@@ -1,14 +1,20 @@
1
1
  import { AutolinkParser } from '../../inline';
2
- import { State } from '../../context';
3
- import { union, state, constraint, verify, rewrite } from '../../../combinator';
2
+ import { State, Backtrack } from '../../context';
3
+ import { union, state, constraint, verify, rewrite, surround } from '../../../combinator';
4
4
  import { str } from '../../source';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
7
7
  // https://html.spec.whatwg.org/multipage/input.html
8
8
 
9
- export const email: AutolinkParser.EmailParser = rewrite(verify(
10
- str(/^[0-9a-z](?:[_.+-](?=[0-9a-z])|[0-9a-z]){0,255}@[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*(?![0-9a-z])/i),
11
- ([source]) => source.length <= 255),
9
+ export const email: AutolinkParser.EmailParser = rewrite(
10
+ surround(
11
+ str(/^[0-9a-z]/i),
12
+ verify(
13
+ str(/^(?:[_.+-](?=[0-9a-z])|[0-9a-z]){0,255}@[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*(?![0-9a-z])/i),
14
+ ([source]) => source.length <= 255 - 1),
15
+ '',
16
+ false, undefined, undefined,
17
+ [3 | Backtrack.autolink]),
12
18
  union([
13
19
  constraint(State.autolink, false, state(State.autolink,
14
20
  ({ source }) => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], ''])),
@@ -1,5 +1,5 @@
1
1
  import { AutolinkParser } from '../../inline';
2
- import { State } from '../../context';
2
+ import { State, Backtrack } from '../../context';
3
3
  import { union, state, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
4
4
  import { unsafelink } from '../link';
5
5
  import { str } from '../../source';
@@ -16,7 +16,9 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => rewrite(
16
16
  str(new RegExp([
17
17
  /^(?!['_])(?=(?:[0-9]{1,9})?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^'\p{C}\p{S}\p{P}\s]|emoji)))/u.source,
18
18
  /(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^'\p{C}\p{S}\p{P}\s]|emoji))+/u.source,
19
- ].join('').replace(/emoji/g, emoji), 'u'))),
19
+ ].join('').replace(/emoji/g, emoji), 'u')),
20
+ false,
21
+ [3 | Backtrack.autolink]),
20
22
  union([
21
23
  constraint(State.autolink, false, state(State.autolink, fmap(convert(
22
24
  source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`,
@@ -4,15 +4,17 @@ import { union, tails, some, recursion, precedence, state, constraint, validate,
4
4
  import { unsafelink } from '../link';
5
5
  import { linebreak, unescsource, str } from '../../source';
6
6
 
7
- const closer = /^[-+*=~^_,.;:!?]*(?=[\\"`|\[\](){}<>]|$)/;
7
+ const closer = /^[-+*=~^_,.;:!?]*(?=[\\"`|\[\](){}<>]|[^\x21-\x7E]|$)/;
8
8
 
9
9
  export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'https://'], rewrite(
10
10
  open(
11
11
  /^https?:\/\/(?=[\x21-\x7E])/,
12
- focus(/^[\x21-\x7E]+/, precedence(1, some(union([
12
+ precedence(1, some(union([
13
13
  verify(bracket, ns => ns.length > 0),
14
14
  some(unescsource, closer),
15
- ]))))),
15
+ ]), undefined, [[/^[^\x21-\x7E]/, 3]])),
16
+ false,
17
+ [3 | Backtrack.autolink]),
16
18
  union([
17
19
  constraint(State.autolink, false, state(State.autolink, convert(
18
20
  url => `{ ${url} }`,
@@ -34,15 +36,17 @@ export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => open(
34
36
  false))),
35
37
  ({ source }) => [[source], ''],
36
38
  ]),
37
- ]))));
39
+ ])),
40
+ false,
41
+ [3 | Backtrack.autolink]));
38
42
 
39
43
  const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => union([
40
44
  surround(str('('), recursion(Recursion.terminal, some(union([bracket, unescsource]), ')')), str(')'), true,
41
- undefined, () => [[], ''], [3 | Backtrack.lineunescbracket]),
45
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
42
46
  surround(str('['), recursion(Recursion.terminal, some(union([bracket, unescsource]), ']')), str(']'), true,
43
- undefined, () => [[], ''], [3 | Backtrack.lineunescbracket]),
47
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
44
48
  surround(str('{'), recursion(Recursion.terminal, some(union([bracket, unescsource]), '}')), str('}'), true,
45
- undefined, () => [[], ''], [3 | Backtrack.lineunescbracket]),
49
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
46
50
  surround(str('"'), precedence(2, recursion(Recursion.terminal, some(unescsource, '"'))), str('"'), true,
47
- undefined, () => [[], ''], [3 | Backtrack.lineunescbracket]),
51
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
48
52
  ]));
@@ -1,12 +1,19 @@
1
1
  import { CodeParser } from '../inline';
2
- import { match } from '../../combinator';
2
+ import { validate, getBacktrack, setBacktrack, match } from '../../combinator';
3
+ import { Backtrack } from '../context';
3
4
  import { html } from 'typed-dom/dom';
4
5
 
5
- export const code: CodeParser = match(
6
- /^(`+)(?!`)([^\n]*?[^`\n])\1(?!`)/,
7
- ([whole, , body]) => ({ source }) =>
8
- [[html('code', { 'data-src': whole }, format(body))], source.slice(whole.length)],
9
- true);
6
+ export const code: CodeParser = validate(
7
+ ({ source, context }) =>
8
+ source[0] === '`' &&
9
+ !getBacktrack(context, [1 | Backtrack.bracket], source, source.length - 1),
10
+ match(
11
+ /^(`+)(?!`)([^\n]*?)(?:((?<!`)\1(?!`))|$|\n)/,
12
+ ([whole, , body, closer]) => ({ source, context }) =>
13
+ closer
14
+ ? [[html('code', { 'data-src': whole }, format(body))], source.slice(whole.length)]
15
+ : void setBacktrack(context, [2 | Backtrack.bracket], source.length),
16
+ true));
10
17
 
11
18
  function format(text: string): string {
12
19
  assert(text.length > 0);
@@ -1,5 +1,5 @@
1
1
  import { ExtensionParser } from '../../inline';
2
- import { State, Backtrack, BacktrackState, Command } from '../../context';
2
+ import { State, Backtrack, Command } from '../../context';
3
3
  import { eval } from '../../../combinator/data/parser';
4
4
  import { union, inits, some, precedence, state, constraint, validate, surround, lazy, fmap } from '../../../combinator';
5
5
  import { inline } from '../../inline';
@@ -19,16 +19,16 @@ export const index: IndexParser = lazy(() => constraint(State.index, false, fmap
19
19
  some(inits([
20
20
  inline,
21
21
  signature,
22
- ]), ']', [['\n', 9], [']', 1]])))),
22
+ ]), ']', [[']', 1]])))),
23
23
  ']',
24
24
  false,
25
- ([, ns], rest) =>
25
+ ([, ns], rest, context) =>
26
+ context.linebreak === undefined &&
26
27
  trimBlankNodeEnd(ns).length > 0
27
28
  ? [[html('a', { 'data-index': dataindex(ns) }, defrag(ns))], rest]
28
29
  : undefined,
29
30
  undefined,
30
- [3 | Backtrack.linebracket],
31
- Backtrack.bracket | BacktrackState.nobreak)),
31
+ [3 | Backtrack.bracket])),
32
32
  ([el]: [HTMLAnchorElement]) => [
33
33
  define(el,
34
34
  {
@@ -13,7 +13,7 @@ export const segment: ExtensionParser.LabelParser.SegmentParser = clear(union([
13
13
 
14
14
  export const label: ExtensionParser.LabelParser = constraint(State.label, false, fmap(
15
15
  union([
16
- surround('[', body, ']', false, undefined, undefined, [1 | Backtrack.linebracket, 1]),
16
+ surround('[', body, ']', false, undefined, undefined, [1 | Backtrack.bracket, 1]),
17
17
  body,
18
18
  ]),
19
19
  ([text]) => [
@@ -1,5 +1,5 @@
1
1
  import { HTMLParser } from '../inline';
2
- import { Recursion } from '../context';
2
+ import { Recursion, Backtrack } from '../context';
3
3
  import { union, subsequence, some, recursion, precedence, validate, focus, surround, open, match, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
@@ -30,13 +30,17 @@ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
30
30
  some(union([attribute])),
31
31
  str(/^[^\S\n]*>/), true,
32
32
  ([as, bs = [], cs], rest) =>
33
- [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest]),
33
+ [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest],
34
+ undefined,
35
+ [3 | Backtrack.bracket]),
34
36
  match(
35
37
  new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`),
36
38
  memoize(
37
39
  ([, tag]) =>
38
40
  surround<HTMLParser.TagParser, string>(
39
- surround(str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true),
41
+ surround(
42
+ str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true,
43
+ undefined, undefined, [3 | Backtrack.bracket]),
40
44
  precedence(3, recursion(Recursion.inline,
41
45
  subsequence([
42
46
  focus(/^[^\S\n]*\n/, some(inline)),
@@ -54,7 +58,9 @@ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
54
58
  memoize(
55
59
  ([, tag]) =>
56
60
  surround<HTMLParser.TagParser, string>(
57
- surround(str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true),
61
+ surround(
62
+ str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true,
63
+ undefined, undefined, [3 | Backtrack.bracket]),
58
64
  precedence(3, recursion(Recursion.inline,
59
65
  subsequence([
60
66
  focus(/^[^\S\n]*\n/, some(inline)),
@@ -1,13 +1,14 @@
1
1
  import { MarkdownParser } from '../../../markdown';
2
2
  import { LinkParser } from '../inline';
3
- import { State, Backtrack, BacktrackState } from '../context';
4
- import { union, inits, tails, sequence, some, creation, precedence, state, constraint, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
3
+ import { State, Backtrack, Command } from '../context';
4
+ import { union, inits, tails, sequence, subsequence, some, creation, precedence, state, constraint, validate, surround, open, setBacktrack, dup, reverse, lazy, fmap, bind } from '../../combinator';
5
5
  import { inline, media, shortmedia } from '../inline';
6
6
  import { attributes } from './html';
7
7
  import { linebreak, unescsource, str } from '../source';
8
8
  import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
9
9
  import { invalid, stringify } from '../util';
10
10
  import { ReadonlyURL } from 'spica/url';
11
+ import { push } from 'spica/array';
11
12
  import { html, define, defrag } from 'typed-dom/dom';
12
13
 
13
14
  const optspec = {
@@ -17,22 +18,36 @@ Object.setPrototypeOf(optspec, null);
17
18
 
18
19
  export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.link, false, creation(10,
19
20
  precedence(1, state(State.linkers | State.media,
20
- bind(reverse(tails([
21
+ bind(subsequence([
21
22
  dup(surround(
22
23
  '[',
23
- trimBlankStart(some(union([inline]), ']', [['\n', 9], [']', 1]])),
24
+ trimBlankStart(some(union([inline]), ']', [[']', 1]])),
24
25
  ']',
25
- true, undefined, undefined,
26
- [1 | Backtrack.linebracket],
27
- Backtrack.bracket | BacktrackState.nobreak)),
26
+ true,
27
+ ([, ns = []], rest, context) =>
28
+ context.linebreak === undefined
29
+ ? [push(ns, [Command.Escape]), rest]
30
+ : undefined,
31
+ undefined,
32
+ [3 | Backtrack.link, 3 | Backtrack.bracket])),
28
33
  dup(surround(
29
34
  /^{(?![{}])/,
30
35
  inits([uri, some(option)]),
31
36
  /^[^\S\n]*}/,
32
37
  false, undefined, undefined,
33
38
  [3 | Backtrack.link])),
34
- ])),
35
- ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
39
+ ]),
40
+ ([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
41
+ if (content.at(-1) === Command.Escape) {
42
+ content.pop();
43
+ if (params === undefined) {
44
+ return void setBacktrack(context, [2 | Backtrack.link], context.recent!.reduce((a, b) => a + b.length, 0));
45
+ }
46
+ }
47
+ else {
48
+ params = content as string[];
49
+ content = [];
50
+ }
36
51
  assert(!html('div', content).querySelector('a, .media, .annotation, .reference'));
37
52
  assert(content[0] !== '');
38
53
  if (content.length !== 0 && trimBlankNodeEnd(content).length === 0) return;
@@ -27,10 +27,15 @@ export const media: MediaParser = lazy(() => constraint(State.media, false, vali
27
27
  unsafehtmlentity,
28
28
  bracket,
29
29
  txt,
30
- ]), ']', [['\n', 9]])),
30
+ ]), ']')),
31
31
  ']',
32
- true, undefined, undefined,
33
- [3 | Backtrack.lineescbracket])),
32
+ true,
33
+ ([, ns = []], rest, context) =>
34
+ context.linebreak === undefined
35
+ ? [ns, rest]
36
+ : undefined,
37
+ undefined,
38
+ [3 | Backtrack.escbracket])),
34
39
  dup(surround(
35
40
  /^{(?![{}])/,
36
41
  inits([uri, some(option)]),
@@ -77,13 +82,13 @@ export const linemedia: MediaParser.LineMediaParser = surround(
77
82
 
78
83
  const bracket: MediaParser.TextParser.BracketParser = lazy(() => recursion(Recursion.terminal, union([
79
84
  surround(str('('), some(union([unsafehtmlentity, bracket, txt]), ')'), str(')'), true,
80
- undefined, () => [[], ''], [3 | Backtrack.lineescbracket]),
85
+ undefined, () => [[], ''], [3 | Backtrack.escbracket]),
81
86
  surround(str('['), some(union([unsafehtmlentity, bracket, txt]), ']'), str(']'), true,
82
- undefined, () => [[], ''], [3 | Backtrack.lineescbracket]),
87
+ undefined, () => [[], ''], [3 | Backtrack.escbracket]),
83
88
  surround(str('{'), some(union([unsafehtmlentity, bracket, txt]), '}'), str('}'), true,
84
- undefined, () => [[], ''], [3 | Backtrack.lineescbracket]),
89
+ undefined, () => [[], ''], [3 | Backtrack.escbracket]),
85
90
  surround(str('"'), precedence(2, some(union([unsafehtmlentity, txt]), '"')), str('"'), true,
86
- undefined, () => [[], ''], [3 | Backtrack.lineescbracket]),
91
+ undefined, () => [[], ''], [3 | Backtrack.escbracket]),
87
92
  ])));
88
93
 
89
94
  const option: MediaParser.ParameterParser.OptionParser = lazy(() => union([
@@ -1,6 +1,6 @@
1
1
  import { ReferenceParser } from '../inline';
2
- import { State, Backtrack, BacktrackState } from '../context';
3
- import { union, subsequence, some, precedence, state, constraint, surround, lazy } from '../../combinator';
2
+ import { State, Backtrack, Command } from '../context';
3
+ import { union, subsequence, some, precedence, state, constraint, surround, setBacktrack, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
6
  import { blank, trimBlankStart, trimBlankNodeEnd } from '../visibility';
@@ -13,20 +13,24 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
13
13
  precedence(1, state(State.annotation | State.reference | State.media,
14
14
  subsequence([
15
15
  abbr,
16
- trimBlankStart(some(inline, ']', [['\n', 9], [']', 1]])),
16
+ trimBlankStart(some(inline, ']', [[']', 1]])),
17
17
  ]))),
18
18
  ']]',
19
19
  false,
20
- ([, ns], rest) =>
20
+ ([, ns], rest, context) =>
21
+ context.linebreak === undefined &&
21
22
  trimBlankNodeEnd(ns).length > 0
22
23
  ? [[html('sup', attributes(ns), [html('span', defrag(ns))])], rest]
23
24
  : undefined,
24
- ([as, bs], rest, { state = 0 }) =>
25
- state & State.annotation
25
+ ([as, bs], rest, context) => {
26
+ if (rest[0] !== ']') {
27
+ setBacktrack(context, [2 | Backtrack.bracket], context.recent!.reduce((a, b) => a + b.length, 0), 2);
28
+ }
29
+ return context.state! & State.annotation
26
30
  ? [unshift(as, bs), rest]
27
- : undefined,
28
- [3 | Backtrack.linedoublebracket, 1 | Backtrack.linebracket],
29
- Backtrack.bracket | BacktrackState.nobreak)));
31
+ : undefined;
32
+ },
33
+ [3 | Backtrack.doublebracket, 1 | Backtrack.bracket])));
30
34
 
31
35
  // Chicago-Style
32
36
  const abbr: ReferenceParser.AbbrParser = surround(
@@ -34,14 +38,14 @@ const abbr: ReferenceParser.AbbrParser = surround(
34
38
  union([str(/^(?=[A-Z])(?:[0-9A-Za-z]'?|(?:[-.:]|\.?\??,? ?)(?!['\-.:?, ]))+/)]),
35
39
  /^\|?(?=]])|^\|[^\S\n]*/,
36
40
  true,
37
- ([, ns], rest) => ns ? [['\n', ns[0].trimEnd()], rest.replace(blank.start, '')] : [[''], `^${rest}`],
41
+ ([, ns], rest) => ns ? [[Command.Escape, ns[0].trimEnd()], rest.replace(blank.start, '')] : [[''], `^${rest}`],
38
42
  ([, , rest]) => [[''], `^${rest}`]);
39
43
 
40
44
  function attributes(ns: (string | HTMLElement)[]): Record<string, string | undefined> {
41
45
  switch (ns[0]) {
42
46
  case '':
43
47
  return { class: 'invalid', ...invalid('reference', 'syntax', 'Invalid abbreviation') };
44
- case '\n':
48
+ case Command.Escape:
45
49
  const abbr = ns[1] as string;
46
50
  ns[0] = ns[1] = '';
47
51
  return { class: 'reference', 'data-abbr': abbr };