tex2typst 0.3.21 → 0.3.23

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.
package/src/convert.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { TexNode, TypstNode, TexSupsubData, TypstSupsubData, TexSqrtData, Tex2TypstOptions, TYPST_NONE, TypstPrimitiveValue, TypstLrData, TexArrayData, TypstNamedParams } from "./types";
1
+ import { TexNode, TypstNode, TexSupsubData, TypstSupsubData, TexSqrtData, Tex2TypstOptions, TYPST_NONE, TypstLrData, TexArrayData, TypstNamedParams } from "./types";
2
2
  import { TypstWriterError } from "./typst-writer";
3
3
  import { symbolMap, reverseSymbolMap } from "./map";
4
- import { array_join } from "./generic";
4
+ import { array_intersperse } from "./generic";
5
5
  import { assert } from "./util";
6
+ import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS } from "./tex-tokenizer";
6
7
 
7
8
 
8
9
  // native textual operators in Typst
@@ -94,6 +95,44 @@ function convert_underset(node: TexNode, options: Tex2TypstOptions): TypstNode {
94
95
  });
95
96
  }
96
97
 
98
+ function convert_tex_array_align_literal(alignLiteral: string): TypstNamedParams {
99
+ const np: TypstNamedParams = {};
100
+ const alignMap: Record<string, string> = { l: '#left', c: '#center', r: '#right' };
101
+ const chars = Array.from(alignLiteral);
102
+
103
+ const vlinePositions: number[] = [];
104
+ let columnIndex = 0;
105
+ for (const c of chars) {
106
+ if (c === '|') {
107
+ vlinePositions.push(columnIndex);
108
+ } else if (c === 'l' || c === 'c' || c === 'r') {
109
+ columnIndex++;
110
+ }
111
+ }
112
+
113
+ if (vlinePositions.length > 0) {
114
+ let augment_str: string;
115
+ if (vlinePositions.length === 1) {
116
+ augment_str = `#${vlinePositions[0]}`;
117
+ } else {
118
+ augment_str = `#(vline: (${vlinePositions.join(', ')}))`;
119
+ }
120
+
121
+ np['augment'] = new TypstNode('literal', augment_str);
122
+ }
123
+
124
+ const alignments = chars
125
+ .map(c => alignMap[c])
126
+ .filter((x) => x !== undefined)
127
+ .map(s => new TypstNode('literal', s!));
128
+
129
+ if (alignments.length > 0) {
130
+ const all_same = alignments.every(item => item.eq(alignments[0]));
131
+ np['align'] = all_same ? alignments[0] : new TypstNode('literal', '#center');
132
+ }
133
+ return np;
134
+ }
135
+
97
136
 
98
137
  export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptions = {}): TypstNode {
99
138
  switch (node.type) {
@@ -121,6 +160,9 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
121
160
  }
122
161
  return new TypstNode('text', node.content);
123
162
  }
163
+ case 'literal':
164
+ // This happens, for example, node={type: 'literal', content: 'myop'} as in `\operatorname{myop}`
165
+ return new TypstNode('literal', node.content);
124
166
  case 'comment':
125
167
  return new TypstNode('comment', node.content);
126
168
  case 'supsub': {
@@ -272,31 +314,19 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
272
314
  }
273
315
  // \operatorname{opname} -> op("opname")
274
316
  if (node.content === '\\operatorname') {
317
+ // arg0 must be of type 'literal' in this situation
275
318
  if (options.optimize) {
276
319
  if (TYPST_INTRINSIC_OP.includes(arg0.content)) {
277
320
  return new TypstNode('symbol', arg0.content);
278
321
  }
279
322
  }
280
- return new TypstNode('funcCall', 'op', [arg0]);
281
- }
282
- // \hspace{1cm} -> #h(1cm)
283
- // TODO: reverse conversion support for this
284
- if (node.content === '\\hspace') {
285
- const text = arg0.content;
286
- return new TypstNode(
287
- 'funcCall',
288
- '#h',
289
- [new TypstNode('symbol', text)]
290
- );
323
+ return new TypstNode('funcCall', 'op', [new TypstNode('text', arg0.content)]);
291
324
  }
