securemark 0.300.0 → 0.300.2

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 (45) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/index.js +237 -161
  3. package/index.d.ts +1 -1
  4. package/markdown.d.ts +3 -10
  5. package/package.json +1 -1
  6. package/src/api/bind.ts +6 -6
  7. package/src/api/parse.ts +2 -2
  8. package/src/combinator/control/inits.ts +13 -4
  9. package/src/combinator/control/sequence.ts +3 -1
  10. package/src/combinator/control/state.ts +1 -10
  11. package/src/combinator/control/union.ts +16 -3
  12. package/src/combinator/parser.ts +18 -8
  13. package/src/combinator/process/fence.ts +23 -33
  14. package/src/combinator/process/line.ts +1 -1
  15. package/src/combinator/process/surround.ts +2 -0
  16. package/src/parser/block/codeblock.test.ts +0 -2
  17. package/src/parser/block/codeblock.ts +4 -4
  18. package/src/parser/block/extension/aside.test.ts +0 -1
  19. package/src/parser/block/extension/aside.ts +2 -2
  20. package/src/parser/block/extension/example.test.ts +0 -2
  21. package/src/parser/block/extension/example.ts +2 -2
  22. package/src/parser/block/extension/fig.ts +5 -7
  23. package/src/parser/block/extension/figure.test.ts +0 -4
  24. package/src/parser/block/extension/figure.ts +6 -7
  25. package/src/parser/block/extension/message.test.ts +0 -1
  26. package/src/parser/block/extension/message.ts +2 -2
  27. package/src/parser/block/extension/placeholder.ts +4 -4
  28. package/src/parser/block/extension/table.test.ts +0 -1
  29. package/src/parser/block/extension/table.ts +2 -8
  30. package/src/parser/block/extension.ts +1 -2
  31. package/src/parser/block/mathblock.test.ts +0 -2
  32. package/src/parser/block/mathblock.ts +4 -4
  33. package/src/parser/context.ts +9 -12
  34. package/src/parser/document.ts +1 -1
  35. package/src/parser/inline/autolink/url.ts +4 -4
  36. package/src/parser/inline/math.ts +1 -1
  37. package/src/parser/inline/media.ts +4 -4
  38. package/src/parser/inline/ruby.ts +45 -8
  39. package/src/parser/inline/template.ts +4 -4
  40. package/src/parser/source/escapable.ts +0 -1
  41. package/src/parser/source/text.ts +14 -43
  42. package/src/parser/source/unescapable.ts +0 -1
  43. package/src/parser/source/whitespace.ts +36 -0
  44. package/src/parser/source.ts +1 -0
  45. package/src/parser/visibility.ts +2 -1
@@ -7,13 +7,13 @@ import { html } from 'typed-dom/dom';
7
7
  const opener = /(~{3,})(?!~)[^\r\n]*(?:$|\r?\n)/y;
8
8
 
9
9
  export const segment: ExtensionParser.PlaceholderParser.SegmentParser = block(
10
- fence(opener, false, 300));
10
+ fence(opener, false));
11
11
 
12
12
  export const segment_: ExtensionParser.PlaceholderParser.SegmentParser = block(
13
- fence(opener, false, 300, false), false);
13
+ fence(opener, false, false), false);
14
14
 
15
15
  export const placeholder: ExtensionParser.PlaceholderParser = block(inits([
16
- fence(opener, true, 300),
16
+ fence(opener, true),
17
17
  (_, output) => {
18
18
  const [body, overflow, closer, opener, delim] = unwrap(output.pop()) as string[];
19
19
  return output.append(
@@ -28,6 +28,6 @@ export const placeholder: ExtensionParser.PlaceholderParser = block(inits([
28
28
  overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
29
29
  'Invalid argument'),
30
30
  },
31
- `${opener}${body}${overflow || closer}`)));
31
+ `${opener}${body}${closer}${overflow}`)));
32
32
  },
33
33
  ]));
