securemark 0.258.2 → 0.258.5

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 (66) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +210 -183
  3. package/markdown.d.ts +8 -10
  4. package/package.json +1 -1
  5. package/src/combinator/control/constraint/block.ts +3 -1
  6. package/src/combinator/control/constraint/line.ts +6 -1
  7. package/src/combinator/control/manipulation/indent.ts +3 -0
  8. package/src/combinator/data/parser/context/memo.ts +7 -4
  9. package/src/combinator/data/parser/context.test.ts +16 -16
  10. package/src/combinator/data/parser/context.ts +40 -37
  11. package/src/combinator/data/parser/some.ts +4 -2
  12. package/src/combinator/data/parser.ts +2 -2
  13. package/src/parser/api/bind.ts +1 -1
  14. package/src/parser/api/parse.test.ts +4 -4
  15. package/src/parser/api/parse.ts +1 -1
  16. package/src/parser/autolink.test.ts +3 -2
  17. package/src/parser/autolink.ts +17 -1
  18. package/src/parser/block/blockquote.ts +4 -4
  19. package/src/parser/block/dlist.ts +3 -3
  20. package/src/parser/block/extension/table.ts +4 -4
  21. package/src/parser/block/ilist.ts +2 -2
  22. package/src/parser/block/olist.ts +2 -2
  23. package/src/parser/block/paragraph.test.ts +3 -1
  24. package/src/parser/block/reply/cite.ts +2 -2
  25. package/src/parser/block/reply/quote.ts +3 -3
  26. package/src/parser/block/sidefence.ts +2 -2
  27. package/src/parser/block/table.ts +5 -5
  28. package/src/parser/block/ulist.ts +4 -4
  29. package/src/parser/block.ts +3 -3
  30. package/src/parser/context.ts +1 -1
  31. package/src/parser/inline/annotation.ts +7 -6
  32. package/src/parser/inline/autolink/email.test.ts +3 -3
  33. package/src/parser/inline/autolink/email.ts +2 -2
  34. package/src/parser/inline/autolink/url.test.ts +6 -6
  35. package/src/parser/inline/autolink/url.ts +2 -2
  36. package/src/parser/inline/autolink.ts +6 -6
  37. package/src/parser/inline/bracket.ts +8 -8
  38. package/src/parser/inline/code.ts +2 -2
  39. package/src/parser/inline/comment.ts +2 -2
  40. package/src/parser/inline/deletion.ts +5 -4
  41. package/src/parser/inline/emphasis.ts +5 -4
  42. package/src/parser/inline/emstrong.ts +5 -4
  43. package/src/parser/inline/extension/index.ts +9 -8
  44. package/src/parser/inline/extension/indexer.ts +2 -2
  45. package/src/parser/inline/extension/label.ts +3 -3
  46. package/src/parser/inline/extension/placeholder.ts +5 -4
  47. package/src/parser/inline/html.test.ts +1 -1
  48. package/src/parser/inline/html.ts +4 -4
  49. package/src/parser/inline/htmlentity.ts +2 -2
  50. package/src/parser/inline/insertion.ts +5 -4
  51. package/src/parser/inline/link.ts +11 -9
  52. package/src/parser/inline/mark.ts +5 -4
  53. package/src/parser/inline/math.ts +3 -3
  54. package/src/parser/inline/media.ts +8 -7
  55. package/src/parser/inline/reference.ts +8 -7
  56. package/src/parser/inline/ruby.ts +4 -4
  57. package/src/parser/inline/shortmedia.ts +2 -2
  58. package/src/parser/inline/strong.ts +5 -4
  59. package/src/parser/inline/template.ts +6 -6
  60. package/src/parser/inline.test.ts +5 -3
  61. package/src/parser/source/escapable.ts +2 -2
  62. package/src/parser/source/str.ts +5 -5
  63. package/src/parser/source/text.test.ts +16 -1
  64. package/src/parser/source/text.ts +5 -4
  65. package/src/parser/source/unescapable.ts +2 -2
  66. package/src/parser/visibility.ts +5 -5