292
325
 
326
+ // \substack{a \\ b} -> `a \ b`
327
+ // as in translation from \sum_{\substack{a \\ b}} to sum_(a \ b)
293
328
  if (node.content === '\\substack') {
294
- // \sum_{\substack{a \\ b}} -> sum_(a \ b)
295
- return new TypstNode(
296
- 'group',
297
- '',
298
- [arg0]
299
- );
329
+ return arg0;
300
330
  }
301
331
 
302
332
  if(options.optimize) {
@@ -321,97 +351,57 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
321
351
  const matrix = node.data as TexNode[][];
322
352
  const data = matrix.map((row) => row.map((n) => convert_tex_node_to_typst(n, options)));
323
353
 
324
- if (node.content!.startsWith('align')) {
354
+ if (node.content.startsWith('align')) {
325
355
  // align, align*, alignat, alignat*, aligned, etc.
326
356
  return new TypstNode('align', '', [], data);
327
357
  }
328
- if (node.content! === 'cases') {
358
+ if (node.content === 'cases') {
329
359
  return new TypstNode('cases', '', [], data);
330
360
  }
331
- if (node.content! === 'subarray') {
361
+ if (node.content === 'subarray') {
332
362
  const align_node = node.args![0];
333
- if (align_node.content == 'r') {
334
- data.forEach(row => row[0].args!.push(new TypstNode('symbol', '&')));
335
- }
336
- if (align_node.content == 'l') {
337
- data.forEach(row => row[0].args!.unshift(new TypstNode('symbol', '&')));
363
+ switch (align_node.content) {
364
+ case 'r':
365
+ data.forEach(row => row[0].args!.push(new TypstNode('symbol', '&')));
366
+ break;
367
+ case 'l':
368
+ data.forEach(row => row[0].args!.unshift(new TypstNode('symbol', '&')));
369
+ break;
370
+ default:
371
+ break;
338
372
  }
339
- return new TypstNode(
340
- 'group',
341
- '',
342
- [new TypstNode('align', '', [], data)]
343
- );
373
+ return new TypstNode('align', '', [], data);
344
374
  }
345
- if (node.content! === 'array') {
346
- const res = new TypstNode('matrix', '', [], data);
347
- const options: TypstNamedParams = { 'delim': TYPST_NONE };
348
-
349
- const align_args = node.args!;
350
- if (align_args.length > 0) {
351
- const align_node = align_args[0];
352
- const align_str = (() => {
353
- if (align_node.type === 'element') return align_node.content;
354
- if (align_node.type === 'ordgroup') {
355
- return align_node.args!.map(n => n.type === 'element' ? n.content : '').join('');
356
- }
357
- return '';
358
- })();
359
-
360
- if (align_str) {
361
- const alignMap: Record<string, string> = { l: '#left', c: '#center', r: '#right' };
362
- const chars = Array.from(align_str);
363
-
364
- const alignments = chars
365
- .map(c => alignMap[c])
366
- .filter(Boolean)
367
- .map(s => new TypstNode('symbol', s!));
368
-
369
- const vlinePositions: number[] = [];
370
- let columnIndex = 0;
371
- for (const c of chars) {
372
- if (c === '|') {
373
- vlinePositions.push(columnIndex);
374
- } else if (c === 'l' || c === 'c' || c === 'r') {
375
- columnIndex++;
376
- }
377
- }
378
-
379
- if (vlinePositions.length > 0) {
380
- if (vlinePositions.length === 1) {
381
- options['augment'] = new TypstNode('symbol', `#${vlinePositions[0]}`);
382
- } else {
383
- options['augment'] = new TypstNode('symbol', `#(vline: (${vlinePositions.join(', ')}))`);
384
- }
385
- }
386
-
387
- if (alignments.length > 0) {
388
- const first_align = alignments[0].content;
389
- const all_same = alignments.every(item => item.content === first_align);
390
- options['align'] = all_same ? alignments[0] : new TypstNode('symbol', '#center');
391
- }
392
- }
393
- }
375
+ if (node.content === 'array') {
376
+ const np: TypstNamedParams = { 'delim': TYPST_NONE };
394
377
 
395
- res.setOptions(options);
378
+ assert(node.args!.length > 0 && node.args![0].type === 'literal');
379
+ const np_new = convert_tex_array_align_literal(node.args![0].content);
380
+ Object.assign(np, np_new);
381
+
382
+ const res = new TypstNode('matrix', '', [], data);
383
+ res.setOptions(np);
396
384
  return res;
397
385
  }
398
- if (node.content!.endsWith('matrix')) {
399
- let delim: TypstPrimitiveValue;
386
+ if (node.content.endsWith('matrix')) {
387
+ const res = new TypstNode('matrix', '', [], data);
388
+ let delim: TypstNode;
400
389
  switch (node.content) {
401
390
  case 'matrix':
402
391
  delim = TYPST_NONE;
403
392
  break;
404
393
  case 'pmatrix':
405
- delim = '(';
406
- break;
394
+ // delim = new TypstNode('text', '(');
395
+ // break;
396
+ return res; // typst mat use delim:"(" by default
407
397
  case 'bmatrix':
408
- delim = '[';
398
+ delim = new TypstNode('text', '[');
409
399
  break;
410
400
  case 'Bmatrix':
411
- delim = '{';
401
+ delim = new TypstNode('text', '{');
412
402
  break;
413
403
  case 'vmatrix':
414
- delim = '|';
404
+ delim = new TypstNode('text', '|');
415
405
  break;
416
406
  case 'Vmatrix': {
417
407
  delim = new TypstNode('symbol', 'bar.v.double');
@@ -420,7 +410,6 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
420
410
  default:
421
411
  throw new TypstWriterError(`Unimplemented beginend: ${node.content}`, node);
422
412
  }
423
- const res = new TypstNode('matrix', '', [], data);
424
413
  res.setOptions({ 'delim': delim });
425
414
  return res;
426
415
  }
@@ -430,7 +419,13 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
430
419
  return new TypstNode('unknown', tex_token_to_typst(node.content));
431
420
  case 'control':
432
421
  if (node.content === '\\\\') {
422
+ // \\ -> \
433
423
  return new TypstNode('symbol', '\\');
424
+ } else if (node.content === '\\!') {
425
+ // \! -> #h(-math.thin.amount)
426
+ return new TypstNode('funcCall', '#h', [
427
+ new TypstNode('literal', '-math.thin.amount')
428
+ ]);
434
429
  } else if (symbolMap.has(node.content.substring(1))) {
435
430
  // node.content is one of \, \: \;
436
431
  const typst_symbol = symbolMap.get(node.content.substring(1))!;
@@ -444,7 +439,7 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
444
439
  }
445
440
 
446
441
 
447
-
442
+ /*
448
443
  const TYPST_UNARY_FUNCTIONS: string[] = [
449
444
  'sqrt',
450
445
  'bold',
@@ -466,6 +461,7 @@ const TYPST_UNARY_FUNCTIONS: string[] = [
466
461
  'ceil',
467
462
  'norm',
468
463
  'limits',
464
+ '#h',
469
465
  ];
470
466
 
471
467
  const TYPST_BINARY_FUNCTIONS: string[] = [
@@ -474,6 +470,7 @@ const TYPST_BINARY_FUNCTIONS: string[] = [
474
470
  'overbrace',
475
471
  'underbrace',
476
472
  ];
473
+ */
477
474
 
478
475
  function apply_escape_if_needed(c: string) {
479
476
  if (['{', '}', '%'].includes(c)) {
@@ -510,18 +507,29 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
510
507
  return new TexNode('whitespace', node.content);
511
508
  case 'atom':
512
509
  return new TexNode('element', node.content);
513
- case 'symbol':
514
- switch (node.content) {
515
- // special hook for comma
516
- case 'comma':
517
- return new TexNode('element', ',');
518
- // special hook for hyph and hyph.minus
519
- case 'hyph':
520
- case 'hyph.minus':
521
- return new TexNode('text', '-');
522
- default:
523
- return new TexNode('symbol', typst_token_to_tex(node.content));
510
+ case 'symbol': {
511
+ // special hook for comma
512
+ if(node.content === 'comma') {
513
+ return new TexNode('element', ',');
514
+ }
515
+ // special hook for dif
516
+ if(node.content === 'dif') {
517
+ return new TexNode('unaryFunc', '\\mathrm', [new TexNode('element', 'd')]);
518
+ }
519
+ // special hook for hyph and hyph.minus
520
+ if(node.content === 'hyph' || node.content === 'hyph.minus') {
521
+ return new TexNode('text', '-');
522
+ }
523
+ // special hook for mathbb{R} <-- RR
524
+ if(/^([A-Z])\1$/.test(node.content)) {
525
+ return new TexNode('unaryFunc', '\\mathbb', [
526
+ new TexNode('element', node.content[0])
527
+ ]);
524
528
  }
529
+ return new TexNode('symbol', typst_token_to_tex(node.content));
530
+ }
531
+ case 'literal':
532
+ return new TexNode('literal', node.content);
525
533
  case 'text':
526
534
  return new TexNode('text', node.content);
527
535
  case 'comment':
@@ -538,83 +546,85 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
538
546
  return new TexNode('ordgroup', node.content, args);
539
547
  }
540
548
  case 'funcCall': {
541
- if (TYPST_UNARY_FUNCTIONS.includes(node.content)) {
542
- // special hook for lr
543
- if (node.content === 'lr') {
544
- const data = node.data as TypstLrData;
545
- if (data.leftDelim !== null) {
546
- let left_delim = apply_escape_if_needed(data.leftDelim);
547
- assert(data.rightDelim !== null, "leftDelim has value but rightDelim not");
548
- let right_delim = apply_escape_if_needed(data.rightDelim!);
549
- // TODO: should be TeXNode('leftright', ...)
550
- // But currently writer will output `\left |` while people commonly prefer `\left|`.
551
- return new TexNode('ordgroup', '', [
552
- new TexNode('element', '\\left' + left_delim),
553
- ...node.args!.map(convert_typst_node_to_tex),
554
- new TexNode('element', '\\right' + right_delim)
555
- ]);
556
- } else {
557
- return new TexNode('ordgroup', '', node.args!.map(convert_typst_node_to_tex));
558
- }
559
- }
560
- // special hook for norm
561
- // `\| a \|` <- `norm(a)`
562
- // `\left\| a + \frac{1}{3} \right\|` <- `norm(a + 1/3)`
563
- if (node.content === 'norm') {
564
- const arg0 = node.args![0];
565
- const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
566
- return new TexNode(tex_node_type, '', [
567
- new TexNode('symbol', "\\|"),
568
- convert_typst_node_to_tex(arg0),
569
- new TexNode('symbol', "\\|")
570
- ]);
571
- }
572
- // special hook for floor, ceil
573
- // `\lfloor a \rfloor` <- `floor(a)`
574
- // `\lceil a \rceil` <- `ceil(a)`
575
- // `\left\lfloor a \right\rfloor` <- `floor(a)`
576
- // `\left\lceil a \right\rceil` <- `ceil(a)`
577
- if (node.content === 'floor' || node.content === 'ceil') {
578
- const left = "\\l" + node.content;
579
- const right = "\\r" + node.content;
580
- const arg0 = node.args![0];
581
- const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
582
- return new TexNode(tex_node_type, '', [
583
- new TexNode('symbol', left),
584
- convert_typst_node_to_tex(arg0),
585
- new TexNode('symbol', right)
549
+ // special hook for lr
550
+ if (node.content === 'lr') {
551
+ const data = node.data as TypstLrData;
552
+ if (data.leftDelim !== null) {
553
+ let left_delim = apply_escape_if_needed(data.leftDelim);
554
+ assert(data.rightDelim !== null, "leftDelim has value but rightDelim not");
555
+ let right_delim = apply_escape_if_needed(data.rightDelim!);
556
+ // TODO: should be TeXNode('leftright', ...)
557
+ // But currently writer will output `\left |` while people commonly prefer `\left|`.
558
+ return new TexNode('ordgroup', '', [
559
+ new TexNode('element', '\\left' + left_delim),
560
+ ...node.args!.map(convert_typst_node_to_tex),
561
+ new TexNode('element', '\\right' + right_delim)
586
562
  ]);
563
+ } else {
564
+ return new TexNode('ordgroup', '', node.args!.map(convert_typst_node_to_tex));
587
565
  }
588
- const command = typst_token_to_tex(node.content);
589
- return new TexNode('unaryFunc', command, node.args!.map(convert_typst_node_to_tex));
590
- } else if (TYPST_BINARY_FUNCTIONS.includes(node.content)) {
591
- // special hook for root
592
- if (node.content === 'root') {
593
- const [degree, radicand] = node.args!;
594
- const data: TexSqrtData = convert_typst_node_to_tex(degree);
595
- return new TexNode('unaryFunc', '\\sqrt', [convert_typst_node_to_tex(radicand)], data);
596
- }
597
- // special hook for overbrace and underbrace
598
- if (node.content === 'overbrace' || node.content === 'underbrace') {
599
- const [body, label] = node.args!;
600
- const base = new TexNode('unaryFunc', '\\' + node.content, [convert_typst_node_to_tex(body)]);
601
- const script = convert_typst_node_to_tex(label);
602
- const data = node.content === 'overbrace' ? { base, sup: script } : { base, sub: script };
603
- return new TexNode('supsub', '', [], data);
604
- }
605
- const command = typst_token_to_tex(node.content);
606
- return new TexNode('binaryFunc', command, node.args!.map(convert_typst_node_to_tex));
566
+ }
567
+ // special hook for norm
568
+ // `\| a \|` <- `norm(a)`
569
+ // `\left\| a + \frac{1}{3} \right\|` <- `norm(a + 1/3)`
570
+ if (node.content === 'norm') {
571
+ const arg0 = node.args![0];
572
+ const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
573
+ return new TexNode(tex_node_type, '', [
574
+ new TexNode('symbol', "\\|"),
575
+ convert_typst_node_to_tex(arg0),
576
+ new TexNode('symbol', "\\|")
577
+ ]);
578
+ }
579
+ // special hook for floor, ceil
580
+ // `\lfloor a \rfloor` <- `floor(a)`
581
+ // `\lceil a \rceil` <- `ceil(a)`
582
+ // `\left\lfloor a \right\rfloor` <- `floor(a)`
583
+ // `\left\lceil a \right\rceil` <- `ceil(a)`
584
+ if (node.content === 'floor' || node.content === 'ceil') {
585
+ const left = "\\l" + node.content;
586
+ const right = "\\r" + node.content;
587
+ const arg0 = node.args![0];
588
+ const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
589
+ return new TexNode(tex_node_type, '', [
590
+ new TexNode('symbol', left),
591
+ convert_typst_node_to_tex(arg0),
592
+ new TexNode('symbol', right)
593
+ ]);
594
+ }
595
+ // special hook for root
596
+ if (node.content === 'root') {
597
+ const [degree, radicand] = node.args!;
598
+ const data: TexSqrtData = convert_typst_node_to_tex(degree);
599
+ return new TexNode('unaryFunc', '\\sqrt', [convert_typst_node_to_tex(radicand)], data);
600
+ }
601
+ // special hook for overbrace and underbrace
602
+ if (node.content === 'overbrace' || node.content === 'underbrace') {
603
+ const [body, label] = node.args!;
604
+ const base = new TexNode('unaryFunc', '\\' + node.content, [convert_typst_node_to_tex(body)]);
605
+ const script = convert_typst_node_to_tex(label);
606
+ const data = node.content === 'overbrace' ? { base, sup: script } : { base, sub: script };
607
+ return new TexNode('supsub', '', [], data);
608
+ }
609
+
610
+ // special hook for vec
611
+ // "vec(a, b, c)" -> "\begin{pmatrix}a\\ b\\ c\end{pmatrix}"
612
+ if (node.content === 'vec') {
613
+ const tex_data = node.args!.map(convert_typst_node_to_tex).map((n) => [n]) as TexArrayData;
614
+ return new TexNode('beginend', 'pmatrix', [], tex_data);
615
+ }
616
+
617
+ // general case
618
+ const func_name_tex = typst_token_to_tex(node.content);
619
+ if (func_name_tex.length > 0 && TEX_UNARY_COMMANDS.includes(func_name_tex.substring(1))) {
620
+ return new TexNode('unaryFunc', func_name_tex, node.args!.map(convert_typst_node_to_tex));
621
+ } else if (func_name_tex.length > 0 && TEX_BINARY_COMMANDS.includes(func_name_tex.substring(1))) {
622
+ return new TexNode('binaryFunc', func_name_tex, node.args!.map(convert_typst_node_to_tex));
607
623
  } else {
608
- // special hook for vec
609
- // "vec(a, b, c)" -> "\begin{pmatrix}a\\ b\\ c\end{pmatrix}"
610
- if (node.content === 'vec') {
611
- const tex_data = node.args!.map(convert_typst_node_to_tex).map((n) => [n]) as TexArrayData;
612
- return new TexNode('beginend', 'pmatrix', [], tex_data);
613
- }
614
624
  return new TexNode('ordgroup', '', [
615
625
  new TexNode('symbol', typst_token_to_tex(node.content)),
616
626
  new TexNode('element', '('),
617
- ...array_join(node.args!.map(convert_typst_node_to_tex), TEX_NODE_COMMA),
627
+ ...array_intersperse(node.args!.map(convert_typst_node_to_tex), TEX_NODE_COMMA),
618
628
  new TexNode('element', ')')
619
629
  ]);
620
630
  }
@@ -659,47 +669,38 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
659
669
  case 'matrix': {
660
670
  const typst_data = node.data as TypstNode[][];
661
671
  const tex_data = typst_data.map(row => row.map(convert_typst_node_to_tex));
662
- let env_type = 'pmatrix';
672
+ let env_type = 'pmatrix'; // typst mat use delim:"(" by default
663
673
  if (node.options) {
664
674
  if ('delim' in node.options) {
665
675
  const delim = node.options.delim;
666
- if (delim instanceof TypstNode) {
667
- switch (delim.content) {
668
- case '#none':
669
- env_type = 'matrix';
670
- break;
671
- case 'bar.v.double':
672
- env_type = 'Vmatrix';
673
- break;
674
- case 'bar':
675
- case 'bar.v':
676
- env_type = 'vmatrix';
677
- break;
678
- default:
679
- throw new Error(`Unexpected delimiter ${delim.content}`);
680
- }
681
- } else {
682
- switch (delim) {
683
- case '[':
684
- env_type = 'bmatrix';
685
- break;
686
- case ']':
687
- env_type = 'bmatrix';
688
- break;
689
- case '{':
690
- env_type = 'Bmatrix';
691
- break;
692
- case '}':
693
- env_type = 'Bmatrix';
694
- break;
695
- case '|':
696
- env_type = 'vmatrix';
697
- break;
698
- case ')':
699
- case '(':
700
- default:
701
- env_type = 'pmatrix';
702
- }
676
+ switch (delim.content) {
677
+ case '#none':
678
+ env_type = 'matrix';
679
+ break;
680
+ case '[':
681
+ case ']':
682
+ env_type = 'bmatrix';
683
+ break;
684
+ case '(':
685
+ case ')':
686
+ env_type = 'pmatrix';
687
+ break;
688
+ case '{':
689
+ case '}':
690
+ env_type = 'Bmatrix';
691
+ break;
692
+ case '|':
693
+ env_type = 'vmatrix';
694
+ break;
695
+ case 'bar':
696
+ case 'bar.v':
697
+ env_type = 'vmatrix';
698
+ break;
699
+ case 'bar.v.double':
700
+ env_type = 'Vmatrix';
701
+ break;
702
+ default:
703
+ throw new Error(`Unexpected delimiter ${delim.content}`);
703
704
  }
704
705
  }
705
706
  }
package/src/generic.ts CHANGED
@@ -38,7 +38,7 @@ export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
38
38
 
39
39
  // e.g. input array=['a', 'b', 'c'], sep = '+'
40
40
  // return ['a','+', 'b', '+','c']
41
- export function array_join<T>(array: T[], sep: T): T[] {
41
+ export function array_intersperse<T>(array: T[], sep: T): T[] {
42
42
  const res: T[] = [];
43
43
  for (let i = 0; i < array.length; i++) {
44
44
  res.push(array[i]);
package/src/jslex.ts CHANGED
@@ -15,9 +15,8 @@ interface IRule<T> {
15
15
 
16
16
  interface IMatch<T> {
17
17
  index: number;
18
- text: string;
19
- len: number;
20
18
  rule: IRule<T>;
19
+ reMatchArray: RegExpMatchArray;
21
20
  }
22
21
 
23
22
 
@@ -31,8 +30,10 @@ const EOF = {};
31
30
  * @return {int} Difference between the matches.
32
31
  */
33
32
  function matchcompare<T>(m1: IMatch<T>, m2: IMatch<T>): number {
34
- if(m2.len !== m1.len) {
35
- return m2.len - m1.len;
33
+ const m1_len = m1.reMatchArray[0].length;
34
+ const m2_len = m2.reMatchArray[0].length;
35
+ if(m2_len !== m1_len) {
36
+ return m2_len - m1_len;
36
37
  } else {
37
38
  return m1.index - m2.index;
38
39
  }
@@ -59,6 +60,7 @@ export class Scanner<T> {
59
60
 
60
61
  private _text: string | null = null;
61
62
  private _leng: number | null = null;
63
+ private _reMatchArray: RegExpMatchArray | null = null;
62
64
 
63
65
  constructor(input: string, lexer: JSLex<T>) {
64
66
  this._input = input;
@@ -77,6 +79,10 @@ export class Scanner<T> {
77
79
  return this._leng;
78
80
  }
79
81
 
82
+ public reMatchArray(): RegExpMatchArray | null {
83
+ return this._reMatchArray;
84
+ }
85
+
80
86
  /**
81
87
  * Position of in stream, line number and column number of match.
82
88
  */
@@ -180,9 +186,8 @@ export class Scanner<T> {
180
186
  if (mt !== null && mt[0].length > 0) {
181
187
  matches.push({
182
188
  index: i,
183
- text: mt[0],
184
- len: mt[0].length,
185
- rule: rule
189
+ rule: rule,
190
+ reMatchArray: mt,
186
191
  });
187
192
  }
188
193
  }
@@ -193,22 +198,24 @@ export class Scanner<T> {
193
198
  this._go = true;
194
199
 
195
200
  let result: T | T[];
196
- let m: IMatch<T>;
201
+ let matched_text: string;
197
202
  for (let j = 0, n = matches.length; j < n && this._go; j++) {
198
203
  this._offset = 0;
199
204
  this._less = null;
200
205
  this._go = false;
201
206
  this._newstate = null;
202
- m = matches[j];
203
- this._text = m.text;
204
- this._leng = m.len;
207
+ const m = matches[j];
208
+ matched_text = m.reMatchArray[0];
209
+ this._text = matched_text;
210
+ this._leng = matched_text.length;
211
+ this._reMatchArray = m.reMatchArray;
205
212
  result = m.rule.action(this);
206
213
  if (this._newstate && this._newstate != this._state) {
207
214
  this._state = this._newstate;
208
215
  break;
209
216
  }
210
217
  }
211
- const text = this._less === null ? m!.text : m!.text.substring(0, this._less);
218
+ const text = this._less === null ? matched_text! : matched_text!.substring(0, this._less);
212
219
  const len = text.length;
213
220
  this._pos += len + this._offset;
214
221