securemark 0.295.8 → 0.296.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/design.md +1 -1
  3. package/dist/index.js +268 -282
  4. package/package.json +1 -1
  5. package/src/combinator/control/constraint/contract.ts +5 -21
  6. package/src/combinator/data/parser/context.ts +3 -1
  7. package/src/combinator/data/parser/sequence.ts +3 -3
  8. package/src/combinator/data/parser.ts +7 -2
  9. package/src/parser/api/normalize.ts +10 -7
  10. package/src/parser/block/extension/figbase.test.ts +3 -0
  11. package/src/parser/block/extension/figbase.ts +1 -1
  12. package/src/parser/block/paragraph.test.ts +2 -2
  13. package/src/parser/block/reply/cite.ts +2 -1
  14. package/src/parser/block/reply/quote.ts +2 -1
  15. package/src/parser/block/reply.ts +2 -1
  16. package/src/parser/inline/annotation.test.ts +4 -5
  17. package/src/parser/inline/annotation.ts +2 -2
  18. package/src/parser/inline/emphasis.ts +3 -3
  19. package/src/parser/inline/emstrong.ts +4 -4
  20. package/src/parser/inline/extension/index.ts +2 -2
  21. package/src/parser/inline/extension/indexer.ts +3 -3
  22. package/src/parser/inline/extension/placeholder.ts +2 -2
  23. package/src/parser/inline/html.ts +2 -1
  24. package/src/parser/inline/htmlentity.test.ts +1 -1
  25. package/src/parser/inline/htmlentity.ts +10 -5
  26. package/src/parser/inline/italic.ts +2 -2
  27. package/src/parser/inline/link.test.ts +5 -4
  28. package/src/parser/inline/link.ts +6 -4
  29. package/src/parser/inline/mark.ts +2 -2
  30. package/src/parser/inline/media.test.ts +5 -2
  31. package/src/parser/inline/media.ts +9 -4
  32. package/src/parser/inline/reference.test.ts +5 -6
  33. package/src/parser/inline/reference.ts +2 -2
  34. package/src/parser/inline/remark.ts +1 -1
  35. package/src/parser/inline/strong.ts +3 -3
  36. package/src/parser/node.ts +17 -0
  37. package/src/parser/source/str.ts +9 -7
  38. package/src/parser/source/text.ts +4 -4
  39. package/src/parser/visibility.ts +53 -75
  40. /package/src/combinator/data/{data.ts → node.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { MediaParser } from '../inline';
2
2
  import { State, Recursion, Backtrack, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
+ import { Flag } from '../node';
4
5
  import { union, inits, tails, some, consume, recursion, precedence, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
5
6
  import { uri, option as linkoption, resolve, decode, parse } from './link';
6
7
  import { attributes } from './html';
@@ -50,10 +51,14 @@ export const media: MediaParser = lazy(() => constraint(State.media, open(
50
51
  nodes =>
51
52
  nodes.length === 1
52
53
  ? new List<Node<List<Node<string>>>>([new Node(new List([new Node('')])), nodes.delete(nodes.head!)])
53
- : new List<Node<List<Node<string>>>>([new Node(new List([new Node(nodes.head!.value.foldl((acc, { value }) => acc + value, ''))])), nodes.delete(nodes.last!)])),
54
- ([{ value: [{ value: text }] }, { value: params }], context) => {
55
- if (text && text.trimStart() === '') return;
56
- text = text.trim();
54
+ : new List<Node<List<Node<string>>>>([new Node(new List([new Node(nodes.head!.value.foldl((acc, { value }) => acc + value, ''), nodes.head!.value.head?.flags)])), nodes.delete(nodes.last!)])),
55
+ ([{ value: [{ value: text, flags }] }, { value: params }], context) => {
56
+ if (flags & Flag.invisible) return;
57
+ if (text) {
58
+ const tmp = text;
59
+ text = text.trim();
60
+ if (text === '' || text[0] !== tmp[0]) return;
61
+ }
57
62
  consume(100, context);
58
63
  if (params.last!.value === Command.Cancel) {
59
64
  params.pop();
@@ -19,7 +19,10 @@ describe('Unit: parser/inline/reference', () => {
19
19
  assert.deepStrictEqual(inspect(parser, input('[[(]]', new Context())), undefined);
20
20
  assert.deepStrictEqual(inspect(parser, input('[[[%]]', new Context())), undefined);
21
21
  assert.deepStrictEqual(inspect(parser, input('[[ ]]', new Context())), undefined);
22
- assert.deepStrictEqual(inspect(parser, input('[[ [a', new Context())), undefined);
22
+ assert.deepStrictEqual(inspect(parser, input('[[ a]]', new Context())), undefined);
23
+ assert.deepStrictEqual(inspect(parser, input('[[ a ]]', new Context())), undefined);
24
+ assert.deepStrictEqual(inspect(parser, input('[[\\ a]]', new Context())), undefined);
25
+ assert.deepStrictEqual(inspect(parser, input('[[<wbr>a]]', new Context())), undefined);
23
26
  assert.deepStrictEqual(inspect(parser, input('[[\n]]', new Context())), undefined);
24
27
  assert.deepStrictEqual(inspect(parser, input('[[\na]]', new Context())), undefined);
25
28
  assert.deepStrictEqual(inspect(parser, input('[[\\\na]]', new Context())), undefined);
@@ -35,10 +38,6 @@ describe('Unit: parser/inline/reference', () => {
35
38
  });
36
39
 
37
40
  it('basic', () => {
38
- assert.deepStrictEqual(inspect(parser, input('[[ a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
39
- assert.deepStrictEqual(inspect(parser, input('[[ a ]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
40
- assert.deepStrictEqual(inspect(parser, input('[[\\ a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
41
- assert.deepStrictEqual(inspect(parser, input('[[<wbr>a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
42
41
  assert.deepStrictEqual(inspect(parser, input('[[a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
43
42
  assert.deepStrictEqual(inspect(parser, input('[[a ]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
44
43
  assert.deepStrictEqual(inspect(parser, input('[[a ]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
@@ -81,7 +80,7 @@ describe('Unit: parser/inline/reference', () => {
81
80
  assert.deepStrictEqual(inspect(parser, input('[[^A| b]]', new Context())), [['<sup class="reference" data-abbr="A"><span>b</span></sup>'], '']);
82
81
  assert.deepStrictEqual(inspect(parser, input('[[^A| ]]', new Context())), undefined);
83
82
  assert.deepStrictEqual(inspect(parser, input('[[^A|<wbr>]]', new Context())), undefined);
84
- assert.deepStrictEqual(inspect(parser, input('[[^A|<wbr>b]]', new Context())), [['<sup class="reference" data-abbr="A"><span>b</span></sup>'], '']);
83
+ assert.deepStrictEqual(inspect(parser, input('[[^A|<wbr>b]]', new Context())), undefined);
85
84
  assert.deepStrictEqual(inspect(parser, input('[[^A|^]]', new Context())), [['<sup class="reference" data-abbr="A"><span>^</span></sup>'], '']);
86
85
  assert.deepStrictEqual(inspect(parser, input('[[^A|^B]]', new Context())), [['<sup class="reference" data-abbr="A"><span>^B</span></sup>'], '']);
87
86
  assert.deepStrictEqual(inspect(parser, input('[[^1]]', new Context())), [['<sup class="invalid"><span>^1</span></sup>'], '']);
@@ -5,7 +5,7 @@ import { union, subsequence, some, precedence, state, constraint, surround, isBa
5
5
  import { inline } from '../inline';
6
6
  import { textlink } from './link';
7
7
  import { str } from '../source';
8
- import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
8
+ import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
9
9
  import { unwrap, invalid } from '../util';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
 
@@ -14,7 +14,7 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
14
14
  precedence(1, state(State.annotation | State.reference,
15
15
  subsequence([
16
16
  abbr,
17
- trimBlankStart(some(inline, ']', [[']', 1]])),
17
+ beforeNonblank(some(inline, ']', [[']', 1]])),
18
18
  ]))),
19
19
  ']]',
20
20
  false,
@@ -11,7 +11,7 @@ export const remark: RemarkParser = lazy(() => fallback(surround(
11
11
  str(/\[%(?=[ \n])/y),
12
12
  precedence(3, recursion(Recursion.inline,
13
13
  some(union([inline]), /[ \n]%\]/y, [[/[ \n]%\]/y, 3]]))),
14
- close(text, str(`%]`)),
14
+ close(text, str('%]')),
15
15
  true, [],
16
16
  ([as, bs = new List(), cs]) => new List([
17
17
  new Node(html('span', { class: 'remark' }, [
@@ -5,14 +5,14 @@ import { union, some, recursion, precedence, surround, lazy } from '../../combin
5
5
  import { inline } from '../inline';
6
6
  import { emphasis } from './emphasis';
7
7
  import { str } from '../source';
8
- import { tightStart, afterNonblank } from '../visibility';
8
+ import { beforeNonblank, afterNonblank } from '../visibility';
9
9
  import { unwrap } from '../util';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
 
12
12
  export const strong: StrongParser = lazy(() => surround(
13
- str(/\*\*(?!\*)/y),
13
+ str('**', (source, position, range) => !source.startsWith('*', position + range)),
14
14
  precedence(0, recursion(Recursion.inline,
15
- tightStart(some(union([
15
+ beforeNonblank(some(union([
16
16
  some(inline, '*', afterNonblank),
17
17
  emphasis,
18
18
  ]))))),
@@ -0,0 +1,17 @@
1
+ import { invisibleHTMLEntityNames } from './api/normalize';
2
+
3
+ export const enum Flag {
4
+ none,
5
+ invisible,
6
+ }
7
+
8
+ export const isInvisibleHTMLEntityName: (name: string) => boolean = eval([
9
+ 'name => {',
10
+ 'switch(name){',
11
+ invisibleHTMLEntityNames.map(name => `case '${name}':`).join(''),
12
+ 'return true;',
13
+ 'default:',
14
+ 'return false;',
15
+ '}',
16
+ '}',
17
+ ].join(''));
@@ -2,21 +2,23 @@ import { StrParser } from '../source';
2
2
  import { Parser, List, Node } from '../../combinator/data/parser';
3
3
  import { matcher } from '../../combinator';
4
4
 
5
- export function str(pattern: string | RegExp): StrParser;
6
- export function str(pattern: string | RegExp): Parser<string> {
7
- return matcher(pattern, true);
5
+ export function str(pattern: string | RegExp, verify?: (source: string, position: number, range: number) => boolean): StrParser;
6
+ export function str(pattern: string | RegExp, verify?: (source: string, position: number, range: number) => boolean): Parser<string> {
7
+ return matcher(pattern, true, verify);
8
8
  }
9
9
 
10
- export function strs(pattern: string): StrParser;
11
- export function strs(pattern: string): Parser<string> {
10
+ export function strs(pattern: string, limit?: number): StrParser;
11
+ export function strs(pattern: string, limit: number = -1): Parser<string> {
12
12
  assert(pattern);
13
13
  return ({ context }) => {
14
14
  const { source } = context;
15
15
  let acc = '';
16
- for (; context.position < source.length && source.startsWith(pattern, context.position);) {
16
+ for (let i = 0; i !== limit && context.position < source.length && source.startsWith(pattern, context.position); ++i) {
17
17
  acc += pattern;
18
18
  context.position += pattern.length;
19
19
  }
20
- return new List([new Node(acc)]);
20
+ return acc
21
+ ? new List([new Node(acc)])
22
+ : undefined;
21
23
  };
22
24
  }
@@ -1,5 +1,6 @@
1
1
  import { TextParser, TxtParser } from '../source';
2
2
  import { State, Command } from '../context';
3
+ import { Flag } from '../node';
3
4
  import { List, Node } from '../../combinator/data/parser';
4
5
  import { union, consume } from '../../combinator';
5
6
  import { html } from 'typed-dom/dom';
@@ -32,7 +33,7 @@ export const text: TextParser = input => {
32
33
  return new List();
33
34
  case '\n':
34
35
  context.linebreak ||= source.length - position;
35
- return new List([new Node(html('br'))]);
36
+ return new List([new Node(html('br'), Flag.invisible)]);
36
37
  default:
37
38
  assert(char !== '\n');
38
39
  if (context.sequential) return new List([new Node(char)]);
@@ -52,9 +53,8 @@ export const text: TextParser = input => {
52
53
  consume(i - 1, context);
53
54
  context.position += i - 1;
54
55
  const linestart = position === 0 || source[position - 1] === '\n';
55
- return position === context.position || s && !linestart || lineend
56
- ? new List()
57
- : new List([new Node(source.slice(position, context.position))]);
56
+ if (position === context.position || s && !linestart || lineend) return new List();
57
+ return new List([new Node(source.slice(position, context.position))]);
58
58
  }
59
59
  };
60
60
 
@@ -1,10 +1,10 @@
1
1
  import { Parser, Input, List, Node, failsafe } from '../combinator/data/parser';
2
2
  import { Context, Command } from './context';
3
+ import { Flag } from './node';
3
4
  import { convert, fmap } from '../combinator';
4
- import { unsafehtmlentity } from './inline/htmlentity';
5
5
  import { invisibleHTMLEntityNames } from './api/normalize';
6
6
 
7
- namespace blank {
7
+ namespace invisible {
8
8
  export const line = new RegExp(
9
9
  /((?:^|\n)[^\S\n]*(?=\S))((?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+(?=$|\n))/g.source
10
10
  .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
@@ -13,12 +13,15 @@ namespace blank {
13
13
  /(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+/y.source
14
14
  .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
15
15
  'y');
16
+ export const unit = new RegExp(
17
+ /(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)/y.source
18
+ .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
19
+ 'y');
16
20
  }
17
21
 
18
- export function visualize<P extends Parser<HTMLElement | string>>(parser: P): P;
19
- export function visualize<N extends HTMLElement | string>(parser: Parser<N>): Parser<N> {
22
+ export function visualize<P extends Parser>(parser: P): P {
20
23
  return convert(
21
- source => source.replace(blank.line, `$1${Command.Escape}$2`),
24
+ source => source.replace(invisible.line, `$1${Command.Escape}$2`),
22
25
  parser);
23
26
  }
24
27
 
@@ -42,114 +45,86 @@ function nonblankWith(delimiter: string | RegExp): RegExp {
42
45
  ].join(''), 'y');
43
46
  }
44
47
 
45
- //export function looseStart<P extends Parser<HTMLElement | string>>(parser: P, except?: string): P;
46
- //export function looseStart<N extends HTMLElement | string>(parser: Parser<N>, except?: string): Parser<N> {
48
+ //export function looseStart<P extends Parser<HTMLElement | string>>(parser: P): P;
49
+ //export function looseStart<N extends HTMLElement | string>(parser: Parser<N>): Parser<N> {
47
50
  // return input =>
48
- // isLooseStart(input, except)
51
+ // isLooseStart(input)
49
52
  // ? parser(input)
50
53
  // : undefined;
51
54
  //}
52
- //const isLooseStart = reduce(({ source, context }: Input<Context>, except?: string): boolean => {
53
- // return isTightStart({ source: source.replace(blank.start, ''), context }, except);
54
- //}, ({ source }, except = '') => `${source}${Command.Separator}${except}`);
55
+ //const isLooseStart = reduce(({ source, context }: Input<Context>): boolean => {
56
+ // return isTightStart({ source: source.replace(invisible.start, ''), context });
57
+ //}, ({ source }) => `${source}${Command.Separator}`);
55
58
 
56
- export function tightStart<P extends Parser>(parser: P, except?: string): P;
57
- export function tightStart<N>(parser: Parser<N>, except?: string): Parser<N, Context> {
59
+ export function beforeNonblank<P extends Parser>(parser: P): P;
60
+ export function beforeNonblank<N>(parser: Parser<N>): Parser<N, Context> {
58
61
  return input =>
59
- isTightStart(input, except)
62
+ isTightStart(input)
60
63
  ? parser(input)
61
64
  : undefined;
62
65
  }
63
- function isTightStart(input: Input<Context>, except?: string): boolean {
66
+ function isTightStart(input: Input<Context>): boolean {
64
67
  const { context } = input;
65
68
  const { source, position } = context;
66
69
  if (position === source.length) return true;
67
- if (except && source.startsWith(except, position)) return false;
68
70
  switch (source[position]) {
69
71
  case ' ':
70
72
  case ' ':
71
73
  case '\t':
72
74
  case '\n':
73
75
  return false;
74
- case '\\':
75
- return source[position + 1]?.trimStart() !== '';
76
- case '&':
77
- switch (true) {
78
- case source.length - position > 2
79
- && source[position + 1] !== ' '
80
- && unsafehtmlentity(input)?.head?.value.trimStart() === '':
81
- context.position = position;
82
- return false;
83
- }
84
- context.position = position;
85
- return true;
86
- case '<':
87
- switch (true) {
88
- case source.length - position >= 5
89
- && source.startsWith('<wbr', position)
90
- && (source[position + 4] === '>' || source.startsWith(' >', position + 4)):
91
- return false;
92
- }
93
- return true;
94
76
  default:
95
- return source[position].trimStart() !== '';
77
+ const reg = invisible.unit;
78
+ reg.lastIndex = position;
79
+ return !reg.test(source);
96
80
  }
97
81
  }
98
82
 
99
83
  export function isLooseNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
100
84
  if (nodes.length === 0) return true;
101
- for (const { value: node } of nodes) {
85
+ for (const node of nodes) {
102
86
  if (isVisible(node)) return true;
103
- if (typeof node === 'object' && node.tagName === 'BR') break;
87
+ if (typeof node.value === 'object' && node.value.tagName === 'BR') break;
104
88
  }
105
89
  return false;
106
90
  }
107
91
  export function isTightNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
108
92
  if (nodes.length === 0) return true;
109
- return isVisible(nodes.head!.value, 0);
93
+ return isVisible(nodes.head!, 0);
110
94
  }
111
95
  //export function isTightNodeEnd(nodes: readonly (HTMLElement | string)[]): boolean {
112
96
  // if (nodes.length === 0) return true;
113
97
  // return isVisible(nodes.at(-1)!, -1);
114
98
  //}
115
- function isVisible(node: HTMLElement | string, strpos?: number): boolean {
116
- switch (typeof node) {
117
- case 'string':
118
- const char = node && strpos !== undefined
119
- ? node[strpos >= 0 ? strpos : node.length + strpos]
120
- : node;
121
- switch (char) {
122
- case '':
123
- case ' ':
124
- case '\t':
125
- case '\n':
126
- return false;
127
- default:
128
- return char.trimStart() !== '';
129
- }
99
+ function isVisible({ value: node, flags }: Node<HTMLElement | string>, strpos?: number): boolean {
100
+ if (flags & Flag.invisible) return false;
101
+ if (typeof node !== 'string') return true;
102
+ const str = node && strpos !== undefined
103
+ ? node[strpos >= 0 ? strpos : node.length + strpos]
104
+ : node;
105
+ switch (str) {
106
+ case '':
107
+ case ' ':
108
+ case '\t':
109
+ case '\n':
110
+ return false;
130
111
  default:
131
- switch (node.tagName) {
132
- case 'BR':
133
- case 'WBR':
134
- return false;
135
- default:
136
- return true;
137
- }
112
+ return str.trimStart() !== '';
138
113
  }
139
114
  }
140
115
 
141
- // デフラグ前の非効率な後方トリムを避けるため必要のない限りtrimBlankStart+trimNodeEndで処理する。
116
+ // 終端が必要な場合は無駄な後方トリムを避けるためtrimBlankStart+trimBlankNodeEndで処理する。
142
117
  export function trimBlank<P extends Parser<HTMLElement | string>>(parser: P): P;
143
118
  export function trimBlank<N extends HTMLElement | string>(parser: Parser<N>): Parser<N> {
144
119
  return trimBlankStart(trimBlankEnd(parser));
145
120
  }
146
- export function trimBlankStart<P extends Parser>(parser: P): P;
147
- export function trimBlankStart<N>(parser: Parser<N>): Parser<N> {
121
+ function trimBlankStart<P extends Parser>(parser: P): P;
122
+ function trimBlankStart<N>(parser: Parser<N>): Parser<N> {
148
123
  return failsafe(input => {
149
124
  const { context } = input;
150
125
  const { source, position } = context;
151
126
  if (position === source.length) return;
152
- const reg = blank.start;
127
+ const reg = invisible.start;
153
128
  reg.lastIndex = position;
154
129
  reg.test(source);
155
130
  context.position = reg.lastIndex || position;
@@ -182,21 +157,24 @@ export function trimBlankEnd<N extends HTMLElement>(parser: Parser<N>): Parser<s
182
157
  // return nodes;
183
158
  //}
184
159
  export function trimBlankNodeEnd<N extends HTMLElement>(nodes: List<Node<string | N>>): List<Node<string | N>> {
185
- const skip = nodes.length > 0 &&
186
- typeof nodes.last?.value === 'object' &&
187
- nodes.last.value.className === 'indexer'
188
- ? nodes.pop()
189
- : undefined;
190
- for (let node = nodes.last; nodes.length > 0 && !isVisible((node = nodes.last!).value, -1);) {
191
- if (typeof node.value === 'string') {
160
+ const skip = nodes.last && ~nodes.last.flags & Flag.invisible && typeof nodes.last.value === 'object'
161
+ ? nodes.last.value.className === 'indexer'
162
+ : false;
163
+ for (let node = skip ? nodes.last?.prev : nodes.last; node;) {
164
+ const visible = ~node.flags & Flag.invisible;
165
+ if (visible && typeof node.value === 'string') {
192
166
  const str = node.value.trimEnd();
193
167
  if (str.length > 0) {
194
168
  node.value = str;
195
169
  break;
196
170
  }
197
171
  }
198
- nodes.pop();
172
+ else if (visible) {
173
+ break;
174
+ }
175
+ const target = node;
176
+ node = node.prev;
177
+ nodes.delete(target);
199
178
  }
200
- skip && nodes.push(skip);
201
179
  return nodes;
202
180
  }
File without changes