@@ -10,7 +10,6 @@ describe('Unit: parser/block/extension/table', () => {
10
10
 
11
11
  it('invalid', () => {
12
12
  assert.deepStrictEqual(inspect(parser, input('~~~table a\n-\n~~~')), [['<pre class="invalid" translate="no">~~~table a\n-\n~~~</pre>'], '']);
13
- assert.deepStrictEqual(inspect(parser, input(`~~~table\n0${'\n'.repeat(10001)}~~~`), '>'), [['<pre class="invalid" translate="no">'], '']);
14
13
  });
15
14
 
16
15
  it('data', () => {
@@ -17,14 +17,8 @@ import CellParser = TableParser.CellParser;
17
17
 
18
18
  const opener = /(~{3,})table(?:\/(\S+))?(?!\S)([^\r\n]*)(?:$|\r?\n)/y;
19
19
 
20
- export const segment: TableParser.SegmentParser = block(
21
- fence(opener, false, 10000));
22
-
23
- export const segment_: TableParser.SegmentParser = block(
24
- fence(opener, false, 10000, false), false);
25
-
26
20
  export const table: TableParser = block(inits([
27
- fence(opener, true, 10000),
21
+ fence(opener, true),
28
22
  (_, output) => {
29
23
  const [body, overflow, closer, opener, delim, type, param] = unwrap(output.pop()) as string[];
30
24
  if (!closer || overflow || param.trimStart()) return output.append(
@@ -37,7 +31,7 @@ export const table: TableParser = block(inits([
37
31
  !closer ? `Missing the closing delimiter "${delim}"` :
38
32
  overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
39
33
  'Invalid argument'),
40
- }, `${opener}${body}${overflow || closer}`)));
34
+ }, `${opener}${body}${closer}${overflow}`)));
41
35
  switch (type) {
42
36
  case undefined:
43
37
  case 'grid':
@@ -2,7 +2,7 @@ import { ExtensionParser } from '../block';
2
2
  import { union, lazy } from '../../combinator';
3
3
  import { segment as seg_fig } from './extension/fig';
4
4
  import { figure, segment as seg_figure } from './extension/figure';
5
- import { table, segment as seg_table } from './extension/table';
5
+ import { table } from './extension/table';
6
6
  import { message } from './extension/message';
7
7
  import { aside } from './extension/aside';
8
8
  import { example } from './extension/example';
@@ -11,7 +11,6 @@ import { placeholder, segment as seg_placeholder } from './extension/placeholder
11
11
  export const segment: ExtensionParser.SegmentParser = union([
12
12
  seg_fig,
13
13
  seg_figure,
14
- seg_table,
15
14
  seg_placeholder,
16
15
  ]);
17
16
 
@@ -25,7 +25,6 @@ describe('Unit: parser/block/mathblock', () => {
25
25
  assert.deepStrictEqual(inspect(parser, input('$$$\n$$')), [['<pre class="invalid" translate="no">$$$\n$$</pre>'], '']);
26
26
  assert.deepStrictEqual(inspect(parser, input('$$$\n$$$')), [['<pre class="invalid" translate="no">$$$\n$$$</pre>'], '']);
27
27
  assert.deepStrictEqual(inspect(parser, input(' $$\n$$')), undefined);
28
- assert.deepStrictEqual(inspect(parser, input(`$$\n0${'\n'.repeat(301)}$$`), '>'), [['<pre class="invalid" translate="no">'], '']);
29
28
  });
30
29
 
31
30
  it('basic', () => {
@@ -40,7 +39,6 @@ describe('Unit: parser/block/mathblock', () => {
40
39
  assert.deepStrictEqual(inspect(parser, input('$$\n$$\n\n$$')), [['<div class="math" translate="no">$$\n$$</div>'], '\n$$']);
41
40
  assert.deepStrictEqual(inspect(parser, input('$$\n$$$\n$$')), [['<div class="math" translate="no">$$\n$$$\n$$</div>'], '']);
42
41
  assert.deepStrictEqual(inspect(parser, input('$$\n$$$\n\n$$')), [['<div class="math" translate="no">$$\n$$$\n\n$$</div>'], '']);
43
- assert.deepStrictEqual(inspect(parser, input(`$$\n0${'\n'.repeat(300)}$$`), '>'), [['<div class="math" translate="no">'], '']);
44
42
  });
45
43
 
46
44
  });
@@ -7,13 +7,13 @@ import { html } from 'typed-dom/dom';
7
7
  const opener = /(\${2,})(?!\$)([^\r\n]*)(?:$|\r?\n)/y;
8
8
 
9
9
  export const segment: MathBlockParser.SegmentParser = block(
10
- fence(opener, false, 300));
10
+ fence(opener, false));
11
11
 
12
12
  export const segment_: MathBlockParser.SegmentParser = block(
13
- fence(opener, false, 300, false), false);
13
+ fence(opener, false, false), false);
14
14
 
15
15
  export const mathblock: MathBlockParser = block(inits([
16
- fence(opener, true, 300),
16
+ fence(opener, true),
17
17
  ({ caches: { math: cache = undefined } = {} }, output) => {
18
18
  const [body, overflow, closer, opener, delim, param] = unwrap(output.pop()) as string[];
19
19
  return output.append(
@@ -32,6 +32,6 @@ export const mathblock: MathBlockParser = block(inits([
32
32
  overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
33
33
  'Invalid argument'),
34
34
  },
35
- `${opener}${body}${overflow || closer}`)));
35
+ `${opener}${body}${closer}${overflow}`)));
36
36
  },
37
37
  ]));