package/markdown.d.ts CHANGED
@@ -964,7 +964,7 @@ export namespace MarkdownParser {
964
964
  Inline<'html'>,
965
965
  Parser<HTMLElement | string, Context, [
966
966
  HTMLParser.OpenTagParser,
967
- SourceParser.StrParser,
967
+ HTMLParser.OpenTagParser,
968
968
  HTMLParser.TagParser,
969
969
  HTMLParser.TagParser,
970
970
  ]> {
@@ -972,8 +972,8 @@ export namespace MarkdownParser {
972
972
  export namespace HTMLParser {
973
973
  export interface OpenTagParser extends
974
974
  Inline<'html/opentag'>,
975
- Parser<HTMLElement, Context, [
976
- TagParser.AttributeParser,
975
+ Parser<HTMLElement | string, Context, [
976
+ AttributeParser,
977
977
  ]> {
978
978
  }
979
979
  export interface TagParser extends
@@ -983,13 +983,11 @@ export namespace MarkdownParser {
983
983
  InlineParser,
984
984
  ]> {
985
985
  }
986
- export namespace TagParser {
987
- export interface AttributeParser extends
988
- Inline<'html/tag/attribute'>,
989
- Parser<string, Context, [
990
- SourceParser.StrParser,
991
- ]> {
992
- }
986
+ export interface AttributeParser extends
987
+ Inline<'html/attribute'>,
988
+ Parser<string, Context, [
989
+ SourceParser.StrParser,
990
+ ]> {
993
991
  }
994
992
  }
995
993
  export interface InsertionParser extends
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.258.2",
3
+ "version": "0.258.5",
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,12 +1,14 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { Parser, exec } from '../../data/parser';
3
+ import { Memo } from '../../data/parser/context/memo';
3
4
  import { firstline, isEmpty } from './line';
4
5
 
5
6
  export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
6
7
  export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
7
8
  assert(parser);
8
- return (source, context) => {
9
+ return (source, context = {}) => {
9
10
  if (source === '') return;
11
+ context.memo ??= new Memo();
10
12
  const result = parser(source, context);
11
13
  if (!result) return;
12
14
  const rest = exec(result);
@@ -1,14 +1,19 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { Parser, eval, exec, check } from '../../data/parser';
3
+ import { Memo } from '../../data/parser/context/memo';
3
4
 
4
5
  export function line<P extends Parser<unknown>>(parser: P): P;
5
6
  export function line<T>(parser: Parser<T>): Parser<T> {
6
7
  assert(parser);
7
- return (source, context) => {
8
+ return (source, context = {}) => {
8
9
  if (source === '') return;
10
+ context.memo ??= new Memo();
9
11
  const line = firstline(source);
12
+ const memo = context.memo!;
13
+ memo.offset += source.length - line.length;
10
14
  const result = parser(line, context);
11
15
  assert(check(line, result));
16
+ memo.offset -= source.length - line.length;
12
17
  if (!result) return;
13
18
  return isEmpty(exec(result))
14
19
  ? [eval(result), source.slice(line.length)]
@@ -21,7 +21,10 @@ export function indent<T>(opener: RegExp | Parser<T>, parser?: Parser<T> | boole
21
21
  ([indent]) => indent.length * 2 + +(indent[0] === ' '), [])), separation),
22
22
  (lines, rest, context) => {
23
23
  assert(parser = parser as Parser<T>);
24
+ const memo = context.memo;
25
+ memo && (memo.offset += rest.length);
24
26
  const result = parser(trimBlockEnd(lines.join('')), context);
27
+ memo && (memo.offset -= rest.length);
25
28
  return result && exec(result) === ''
26
29
  ? [eval(result), rest]
27
30
  : undefined;
@@ -1,5 +1,5 @@
1
1
  export class Memo {
2
- private memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
2
+ private readonly memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
3
3
  public get length(): number {
4
4
  return this.memory.length;
5
5
  }
@@ -10,7 +10,10 @@ export class Memo {
10
10
  state: number,
11
11
  ): readonly [any[], number] | readonly [] | undefined {
12
12
  //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
13
- return this.memory[position + this.offset - 1]?.[`${syntax}:${state}`];
13
+ const cache = this.memory[position + this.offset - 1]?.[`${syntax}:${state}`];
14
+ return cache?.length === 2
15
+ ? [cache[0].slice(), cache[1]]
16
+ : cache;
14
17
  }
15
18
  public set(
16
19
  position: number,
@@ -24,13 +27,13 @@ export class Memo {
24
27
  record[`${syntax}:${state}`] = nodes
25
28
  ? [nodes.slice(), offset]
26
29
  : [];
27
- //console.log('set', position + this.offset, syntax, state);
30
+ //console.log('set', position + this.offset, syntax, state, record[`${syntax}:${state}`]);
28
31
  }
29
32
  public clear(position: number): void {
30
33
  const memory = this.memory;
31
34
  for (let i = position + this.offset, len = memory.length; i < len; ++i) {
32
35
  memory.pop();
33
36
  }
34
- //console.log('clear', position);
37
+ //console.log('clear', position + 1);
35
38
  }
36
39
  }
@@ -1,7 +1,7 @@
1
1
  import { Parser, Ctx } from '../parser';
2
2
  import { some } from './some';
3
3
  import { reset, context } from './context';
4
- import { creator } from './context';
4
+ import { creation } from './context';
5
5
 
6
6
  describe('Unit: combinator/data/parser/context', () => {
7
7
  interface Context extends Ctx {
@@ -9,44 +9,44 @@ describe('Unit: combinator/data/parser/context', () => {
9
9
  }
10
10
 
11
11
  describe('reset', () => {
12
- const parser: Parser<number> = some(creator(
13
- (s, context) => [[context.resources?.budget ?? NaN], s.slice(1)]));
12
+ const parser: Parser<number> = some(creation(
13
+ (s, context) => [[context.resources?.clock ?? NaN], s.slice(1)]));
14
14
 
15
15
  it('root', () => {
16
- const base: Context = { resources: { budget: 3, recursion: 1 } };
16
+ const base: Context = { resources: { clock: 3, recursion: 1 } };
17
17
  const ctx: Context = {};
18
18
  assert.deepStrictEqual(reset(base, parser)('123', ctx), [[3, 2, 1], '']);
19
- assert(base.resources?.budget === 3);
20
- assert(ctx.resources?.budget === undefined);
19
+ assert(base.resources?.clock === 3);
20
+ assert(ctx.resources?.clock === undefined);
21
21
  assert.throws(() => reset(base, parser)('1234', ctx));
22
- assert(ctx.resources?.budget === undefined);
22
+ assert(ctx.resources?.clock === undefined);
23
23
  assert.deepStrictEqual(reset(base, parser)('123', ctx), [[3, 2, 1], '']);
24
24
  });
25
25
 
26
26
  it('node', () => {
27
- const base: Context = { resources: { budget: 3, recursion: 1 } };
28
- const ctx: Context = { resources: { budget: 1, recursion: 1 } };
27
+ const base: Context = { resources: { clock: 3, recursion: 1 } };
28
+ const ctx: Context = { resources: { clock: 1, recursion: 1 } };
29
29
  assert.deepStrictEqual(reset(base, parser)('1', ctx), [[1], '']);
30
- assert(base.resources?.budget === 3);
31
- assert(ctx.resources?.budget === 0);
30
+ assert(base.resources?.clock === 3);
31
+ assert(ctx.resources?.clock === 0);
32
32
  assert.throws(() => reset(base, parser)('1', ctx));
33
- assert(ctx.resources?.budget === 0);
33
+ assert(ctx.resources?.clock === 0);
34
34
  });
35
35
 
36
36
  });
37
37
 
38
38
  describe('context', () => {
39
- const parser: Parser<boolean, Context> = some(creator(
39
+ const parser: Parser<boolean, Context> = some(creation(
40
40
  (s, context) => [[context.status!], s.slice(1)]));
41
41
 
42
42
  it('', () => {
43
43
  const base: Context = { status: true };
44
- const ctx: Context = { resources: { budget: 3, recursion: 1 } };
44
+ const ctx: Context = { resources: { clock: 3, recursion: 1 } };
45
45
  assert.deepStrictEqual(context(base, parser)('123', ctx), [[true, true, true], '']);
46
- assert(ctx.resources?.budget === 0);
46
+ assert(ctx.resources?.clock === 0);
47
47
  assert(ctx.status === undefined);
48
48
  assert.throws(() => reset(base, parser)('1', ctx));
49
- assert(ctx.resources?.budget === 0);
49
+ assert(ctx.resources?.clock === 0);
50
50
  assert(ctx.status === undefined);
51
51
  });
52
52
 
@@ -56,65 +56,49 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
56
56
  return result;
57
57
  }
58
58
 
59
- export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, parser: P): P;
60
59
  export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, parser: P): P;
61
- export function syntax<T>(syntax: number, precedence: number, cost: number | Parser<T>, parser?: Parser<T>): Parser<T> {
62
- if (typeof cost === 'function') {
63
- parser = cost;
64
- cost = 1;
65
- }
66
- return (source, context) => {
60
+ export function syntax<T>(syntax: number, precedence: number, cost: number, parser?: Parser<T>): Parser<T> {
61
+ return creation(cost, (source, context) => {
67
62
  if (source === '') return;
68
- context.backtrackable ??= ~0;
69
- const state = context.state ??= 0;
63
+ const memo = context.memo ??= new Memo();
64
+ context.memorable ??= ~0;
70
65
  const p = context.precedence;
71
66
  context.precedence = precedence;
72
- const { resources = { budget: 1, recursion: 1 } } = context;
73
- if (resources.budget <= 0) throw new Error('Too many creations');
74
- if (resources.recursion <= 0) throw new Error('Too much recursion');
75
- --resources.recursion;
76
- const pos = source.length;
77
- const cache = syntax && context.memo?.get(pos, syntax, state);
67
+ const position = source.length;
68
+ const state = context.state ?? 0;
69
+ const cache = syntax && memo.get(position, syntax, state);
78
70
  const result: Result<T> = cache
79
71
  ? cache.length === 0
80
72
  ? undefined
81
73
  : [cache[0], source.slice(cache[1])]
82
74
  : parser!(source, context);
83
- ++resources.recursion;
84
- if (result && !cache) {
85
- assert(cost = cost as number);
86
- resources.budget -= cost;
75
+ if (syntax && state & context.memorable!) {
76
+ cache ?? memo.set(position, syntax, state, eval(result), source.length - exec(result, '').length);
77
+ assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, state));
87
78
  }
88
- if (syntax) {
89
- if (state & context.backtrackable) {
90
- context.memo ??= new Memo();
91
- cache ?? context.memo.set(pos, syntax, state, eval(result), source.length - exec(result, '').length);
92
- assert.deepStrictEqual(cache && cache, cache && context.memo.get(pos, syntax, state));
93
- }
94
- else if (result && context.memo?.length! >= pos) {
95
- assert(!(state & context.backtrackable));
96
- context.memo!.clear(pos);
97
- }
79
+ if (result && !state && memo.length! >= position) {
80
+ assert(!(state & context.memorable!));
81
+ memo.clear(position);
98
82
  }
99
83
  context.precedence = p;
100
84
  return result;
101
- };
85
+ });
102
86
  }
103
87
 
104
- export function creator<P extends Parser<unknown>>(parser: P): P;
105
- export function creator<P extends Parser<unknown>>(cost: number, parser: P): P;
106
- export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
107
- if (typeof cost === 'function') return creator(1, cost);
88
+ export function creation<P extends Parser<unknown>>(parser: P): P;
89
+ export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
90
+ export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
91
+ if (typeof cost === 'function') return creation(1, cost);
108
92
  assert(cost >= 0);
109
93
  return (source, context) => {
110
- const { resources = { budget: 1, recursion: 1 } } = context;
111
- if (resources.budget <= 0) throw new Error('Too many creations');
94
+ const { resources = { clock: 1, recursion: 1 } } = context;
95
+ if (resources.clock <= 0) throw new Error('Too many creations');
112
96
  if (resources.recursion <= 0) throw new Error('Too much recursion');
113
97
  --resources.recursion;
114
98
  const result = parser!(source, context);
115
99
  ++resources.recursion;
116
100
  if (result) {
117
- resources.budget -= cost;
101
+ resources.clock -= cost;
118
102
  }
119
103
  return result;
120
104
  };
@@ -139,6 +123,24 @@ export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T
139
123
  : undefined;
140
124
  }
141
125
 
126
+ export function constraint<P extends Parser<unknown>>(state: number, parser: P): P;
127
+ export function constraint<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
128
+ export function constraint<T>(state: number, positive: boolean | Parser<T>, parser?: Parser<T>): Parser<T> {
129
+ if (typeof positive === 'function') {
130
+ parser = positive;
131
+ positive = true;
132
+ }
133
+ assert(state);
134
+ return (source, context) => {
135
+ const s = positive
136
+ ? state & context.state!
137
+ : state & ~context.state!;
138
+ return s === state
139
+ ? parser!(source, context)
140
+ : undefined;
141
+ };
142
+ }
143
+
142
144
  export function state<P extends Parser<unknown>>(state: number, parser: P): P;
143
145
  export function state<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
144
146
  export function state<T>(state: number, positive: boolean | Parser<T>, parser?: Parser<T>): Parser<T> {
@@ -146,6 +148,7 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
146
148
  parser = positive;
147
149
  positive = true;
148
150
  }
151
+ assert(state);
149
152
  return (source, context) => {
150
153
  const s = context.state ?? 0;
151
154
  context.state = positive
@@ -1,7 +1,7 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { Parser, eval, exec, check } from '../parser';
3
3
  import { Delimiters } from './context/delimiter';
4
- import { push } from 'spica/array';
4
+ import { unshift, push } from 'spica/array';
5
5
 
6
6
  type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
7
7
 
@@ -32,7 +32,9 @@ export function some<T>(parser: Parser<T>, end?: string | RegExp | number, delim
32
32
  assert.doesNotThrow(() => limit < 0 && check(rest, result));
33
33
  if (!result) break;
34
34
  nodes = nodes
35
- ? push(nodes, eval(result))
35
+ ? nodes.length < eval(result).length
36
+ ? unshift(nodes, eval(result))
37
+ : push(nodes, eval(result))
36
38
  : eval(result);
37
39
  rest = exec(result);
38
40
  if (limit >= 0 && source.length - rest.length > limit) break;
@@ -9,13 +9,13 @@ export type Result<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
9
9
  | undefined;
10
10
  export interface Ctx {
11
11
  readonly resources?: {
12
- budget: number;
12
+ clock: number;
13
13
  recursion: number;
14
14
  };
15
15
  precedence?: number;
16
16
  delimiters?: Delimiters;
17
17
  state?: number;
18
- backtrackable?: number;
18
+ memorable?: number;
19
19
  memo?: Memo;
20
20
  }
21
21
  export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
@@ -24,7 +24,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
24
24
  let context: MarkdownParser.Context = {
25
25
  ...settings,
26
26
  host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
27
- backtrackable,
27
+ memorable: backtrackable,
28
28
  };
29
29
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
30
30
  assert(!settings.id);
@@ -247,19 +247,19 @@ describe('Unit: parser/api/parse', () => {
247
247
  [...parse('('.repeat(20)).children].map(el => el.outerHTML),
248
248
  [`<p>${'('.repeat(20)}</p>`]);
249
249
  assert.deepStrictEqual(
250
- [...parse('('.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
250
+ [...parse('('.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
251
251
  [
252
252
  '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
253
- `<pre class="error" translate="no">${'('.repeat(21)}</pre>`,
253
+ `<pre class="error" translate="no">${'('.repeat(22)}</pre>`,
254
254
  ]);
255
255
  assert.deepStrictEqual(
256
256
  [...parse('['.repeat(20)).children].map(el => el.outerHTML),
257
257
  [`<p>${'['.repeat(20)}</p>`]);
258
258
  assert.deepStrictEqual(
259
- [...parse('['.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
259
+ [...parse('['.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
260
260
  [
261
261
  '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
262
- `<pre class="error" translate="no">${'['.repeat(21)}</pre>`,
262
+ `<pre class="error" translate="no">${'['.repeat(22)}</pre>`,
263
263
  ]);
264
264
  assert.deepStrictEqual(
265
265
  [...parse('['.repeat(17) + '\na').children].map(el => el.outerHTML),
@@ -30,7 +30,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
30
30
  ...context?.resources && {
31
31
  resources: context.resources,
32
32
  },
33
- backtrackable,
33
+ memorable: backtrackable,
34
34
  };
35
35
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
36
36
  const node = frag();
@@ -13,13 +13,14 @@ describe('Unit: parser/autolink', () => {
13
13
  assert.deepStrictEqual(inspect(parser('@a#b')), [['<a href="/@a?ch=b" class="channel">@a#b</a>'], '']);
14
14
  assert.deepStrictEqual(inspect(parser('\\\n')), [['\\', '<br>'], '']);
15
15
  assert.deepStrictEqual(inspect(parser('a#b')), [['a#b'], '']);
16
- assert.deepStrictEqual(inspect(parser('0a#b')), [['0', 'a#b'], '']);
16
+ assert.deepStrictEqual(inspect(parser('0a#b')), [['0a#b'], '']);
17
17
  assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
18
18
  assert.deepStrictEqual(inspect(parser('あい#b')), [['あ', 'い#b'], '']);
19
- assert.deepStrictEqual(inspect(parser('0aあ#b')), [['0a', 'あ#b'], '']);
19
+ assert.deepStrictEqual(inspect(parser('0aあ#b')), [['0aあ#b'], '']);
20
20
  assert.deepStrictEqual(inspect(parser('0aあい#b')), [['0a', 'あ', 'い#b'], '']);
21
21
  assert.deepStrictEqual(inspect(parser('a\n#b')), [['a', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
22
22
  assert.deepStrictEqual(inspect(parser('a\\\n#b')), [['a', '\\', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
23
+ assert.deepStrictEqual(inspect(parser('0a>>b')), [['0a>>b'], '']);
23
24
  });
24
25
 
25
26
  });
@@ -5,7 +5,23 @@ import { linebreak, unescsource } from './source';
5
5
 
6
6
  export import AutolinkParser = MarkdownParser.AutolinkParser;
7
7
 
8
- export const autolink: AutolinkParser = lazy(() => union([
8
+ const delimiter = /[@#>0-9A-Za-z\n]|\S[#>]/;
9
+
10
+ export const autolink: AutolinkParser = (source, context) => {
11
+ if (source === '') return;
12
+ assert(source[0] !== '\x1B');
13
+ const i = source.search(delimiter);
14
+ switch (i) {
15
+ case -1:
16
+ return [[source], ''];
17
+ case 0:
18
+ return parser(source, context);
19
+ default:
20
+ return [[source.slice(0, i)], source.slice(i)];
21
+ }
22
+ };
23
+
24
+ const parser: AutolinkParser = lazy(() => union([
9
25
  autolink_,
10
26
  linebreak,
11
27
  unescsource
@@ -1,5 +1,5 @@
1
1
  import { BlockquoteParser } from '../block';
2
- import { union, some, creator, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
2
+ import { union, some, creation, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
3
3
  import { autolink } from '../autolink';
4
4
  import { contentline } from '../source';
5
5
  import { parse } from '../api/parse';
@@ -19,7 +19,7 @@ const indent = block(open(opener, some(contentline, /^>(?:$|\s)/)), false);
19
19
  const unindent = (source: string) => source.replace(/(^|\n)>(?:[^\S\n]|(?=>*(?:$|\s)))|\n$/g, '$1');
20
20
 
21
21
  const source: BlockquoteParser.SourceParser = lazy(() => fmap(
22
- some(creator(union([
22
+ some(creation(union([
23
23
  rewrite(
24
24
  indent,
25
25
  convert(unindent, source)),
@@ -30,11 +30,11 @@ const source: BlockquoteParser.SourceParser = lazy(() => fmap(
30
30
  ns => [html('blockquote', ns)]));
31
31
 
32
32
  const markdown: BlockquoteParser.MarkdownParser = lazy(() => fmap(
33
- some(creator(union([
33
+ some(creation(union([
34
34
  rewrite(
35
35
  indent,
36
36
  convert(unindent, markdown)),
37
- creator(99,
37
+ creation(99,
38
38
  rewrite(
39
39
  some(contentline, opener),
40
40
  convert(unindent, (source, context) => {
@@ -1,5 +1,5 @@
1
1
  import { DListParser } from '../block';
2
- import { union, inits, some, creator, state, block, line, validate, rewrite, open, trimEnd, lazy, fmap } from '../../combinator';
2
+ import { union, inits, some, creation, state, block, line, validate, rewrite, open, trimEnd, lazy, fmap } from '../../combinator';
3
3
  import { inline, indexee, indexer } from '../inline';
4
4
  import { anyline } from '../source';
5
5
  import { State } from '../context';
@@ -17,13 +17,13 @@ export const dlist: DListParser = lazy(() => block(localize(fmap(validate(
17
17
  ]))),
18
18
  es => [html('dl', fillTrailingDescription(es))]))));
19
19
 
20
- const term: DListParser.TermParser = creator(line(indexee(fmap(open(
20
+ const term: DListParser.TermParser = creation(line(indexee(fmap(open(
21
21
  /^~[^\S\n]+(?=\S)/,
22
22
  visualize(trimBlank(some(union([indexer, inline])))),
23
23
  true),
24
24
  ns => [html('dt', defrag(ns))]))));
25
25
 
26
- const desc: DListParser.DescriptionParser = creator(block(fmap(open(
26
+ const desc: DListParser.DescriptionParser = creation(block(fmap(open(
27
27
  /^:[^\S\n]+(?=\S)|/,
28
28
  rewrite(
29
29
  some(anyline, /^[~:][^\S\n]+\S/),
@@ -2,7 +2,7 @@ import { undefined, BigInt, Array } from 'spica/global';
2
2
  import { max, min, isArray } from 'spica/alias';
3
3
  import { ExtensionParser } from '../../block';
4
4
  import { Tree, eval } from '../../../combinator/data/parser';
5
- import { union, subsequence, inits, some, creator, block, line, validate, fence, rewrite, open, clear, convert, trim, dup, lazy, fmap } from '../../../combinator';
5
+ import { union, subsequence, inits, some, creation, block, line, validate, fence, rewrite, open, clear, convert, trim, dup, lazy, fmap } from '../../../combinator';
6
6
  import { inline } from '../../inline';
7
7
  import { str, anyline, emptyline, contentline } from '../../source';
8
8
  import { localize } from '../../locale';
@@ -79,7 +79,7 @@ const align: AlignParser = line(fmap(
79
79
 
80
80
  const delimiter = /^[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|^[#:](?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/;
81
81
 
82
- const head: CellParser.HeadParser = creator(block(fmap(open(
82
+ const head: CellParser.HeadParser = creation(block(fmap(open(
83
83
  str(/^#(?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/),
84
84
  rewrite(
85
85
  inits([
@@ -91,7 +91,7 @@ const head: CellParser.HeadParser = creator(block(fmap(open(
91
91
  ns => [html('th', attributes(ns.shift()! as string), defrag(ns))]),
92
92
  false));
93
93
 
94
- const data: CellParser.DataParser = creator(block(fmap(open(
94
+ const data: CellParser.DataParser = creation(block(fmap(open(
95
95
  str(/^:(?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/),
96
96
  rewrite(
97
97
  inits([
@@ -103,7 +103,7 @@ const data: CellParser.DataParser = creator(block(fmap(open(
103
103
  ns => [html('td', attributes(ns.shift()! as string), defrag(ns))]),
104
104
  false));
105
105
 
106
- const dataline: CellParser.DatalineParser = creator(line(
106
+ const dataline: CellParser.DatalineParser = creation(line(
107
107
  rewrite(
108
108
  contentline,
109
109
  union([
@@ -1,5 +1,5 @@
1
1
  import { IListParser } from '../block';
2
- import { union, inits, some, creator, state, block, line, validate, indent, open, fallback, lazy, fmap } from '../../combinator';
2
+ import { union, inits, some, creation, state, block, line, validate, indent, open, fallback, lazy, fmap } from '../../combinator';
3
3
  import { ulist_, fillFirstLine } from './ulist';
4
4
  import { olist_, invalid } from './olist';
5
5
  import { inline } from '../inline';
@@ -13,7 +13,7 @@ export const ilist: IListParser = lazy(() => block(validate(
13
13
 
14
14
  export const ilist_: IListParser = lazy(() => block(fmap(validate(
15
15
  /^[-+*](?:$|\s)/,
16
- some(creator(union([
16
+ some(creation(union([
17
17
  fmap(fallback(
18
18
  inits([
19
19
  line(open(/^[-+*](?:$|\s)/, some(inline), true)),
@@ -1,6 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { OListParser } from '../block';
3
- import { union, inits, subsequence, some, creator, state, block, line, validate, indent, focus, rewrite, open, match, fallback, lazy, fmap } from '../../combinator';
3
+ import { union, inits, subsequence, some, creation, state, block, line, validate, indent, focus, rewrite, open, match, fallback, lazy, fmap } from '../../combinator';
4
4
  import { checkbox, ulist_, fillFirstLine } from './ulist';
5
5
  import { ilist_ } from './ilist';
6
6
  import { inline, indexee, indexer } from '../inline';
@@ -36,7 +36,7 @@ export const olist_: OListParser = lazy(() => block(union([
36
36
  ])));
37
37
 
38
38
  const list = (type: string, form: string): OListParser.ListParser => fmap(
39
- some(creator(union([
39
+ some(creation(union([
40
40
  indexee(fmap(fallback(
41
41
  inits([
42
42
  line(open(heads[form], subsequence([checkbox, trimBlank(some(union([indexer, inline])))]), true)),
@@ -45,13 +45,15 @@ describe('Unit: parser/block/paragraph', () => {
45
45
  assert.deepStrictEqual(inspect(parser('>>11 a')), [['<p><a href="?at=11" class="anchor">&gt;&gt;11</a> a</p>'], '']);
46
46
  assert.deepStrictEqual(inspect(parser('>>>11 a')), [['<p>&gt;<a href="?at=11" class="anchor">&gt;&gt;11</a> a</p>'], '']);
47
47
  assert.deepStrictEqual(inspect(parser('>> a\n>>1')), [['<p>&gt;&gt; a<br><a href="?at=1" class="anchor">&gt;&gt;1</a></p>'], '']);
48
- assert.deepStrictEqual(inspect(parser('a>>1')), [['<p>a<a href="?at=1" class="anchor">&gt;&gt;1</a></p>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('a>>1')), [['<p>a&gt;&gt;1</p>'], '']);
49
+ assert.deepStrictEqual(inspect(parser('ab>>1')), [['<p>ab&gt;&gt;1</p>'], '']);
49
50
  assert.deepStrictEqual(inspect(parser('a >>1')), [['<p>a <a href="?at=1" class="anchor">&gt;&gt;1</a></p>'], '']);
50
51
  assert.deepStrictEqual(inspect(parser('a\n>>1')), [['<p>a<br><a href="?at=1" class="anchor">&gt;&gt;1</a></p>'], '']);
51
52
  assert.deepStrictEqual(inspect(parser('a\n>>1\nb')), [['<p>a<br><a href="?at=1" class="anchor">&gt;&gt;1</a><br>b</p>'], '']);
52
53
  assert.deepStrictEqual(inspect(parser('a\n>> b\nc')), [['<p>a<br>&gt;&gt; b<br>c</p>'], '']);
53
54
  assert.deepStrictEqual(inspect(parser('\t>>1')), [['<p>\t<a href="?at=1" class="anchor">&gt;&gt;1</a></p>'], '']);
54
55
  assert.deepStrictEqual(inspect(parser('\t>>>1')), [['<p>\t&gt;<a href="?at=1" class="anchor">&gt;&gt;1</a></p>'], '']);
56
+ assert.deepStrictEqual(inspect(parser('あ>>1')), [['<p>あ<a href="?at=1" class="anchor">&gt;&gt;1</a></p>'], '']);
55
57
  });
56
58
 
57
59
  it('comment', () => {
@@ -1,10 +1,10 @@
1
1
  import { ReplyParser } from '../../block';
2
- import { union, tails, creator, line, validate, focus, reverse, fmap } from '../../../combinator';
2
+ import { union, tails, creation, line, validate, focus, reverse, fmap } from '../../../combinator';
3
3
  import { anchor } from '../../inline/autolink/anchor';
4
4
  import { str } from '../../source';
5
5
  import { html, define, defrag } from 'typed-dom/dom';
6
6
 
7
- export const cite: ReplyParser.CiteParser = creator(line(fmap(validate(
7
+ export const cite: ReplyParser.CiteParser = creation(line(fmap(validate(
8
8
  '>>',
9
9
  reverse(tails([
10
10
  str(/^>*(?=>>[^>\s]+[^\S\n]*(?:$|\n))/),
@@ -1,6 +1,6 @@
1
1
  import { ReplyParser } from '../../block';
2
2
  import { eval } from '../../../combinator/data/parser';
3
- import { union, some, creator, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
3
+ import { union, some, creation, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
4
4
  import { math } from '../../inline/math';
5
5
  import { str, anyline } from '../../source';
6
6
  import { autolink } from '../../autolink';
@@ -8,7 +8,7 @@ import { html, defrag } from 'typed-dom/dom';
8
8
 
9
9
  export const syntax = /^>+(?=[^\S\n])|^>(?=[^\s>])|^>+(?=[^\s>])(?![0-9a-z]+(?:-[0-9a-z]+)*(?![0-9A-Za-z@#:]))/;
10
10
 
11
- export const quote: ReplyParser.QuoteParser = lazy(() => creator(block(fmap(validate(
11
+ export const quote: ReplyParser.QuoteParser = lazy(() => creation(block(fmap(validate(
12
12
  '>',
13
13
  union([
14
14
  rewrite(
@@ -58,7 +58,7 @@ const qblock: ReplyParser.QuoteParser.BlockParser = (source, context) => {
58
58
  continue;
59
59
  }
60
60
  if (child.classList.contains('cite') || child.classList.contains('quote')) {
61
- context.resources && (context.resources.budget -= child.childNodes.length);
61
+ context.resources && (context.resources.clock -= child.childNodes.length);
62
62
  nodes.splice(i, 1, ...child.childNodes as NodeListOf<HTMLElement>);
63
63
  --i;
64
64
  continue;