tex2typst 0.3.1 → 0.3.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.
- package/README.md +5 -3
- package/dist/index.js +454 -322
- package/dist/jslex.d.ts +105 -0
- package/dist/tex-parser.d.ts +1 -1
- package/dist/tex2typst.min.js +13 -20
- package/dist/types.d.ts +3 -1
- package/dist/typst-parser.d.ts +1 -1
- package/dist/typst-shorthands.d.ts +3 -0
- package/dist/typst-writer.d.ts +9 -3
- package/docs/api-reference.md +64 -0
- package/package.json +1 -1
- package/src/convert.ts +31 -18
- package/src/index.ts +11 -14
- package/src/jslex.ts +304 -0
- package/src/map.ts +13 -36
- package/src/tex-parser.ts +44 -137
- package/src/types.ts +3 -1
- package/src/typst-parser.ts +72 -126
- package/src/typst-shorthands.ts +51 -0
- package/src/typst-writer.ts +29 -22
- package/tools/make-shorthand-map.py +33 -0
- package/tools/make-symbol-map.py +4 -3
package/dist/types.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export interface TypstSupsubData {
|
|
|
69
69
|
}
|
|
70
70
|
export type TypstArrayData = TypstNode[][];
|
|
71
71
|
type TypstNodeType = 'atom' | 'symbol' | 'text' | 'control' | 'comment' | 'whitespace' | 'empty' | 'group' | 'supsub' | 'funcCall' | 'fraction' | 'align' | 'matrix' | 'unknown';
|
|
72
|
-
export type TypstPrimitiveValue = string | boolean | null;
|
|
72
|
+
export type TypstPrimitiveValue = string | boolean | null | TypstToken;
|
|
73
73
|
export type TypstNamedParams = {
|
|
74
74
|
[key: string]: TypstPrimitiveValue;
|
|
75
75
|
};
|
|
@@ -89,8 +89,10 @@ export declare class TypstNode {
|
|
|
89
89
|
export interface Tex2TypstOptions {
|
|
90
90
|
nonStrict?: boolean;
|
|
91
91
|
preferTypstIntrinsic?: boolean;
|
|
92
|
+
preferShorthands?: boolean;
|
|
92
93
|
keepSpaces?: boolean;
|
|
93
94
|
fracToSlash?: boolean;
|
|
95
|
+
inftyToOo?: boolean;
|
|
94
96
|
customTexMacros?: {
|
|
95
97
|
[key: string]: string;
|
|
96
98
|
};
|
package/dist/typst-parser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TypstNamedParams, TypstNode, TypstToken } from "./types";
|
|
2
|
-
export declare function tokenize_typst(
|
|
2
|
+
export declare function tokenize_typst(input: string): TypstToken[];
|
|
3
3
|
export declare class TypstParserError extends Error {
|
|
4
4
|
constructor(message: string);
|
|
5
5
|
}
|
package/dist/typst-writer.d.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { TexNode, TypstNode, TypstToken } from "./types";
|
|
2
|
-
export declare const TYPST_INTRINSIC_SYMBOLS: string[];
|
|
3
2
|
export declare class TypstWriterError extends Error {
|
|
4
3
|
node: TexNode | TypstNode | TypstToken;
|
|
5
4
|
constructor(message: string, node: TexNode | TypstNode | TypstToken);
|
|
6
5
|
}
|
|
6
|
+
export interface TypstWriterOptions {
|
|
7
|
+
nonStrict: boolean;
|
|
8
|
+
preferShorthands: boolean;
|
|
9
|
+
keepSpaces: boolean;
|
|
10
|
+
inftyToOo: boolean;
|
|
11
|
+
}
|
|
7
12
|
export declare class TypstWriter {
|
|
8
13
|
private nonStrict;
|
|
9
|
-
private
|
|
14
|
+
private preferShorthands;
|
|
10
15
|
private keepSpaces;
|
|
16
|
+
private inftyToOo;
|
|
11
17
|
protected buffer: string;
|
|
12
18
|
protected queue: TypstToken[];
|
|
13
19
|
private insideFunctionDepth;
|
|
14
|
-
constructor(
|
|
20
|
+
constructor(opt: TypstWriterOptions);
|
|
15
21
|
private writeBuffer;
|
|
16
22
|
serialize(node: TypstNode): void;
|
|
17
23
|
private appendWithBracketsIfNeeded;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# API Reference of tex2typst.js
|
|
2
|
+
|
|
3
|
+
## Basic usage
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
import { tex2typst, typst2tex } from 'tex2typst';
|
|
7
|
+
|
|
8
|
+
let tex = "e \overset{\text{def}}{=} \lim_{{n \to \infty}} \left(1 + \frac{1}{n}\right)^n";
|
|
9
|
+
let typst = tex2typst(tex);
|
|
10
|
+
console.log(typst);
|
|
11
|
+
// e eq.def lim_(n -> infinity)(1 + 1/n)^n
|
|
12
|
+
|
|
13
|
+
let tex_recovered = typst2tex(typst);
|
|
14
|
+
console.log(tex_recovered);
|
|
15
|
+
// e \overset{\text{def}}{=} \lim_{n \rightarrow \infty} \left(1 + \frac{1}{n} \right)^n
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Advanced options
|
|
19
|
+
|
|
20
|
+
`tex2typst` function accepts an optional second argument, which is an object containing options to customize the conversion.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
interface Tex2TypstOptions {
|
|
24
|
+
preferShorthands?: boolean;
|
|
25
|
+
fracToSlash?: boolean;
|
|
26
|
+
inftyToOo?: boolean;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- `preferShorthands`: If set to `true`, the function will prefer using shorthands in Typst (e.g., `->` instead of `arrow.r`, `<<` instead of `lt.double`) when converting TeX to Typst. Default is `ture`.
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
let tex = "a \\rightarrow b \\ll c";
|
|
34
|
+
let typst1 = tex2typst(tex, { preferShorthands: false });
|
|
35
|
+
console.log(typst1);
|
|
36
|
+
// a arrow.r b lt.double c
|
|
37
|
+
let typst2 = tex2typst(tex, { preferShorthands: true });
|
|
38
|
+
console.log(typst2);
|
|
39
|
+
// a -> b << c
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `fracToSlash`: If set to `true`, the Typst result will use the slash notation for fractions. Default is `true`.
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
let tex = "\\frac{a}{b}";
|
|
46
|
+
let tpyst1 = tex2typst(tex, { fracToSlash: false });
|
|
47
|
+
console.log(typst1);
|
|
48
|
+
// frac(a, b)
|
|
49
|
+
let typst2 = tex2typst(tex, { fracToSlash: true });
|
|
50
|
+
console.log(typst2);
|
|
51
|
+
// a / b
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `inftyToOo`: If set to `true`, `\\infty` converts to `oo` instead of `infinity`. Default is `false`.
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
let tex = "\\infty";
|
|
58
|
+
let typst1 = tex2typst(tex, { inftyToOo: false });
|
|
59
|
+
console.log(typst1);
|
|
60
|
+
// infinity
|
|
61
|
+
let typst2 = tex2typst(tex, { inftyToOo: true });
|
|
62
|
+
console.log(typst2);
|
|
63
|
+
// oo
|
|
64
|
+
```
|
package/package.json
CHANGED
package/src/convert.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import { TexNode, TypstNode, TexSupsubData, TypstSupsubData, TexSqrtData, Tex2TypstOptions, TYPST_NONE, TYPST_TRUE, TypstPrimitiveValue } from "./types";
|
|
2
|
-
import { TypstWriterError
|
|
1
|
+
import { TexNode, TypstNode, TexSupsubData, TypstSupsubData, TexSqrtData, Tex2TypstOptions, TYPST_NONE, TYPST_TRUE, TypstPrimitiveValue, TypstToken, TypstTokenType } from "./types";
|
|
2
|
+
import { TypstWriterError } from "./typst-writer";
|
|
3
3
|
import { symbolMap, reverseSymbolMap } from "./map";
|
|
4
4
|
|
|
5
|
+
// symbols that are supported by Typst but not by KaTeX
|
|
6
|
+
const TYPST_INTRINSIC_SYMBOLS = [
|
|
7
|
+
'dim',
|
|
8
|
+
'id',
|
|
9
|
+
'im',
|
|
10
|
+
'mod',
|
|
11
|
+
'Pr',
|
|
12
|
+
'sech',
|
|
13
|
+
'csch',
|
|
14
|
+
// 'sgn
|
|
15
|
+
];
|
|
5
16
|
|
|
6
17
|
function tex_token_to_typst(token: string): string {
|
|
7
18
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
@@ -207,6 +218,22 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
207
218
|
if (node.content === '\\mathbb' && arg0.type === 'atom' && /^[A-Z]$/.test(arg0.content)) {
|
|
208
219
|
return new TypstNode('symbol', arg0.content + arg0.content);
|
|
209
220
|
}
|
|
221
|
+
// \overrightarrow{AB} -> arrow(A B)
|
|
222
|
+
if (node.content === '\\overrightarrow') {
|
|
223
|
+
return new TypstNode(
|
|
224
|
+
'funcCall',
|
|
225
|
+
'arrow',
|
|
226
|
+
[arg0]
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
// \overleftarrow{AB} -> accent(A B, arrow.l)
|
|
230
|
+
if (node.content === '\\overleftarrow') {
|
|
231
|
+
return new TypstNode(
|
|
232
|
+
'funcCall',
|
|
233
|
+
'accent',
|
|
234
|
+
[arg0, new TypstNode('symbol', 'arrow.l')]
|
|
235
|
+
);
|
|
236
|
+
}
|
|
210
237
|
// \operatorname{opname} -> op("opname")
|
|
211
238
|
if (node.content === '\\operatorname') {
|
|
212
239
|
const body = node.args!;
|
|
@@ -260,22 +287,8 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
260
287
|
delim = '|';
|
|
261
288
|
break;
|
|
262
289
|
case 'Vmatrix': {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// \begin{Vmatrix}
|
|
266
|
-
// a & b \\
|
|
267
|
-
// c & d
|
|
268
|
-
// \end{Vmatrix}
|
|
269
|
-
// to
|
|
270
|
-
// lr(||mat(delim: #none, a, b; c, d)||)
|
|
271
|
-
const matrix = new TypstNode('matrix', '', [], data);
|
|
272
|
-
matrix.setOptions({ 'delim': TYPST_NONE });
|
|
273
|
-
const group = new TypstNode('group', '', [
|
|
274
|
-
new TypstNode('symbol', '||'),
|
|
275
|
-
matrix,
|
|
276
|
-
new TypstNode('symbol', '||'),
|
|
277
|
-
]);
|
|
278
|
-
return new TypstNode('funcCall', 'lr', [ group ]);
|
|
290
|
+
delim = new TypstToken(TypstTokenType.SYMBOL, 'bar.v.double');
|
|
291
|
+
break;
|
|
279
292
|
}
|
|
280
293
|
default:
|
|
281
294
|
throw new TypstWriterError(`Unimplemented beginend: ${node.content}`, node);
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseTex } from "./tex-parser";
|
|
2
2
|
import type { Tex2TypstOptions } from "./types";
|
|
3
|
-
import { TypstWriter } from "./typst-writer";
|
|
3
|
+
import { TypstWriter, type TypstWriterOptions } from "./typst-writer";
|
|
4
4
|
import { convert_tex_node_to_typst, convert_typst_node_to_tex } from "./convert";
|
|
5
5
|
import { symbolMap } from "./map";
|
|
6
6
|
import { parseTypst } from "./typst-parser";
|
|
@@ -11,27 +11,24 @@ export function tex2typst(tex: string, options?: Tex2TypstOptions): string {
|
|
|
11
11
|
const opt: Tex2TypstOptions = {
|
|
12
12
|
nonStrict: true,
|
|
13
13
|
preferTypstIntrinsic: true,
|
|
14
|
+
preferShorthands: true,
|
|
14
15
|
keepSpaces: false,
|
|
15
16
|
fracToSlash: true,
|
|
17
|
+
inftyToOo: false,
|
|
16
18
|
customTexMacros: {}
|
|
17
19
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
if (options.customTexMacros) {
|
|
26
|
-
opt.customTexMacros = options.customTexMacros;
|
|
27
|
-
}
|
|
28
|
-
if (options.fracToSlash !== undefined) {
|
|
29
|
-
opt.fracToSlash = options.fracToSlash;
|
|
20
|
+
|
|
21
|
+
if(options !== undefined) {
|
|
22
|
+
for (const key in opt) {
|
|
23
|
+
if (options[key] !== undefined) {
|
|
24
|
+
opt[key] = options[key];
|
|
25
|
+
}
|
|
30
26
|
}
|
|
31
27
|
}
|
|
28
|
+
|
|
32
29
|
const texTree = parseTex(tex, opt.customTexMacros!);
|
|
33
30
|
const typstTree = convert_tex_node_to_typst(texTree, opt);
|
|
34
|
-
const writer = new TypstWriter(opt
|
|
31
|
+
const writer = new TypstWriter(opt as TypstWriterOptions);
|
|
35
32
|
writer.serialize(typstTree);
|
|
36
33
|
return writer.finalize();
|
|
37
34
|
}
|
package/src/jslex.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from jslex - A lexer in JavaScript. https://github.com/jimbojw/jslex
|
|
3
|
+
* Licensed under MIT license
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
interface ILexSpec<T> {
|
|
8
|
+
start: Map<string, (arg0: Scanner<T>) => T | T[]>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface IRule<T> {
|
|
12
|
+
re: RegExp;
|
|
13
|
+
action: (a: Scanner<T>) => T | T[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface IMatch<T> {
|
|
17
|
+
index: number;
|
|
18
|
+
text: string;
|
|
19
|
+
len: number;
|
|
20
|
+
rule: IRule<T>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// End of File marker
|
|
25
|
+
const EOF = {};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Utility function for comparing two matches.
|
|
29
|
+
* @param {object} m1 Left-hand side match.
|
|
30
|
+
* @param {object} m2 Right-hand side match.
|
|
31
|
+
* @return {int} Difference between the matches.
|
|
32
|
+
*/
|
|
33
|
+
function matchcompare<T>(m1: IMatch<T>, m2: IMatch<T>): number {
|
|
34
|
+
if(m2.len !== m1.len) {
|
|
35
|
+
return m2.len - m1.len;
|
|
36
|
+
} else {
|
|
37
|
+
return m1.index - m2.index;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class Scanner<T> {
|
|
42
|
+
private _input: string;
|
|
43
|
+
private _lexer: JSLex<T>;
|
|
44
|
+
|
|
45
|
+
// position within input stream
|
|
46
|
+
private _pos: number = 0;
|
|
47
|
+
|
|
48
|
+
// current line number
|
|
49
|
+
private _line: number = 0;
|
|
50
|
+
|
|
51
|
+
// current column number
|
|
52
|
+
private _col: number = 0;
|
|
53
|
+
|
|
54
|
+
private _offset: number = 0;
|
|
55
|
+
private _less: number | null = null;
|
|
56
|
+
private _go: boolean = false;
|
|
57
|
+
private _newstate: string | null = null;
|
|
58
|
+
private _state: string;
|
|
59
|
+
|
|
60
|
+
private _text: string | null = null;
|
|
61
|
+
private _leng: number | null = null;
|
|
62
|
+
|
|
63
|
+
constructor(input: string, lexer: JSLex<T>) {
|
|
64
|
+
this._input = input;
|
|
65
|
+
this._lexer = lexer;
|
|
66
|
+
this._state = lexer.states[0];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Analogous to yytext and yyleng in lex - will be set during scan.
|
|
71
|
+
*/
|
|
72
|
+
public text(): string | null {
|
|
73
|
+
return this._text;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public leng(): number | null {
|
|
77
|
+
return this._leng;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Position of in stream, line number and column number of match.
|
|
82
|
+
*/
|
|
83
|
+
public pos(): number {
|
|
84
|
+
return this._pos;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public line(): number {
|
|
88
|
+
return this._line;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public column(): number {
|
|
92
|
+
return this._col;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Analogous to input() in lex.
|
|
97
|
+
* @return {string} The next character in the stream.
|
|
98
|
+
*/
|
|
99
|
+
public input(): string {
|
|
100
|
+
return this._input.charAt(this._pos + this._leng! + this._offset++);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Similar to unput() in lex, but does not allow modifying the stream.
|
|
105
|
+
* @return {int} The offset position after the operation.
|
|
106
|
+
*/
|
|
107
|
+
public unput(): number {
|
|
108
|
+
return this._offset = this._offset > 0 ? this._offset-- : 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Analogous to yyless(n) in lex - retains the first n characters from this pattern, and returns
|
|
113
|
+
* the rest to the input stream, such that they will be used in the next pattern-matching operation.
|
|
114
|
+
* @param {int} n Number of characters to retain.
|
|
115
|
+
* @return {int} Length of the stream after the operation has completed.
|
|
116
|
+
*/
|
|
117
|
+
public less(n: number): number {
|
|
118
|
+
this._less = n;
|
|
119
|
+
this._offset = 0;
|
|
120
|
+
this._text = this._text!.substring(0, n);
|
|
121
|
+
return this._leng = this._text.length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Like less(), but instead of retaining the first n characters, it chops off the last n.
|
|
126
|
+
* @param {int} n Number of characters to chop.
|
|
127
|
+
* @return {int} Length of the stream after the operation has completed.
|
|
128
|
+
*/
|
|
129
|
+
public pushback(n: number): number {
|
|
130
|
+
return this.less(this._leng! - n);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Similar to REJECT in lex, except it doesn't break the current execution context.
|
|
135
|
+
* TIP: reject() should be the last instruction in a spec callback.
|
|
136
|
+
*/
|
|
137
|
+
public reject(): void {
|
|
138
|
+
this._go = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Analogous to BEGIN in lex - sets the named state (start condition).
|
|
143
|
+
* @param {string|int} state Name of state to switch to, or ordinal number (0 is first, etc).
|
|
144
|
+
* @return {string} The new state on successful switch, throws exception on failure.
|
|
145
|
+
*/
|
|
146
|
+
public begin(state: string | number): string {
|
|
147
|
+
if (this._lexer.specification[state]) {
|
|
148
|
+
return this._newstate = state as string;
|
|
149
|
+
}
|
|
150
|
+
const s = this._lexer.states[parseInt(state as string)];
|
|
151
|
+
if (s) {
|
|
152
|
+
return this._newstate = s;
|
|
153
|
+
}
|
|
154
|
+
throw "Unknown state '" + state + "' requested";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Simple accessor for reading in the current state.
|
|
159
|
+
* @return {string} The current state.
|
|
160
|
+
*/
|
|
161
|
+
public state(): string {
|
|
162
|
+
return this._state;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Scan method to be returned to caller - grabs the next token and fires appropriate calback.
|
|
167
|
+
* @return {T} The next token extracted from the stream.
|
|
168
|
+
*/
|
|
169
|
+
public scan(): T | T[] {
|
|
170
|
+
if(this._pos >= this._input.length) {
|
|
171
|
+
return EOF as T;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const str = this._input.substring(this._pos);
|
|
175
|
+
const rules = this._lexer.specification[this._state];
|
|
176
|
+
const matches: IMatch<T>[] = [];
|
|
177
|
+
for (let i = 0; i < rules.length; i++) {
|
|
178
|
+
const rule = rules[i];
|
|
179
|
+
const mt = str.match(rule.re);
|
|
180
|
+
if (mt !== null && mt[0].length > 0) {
|
|
181
|
+
matches.push({
|
|
182
|
+
index: i,
|
|
183
|
+
text: mt[0],
|
|
184
|
+
len: mt[0].length,
|
|
185
|
+
rule: rule
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (matches.length === 0) {
|
|
190
|
+
throw new Error("No match found for input '" + str + "'");
|
|
191
|
+
}
|
|
192
|
+
matches.sort(matchcompare);
|
|
193
|
+
this._go = true;
|
|
194
|
+
|
|
195
|
+
let result: T | T[];
|
|
196
|
+
let m: IMatch<T>;
|
|
197
|
+
for (let j = 0, n = matches.length; j < n && this._go; j++) {
|
|
198
|
+
this._offset = 0;
|
|
199
|
+
this._less = null;
|
|
200
|
+
this._go = false;
|
|
201
|
+
this._newstate = null;
|
|
202
|
+
m = matches[j];
|
|
203
|
+
this._text = m.text;
|
|
204
|
+
this._leng = m.len;
|
|
205
|
+
result = m.rule.action(this);
|
|
206
|
+
if (this._newstate && this._newstate != this._state) {
|
|
207
|
+
this._state = this._newstate;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const text = this._less === null ? m!.text : m!.text.substring(0, this._less);
|
|
212
|
+
const len = text.length;
|
|
213
|
+
this._pos += len + this._offset;
|
|
214
|
+
|
|
215
|
+
const nlm = text.match(/\n/g);
|
|
216
|
+
if (nlm !== null) {
|
|
217
|
+
this._line += nlm.length;
|
|
218
|
+
this._col = len - text.lastIndexOf("\n") - 1;
|
|
219
|
+
} else {
|
|
220
|
+
this._col += len;
|
|
221
|
+
}
|
|
222
|
+
return result!;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export class JSLex<T> {
|
|
227
|
+
public states: string[];
|
|
228
|
+
public specification: Record<string, IRule<T>[]>;
|
|
229
|
+
|
|
230
|
+
constructor(spec: ILexSpec<T>) {
|
|
231
|
+
this.states = Object.keys(spec);
|
|
232
|
+
this.specification = {};
|
|
233
|
+
|
|
234
|
+
// build out internal representation of the provided spec
|
|
235
|
+
for (const s of this.states) {
|
|
236
|
+
// e.g. s = "start"
|
|
237
|
+
const rule_map = spec[s] as Map<string, (arg0: Scanner<T>) => T | T[]>;
|
|
238
|
+
|
|
239
|
+
if (s in this.specification) {
|
|
240
|
+
throw "Duplicate state declaration encountered for state '" + s + "'";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.specification[s] = [] as IRule<T>[];
|
|
244
|
+
|
|
245
|
+
for (const [k,v] of rule_map.entries()) {
|
|
246
|
+
let re: RegExp;
|
|
247
|
+
try {
|
|
248
|
+
re = new RegExp('^' + k);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
throw "Invalid regexp '" + k + "' in state '" + s + "' (" + (err as Error).message + ")";
|
|
251
|
+
}
|
|
252
|
+
this.specification[s].push({
|
|
253
|
+
re: re,
|
|
254
|
+
action: v
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Scanner function - makes a new scanner object which is used to get tokens one at a time.
|
|
262
|
+
* @param {string} input Input text to tokenize.
|
|
263
|
+
* @return {function} Scanner function.
|
|
264
|
+
*/
|
|
265
|
+
public scanner(input: string): Scanner<T> {
|
|
266
|
+
return new Scanner(input, this);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Similar to lex's yylex() function, consumes all input, calling calback for each token.
|
|
271
|
+
* @param {string} input Text to lex.
|
|
272
|
+
* @param {function} callback Function to execute for each token.
|
|
273
|
+
*/
|
|
274
|
+
public lex(input: string, callback: (arg0: T | T[]) => void) {
|
|
275
|
+
const scanner = this.scanner(input);
|
|
276
|
+
while (true) {
|
|
277
|
+
const token = scanner.scan();
|
|
278
|
+
if (token === EOF) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (token !== undefined) {
|
|
282
|
+
callback(token);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Consumes all input, collecting tokens along the way.
|
|
289
|
+
* @param {string} input Text to lex.
|
|
290
|
+
* @return {array} List of tokens, may contain an Error at the end.
|
|
291
|
+
*/
|
|
292
|
+
public collect(input: string): T[] {
|
|
293
|
+
const tokens: T[] = [];
|
|
294
|
+
const callback = function(item: T | T[]) {
|
|
295
|
+
if (Array.isArray(item)) {
|
|
296
|
+
tokens.push(...item);
|
|
297
|
+
} else {
|
|
298
|
+
tokens.push(item);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
this.lex(input, callback);
|
|
302
|
+
return tokens;
|
|
303
|
+
}
|
|
304
|
+
};
|
package/src/map.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
const symbolMap = new Map<string, string>([
|
|
2
|
+
['cos', 'cos'],
|
|
3
|
+
['sin', 'sin'],
|
|
4
|
+
['tan', 'tan'],
|
|
5
|
+
['cot', 'cot'],
|
|
6
|
+
['sec', 'sec'],
|
|
7
|
+
['csc', 'csc'],
|
|
8
|
+
['mod', 'mod'],
|
|
9
|
+
['omicron', 'omicron'],
|
|
10
|
+
['Xi', 'Xi'],
|
|
11
|
+
['Upsilon', 'Upsilon'],
|
|
12
|
+
['lim', 'lim'],
|
|
13
|
+
|
|
2
14
|
['nonumber', ''],
|
|
3
15
|
['vec', 'arrow'],
|
|
4
16
|
['neq', 'eq.not'],
|
|
@@ -59,37 +71,11 @@ const symbolMap = new Map<string, string>([
|
|
|
59
71
|
|
|
60
72
|
/* arrows */
|
|
61
73
|
['gets', 'arrow.l'],
|
|
62
|
-
['hookleftarrow', 'arrow.l.hook'],
|
|
63
|
-
['leftharpoonup', 'harpoon.lt'],
|
|
64
|
-
['leftharpoondown', 'harpoon.lb'],
|
|
65
|
-
['rightleftharpoons', 'harpoons.rtlb'],
|
|
66
|
-
['longleftarrow', 'arrow.l.long'],
|
|
67
|
-
['longrightarrow', 'arrow.r.long'],
|
|
68
|
-
['longleftrightarrow', 'arrow.l.r.long'],
|
|
69
|
-
['Longleftarrow', 'arrow.l.double.long'],
|
|
70
|
-
['Longrightarrow', 'arrow.r.double.long'],
|
|
71
|
-
['Longleftrightarrow', 'arrow.l.r.double.long'],
|
|
72
74
|
// ['longmapsto', 'arrow.r.bar'],
|
|
73
|
-
['hookrightarrow', 'arrow.r.hook'],
|
|
74
|
-
['rightharpoonup', 'harpoon.rt'],
|
|
75
|
-
['rightharpoondown', 'harpoon.rb'],
|
|
76
75
|
['iff', 'arrow.l.r.double.long'],
|
|
77
76
|
['implies', 'arrow.r.double.long'],
|
|
78
|
-
['uparrow', 'arrow.t'],
|
|
79
|
-
['downarrow', 'arrow.b'],
|
|
80
|
-
['updownarrow', 'arrow.t.b'],
|
|
81
|
-
['Uparrow', 'arrow.t.double'],
|
|
82
|
-
['Downarrow', 'arrow.b.double'],
|
|
83
|
-
['Updownarrow', 'arrow.t.b.double'],
|
|
84
|
-
['nearrow', 'arrow.tr'],
|
|
85
|
-
['searrow', 'arrow.br'],
|
|
86
|
-
['swarrow', 'arrow.bl'],
|
|
87
|
-
['nwarrow', 'arrow.tl'],
|
|
88
77
|
['leadsto', 'arrow.squiggly'],
|
|
89
78
|
|
|
90
|
-
['leftleftarrows', 'arrows.ll'],
|
|
91
|
-
['rightrightarrows', 'arrows.rr'],
|
|
92
|
-
|
|
93
79
|
|
|
94
80
|
['Cap', 'sect.double'],
|
|
95
81
|
['Cup', 'union.double'],
|
|
@@ -97,9 +83,6 @@ const symbolMap = new Map<string, string>([
|
|
|
97
83
|
['Gamma', 'Gamma'],
|
|
98
84
|
['Join', 'join'],
|
|
99
85
|
['Lambda', 'Lambda'],
|
|
100
|
-
['Leftarrow', 'arrow.l.double'],
|
|
101
|
-
['Leftrightarrow', 'arrow.l.r.double'],
|
|
102
|
-
['Longrightarrow', 'arrow.r.double.long'],
|
|
103
86
|
['Omega', 'Omega'],
|
|
104
87
|
['P', 'pilcrow'],
|
|
105
88
|
['Phi', 'Phi'],
|
|
@@ -153,7 +136,6 @@ const symbolMap = new Map<string, string>([
|
|
|
153
136
|
['div', 'div'],
|
|
154
137
|
['divideontimes', 'times.div'],
|
|
155
138
|
['dotplus', 'plus.dot'],
|
|
156
|
-
['downarrow', 'arrow.b'],
|
|
157
139
|
['ell', 'ell'],
|
|
158
140
|
['emptyset', 'nothing'],
|
|
159
141
|
['epsilon', 'epsilon.alt'],
|
|
@@ -186,8 +168,6 @@ const symbolMap = new Map<string, string>([
|
|
|
186
168
|
['lbrack', 'bracket.l'],
|
|
187
169
|
['ldots', 'dots.h'],
|
|
188
170
|
['le', 'lt.eq'],
|
|
189
|
-
['leadsto', 'arrow.squiggly'],
|
|
190
|
-
['leftarrow', 'arrow.l'],
|
|
191
171
|
['leftthreetimes', 'times.three.l'],
|
|
192
172
|
['leftrightarrow', 'arrow.l.r'],
|
|
193
173
|
['leq', 'lt.eq'],
|
|
@@ -224,7 +204,6 @@ const symbolMap = new Map<string, string>([
|
|
|
224
204
|
['nu', 'nu'],
|
|
225
205
|
['ntriangleleft', 'lt.tri.not'],
|
|
226
206
|
['ntriangleright', 'gt.tri.not'],
|
|
227
|
-
['nwarrow', 'arrow.tl'],
|
|
228
207
|
['odot', 'dot.circle'],
|
|
229
208
|
['oint', 'integral.cont'],
|
|
230
209
|
['oiint', 'integral.surf'],
|
|
@@ -277,7 +256,6 @@ const symbolMap = new Map<string, string>([
|
|
|
277
256
|
['supset', 'supset'],
|
|
278
257
|
['supseteq', 'supset.eq'],
|
|
279
258
|
['supsetneq', 'supset.neq'],
|
|
280
|
-
['swarrow', 'arrow.bl'],
|
|
281
259
|
['tau', 'tau'],
|
|
282
260
|
['theta', 'theta'],
|
|
283
261
|
['times', 'times'],
|
|
@@ -288,8 +266,6 @@ const symbolMap = new Map<string, string>([
|
|
|
288
266
|
// ['triangleleft', 'triangle.l.small'],
|
|
289
267
|
// ['triangleright', 'triangle.r.small'],
|
|
290
268
|
['twoheadrightarrow', 'arrow.r.twohead'],
|
|
291
|
-
['uparrow', 'arrow.t'],
|
|
292
|
-
['updownarrow', 'arrow.t.b'],
|
|
293
269
|
['upharpoonright', 'harpoon.tr'],
|
|
294
270
|
['uplus', 'union.plus'],
|
|
295
271
|
['upsilon', 'upsilon'],
|
|
@@ -1086,6 +1062,7 @@ for(const [key, value] of Array.from(symbolMap.entries()).reverse()) {
|
|
|
1086
1062
|
reverseSymbolMap.set(value, key);
|
|
1087
1063
|
}
|
|
1088
1064
|
reverseSymbolMap.set('dif', 'mathrm{d}');
|
|
1065
|
+
reverseSymbolMap.set('oo', 'infty');
|
|
1089
1066
|
|
|
1090
1067
|
// force override some one-to-multiple mappings
|
|
1091
1068
|
const typst_to_tex_map = new Map<string, string>([
|