tex2typst 0.2.16 → 0.3.0-beta-1

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/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { array_includes } from "./generic";
2
+
1
3
  export enum TexTokenType {
2
4
  ELEMENT,
3
5
  COMMAND,
@@ -9,6 +11,30 @@ export enum TexTokenType {
9
11
  UNKNOWN,
10
12
  }
11
13
 
14
+ export class TexToken {
15
+ type: TexTokenType;
16
+ value: string;
17
+
18
+ constructor(type: TexTokenType, value: string) {
19
+ this.type = type;
20
+ this.value = value;
21
+ }
22
+
23
+ public eq(token: TexToken): boolean {
24
+ return this.type === token.type && this.value === token.value;
25
+ }
26
+
27
+ public toString(): string {
28
+ switch (this.type) {
29
+ case TexTokenType.TEXT:
30
+ return `\\text{${this.value}}`;
31
+ case TexTokenType.COMMENT:
32
+ return `%${this.value}`;
33
+ default:
34
+ return this.value;
35
+ }
36
+ }
37
+ }
12
38
 
13
39
 
14
40
  export interface TexSupsubData {
@@ -33,6 +59,14 @@ export type TexArrayData = TexNode[][];
33
59
  type TexNodeType = 'element' | 'text' | 'comment' | 'whitespace' | 'control' | 'ordgroup' | 'supsub'
34
60
  | 'unaryFunc' | 'binaryFunc' | 'leftright' | 'beginend' | 'symbol' | 'empty' | 'unknownMacro';
35
61
 
62
+
63
+ function apply_escape_if_needed(c: string) {
64
+ if (['{', '}', '%'].includes(c)) {
65
+ return '\\' + c;
66
+ }
67
+ return c;
68
+ }
69
+
36
70
  export class TexNode {
37
71
  type: TexNodeType;
38
72
  content: string;
@@ -50,7 +84,8 @@ export class TexNode {
50
84
  this.data = data;
51
85
  }
52
86
 
53
- public eq_shallow(other: TexNode): boolean {
87
+ // Note that this is only shallow equality.
88
+ public eq(other: TexNode): boolean {
54
89
  return this.type === other.type && this.content === other.content;
55
90
  }
56
91
 
@@ -62,11 +97,130 @@ export class TexNode {
62
97
  throw new Error(`toString() is not implemented for type ${this.type}`);
63
98
  }
64
99
  }
100
+
101
+
102
+ public serialize(): TexToken[] {
103
+ switch (this.type) {
104
+ case 'empty':
105
+ return [];
106
+ case 'element': {
107
+ let c = this.content;
108
+ c = apply_escape_if_needed(c);
109
+ return [new TexToken(TexTokenType.ELEMENT, c)];
110
+ }
111
+ case 'symbol':
112
+ return [new TexToken(TexTokenType.COMMAND, this.content)];
113
+ case 'text':
114
+ return [new TexToken(TexTokenType.TEXT, this.content)];
115
+ case 'comment':
116
+ return [new TexToken(TexTokenType.COMMENT, this.content)];
117
+ case 'whitespace': {
118
+ const tokens: TexToken[] = [];
119
+ for (const c of this.content) {
120
+ const token_type = c === ' ' ? TexTokenType.SPACE : TexTokenType.NEWLINE;
121
+ tokens.push(new TexToken(token_type, c));
122
+ }
123
+ return tokens;
124
+ }
125
+ case 'ordgroup':
126
+ return this.args!.map((n) => n.serialize()).flat();
127
+ case 'unaryFunc': {
128
+ let tokens: TexToken[] = [];
129
+ tokens.push(new TexToken(TexTokenType.COMMAND, this.content));
130
+
131
+ // special hook for \sqrt
132
+ if (this.content === '\\sqrt' && this.data) {
133
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '['));
134
+ tokens = tokens.concat((this.data! as TexSqrtData).serialize());
135
+ tokens.push(new TexToken(TexTokenType.ELEMENT, ']'));
136
+ }
137
+ // special hook for \operatorname
138
+ if (this.content === '\\operatorname' && this.args!.length === 1 && this.args![0].type === 'text') {
139
+ const text = this.args![0].content;
140
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
141
+ // this.serialize(new TexNode('symbol', text));
142
+ tokens.push(new TexToken(TexTokenType.COMMAND, text));
143
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
144
+ return tokens;
145
+ }
146
+
147
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
148
+ tokens = tokens.concat(this.args![0].serialize());
149
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
150
+
151
+ return tokens;
152
+ }
153
+ case 'binaryFunc': {
154
+ let tokens: TexToken[] = [];
155
+ tokens.push(new TexToken(TexTokenType.COMMAND, this.content));
156
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
157
+ tokens = tokens.concat(this.args![0].serialize());
158
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
159
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
160
+ tokens = tokens.concat(this.args![1].serialize());
161
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
162
+ return tokens;
163
+ }
164
+ case 'supsub': {
165
+ let tokens: TexToken[] = [];
166
+ const { base, sup, sub } = this.data! as TexSupsubData;
167
+ tokens = tokens.concat(base.serialize());
168
+ if (sub) {
169
+ tokens.push(new TexToken(TexTokenType.CONTROL, '_'));
170
+ if (sub.type === 'ordgroup' || sub.type === 'supsub' || sub.type === 'empty') {
171
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
172
+ tokens = tokens.concat(sub.serialize());
173
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
174
+ } else {
175
+ tokens = tokens.concat(sub.serialize());
176
+ }
177
+ }
178
+ if (sup) {
179
+ tokens.push(new TexToken(TexTokenType.CONTROL, '^'));
180
+ if (sup.type === 'ordgroup' || sup.type === 'supsub' || sup.type === 'empty') {
181
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
182
+ tokens = tokens.concat(sup.serialize());
183
+ tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
184
+ } else {
185
+ tokens = tokens.concat(sup.serialize());
186
+ }
187
+ }
188
+ return tokens;
189
+ }
190
+ case 'control': {
191
+ return [new TexToken(TexTokenType.CONTROL, this.content)];
192
+ }
193
+ case 'beginend': {
194
+ let tokens: TexToken[] = [];
195
+ const matrix = this.data as TexArrayData;
196
+ tokens.push(new TexToken(TexTokenType.COMMAND, `\\begin{${this.content}}`));
197
+ tokens.push(new TexToken(TexTokenType.NEWLINE, '\n'));
198
+ for (let i = 0; i < matrix.length; i++) {
199
+ const row = matrix[i];
200
+ for (let j = 0; j < row.length; j++) {
201
+ const cell = row[j];
202
+ tokens = tokens.concat(cell.serialize());
203
+ if (j !== row.length - 1) {
204
+ tokens.push(new TexToken(TexTokenType.CONTROL, '&'));
205
+ }
206
+ }
207
+ if (i !== matrix.length - 1) {
208
+ tokens.push(new TexToken(TexTokenType.CONTROL, '\\\\'));
209
+ }
210
+ }
211
+ tokens.push(new TexToken(TexTokenType.NEWLINE, '\n'));
212
+ tokens.push(new TexToken(TexTokenType.COMMAND, `\\end{${this.content}}`));
213
+ return tokens;
214
+ }
215
+ default:
216
+ throw new Error('[TexNode.serialize] Unimplemented type: ' + this.type);
217
+ }
218
+ }
65
219
  }
