securemark 0.299.1 → 0.299.3

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 (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/index.js +192 -138
  3. package/markdown.d.ts +2 -2
  4. package/package.json +1 -1
  5. package/src/combinator/control/manipulation/fence.ts +2 -2
  6. package/src/combinator/control/manipulation/match.ts +2 -2
  7. package/src/combinator/data/delimiter.ts +5 -3
  8. package/src/combinator/data/parser/context.ts +12 -38
  9. package/src/combinator/data/parser/some.ts +13 -6
  10. package/src/parser/api/header.ts +5 -1
  11. package/src/parser/api/parse.test.ts +9 -9
  12. package/src/parser/block/blockquote.ts +2 -2
  13. package/src/parser/block.ts +1 -1
  14. package/src/parser/context.ts +5 -6
  15. package/src/parser/header.test.ts +5 -5
  16. package/src/parser/header.ts +2 -3
  17. package/src/parser/inline/annotation.ts +9 -4
  18. package/src/parser/inline/autolink/url.ts +3 -4
  19. package/src/parser/inline/deletion.ts +1 -1
  20. package/src/parser/inline/emstrong.ts +1 -1
  21. package/src/parser/inline/insertion.ts +1 -1
  22. package/src/parser/inline/italic.ts +1 -1
  23. package/src/parser/inline/link.ts +2 -2
  24. package/src/parser/inline/mark.ts +1 -1
  25. package/src/parser/inline/math.test.ts +2 -2
  26. package/src/parser/inline/math.ts +3 -3
  27. package/src/parser/inline/media.ts +2 -2
  28. package/src/parser/inline/ruby.ts +2 -3
  29. package/src/parser/inline.test.ts +1 -1
  30. package/src/parser/inline.ts +1 -1
  31. package/src/parser/repeat.ts +11 -23
  32. package/src/parser/source/escapable.ts +33 -10
  33. package/src/parser/source/text.ts +15 -24
  34. package/src/parser/source/unescapable.test.ts +1 -1
  35. package/src/parser/source/unescapable.ts +90 -9
@@ -2,7 +2,7 @@ import { MediaParser } from '../inline';
2
2
  import { State, Recursion, Backtrack, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
4
  import { Flag } from '../node';
5
- import { union, inits, tails, some, consume, recursion, precedence, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
5
+ import { union, inits, tails, some, spend, recursion, precedence, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
6
6
  import { uri, option as linkoption, resolve, decode, parse } from './link';
7
7
  import { attributes } from './html';
8
8
  import { unsafehtmlentity } from './htmlentity';
@@ -59,7 +59,7 @@ export const media: MediaParser = lazy(() => constraint(State.media, open(
59
59
  text = text.trim();
60
60
  if (text === '' || text[0] !== tmp[0]) return;
61
61
  }
62
- consume(100, context);
62
+ spend(context, 100);
63
63
  if (params.last!.value === Command.Cancel) {
64
64
  params.pop();
65
65
  return new List([
@@ -20,13 +20,12 @@ export const ruby: RubyParser = lazy(() => bind(
20
20
  })),
21
21
  dup(surround(
22
22
  '(', text, ')',
23
- false,
24
- [3 | Backtrack.ruby])),
23
+ false)),
25
24
  ]),
26
25
  ([{ value: texts }, { value: rubies = undefined } = {}], context) => {
27
26
  if (rubies === undefined) {
28
27
  const head = context.position - context.range;
29
- return void setBacktrack(context, 2 | Backtrack.ruby, head);
28
+ return void setBacktrack(context, 2 | Backtrack.link | Backtrack.ruby, head);
30
29
  }
31
30
  switch (true) {
32
31
  case texts.length >= rubies.length:
@@ -167,7 +167,7 @@ describe('Unit: parser/inline', () => {
167
167
  assert.deepStrictEqual(inspect(parser, input('"[% "*"* %]', new Context())), [['"', '<span class="remark"><input type="checkbox"><span>[% "*"* %]</span></span>'], '']);
168
168
  assert.deepStrictEqual(inspect(parser, input('"{{""}}', new Context())), [['"', '{', '<a class="url" href="&quot;&quot;">""</a>', '}'], '']);
169
169
  assert.deepStrictEqual(inspect(parser, input('[#http://host/(<bdi>)]</bdi>', new Context())), [['<a class="index" href="#index::http://host/(&lt;bdi&gt;)">http://host/<span class="paren">(<span class="invalid">&lt;bdi&gt;</span>)</span></a>', '</bdi', '>'], '']);
170
- assert.deepStrictEqual(inspect(parser, input('[#@a/http://host/(<bdi>)]</bdi>', new Context())), [['<a class="index" href="#index::@a/http://host/(&lt;bdi&gt;)">@a/http://host/<span class="paren">(<span class="invalid">&lt;bdi&gt;</span>)</span></a>', '</bdi', '>'], '']);
170
+ assert.deepStrictEqual(inspect(parser, input('[#@a/http://host/(<bdi>)]</bdi>', new Context())), [['<a class="index" href="#index::@a/http://host/(&lt;bdi&gt;)">@a/http://host/(&lt;bdi&gt;)</a>', '</bdi', '>'], '']);
171
171
  assert.deepStrictEqual(inspect(parser, input('[#a|<bdi>]</bdi>', new Context())), [['<a class="index" href="#index::a|&lt;bdi&gt;">a|<span class="invalid">&lt;bdi&gt;</span></a>', '</bdi', '>'], '']);
172
172
  assert.deepStrictEqual(inspect(parser, input('[[#a|<bdi>]</bdi>', new Context())), [['[', '<a class="index" href="#index::a|&lt;bdi&gt;">a|<span class="invalid">&lt;bdi&gt;</span></a>', '</bdi', '>'], '']);
173
173
  assert.deepStrictEqual(inspect(parser, input('[*==*]{a}', new Context())), [['<a class="link" href="a">*==*</a>'], '']);
@@ -135,5 +135,5 @@ export { shortmedia, lineshortmedia } from './inline/shortmedia';
135
135
 
136
136
  function isAlphabet(char: string): boolean {
137
137
  assert(char.length === 1);
138
- return 'a' <= char && char <= 'z';
138
+ return char <= 'z' && 'a' <= char;
139
139
  }
@@ -1,17 +1,18 @@
1
1
  import { Parser, Result, List, Node } from '../combinator/data/parser';
2
2
  import { tester } from '../combinator/data/delimiter';
3
+ import { recur } from '../combinator';
3
4
  import { Context, Recursion, Command } from './context';
4
5
  import { min } from 'spica/alias';
5
6
 
6
7
  export function repeat<P extends Parser<HTMLElement | string, Context>>(
7
- opener: string, after: string | RegExp, closer: string, recursions: readonly Recursion[], parser: P,
8
+ opener: string, after: string | RegExp, closer: string, recursion: Recursion, parser: P,
8
9
  cons: (nodes: List<Node<Parser.Node<P>>>, context: Parser.Context<P>, lead: number, follow: number) =>
9
10
  List<Node<Parser.Node<P>>>,
10
11
  termination?: (acc: List<Node<Parser.Node<P>>>, context: Context, prefix: number, postfix: number, state: boolean) =>
11
12
  Result<string | Parser.Node<P>>,
12
13
  ): P;
13
14
  export function repeat<N extends HTMLElement | string>(
14
- opener: string, after: string | RegExp, closer: string, rs: readonly Recursion[], parser: Parser<N>,
15
+ opener: string, after: string | RegExp, closer: string, recursion: Recursion, parser: Parser<N>,
15
16
  cons: (nodes: List<Node<N>>, context: Context, lead: number, follow: number) =>
16
17
  List<Node<N>>,
17
18
  termination: (acc: List<Node<N>>, context: Context, prefix: number, postfix: number, state: boolean) =>
@@ -35,7 +36,7 @@ export function repeat<N extends HTMLElement | string>(
35
36
  const test = tester(after, false);
36
37
  return input => {
37
38
  const context = input;
38
- const { source, position, resources: { recursions } = {} } = context;
39
+ const { source, position, resources: { recursions } } = context;
39
40
  if (!source.startsWith(opener, context.position)) return;
40
41
  let nodes = new List<Node<N>>();
41
42
  let i = opener.length;
@@ -46,20 +47,11 @@ export function repeat<N extends HTMLElement | string>(
46
47
  return;
47
48
  }
48
49
  let depth = i / opener.length + 1 | 0;
49
- if (recursions) for (const recursion of rs) {
50
- const rec = min(recursion, recursions.length - 1);
51
- if (rec === -1) continue;
52
- if (recursions[rec] < depth - 1) throw new Error('Too much recursion');
53
- recursions[rec] -= depth;
54
- }
50
+ recur(recursions, recursion, depth, true);
55
51
  let state = false;
56
52
  let follow = 0;
57
53
  for (; i >= opener.length; i -= opener.length, follow -= closer.length) {
58
- if (recursions) for (const recursion of rs) {
59
- const rec = min(recursion, recursions.length - 1);
60
- if (rec === -1) continue;
61
- recursions[rec] += 1;
62
- }
54
+ recur(recursions, recursion, -1);
63
55
  depth -= 1;
64
56
  const lead = i - opener.length;
65
57
  if (source.startsWith(closer, context.position)) {
@@ -68,10 +60,10 @@ export function repeat<N extends HTMLElement | string>(
68
60
  follow = follow > 0 ? follow : countFollows(source, pos, closer, lead / opener.length | 0);
69
61
  nodes = cons(nodes, context, lead, follow);
70
62
  if (context.position > pos) {
71
- const advance = opener.length * (context.position - pos) / closer.length | 0;
63
+ const advance = context.position - pos;
72
64
  i -= advance;
73
65
  follow -= advance;
74
- depth -= advance;
66
+ depth -= advance / closer.length | 0;
75
67
  }
76
68
  continue;
77
69
  }
@@ -100,20 +92,16 @@ export function repeat<N extends HTMLElement | string>(
100
92
  nodes = cons(nodes, context, lead, follow);
101
93
  state = true;
102
94
  if (context.position > pos) {
103
- const advance = opener.length * (context.position - pos) / closer.length | 0;
95
+ const advance = context.position - pos;
104
96
  i -= advance;
105
97
  follow -= advance;
106
- depth -= advance;
98
+ depth -= advance / closer.length | 0;
107
99
  }
108
100
  continue;
109
101
  }
110
102
  break;
111
103
  }
112
- if (recursions) for (let i = 0; i < depth; ++i) for (const recursion of rs) {
113
- const rec = min(recursion, recursions.length - 1);
114
- if (rec === -1) continue;
115
- recursions[rec] += depth;
116
- }
104
+ recur(recursions, recursion, -depth);
117
105
  depth = 0;
118
106
  const prefix = i;
119
107
  i = 0;
@@ -2,21 +2,18 @@ import { EscapableSourceParser } from '../source';
2
2
  import { Command } from '../context';
3
3
  import { Flag } from '../node';
4
4
  import { List, Node } from '../../combinator/data/parser';
5
- import { consume } from '../../combinator';
6
- import { next } from './text';
5
+ import { spend } from '../../combinator';
7
6
  import { html } from 'typed-dom/dom';
8
7
 
9
- const delimiter = /(?=[\\$"`\[\](){}\r\n]|\s\$|:\/\/)/g;
10
-
11
8
  export const escsource: EscapableSourceParser = context => {
12
- const { source, position, state } = context;
9
+ const { source, position } = context;
13
10
  if (position === source.length) return;
14
11
  const char = source[position];
15
- consume(1, context);
12
+ spend(context, 1);
16
13
  context.position += 1;
17
14
  switch (char) {
18
15
  case Command.Escape:
19
- consume(1, context);
16
+ spend(context, 1);
20
17
  context.position += 1;
21
18
  return new List([new Node(source.slice(position + 1, position + 2))]);
22
19
  case '\\':
@@ -26,7 +23,7 @@ export const escsource: EscapableSourceParser = context => {
26
23
  case '\n':
27
24
  return new List([new Node(char)]);
28
25
  default:
29
- consume(1, context);
26
+ spend(context, 1);
30
27
  context.position += 1;
31
28
  return new List([new Node(source.slice(position, position + 2))]);
32
29
  }
@@ -38,11 +35,37 @@ export const escsource: EscapableSourceParser = context => {
38
35
  default:
39
36
  assert(char !== '\n');
40
37
  if (context.sequential) return new List([new Node(char)]);
41
- let i = next(source, position, state, delimiter);
38
+ let i = seek(source, position);
42
39
  assert(i > position);
43
40
  i -= position;
44
- consume(i - 1, context);
41
+ spend(context, i - 1);
45
42
  context.position += i - 1;
46
43
  return new List([new Node(source.slice(position, context.position))]);
47
44
  }
48
45
  };
46
+
47
+ function seek(source: string, position: number): number {
48
+ for (let i = position + 1; i < source.length; ++i) {
49
+ const char = source[i];
50
+ switch (char) {
51
+ case '\\':
52
+ case '$':
53
+ case '"':
54
+ case '`':
55
+ case ':':
56
+ case '[':
57
+ case ']':
58
+ case '(':
59
+ case ')':
60
+ case '{':
61
+ case '}':
62
+ case '\r':
63
+ case '\n':
64
+ return i;
65
+ default:
66
+ continue;
67
+ }
68
+ assert(false);
69
+ }
70
+ return source.length;
71
+ }
@@ -2,7 +2,7 @@ import { TextParser, TxtParser } from '../source';
2
2
  import { State, Command } from '../context';
3
3
  import { Flag } from '../node';
4
4
  import { List, Node } from '../../combinator/data/parser';
5
- import { union, consume } from '../../combinator';
5
+ import { union, spend } from '../../combinator';
6
6
  import { html } from 'typed-dom/dom';
7
7
 
8
8
  export const nonWhitespace = /[^ \t ]/g;
@@ -12,7 +12,7 @@ export const text: TextParser = input => {
12
12
  const { source, position, state } = context;
13
13
  if (position === source.length) return;
14
14
  const char = source[position];
15
- consume(1, context);
15
+ spend(context, 1);
16
16
  context.position += 1;
17
17
  switch (char) {
18
18
  case Command.Escape:
@@ -25,7 +25,7 @@ export const text: TextParser = input => {
25
25
  assert(char !== Command.Escape);
26
26
  return new List();
27
27
  default:
28
- consume(1, context);
28
+ spend(context, 1);
29
29
  context.position += 1;
30
30
  return new List([new Node(source.slice(position + 1, context.position))]);
31
31
  }
@@ -51,7 +51,7 @@ export const text: TextParser = input => {
51
51
  || s && source[i] === '\n';
52
52
  i -= position;
53
53
  i = lineend ? i : i - +s || 1;
54
- consume(i - 1, context);
54
+ spend(context, i - 1);
55
55
  context.position += i - 1;
56
56
  const linestart = position === 0 || source[position - 1] === '\n';
57
57
  if (position === context.position || s && !linestart || lineend) return new List();
@@ -83,28 +83,20 @@ function isWhitespace(char: string, linebreak: boolean): boolean {
83
83
  }
84
84
  }
85
85
 
86
- export function next(source: string, position: number, state: number, delimiter?: RegExp): number {
87
- let index: number;
88
- if (delimiter) {
89
- delimiter.lastIndex = position + 1;
90
- delimiter.test(source);
91
- index = delimiter.lastIndex || position;
92
- }
93
- else {
94
- index = seek(source, position, state);
95
- }
96
- if (index === position || index === source.length) return source.length;
86
+ function next(source: string, position: number, state: number): number {
87
+ let index= seek(source, position, state);
97
88
  assert(index > position);
89
+ if (index === source.length) return index;
98
90
  const char = source[index];
99
91
  switch (char) {
100
92
  case '%':
101
- assert(source.startsWith('%]', index) && isWhitespace(source[index - 1], true) || delimiter);
102
- index += !delimiter && index - 1 > position
93
+ assert(source.startsWith('%]', index) && isWhitespace(source[index - 1], true));
94
+ index += index - 1 > position
103
95
  ? -1
104
96
  : 0;
105
97
  break;
106
98
  case '[':
107
- index += !delimiter && index - 1 > position && source.startsWith(' [|', index - 1)
99
+ index += index - 1 > position && source.startsWith(' [|', index - 1)
108
100
  ? -1
109
101
  : 0;
110
102
  break;
@@ -122,7 +114,7 @@ export function next(source: string, position: number, state: number, delimiter?
122
114
  assert(index > position);
123
115
  return index;
124
116
  }
125
- function backToUrlHead(source: string, position: number, index: number): number {
117
+ export function backToUrlHead(source: string, position: number, index: number): number {
126
118
  const delim = index;
127
119
  let state = false;
128
120
  for (let i = index - 1; i >= position; --i) {
@@ -145,7 +137,7 @@ function backToUrlHead(source: string, position: number, index: number): number
145
137
  ? delim
146
138
  : index;
147
139
  }
148
- function backToEmailHead(source: string, position: number, index: number): number {
140
+ export function backToEmailHead(source: string, position: number, index: number): number {
149
141
  const delim = index;
150
142
  let state = false;
151
143
  for (let i = index - 1; i >= position; --i) {
@@ -171,10 +163,9 @@ function backToEmailHead(source: string, position: number, index: number): numbe
171
163
  }
172
164
  export function isAlphanumeric(char: string): boolean {
173
165
  assert(char.length === 1);
174
- if (char < '0' || '\x7F' < char) return false;
175
- return '0' <= char && char <= '9'
176
- || 'A' <= char && char <= 'Z'
177
- || 'a' <= char && char <= 'z';
166
+ if (char < '0' || 'z' < char) return false;
167
+ if (char <= '9' || 'a' <= char) return true;
168
+ return 'A' <= char && char <= 'Z';
178
169
  }
179
170
 
180
171
  function seek(source: string, position: number, state: number): number {
@@ -15,7 +15,7 @@ describe('Unit: parser/source/unescapable', () => {
15
15
  it('basic', () => {
16
16
  assert.deepStrictEqual(inspect(parser, input('a', new Context())), [['a'], '']);
17
17
  assert.deepStrictEqual(inspect(parser, input('ab', new Context())), [['ab'], '']);
18
- assert.deepStrictEqual(inspect(parser, input('a b c', new Context())), [['a', ' b', ' c'], '']);
18
+ assert.deepStrictEqual(inspect(parser, input('a b c', new Context())), [['a', ' b c'], '']);
19
19
  assert.deepStrictEqual(inspect(parser, input('09あいAZaz', new Context())), [['09', 'あいAZaz'], '']);
20
20
  });
21
21
 
@@ -1,22 +1,20 @@
1
1
  import { UnescapableSourceParser } from '../source';
2
- import { Command } from '../context';
2
+ import { State, Command } from '../context';
3
3
  import { Flag } from '../node';
4
4
  import { List, Node } from '../../combinator/data/parser';
5
- import { consume } from '../../combinator';
6
- import { nonWhitespace, canSkip, next } from './text';
5
+ import { spend } from '../../combinator';
6
+ import { nonWhitespace, canSkip, backToUrlHead, backToEmailHead } from './text';
7
7
  import { html } from 'typed-dom/dom';
8
8
 
9
- export const delimiter = /(?=(?=[\x00-\x7F])[^0-9A-Za-z]|(?<=[\x00-\x7F])[^\x00-\x7F])/g;
10
-
11
9
  export const unescsource: UnescapableSourceParser = context => {
12
10
  const { source, position, state } = context;
13
11
  if (position === source.length) return;
14
12
  const char = source[position];
15
- consume(1, context);
13
+ spend(context, 1);
16
14
  context.position += 1;
17
15
  switch (char) {
18
16
  case Command.Escape:
19
- consume(1, context);
17
+ spend(context, 1);
20
18
  context.position += 1;
21
19
  return new List([new Node(source.slice(position + 1, position + 2))]);
22
20
  case '\r':
@@ -32,11 +30,94 @@ export const unescsource: UnescapableSourceParser = context => {
32
30
  ? nonWhitespace.test(source)
33
31
  ? nonWhitespace.lastIndex - 1
34
32
  : source.length
35
- : next(source, position, state, delimiter);
33
+ : next(source, position, state);
36
34
  assert(i > position);
37
35
  i -= position;
38
- consume(i - 1, context);
36
+ spend(context, i - 1);
39
37
  context.position += i - 1;
40
38
  return new List([new Node(source.slice(position, context.position))]);
41
39
  }
42
40
  };
41
+
42
+ function next(source: string, position: number, state: number): number {
43
+ let index= seek(source, position, state);
44
+ assert(index > position);
45
+ if (index === source.length) return index;
46
+ const char = source[index];
47
+ switch (char) {
48
+ case ':':
49
+ index = source.startsWith('//', index + 1)
50
+ ? backToUrlHead(source, position, index)
51
+ : index;
52
+ break;
53
+ case '@':
54
+ index = ~state & State.autolink
55
+ ? backToEmailHead(source, position, index)
56
+ : index;
57
+ break;
58
+ }
59
+ assert(index > position);
60
+ return index;
61
+ }
62
+
63
+ function seek(source: string, position: number, state: number): number {
64
+ const cat = category(source[position]);
65
+ for (let i = position + 1; i < source.length; ++i) {
66
+ const char = source[i];
67
+ switch (char) {
68
+ case '\\':
69
+ case '!':
70
+ case '$':
71
+ case '"':
72
+ case '`':
73
+ case '[':
74
+ case ']':
75
+ case '(':
76
+ case ')':
77
+ case '{':
78
+ case '}':
79
+ case '<':
80
+ case '>':
81
+ case '(':
82
+ case ')':
83
+ case '[':
84
+ case ']':
85
+ case '{':
86
+ case '}':
87
+ case '-':
88
+ case '+':
89
+ case '*':
90
+ case '=':
91
+ case '~':
92
+ case '^':
93
+ case '_':
94
+ case ',':
95
+ case '.':
96
+ case ';':
97
+ case ':':
98
+ case '!':
99
+ case '?':
100
+ case '/':
101
+ case '|':
102
+ case '\r':
103
+ case '\n':
104
+ return i;
105
+ case '@':
106
+ case '#':
107
+ if (~state & State.autolink) return i;
108
+ continue;
109
+ case ':':
110
+ if (source[i + 1] === '/' && source[i + 2] === '/') return i;
111
+ continue;
112
+ default:
113
+ if (cat && !category(char)) return i;
114
+ continue;
115
+ }
116
+ assert(false);
117
+ }
118
+ return source.length;
119
+ }
120
+
121
+ function category(char: string): boolean {
122
+ return char <= '\x7E' && '\x21' <= char;
123
+ }