tex2typst 0.3.23 → 0.3.25
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/dist/convert.d.ts +9 -3
- package/dist/generic.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2278 -2145
- package/dist/tex-parser.d.ts +1 -1
- package/dist/tex-tokenizer.d.ts +1 -1
- package/dist/tex-types.d.ts +107 -0
- package/dist/tex-writer.d.ts +1 -2
- package/dist/tex2typst.min.js +13 -13
- package/dist/typst-parser.d.ts +6 -4
- package/dist/typst-tokenizer.d.ts +1 -1
- package/dist/typst-types.d.ts +97 -0
- package/dist/typst-writer.d.ts +4 -2
- package/package.json +1 -1
- package/src/convert.ts +538 -446
- package/src/generic.ts +28 -2
- package/src/index.ts +1 -1
- package/src/map.ts +5 -0
- package/src/tex-parser.ts +49 -63
- package/src/tex-tokenizer.ts +4 -3
- package/src/tex-types.ts +369 -0
- package/src/tex-writer.ts +3 -51
- package/src/typst-parser.ts +83 -65
- package/src/typst-tokenizer.ts +86 -85
- package/src/typst-types.ts +229 -0
- package/src/typst-writer.ts +143 -129
- package/src/util.ts +1 -1
- package/dist/types.d.ts +0 -109
- package/src/types.ts +0 -414
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { array_includes } from "./generic";
|
|
2
|
+
|
|
3
|
+
export enum TypstTokenType {
|
|
4
|
+
NONE,
|
|
5
|
+
SYMBOL,
|
|
6
|
+
ELEMENT,
|
|
7
|
+
LITERAL,
|
|
8
|
+
TEXT,
|
|
9
|
+
COMMENT,
|
|
10
|
+
SPACE,
|
|
11
|
+
CONTROL,
|
|
12
|
+
NEWLINE
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export class TypstToken {
|
|
17
|
+
readonly type: TypstTokenType;
|
|
18
|
+
value: string;
|
|
19
|
+
|
|
20
|
+
constructor(type: TypstTokenType, content: string) {
|
|
21
|
+
this.type = type;
|
|
22
|
+
this.value = content;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
eq(other: TypstToken): boolean {
|
|
26
|
+
return this.type === other.type && this.value === other.value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
isOneOf(tokens: TypstToken[]): boolean {
|
|
30
|
+
return array_includes(tokens, this);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public toNode(): TypstNode {
|
|
34
|
+
return new TypstTerminal(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public toString(): string {
|
|
38
|
+
switch (this.type) {
|
|
39
|
+
case TypstTokenType.TEXT:
|
|
40
|
+
return `"${this.value}"`;
|
|
41
|
+
case TypstTokenType.COMMENT:
|
|
42
|
+
return `//${this.value}`;
|
|
43
|
+
default:
|
|
44
|
+
return this.value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static readonly NONE = new TypstToken(TypstTokenType.NONE, '#none');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TypstSupsubData {
|
|
52
|
+
base: TypstNode;
|
|
53
|
+
sup: TypstNode | null;
|
|
54
|
+
sub: TypstNode | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
export interface TypstLeftRightData {
|
|
59
|
+
body: TypstNode;
|
|
60
|
+
left: TypstToken | null;
|
|
61
|
+
right: TypstToken | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* fraction: `1/2`, `(x + y)/2`, `(1+x)/(1-x)`
|
|
66
|
+
* group: `a + 1/3`
|
|
67
|
+
* leftright: `(a + 1/3)`, `[a + 1/3)`, `lr(]sum_(x=1)^n])`
|
|
68
|
+
* markupFunc: `#heading(level: 2)[something]`, `#text(fill: red)[some text and math $x + y$]`
|
|
69
|
+
*/
|
|
70
|
+
export type TypstNodeType = 'terminal' | 'group' | 'supsub' | 'funcCall' | 'fraction'| 'leftright' | 'matrixLike'| 'markupFunc';
|
|
71
|
+
|
|
72
|
+
export type TypstNamedParams = { [key: string]: TypstNode; };
|
|
73
|
+
|
|
74
|
+
export abstract class TypstNode {
|
|
75
|
+
readonly type: TypstNodeType;
|
|
76
|
+
head: TypstToken;
|
|
77
|
+
// Some Typst functions accept additional options. e.g. mat() has option "delim", op() has option "limits"
|
|
78
|
+
options?: TypstNamedParams;
|
|
79
|
+
|
|
80
|
+
constructor(type: TypstNodeType, head: TypstToken | null) {
|
|
81
|
+
this.type = type;
|
|
82
|
+
this.head = head ? head : TypstToken.NONE;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// whether the node is over high so that if it's wrapped in braces, \left and \right should be used in its TeX form
|
|
86
|
+
// e.g. 1/2 is over high, "2" is not.
|
|
87
|
+
abstract isOverHigh(): boolean;
|
|
88
|
+
|
|
89
|
+
public setOptions(options: TypstNamedParams) {
|
|
90
|
+
this.options = options;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Note that this is only shallow equality.
|
|
94
|
+
public eq(other: TypstNode): boolean {
|
|
95
|
+
return this.type === other.type && this.head.eq(other.head);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public toString(): string {
|
|
99
|
+
throw new Error(`Unimplemented toString() in base class TypstNode`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class TypstTerminal extends TypstNode {
|
|
104
|
+
constructor(head: TypstToken) {
|
|
105
|
+
super('terminal', head);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public isOverHigh(): boolean {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public toString(): string {
|
|
113
|
+
return this.head.toString();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class TypstGroup extends TypstNode {
|
|
118
|
+
public items: TypstNode[];
|
|
119
|
+
constructor(items: TypstNode[]) {
|
|
120
|
+
super('group', TypstToken.NONE);
|
|
121
|
+
this.items = items;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public isOverHigh(): boolean {
|
|
125
|
+
return this.items.some((n) => n.isOverHigh());
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class TypstSupsub extends TypstNode {
|
|
130
|
+
public base: TypstNode;
|
|
131
|
+
public sup: TypstNode | null;
|
|
132
|
+
public sub: TypstNode | null;
|
|
133
|
+
|
|
134
|
+
constructor(data: TypstSupsubData) {
|
|
135
|
+
super('supsub', TypstToken.NONE);
|
|
136
|
+
this.base = data.base;
|
|
137
|
+
this.sup = data.sup;
|
|
138
|
+
this.sub = data.sub;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public isOverHigh(): boolean {
|
|
142
|
+
return this.base.isOverHigh();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export class TypstFuncCall extends TypstNode {
|
|
147
|
+
public args: TypstNode[];
|
|
148
|
+
constructor(head: TypstToken, args: TypstNode[]) {
|
|
149
|
+
super('funcCall', head);
|
|
150
|
+
this.args = args;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public isOverHigh(): boolean {
|
|
154
|
+
if (this.head.value === 'frac') {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return this.args.some((n) => n.isOverHigh());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class TypstFraction extends TypstNode {
|
|
162
|
+
public args: TypstNode[];
|
|
163
|
+
|
|
164
|
+
constructor(args: TypstNode[]) {
|
|
165
|
+
super('fraction', TypstToken.NONE);
|
|
166
|
+
this.args = args;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public isOverHigh(): boolean {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
export class TypstLeftright extends TypstNode {
|
|
176
|
+
public body: TypstNode;
|
|
177
|
+
public left: TypstToken | null;
|
|
178
|
+
public right: TypstToken | null;
|
|
179
|
+
|
|
180
|
+
// head is either null or 'lr'
|
|
181
|
+
constructor(head: TypstToken | null, data: TypstLeftRightData) {
|
|
182
|
+
super('leftright', head);
|
|
183
|
+
this.body = data.body;
|
|
184
|
+
this.left = data.left;
|
|
185
|
+
this.right = data.right;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public isOverHigh(): boolean {
|
|
189
|
+
return this.body.isOverHigh();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
export class TypstMatrixLike extends TypstNode {
|
|
195
|
+
public matrix: TypstNode[][];
|
|
196
|
+
|
|
197
|
+
// head is 'mat', 'cases' or null
|
|
198
|
+
constructor(head: TypstToken | null, data: TypstNode[][]) {
|
|
199
|
+
super('matrixLike', head);
|
|
200
|
+
this.matrix = data;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public isOverHigh(): boolean {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static readonly MAT = new TypstToken(TypstTokenType.SYMBOL, 'mat');
|
|
208
|
+
static readonly CASES = new TypstToken(TypstTokenType.SYMBOL, 'cases');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export class TypstMarkupFunc extends TypstNode {
|
|
212
|
+
/*
|
|
213
|
+
In idealized situations, for `#heading([some text and math $x + y$ example])`,
|
|
214
|
+
fragments would be [TypstMarkup{"some text and math "}, TypstNode{"x + y"}, TypstMarkup{" example"}]
|
|
215
|
+
At present, we haven't implemented anything about TypstMarkup.
|
|
216
|
+
So only pattens like `#heading(level: 2)[$x+y$]`, `#text(fill: red)[$x + y$]` are supported.
|
|
217
|
+
Therefore, fragments is always a list containing exactly 1 TypstNode in well-working situations.
|
|
218
|
+
*/
|
|
219
|
+
public fragments: TypstNode[];
|
|
220
|
+
|
|
221
|
+
constructor(head: TypstToken, fragments: TypstNode[]) {
|
|
222
|
+
super('markupFunc', head);
|
|
223
|
+
this.fragments = fragments;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public isOverHigh(): boolean {
|
|
227
|
+
return this.fragments.some((n) => n.isOverHigh());
|
|
228
|
+
}
|
|
229
|
+
}
|
package/src/typst-writer.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { TexNode
|
|
1
|
+
import { TexNode } from "./tex-types";
|
|
2
|
+
import { TypstFraction, TypstFuncCall, TypstGroup, TypstLeftright, TypstMarkupFunc, TypstMatrixLike, TypstNode, TypstSupsub, TypstTerminal } from "./typst-types";
|
|
3
|
+
import { TypstToken } from "./typst-types";
|
|
4
|
+
import { TypstTokenType } from "./typst-types";
|
|
2
5
|
import { shorthandMap } from "./typst-shorthands";
|
|
3
6
|
|
|
4
7
|
function is_delimiter(c: TypstNode): boolean {
|
|
5
|
-
return c.type ===
|
|
8
|
+
return c.head.type === TypstTokenType.ELEMENT && ['(', ')', '[', ']', '{', '}', '|', '⌊', '⌋', '⌈', '⌉'].includes(c.head.value);
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
const TYPST_LEFT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '(');
|
|
@@ -51,7 +54,7 @@ export class TypstWriter {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
|
|
54
|
-
private writeBuffer(token: TypstToken) {
|
|
57
|
+
private writeBuffer(previousToken: TypstToken | null, token: TypstToken) {
|
|
55
58
|
const str = token.toString();
|
|
56
59
|
|
|
57
60
|
if (str === '') {
|
|
@@ -81,8 +84,13 @@ export class TypstWriter {
|
|
|
81
84
|
no_need_space ||= this.buffer.endsWith('&') && str === '=';
|
|
82
85
|
// before or after a slash e.g. "a/b" instead of "a / b"
|
|
83
86
|
no_need_space ||= this.buffer.endsWith('/') || str === '/';
|
|
87
|
+
// "[$x + y$]" instead of "[ $ x + y $ ]"
|
|
88
|
+
no_need_space ||= token.type === TypstTokenType.LITERAL;
|
|
84
89
|
// other cases
|
|
85
90
|
no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
|
|
91
|
+
if (previousToken !== null) {
|
|
92
|
+
no_need_space ||= previousToken.type === TypstTokenType.LITERAL;
|
|
93
|
+
}
|
|
86
94
|
if (!no_need_space) {
|
|
87
95
|
this.buffer += ' ';
|
|
88
96
|
}
|
|
@@ -91,65 +99,81 @@ export class TypstWriter {
|
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
// Serialize a tree of TypstNode into a list of TypstToken
|
|
94
|
-
public serialize(
|
|
95
|
-
switch (
|
|
96
|
-
case '
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
public serialize(abstractNode: TypstNode) {
|
|
103
|
+
switch (abstractNode.type) {
|
|
104
|
+
case 'terminal': {
|
|
105
|
+
const node = abstractNode as TypstTerminal;
|
|
106
|
+
if (node.head.type === TypstTokenType.ELEMENT) {
|
|
107
|
+
if (node.head.value === ',' && this.insideFunctionDepth > 0) {
|
|
108
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'comma'));
|
|
109
|
+
} else {
|
|
110
|
+
this.queue.push(node.head);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
} else if (node.head.type === TypstTokenType.SYMBOL) {
|
|
114
|
+
let symbol_name = node.head.value;
|
|
115
|
+
if(this.preferShorthands) {
|
|
116
|
+
if (shorthandMap.has(symbol_name)) {
|
|
117
|
+
symbol_name = shorthandMap.get(symbol_name)!;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (this.inftyToOo && symbol_name === 'infinity') {
|
|
121
|
+
symbol_name = 'oo';
|
|
122
|
+
}
|
|
123
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, symbol_name));
|
|
124
|
+
break;
|
|
125
|
+
} else if (node.head.type === TypstTokenType.SPACE || node.head.type === TypstTokenType.NEWLINE) {
|
|
126
|
+
for (const c of node.head.value) {
|
|
127
|
+
if (c === ' ') {
|
|
128
|
+
if (this.keepSpaces) {
|
|
129
|
+
this.queue.push(new TypstToken(TypstTokenType.SPACE, c));
|
|
130
|
+
}
|
|
131
|
+
} else if (c === '\n') {
|
|
132
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, c));
|
|
133
|
+
} else {
|
|
134
|
+
throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
102
138
|
} else {
|
|
103
|
-
this.queue.push(
|
|
139
|
+
this.queue.push(node.head);
|
|
140
|
+
break;
|
|
104
141
|
}
|
|
105
|
-
break;
|
|
106
142
|
}
|
|
107
|
-
case '
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
content = shorthandMap.get(content)!;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (this.inftyToOo && content === 'infinity') {
|
|
115
|
-
content = 'oo';
|
|
143
|
+
case 'group': {
|
|
144
|
+
const node = abstractNode as TypstGroup;
|
|
145
|
+
for (const item of node.items) {
|
|
146
|
+
this.serialize(item);
|
|
116
147
|
}
|
|
117
|
-
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, content));
|
|
118
148
|
break;
|
|
119
149
|
}
|
|
120
|
-
case '
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.queue.push(new TypstToken(TypstTokenType.COMMENT, node.content));
|
|
128
|
-
break;
|
|
129
|
-
case 'whitespace':
|
|
130
|
-
for (const c of node.content) {
|
|
131
|
-
if (c === ' ') {
|
|
132
|
-
if (this.keepSpaces) {
|
|
133
|
-
this.queue.push(new TypstToken(TypstTokenType.SPACE, c));
|
|
134
|
-
}
|
|
135
|
-
} else if (c === '\n') {
|
|
136
|
-
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, c));
|
|
137
|
-
} else {
|
|
138
|
-
throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
|
|
139
|
-
}
|
|
150
|
+
case 'leftright': {
|
|
151
|
+
const node = abstractNode as TypstLeftright;
|
|
152
|
+
const LR = new TypstToken(TypstTokenType.SYMBOL, 'lr');
|
|
153
|
+
const {left, right} = node;
|
|
154
|
+
if (node.head.eq(LR)) {
|
|
155
|
+
this.queue.push(LR);
|
|
156
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
140
157
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
if (left) {
|
|
159
|
+
this.queue.push(left);
|
|
160
|
+
}
|
|
161
|
+
this.serialize(node.body);
|
|
162
|
+
if (right) {
|
|
163
|
+
this.queue.push(right);
|
|
164
|
+
}
|
|
165
|
+
if (node.head.eq(LR)) {
|
|
166
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
145
167
|
}
|
|
146
168
|
break;
|
|
169
|
+
}
|
|
147
170
|
case 'supsub': {
|
|
148
|
-
|
|
171
|
+
const node = abstractNode as TypstSupsub;
|
|
172
|
+
let { base, sup, sub } = node;
|
|
149
173
|
this.appendWithBracketsIfNeeded(base);
|
|
150
174
|
|
|
151
175
|
let trailing_space_needed = false;
|
|
152
|
-
const has_prime = (sup && sup.
|
|
176
|
+
const has_prime = (sup && sup.head.eq(new TypstToken(TypstTokenType.ELEMENT, "'")));
|
|
153
177
|
if (has_prime) {
|
|
154
178
|
// Put prime symbol before '_'. Because $y_1'$ is not displayed properly in Typst (so far)
|
|
155
179
|
// e.g.
|
|
@@ -172,15 +196,14 @@ export class TypstWriter {
|
|
|
172
196
|
break;
|
|
173
197
|
}
|
|
174
198
|
case 'funcCall': {
|
|
175
|
-
const
|
|
199
|
+
const node = abstractNode as TypstFuncCall;
|
|
200
|
+
const func_symbol: TypstToken = node.head;
|
|
176
201
|
this.queue.push(func_symbol);
|
|
177
|
-
|
|
178
|
-
this.insideFunctionDepth++;
|
|
179
|
-
}
|
|
202
|
+
this.insideFunctionDepth++;
|
|
180
203
|
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
181
|
-
for (let i = 0; i < node.args
|
|
182
|
-
this.serialize(node.args
|
|
183
|
-
if (i < node.args
|
|
204
|
+
for (let i = 0; i < node.args.length; i++) {
|
|
205
|
+
this.serialize(node.args[i]);
|
|
206
|
+
if (i < node.args.length - 1) {
|
|
184
207
|
this.queue.push(new TypstToken(TypstTokenType.ELEMENT, ','));
|
|
185
208
|
}
|
|
186
209
|
}
|
|
@@ -190,13 +213,12 @@ export class TypstWriter {
|
|
|
190
213
|
}
|
|
191
214
|
}
|
|
192
215
|
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
193
|
-
|
|
194
|
-
this.insideFunctionDepth--;
|
|
195
|
-
}
|
|
216
|
+
this.insideFunctionDepth--;
|
|
196
217
|
break;
|
|
197
218
|
}
|
|
198
219
|
case 'fraction': {
|
|
199
|
-
const
|
|
220
|
+
const node = abstractNode as TypstFraction;
|
|
221
|
+
const [numerator, denominator] = node.args;
|
|
200
222
|
const pos = this.queue.length;
|
|
201
223
|
const no_wrap = this.appendWithBracketsIfNeeded(numerator);
|
|
202
224
|
|
|
@@ -211,105 +233,95 @@ export class TypstWriter {
|
|
|
211
233
|
this.appendWithBracketsIfNeeded(denominator);
|
|
212
234
|
break;
|
|
213
235
|
}
|
|
214
|
-
case '
|
|
215
|
-
const
|
|
216
|
-
matrix
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
236
|
+
case 'matrixLike': {
|
|
237
|
+
const node = abstractNode as TypstMatrixLike;
|
|
238
|
+
const matrix = node.matrix;
|
|
239
|
+
|
|
240
|
+
let cell_sep: TypstToken;
|
|
241
|
+
let row_sep: TypstToken;
|
|
242
|
+
if (node.head.eq(TypstMatrixLike.MAT)) {
|
|
243
|
+
cell_sep = new TypstToken(TypstTokenType.ELEMENT, ',');
|
|
244
|
+
row_sep = new TypstToken(TypstTokenType.ELEMENT, ';');
|
|
245
|
+
} else if (node.head.eq(TypstMatrixLike.CASES)) {
|
|
246
|
+
cell_sep = new TypstToken(TypstTokenType.ELEMENT, '&');
|
|
247
|
+
row_sep = new TypstToken(TypstTokenType.ELEMENT, ',');
|
|
248
|
+
} else if (node.head.eq(TypstToken.NONE)){ // head is null
|
|
249
|
+
cell_sep = new TypstToken(TypstTokenType.ELEMENT, '&');
|
|
250
|
+
row_sep = new TypstToken(TypstTokenType.SYMBOL, '\\');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!node.head.eq(TypstToken.NONE)) {
|
|
254
|
+
this.queue.push(node.head);
|
|
255
|
+
this.insideFunctionDepth++;
|
|
256
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
257
|
+
if (node.options) {
|
|
258
|
+
for (const [key, value] of Object.entries(node.options)) {
|
|
259
|
+
this.queue.push(new TypstToken(TypstTokenType.LITERAL, `${key}: ${value.toString()}, `));
|
|
220
260
|
}
|
|
221
|
-
this.serialize(cell);
|
|
222
|
-
});
|
|
223
|
-
if (i < matrix.length - 1) {
|
|
224
|
-
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, '\\'));
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
case 'matrix': {
|
|
230
|
-
const matrix = node.data as TypstNode[][];
|
|
231
|
-
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'mat'));
|
|
232
|
-
this.insideFunctionDepth++;
|
|
233
|
-
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
234
|
-
if (node.options) {
|
|
235
|
-
for (const [key, value] of Object.entries(node.options)) {
|
|
236
|
-
this.queue.push(new TypstToken(TypstTokenType.LITERAL, `${key}: ${value.toString()}, `));
|
|
237
261
|
}
|
|
238
262
|
}
|
|
263
|
+
|
|
239
264
|
matrix.forEach((row, i) => {
|
|
240
265
|
row.forEach((cell, j) => {
|
|
241
|
-
// There is a leading & in row
|
|
242
|
-
// if (cell.type === 'ordgroup' && cell.args!.length === 0) {
|
|
243
|
-
// this.queue.push(new TypstNode('atom', ','));
|
|
244
|
-
// return;
|
|
245
|
-
// }
|
|
246
|
-
// if (j == 0 && cell.type === 'newline' && cell.content === '\n') {
|
|
247
|
-
// return;
|
|
248
|
-
// }
|
|
249
266
|
this.serialize(cell);
|
|
250
|
-
// cell.args!.forEach((n) => this.append(n));
|
|
251
267
|
if (j < row.length - 1) {
|
|
252
|
-
this.queue.push(
|
|
268
|
+
this.queue.push(cell_sep);
|
|
253
269
|
} else {
|
|
254
270
|
if (i < matrix.length - 1) {
|
|
255
|
-
this.queue.push(
|
|
271
|
+
this.queue.push(row_sep);
|
|
256
272
|
}
|
|
257
273
|
}
|
|
258
274
|
});
|
|
259
275
|
});
|
|
260
|
-
|
|
261
|
-
|
|
276
|
+
|
|
277
|
+
if (!node.head.eq(TypstToken.NONE)) {
|
|
278
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
279
|
+
this.insideFunctionDepth--;
|
|
280
|
+
}
|
|
262
281
|
break;
|
|
263
282
|
}
|
|
264
|
-
case '
|
|
265
|
-
const
|
|
266
|
-
this.queue.push(
|
|
267
|
-
this.insideFunctionDepth++;
|
|
283
|
+
case 'markupFunc': {
|
|
284
|
+
const node = abstractNode as TypstMarkupFunc;
|
|
285
|
+
this.queue.push(node.head);
|
|
268
286
|
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
269
287
|
if (node.options) {
|
|
270
|
-
|
|
271
|
-
|
|
288
|
+
const entries = Object.entries(node.options);
|
|
289
|
+
for (let i = 0; i < entries.length; i++) {
|
|
290
|
+
const [key, value] = entries[i];
|
|
291
|
+
this.queue.push(new TypstToken(TypstTokenType.LITERAL, `${key}: ${value.toString()}`));
|
|
292
|
+
if (i < entries.length - 1) {
|
|
293
|
+
this.queue.push(new TypstToken(TypstTokenType.ELEMENT, ','));
|
|
294
|
+
}
|
|
272
295
|
}
|
|
273
296
|
}
|
|
274
|
-
cases.forEach((row, i) => {
|
|
275
|
-
row.forEach((cell, j) => {
|
|
276
|
-
this.serialize(cell);
|
|
277
|
-
if (j < row.length - 1) {
|
|
278
|
-
this.queue.push(new TypstToken(TypstTokenType.ELEMENT, '&'));
|
|
279
|
-
} else {
|
|
280
|
-
if (i < cases.length - 1) {
|
|
281
|
-
this.queue.push(new TypstToken(TypstTokenType.ELEMENT, ','));
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
297
|
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
this.queue.push(new TypstToken(TypstTokenType.
|
|
293
|
-
} else {
|
|
294
|
-
throw new TypstWriterError(`Unknown macro: ${node.content}`, node);
|
|
298
|
+
|
|
299
|
+
this.queue.push(new TypstToken(TypstTokenType.LITERAL, '['));
|
|
300
|
+
for (const frag of node.fragments) {
|
|
301
|
+
this.queue.push(new TypstToken(TypstTokenType.LITERAL, '$'));
|
|
302
|
+
this.serialize(frag);
|
|
303
|
+
this.queue.push(new TypstToken(TypstTokenType.LITERAL, '$'));
|
|
295
304
|
}
|
|
305
|
+
this.queue.push(new TypstToken(TypstTokenType.LITERAL, ']'));
|
|
306
|
+
|
|
296
307
|
break;
|
|
297
308
|
}
|
|
298
309
|
default:
|
|
299
|
-
throw new TypstWriterError(`Unimplemented node type to append: ${
|
|
310
|
+
throw new TypstWriterError(`Unimplemented node type to append: ${abstractNode.type}`, abstractNode);
|
|
300
311
|
}
|
|
301
312
|
}
|
|
302
313
|
|
|
303
314
|
private appendWithBracketsIfNeeded(node: TypstNode): boolean {
|
|
304
|
-
let need_to_wrap = ['group', 'supsub', '
|
|
315
|
+
let need_to_wrap = ['group', 'supsub', 'matrixLike', 'fraction','empty'].includes(node.type);
|
|
305
316
|
|
|
306
317
|
if (node.type === 'group') {
|
|
307
|
-
|
|
318
|
+
const group = node as TypstGroup;
|
|
319
|
+
if (group.items.length === 0) {
|
|
308
320
|
// e.g. TeX `P_{}` converts to Typst `P_()`
|
|
309
321
|
need_to_wrap = true;
|
|
310
322
|
} else {
|
|
311
|
-
const first =
|
|
312
|
-
const last =
|
|
323
|
+
const first = group.items[0];
|
|
324
|
+
const last = group.items[group.items.length - 1];
|
|
313
325
|
if (is_delimiter(first) && is_delimiter(last)) {
|
|
314
326
|
need_to_wrap = false;
|
|
315
327
|
}
|
|
@@ -347,9 +359,11 @@ export class TypstWriter {
|
|
|
347
359
|
|
|
348
360
|
this.queue = this.queue.filter((token) => !token.eq(dummy_token));
|
|
349
361
|
|
|
350
|
-
this.queue.
|
|
351
|
-
this.
|
|
352
|
-
|
|
362
|
+
for(let i = 0; i < this.queue.length; i++) {
|
|
363
|
+
let token = this.queue[i];
|
|
364
|
+
let previous_token = i === 0 ? null : this.queue[i - 1];
|
|
365
|
+
this.writeBuffer(previous_token, token);
|
|
366
|
+
}
|
|
353
367
|
|
|
354
368
|
this.queue = [];
|
|
355
369
|
}
|
package/src/util.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function isdigit(char: string): boolean {
|
|
|
7
7
|
return '0123456789'.includes(char);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function assert(condition: boolean, message: string = ''): void {
|
|
10
|
+
export function assert(condition: boolean, message: string = 'Assertion failed.'): void {
|
|
11
11
|
if (!condition) {
|
|
12
12
|
throw new Error(message);
|
|
13
13
|
}
|