66
220
 
67
221
  export enum TypstTokenType {
68
222
  SYMBOL,
69
- ATOM,
223
+ ELEMENT,
70
224
  TEXT,
71
225
  COMMENT,
72
226
  SPACE,
@@ -77,26 +231,63 @@ export enum TypstTokenType {
77
231
 
78
232
  export class TypstToken {
79
233
  type: TypstTokenType;
80
- content: string;
234
+ value: string;
81
235
 
82
236
  constructor(type: TypstTokenType, content: string) {
83
237
  this.type = type;
84
- this.content = content;
238
+ this.value = content;
85
239
  }
86
240
 
87
241
  eq(other: TypstToken): boolean {
88
- return this.type === other.type && this.content === other.content;
242
+ return this.type === other.type && this.value === other.value;
89
243
  }
90
244
 
91
245
  isOneOf(tokens: TypstToken[]): boolean {
92
- let found = false;
93
- for (const token of tokens) {
94
- if (this.eq(token)) {
95
- found = true;
96
- break;
246
+ return array_includes(tokens, this);
247
+ }
248
+
249
+ public toNode(): TypstNode {
250
+ switch(this.type) {
251
+ case TypstTokenType.TEXT:
252
+ return new TypstNode('text', this.value);
253
+ case TypstTokenType.COMMENT:
254
+ return new TypstNode('comment', this.value);
255
+ case TypstTokenType.SPACE:
256
+ case TypstTokenType.NEWLINE:
257
+ return new TypstNode('whitespace', this.value);
258
+ case TypstTokenType.ELEMENT:
259
+ return new TypstNode('atom', this.value);
260
+ case TypstTokenType.SYMBOL:
261
+ return new TypstNode('symbol', this.value);
262
+ case TypstTokenType.CONTROL: {
263
+ const controlChar = this.value;
264
+ switch (controlChar) {
265
+ case '':
266
+ case '_':
267
+ case '^':
268
+ return new TypstNode('empty', '');
269
+ case '&':
270
+ return new TypstNode('control', '&');
271
+ case '\\':
272
+ return new TypstNode('control', '\\');
273
+ default:
274
+ throw new Error(`Unexpected control character ${controlChar}`);
275
+ }
97
276
  }
277
+ default:
278
+ throw new Error(`Unexpected token type ${this.type}`);
279
+ }
280
+ }
281
+
282
+ public toString(): string {
283
+ switch (this.type) {
284
+ case TypstTokenType.TEXT:
285
+ return `"${this.value}"`;
286
+ case TypstTokenType.COMMENT:
287
+ return `//${this.value}`;
288
+ default:
289
+ return this.value;
98
290
  }
99
- return found;
100
291
  }
101
292
  }
102
293
 
@@ -108,8 +299,8 @@ export interface TypstSupsubData {
108
299
 
109
300
  export type TypstArrayData = TypstNode[][];
110
301
 
111
- type TypstNodeType = 'atom' | 'symbol' | 'text' | 'softSpace' | 'comment' | 'whitespace'
112
- | 'empty' | 'group' | 'supsub' | 'unaryFunc' | 'binaryFunc' | 'align' | 'matrix' | 'unknown';
302
+ type TypstNodeType = 'atom' | 'symbol' | 'text' | 'control' | 'comment' | 'whitespace'
303
+ | 'empty' | 'group' | 'supsub' | 'funcCall' | 'fraction' | 'align' | 'matrix' | 'unknown';
113
304
 
114
305
 
115
306
  export class TypstNode {
@@ -132,7 +323,8 @@ export class TypstNode {
132
323
  this.options = options;
133
324
  }
134
325
 
135
- public eq_shallow(other: TypstNode): boolean {
326
+ // Note that this is only shallow equality.
327
+ public eq(other: TypstNode): boolean {
136
328
  return this.type === other.type && this.content === other.content;
137
329
  }
138
330
  }