@@ -8,13 +8,14 @@ export function input(source: string, input: Input = new Input()): Input {
8
8
  export class Input<M extends object = object> extends Ipt<M> {
9
9
  constructor(
10
10
  options: Partial<Input> = {},
11
+ source?: string,
11
12
  ) {
12
13
  super(options);
13
14
  const {
14
15
  segment,
15
16
  header,
16
17
  local,
17
- sequential,
18
+ whitespace,
18
19
  host,
19
20
  url,
20
21
  id,
@@ -22,20 +23,21 @@ export class Input<M extends object = object> extends Ipt<M> {
22
23
  caches,
23
24
  test,
24
25
  } = options;
26
+ this.source = source ?? options.source ?? '';
25
27
  this.resources ??= {
26
28
  clock: -1,
27
29
  interval: 200,
28
30
  recursions: [
29
- 10 || Recursion.scope,
31
+ 10 || Recursion.document,
30
32
  100 || Recursion.block,
31
33
  100 || Recursion.inline,
32
- 100 || Recursion.terminal,
34
+ 100 || Recursion.bracket,
33
35
  ],
34
36
  };
35
37
  this.segment = segment ?? Segment.unknown;
36
38
  this.header = header ?? true;
37
39
  this.local = local ?? false;
38
- this.sequential = sequential ?? false;
40
+ this.whitespace = whitespace ?? false;
39
41
  this.host = host;
40
42
  this.url = url;
41
43
  this.id = id;
@@ -51,7 +53,7 @@ export class Input<M extends object = object> extends Ipt<M> {
51
53
  public override segment: Segment;
52
54
  public header: boolean;
53
55
  public local: boolean;
54
- public sequential: boolean;
56
+ public whitespace: boolean;
55
57
  public recursion = new RecursionCounter(2);
56
58
  public readonly host?: URL;
57
59
  public readonly url?: URL;
@@ -115,10 +117,10 @@ export const enum State {
115
117
  }
116
118
 
117
119
  export const enum Recursion {
118
- scope,
120
+ document,
119
121
  block,
120
122
  inline,
121
- terminal,
123
+ bracket,
122
124
  }
123
125
 
124
126
  export const enum Backtrack {
@@ -134,12 +136,7 @@ export const enum Backtrack {
134
136
  }
135
137
 
136
138
  export const enum Command {
137
- Error = '\x07',
138
139
  Cancel = '\x18',
139
140
  Escape = '\x1B',
140
141
  Separator = '\x1F',
141
142
  }
142
-
143
- export const CmdRegExp = {
144
- Error: /\x07/g,
145
- } as const;
@@ -29,7 +29,7 @@ export const document: MarkdownParser = (() => {
29
29
  output.push();
30
30
  return output.context;
31
31
  },
32
- recursion(Recursion.scope, force(() => loop)),
32
+ recursion(Recursion.document, force(() => loop)),
33
33
  (input, output) => {
34
34
  assert(input.position === input.source.length);
35
35
  const doc = frag(unwrap(output.pop()));
@@ -39,12 +39,12 @@ export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => focus(
39
39
  ])));
40
40
 
41
41
  const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => backtrack(union([
42
- surround(str('('), recursion(Recursion.terminal, some(union([bracket, unescsource]), ')')), str(')'),
42
+ surround(str('('), recursion(Recursion.bracket, some(union([bracket, unescsource]), ')')), str(')'),
43
43
  true, [3 | Backtrack.unescapable]),
44
- surround(str('['), recursion(Recursion.terminal, some(union([bracket, unescsource]), ']')), str(']'),
44
+ surround(str('['), recursion(Recursion.bracket, some(union([bracket, unescsource]), ']')), str(']'),
45
45
  true, [3 | Backtrack.unescapable]),
46
- surround(str('{'), recursion(Recursion.terminal, some(union([bracket, unescsource]), '}')), str('}'),
46
+ surround(str('{'), recursion(Recursion.bracket, some(union([bracket, unescsource]), '}')), str('}'),
47
47
  true, [3 | Backtrack.unescapable]),
48
- surround(str('"'), precedence(2, recursion(Recursion.terminal, some(unescsource, '"'))), str('"'),
48
+ surround(str('"'), precedence(2, recursion(Recursion.bracket, some(unescsource, '"'))), str('"'),
49
49
  true, [3 | Backtrack.unescapable]),
50
50
  ])));
@@ -41,7 +41,7 @@ export const math: MathParser = lazy(() => rewrite(
41
41
 
42
42
  const bracket: MathParser.BracketParser = lazy(() => backtrack(surround(
43
43
  str('{'),
44
- recursion(Recursion.terminal,
44
+ recursion(Recursion.bracket,
45
45
  some(union([
46
46
  bracket,
47
47
  some(escsource, /[{}$\r\n]|(?<=[0-9A-Za-z]):\/\/[[0-9A-Za-z]/y),
@@ -107,13 +107,13 @@ export const media: MediaParser = lazy(() => constraint(State.media, backtrack(o
107
107
  })))));
108
108
 
109
109
  const bracket: MediaParser.TextParser.BracketParser = lazy(() => union([
110
- surround(str('('), recursion(Recursion.terminal, some(union([unsafehtmlentity, bracket, txt]), ')')), str(')'),
110
+ surround(str('('), recursion(Recursion.bracket, some(union([unsafehtmlentity, bracket, txt]), ')')), str(')'),
111
111
  true, [], undefined, () => Result.succ),
112
- surround(str('['), recursion(Recursion.terminal, some(union([unsafehtmlentity, bracket, txt]), ']')), str(']'),
112
+ surround(str('['), recursion(Recursion.bracket, some(union([unsafehtmlentity, bracket, txt]), ']')), str(']'),
113
113
  true, [], undefined, () => Result.succ),
114
- surround(str('{'), recursion(Recursion.terminal, some(union([unsafehtmlentity, bracket, txt]), '}')), str('}'),
114
+ surround(str('{'), recursion(Recursion.bracket, some(union([unsafehtmlentity, bracket, txt]), '}')), str('}'),
115
115
  true, [], undefined, () => Result.succ),
116
- surround(str('"'), precedence(2, recursion(Recursion.terminal, some(union([unsafehtmlentity, txt]), '"'))), str('"'),
116
+ surround(str('"'), precedence(2, recursion(Recursion.bracket, some(union([unsafehtmlentity, txt]), '"'))), str('"'),
117
117
  true, [], undefined, () => Result.succ),
118
118
  ]));
119
119
 
@@ -3,7 +3,7 @@ import { Input, Backtrack } from '../context';
3
3
  import { Parser, Result, List, Node } from '../../combinator/parser';
4
4
  import { union, inits, always, backtrack, surround, setBacktrack, dup, lazy, bind } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
- import { txt } from '../source';
6
+ import { txt, isWhitespace } from '../source';
7
7
  import { isNonblankNodeStart } from '../visibility';
8
8
  import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
@@ -63,8 +63,6 @@ export const ruby: RubyParser = lazy(() => backtrack(bind(
63
63
  }
64
64
  })));
65
65
 
66
- const delimiter = /[$"`\[\](){}<>()[]{}|]|\\?\r?\n/y;
67
-
68
66
  interface Memory {
69
67
  position: number;
70
68
  state: boolean;
@@ -72,7 +70,7 @@ interface Memory {
72
70
  }
73
71
  const text: RubyParser.TextParser = always<Parser<string, Input<Memory>>>([
74
72
  (input, output) => {
75
- input.sequential = true;
73
+ input.whitespace = true;
76
74
  input.memory = {
77
75
  position: 0,
78
76
  state: false,
@@ -83,7 +81,7 @@ const text: RubyParser.TextParser = always<Parser<string, Input<Memory>>>([
83
81
  () => loop,
84
82
  (input, output) => {
85
83
  const { memory } = input;
86
- input.sequential = false;
84
+ input.whitespace = false;
87
85
  return memory.state || memory.nodes.last!.value.trimStart() !== ''
88
86
  ? output.import(memory.nodes)
89
87
  : undefined;
@@ -94,10 +92,9 @@ const loop: Result<string, Input<Memory>> = [
94
92
  const { source, memory } = input;
95
93
  for (let { position } = input; ; position = input.position) {
96
94
  if (position === source.length) return Result.skip;
97
- delimiter.lastIndex = position;
98
- if (delimiter.test(source)) return Result.skip;
95
+ if (isDelimiter(source, position)) return Result.skip;
99
96
  assert(source[position] !== '\n');
100
- if (source[position].trimStart() !== '') break;
97
+ if (!isWhitespace(source[position])) break;
101
98
  memory.state ||= memory.nodes.last!.value.trimStart() !== '';
102
99
  memory.nodes.push(new Node(''));
103
100
  input.position += 1;
@@ -124,3 +121,43 @@ function* zip<N extends Node<unknown>>(a: List<N>, b: List<N>): Iterable<[N | un
124
121
  yield [ra.value, rb.value];
125
122
  }
126
123
  }
124
+
125
+ function isDelimiter(source: string, position: number): boolean {
126
+ switch (source[position]) {
127
+ case '$':
128
+ case '"':
129
+ case '`':
130
+ case '[':
131
+ case ']':
132
+ case '(':
133
+ case ')':
134
+ case '{':
135
+ case '}':
136
+ case '<':
137
+ case '>':
138
+ case '(':
139
+ case ')':
140
+ case '[':
141
+ case ']':
142
+ case '{':
143
+ case '}':
144
+ case '|':
145
+ return true;
146
+ case '\\':
147
+ switch (source[position + 1]) {
148
+ case '\r':
149
+ return source[position + 2] === '\n';
150
+ case '\n':
151
+ return true;
152
+ default:
153
+ return false;
154
+ }
155
+ case '\r':
156
+ return source[position + 1] === '\n';
157
+ case '\n':
158
+ return true;
159
+ default:
160
+ return false;
161
+ }
162
+ assert(false);
163
+ }
@@ -25,15 +25,15 @@ export const template: TemplateParser = lazy(() => backtrack(surround(
25
25
  input.source.slice(input.position - input.range, input.position)))))));
26
26
 
27
27
  const bracket: TemplateParser.BracketParser = lazy(() => union([
28
- surround(str('('), recursion(Recursion.terminal, some(union([bracket, escsource]), ')')), str(')'),
28
+ surround(str('('), recursion(Recursion.bracket, some(union([bracket, escsource]), ')')), str(')'),
29
29
  true, [], undefined, ([as, bs], _, output) => bs && output.import(as.import(bs as List<Node<string>>))),
30
- surround(str('['), recursion(Recursion.terminal, some(union([bracket, escsource]), ']')), str(']'),
30
+ surround(str('['), recursion(Recursion.bracket, some(union([bracket, escsource]), ']')), str(']'),
31
31
  true, [], undefined, ([as, bs], _, output) => bs && output.import(as.import(bs as List<Node<string>>))),
32
- surround(str('{'), recursion(Recursion.terminal, some(union([bracket, escsource]), '}')), str('}'),
32
+ surround(str('{'), recursion(Recursion.bracket, some(union([bracket, escsource]), '}')), str('}'),
33
33
  true, [], undefined, ([as, bs], _, output) => bs && output.import(as.import(bs as List<Node<string>>))),
34
34
  surround(
35
35
  str('"'),
36
- precedence(2, recursion(Recursion.terminal, some(escsource, /["\n]/y, [['"', 2], ['\n', 3]]))),
36
+ precedence(2, recursion(Recursion.bracket, some(escsource, /["\n]/y, [['"', 2], ['\n', 3]]))),
37
37
  str('"'),
38
38
  true, [], undefined, ([as, bs], _, output) => bs && output.import(as.import(bs as List<Node<string>>))),
39
39
  ]));
@@ -34,7 +34,6 @@ export const escsource: EscapableSourceParser = (input, output) => {
34
34
  return output.append(new Node(html('br'), Flag.blank));
35
35
  default:
36
36
  assert(char !== '\n');
37
- if (input.sequential) return output.append(new Node(char));
38
37
  let i = seek(source, position);
39
38
  assert(i > position);
40
39
  i -= position;
@@ -3,6 +3,7 @@ import { Result, Node } from '../../combinator/parser';
3
3
  import { union, spend } from '../../combinator';
4
4
  import { State, Command } from '../context';
5
5
  import { Flag } from '../node';
6
+ import { isWhitespace } from './whitespace';
6
7
  import { html } from 'typed-dom/dom';
7
8
 
8
9
  export const nonWhitespace = /[^ \t ]/g;
@@ -35,14 +36,13 @@ export const text: TextParser = (input, output) => {
35
36
  return output.append(new Node(html('br'), Flag.blank));
36
37
  default:
37
38
  assert(char !== '\n');
38
- if (input.sequential) return output.append(new Node(char));
39
39
  nonWhitespace.lastIndex = position + 1;
40
40
  const s = canSkip(source, position);
41
41
  let i = s
42
42
  ? nonWhitespace.test(source)
43
43
  ? nonWhitespace.lastIndex - 1
44
44
  : source.length
45
- : next(source, position, state);
45
+ : next(source, position, input.whitespace, state);
46
46
  assert(i > position);
47
47
  const lineend = 0
48
48
  || s && i === source.length
@@ -68,28 +68,15 @@ export function canSkip(source: string, position: number): boolean {
68
68
  if (position + 1 === source.length) return true;
69
69
  return isWhitespace(source[position + 1], true);
70
70
  }
71
- function isWhitespace(char: string, linebreak: boolean): boolean {
72
- switch (char) {
73
- case ' ':
74
- case '\t':
75
- case ' ':
76
- return true;
77
- case '\r':
78
- case '\n':
79
- return linebreak;
80
- default:
81
- return false;
82
- }
83
- }
84
71
 
85
- function next(source: string, position: number, state: number): number {
86
- let index= seek(source, position, state);
72
+ function next(source: string, position: number, space: boolean, state: number): number {
73
+ let index= seek(source, position, space, state);
87
74
  assert(index > position);
88
75
  if (index === source.length) return index;
89
76
  const char = source[index];
90
77
  switch (char) {
91
78
  case '%':
92
- assert(source.startsWith('%]', index) && isWhitespace(source[index - 1], true));
79
+ assert(source.startsWith('%]', index) && isWhitespace(source[index - 1]));
93
80
  index += index - 1 > position
94
81
  ? -1
95
82
  : 0;
@@ -167,7 +154,7 @@ export function isAlphanumeric(char: string): boolean {
167
154
  return 'A' <= char && char <= 'Z';
168
155
  }
169
156
 
170
- function seek(source: string, position: number, state: number): number {
157
+ function seek(source: string, position: number, space: boolean, state: number): number {
171
158
  for (let i = position + 1; i < source.length; ++i) {
172
159
  const char = source[i];
173
160
  switch (char) {
@@ -208,7 +195,7 @@ function seek(source: string, position: number, state: number): number {
208
195
  if (source[i + 1] === char && source[i + 2] === char) return i;
209
196
  continue;
210
197
  case '%':
211
- if (source[i + 1] === ']' && isWhitespace(source[i - 1], true)) return i;
198
+ if (source[i + 1] === ']' && isWhitespace(source[i - 1])) return i;
212
199
  continue;
213
200
  case ':':
214
201
  if (source[i + 1] === '/' && source[i + 2] === '/') return i;
@@ -216,30 +203,14 @@ function seek(source: string, position: number, state: number): number {
216
203
  case '&':
217
204
  if (source[i + 1] !== ' ') return i;
218
205
  continue;
219
- case ' ':
220
- case '\t':
221
- case ' ':
222
- if (i + 1 === source.length) return i;
223
- switch (source[i + 1]) {
224
- case ' ':
225
- case '\t':
226
- case '\r':
227
- case '\n':
228
- case ' ':
229
- return i;
230
- case '\\':
231
- if (i + 2 === source.length) return i;
232
- switch (source[i + 2]) {
233
- case ' ':
234
- case '\t':
235
- case '\r':
236
- case '\n':
237
- case ' ':
238
- return i;
239
- }
240
- }
241
- continue;
242
206
  default:
207
+ if (!isWhitespace(char)) continue;
208
+ if (space) return i;
209
+ if (i + 1 === source.length) return i;
210
+ if (isWhitespace(source[i + 1])) return i;
211
+ if (source[i + 1] !== '\\') continue;
212
+ if (i + 2 === source.length) return i;
213
+ if (isWhitespace(source[i + 2])) return i;
243
214
  continue;
244
215
  }
245
216
  assert(false);
@@ -24,7 +24,6 @@ export const unescsource: UnescapableSourceParser = (input, output) => {
24
24
  return output.append(new Node(html('br'), Flag.blank));
25
25
  default:
26
26
  assert(char !== '\n');
27
- if (input.sequential) return output.append(new Node(char));
28
27
  nonWhitespace.lastIndex = position + 1;
29
28
  let i = canSkip(source, position)
30
29
  ? nonWhitespace.test(source)
@@ -0,0 +1,36 @@
1
+ // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5Cp%7BWhite_Space%7D&g=&i=
2
+ // https://en.wikipedia.org/wiki/Whitespace_character
3
+ // https://en.wikipedia.org/wiki/Newline
4
+ export function isWhitespace(char: string, linebreak: boolean = true): boolean {
5
+ switch (char) {
6
+ case '\u0009':
7
+ case '\u000B':
8
+ case '\u000C':
9
+ case '\u0020':
10
+ case '\u0085':
11
+ case '\u00A0':
12
+ case '\u1680':
13
+ case '\u2000':
14
+ case '\u2001':
15
+ case '\u2002':
16
+ case '\u2003':
17
+ case '\u2004':
18
+ case '\u2005':
19
+ case '\u2006':
20
+ case '\u2007':
21
+ case '\u2008':
22
+ case '\u2009':
23
+ case '\u200A':
24
+ case '\u2028':
25
+ case '\u2029':
26
+ case '\u202F':
27
+ case '\u205F':
28
+ case '\u3000':
29
+ return true;
30
+ case '\u000A':
31
+ case '\u000D':
32
+ return linebreak;
33
+ default:
34
+ return false;
35
+ }
36
+ }
@@ -15,4 +15,5 @@ export { text, txt } from './source/text';
15
15
  export { escsource } from './source/escapable';
16
16
  export { unescsource } from './source/unescapable';
17
17
  export { str, strs } from './source/str';
18
+ export { isWhitespace } from './source/whitespace';
18
19
  export { contentline, emptyline, emptysegment, anyline } from './source/line';
@@ -3,6 +3,7 @@ import { Input, Command } from './context';
3
3
  import { Flag } from './node';
4
4
  import { always, fmap } from '../combinator';
5
5
  import { invisibleBlankHTMLEntityNames } from '../api/normalize';
6
+ import { isWhitespace } from './source';
6
7
 
7
8
  namespace blank {
8
9
  export const line = new RegExp(
@@ -102,7 +103,7 @@ function isNonblank({ value: node, flags }: Node<HTMLElement | string>, strpos?:
102
103
  case '\n':
103
104
  return false;
104
105
  default:
105
- return str.trimStart() !== '';
106
+ return !isWhitespace(str.trimStart());
106
107
  }
107
108
  }
108
109