imxc 0.2.0 → 0.3.0
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/compile.d.ts +10 -0
- package/dist/compile.js +169 -0
- package/dist/components.js +249 -1
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +23 -0
- package/dist/emitter.d.ts +3 -3
- package/dist/emitter.js +814 -84
- package/dist/index.js +31 -111
- package/dist/init.js +144 -17
- package/dist/ir.d.ts +157 -3
- package/dist/lowering.d.ts +1 -0
- package/dist/lowering.js +400 -63
- package/dist/validator.js +3 -5
- package/dist/watch.d.ts +4 -0
- package/dist/watch.js +66 -0
- package/package.json +2 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CompileResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
componentCount: number;
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Compile a list of .tsx files and write generated C++ to outputDir.
|
|
8
|
+
* Returns a result object instead of calling process.exit().
|
|
9
|
+
*/
|
|
10
|
+
export declare function compile(files: string[], outputDir: string): CompileResult;
|
package/dist/compile.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { parseFile, extractImports } from './parser.js';
|
|
4
|
+
import { validate } from './validator.js';
|
|
5
|
+
import { lowerComponent } from './lowering.js';
|
|
6
|
+
import { emitComponent, emitComponentHeader, emitRoot } from './emitter.js';
|
|
7
|
+
import { formatDiagnostic } from './diagnostics.js';
|
|
8
|
+
/**
|
|
9
|
+
* Compile a list of .tsx files and write generated C++ to outputDir.
|
|
10
|
+
* Returns a result object instead of calling process.exit().
|
|
11
|
+
*/
|
|
12
|
+
export function compile(files, outputDir) {
|
|
13
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
14
|
+
let hasErrors = false;
|
|
15
|
+
const errorMessages = [];
|
|
16
|
+
const compiled = [];
|
|
17
|
+
// Phase 1: Parse, validate, and lower all components
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
if (!fs.existsSync(file)) {
|
|
20
|
+
const msg = `${file}:1:1 - error: file not found`;
|
|
21
|
+
errorMessages.push(msg);
|
|
22
|
+
hasErrors = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const source = fs.readFileSync(file, 'utf-8');
|
|
26
|
+
const parsed = parseFile(file, source);
|
|
27
|
+
if (parsed.errors.length > 0) {
|
|
28
|
+
parsed.errors.forEach(e => errorMessages.push(formatDiagnostic(e, source)));
|
|
29
|
+
hasErrors = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const validation = validate(parsed);
|
|
33
|
+
if (validation.errors.length > 0) {
|
|
34
|
+
validation.errors.forEach(e => errorMessages.push(formatDiagnostic(e, source)));
|
|
35
|
+
hasErrors = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const ir = lowerComponent(parsed, validation);
|
|
39
|
+
const imports = extractImports(parsed.sourceFile);
|
|
40
|
+
compiled.push({
|
|
41
|
+
name: ir.name,
|
|
42
|
+
sourceFile: path.basename(file),
|
|
43
|
+
sourcePath: file,
|
|
44
|
+
stateCount: ir.stateSlots.length,
|
|
45
|
+
bufferCount: ir.bufferCount,
|
|
46
|
+
ir,
|
|
47
|
+
imports,
|
|
48
|
+
hasProps: ir.params.length > 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (hasErrors) {
|
|
52
|
+
return { success: false, componentCount: 0, errors: errorMessages };
|
|
53
|
+
}
|
|
54
|
+
// Phase 2: Build lookup of compiled components for cross-file resolution
|
|
55
|
+
const componentMap = new Map();
|
|
56
|
+
for (const comp of compiled) {
|
|
57
|
+
componentMap.set(comp.name, comp);
|
|
58
|
+
}
|
|
59
|
+
// Phase 3: Resolve imported component stateCount/bufferCount in IR, then emit
|
|
60
|
+
for (const comp of compiled) {
|
|
61
|
+
resolveCustomComponents(comp.ir.body, componentMap);
|
|
62
|
+
const importInfos = [];
|
|
63
|
+
for (const [importedName] of comp.imports) {
|
|
64
|
+
const importedComp = componentMap.get(importedName);
|
|
65
|
+
if (importedComp && importedComp.hasProps) {
|
|
66
|
+
importInfos.push({
|
|
67
|
+
name: importedName,
|
|
68
|
+
headerFile: `${importedName}.gen.h`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const cppOutput = emitComponent(comp.ir, importInfos, comp.sourceFile);
|
|
73
|
+
const baseName = comp.name;
|
|
74
|
+
const outPath = path.join(outputDir, `${baseName}.gen.cpp`);
|
|
75
|
+
fs.writeFileSync(outPath, cppOutput);
|
|
76
|
+
console.log(` ${baseName} -> ${outPath}`);
|
|
77
|
+
// Generate embed headers for any <Image embed> nodes
|
|
78
|
+
const embedImages = collectEmbedImages(comp.ir.body);
|
|
79
|
+
if (embedImages.length > 0) {
|
|
80
|
+
const sourceDir = path.dirname(path.resolve(comp.sourcePath));
|
|
81
|
+
generateEmbedHeaders(embedImages, sourceDir, outputDir);
|
|
82
|
+
}
|
|
83
|
+
if (comp.hasProps) {
|
|
84
|
+
const headerOutput = emitComponentHeader(comp.ir, comp.sourceFile);
|
|
85
|
+
const headerPath = path.join(outputDir, `${baseName}.gen.h`);
|
|
86
|
+
fs.writeFileSync(headerPath, headerOutput);
|
|
87
|
+
console.log(` ${baseName} -> ${headerPath} (header)`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Phase 4: Emit root entry point
|
|
91
|
+
if (compiled.length > 0) {
|
|
92
|
+
const root = compiled[0];
|
|
93
|
+
const rootOutput = emitRoot(root.name, root.stateCount, root.bufferCount, root.sourceFile);
|
|
94
|
+
const rootPath = path.join(outputDir, 'app_root.gen.cpp');
|
|
95
|
+
fs.writeFileSync(rootPath, rootOutput);
|
|
96
|
+
console.log(` -> ${rootPath} (root entry point)`);
|
|
97
|
+
}
|
|
98
|
+
return { success: true, componentCount: compiled.length, errors: [] };
|
|
99
|
+
}
|
|
100
|
+
function resolveCustomComponents(nodes, map) {
|
|
101
|
+
for (const node of nodes) {
|
|
102
|
+
if (node.kind === 'custom_component') {
|
|
103
|
+
const target = map.get(node.name);
|
|
104
|
+
if (target) {
|
|
105
|
+
node.stateCount = target.stateCount;
|
|
106
|
+
node.bufferCount = target.bufferCount;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (node.kind === 'conditional') {
|
|
110
|
+
resolveCustomComponents(node.body, map);
|
|
111
|
+
if (node.elseBody)
|
|
112
|
+
resolveCustomComponents(node.elseBody, map);
|
|
113
|
+
}
|
|
114
|
+
else if (node.kind === 'list_map') {
|
|
115
|
+
resolveCustomComponents(node.body, map);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function collectEmbedImages(nodes) {
|
|
120
|
+
const images = [];
|
|
121
|
+
for (const node of nodes) {
|
|
122
|
+
if (node.kind === 'image' && node.embed && node.embedKey) {
|
|
123
|
+
images.push(node);
|
|
124
|
+
}
|
|
125
|
+
else if (node.kind === 'conditional') {
|
|
126
|
+
images.push(...collectEmbedImages(node.body));
|
|
127
|
+
if (node.elseBody)
|
|
128
|
+
images.push(...collectEmbedImages(node.elseBody));
|
|
129
|
+
}
|
|
130
|
+
else if (node.kind === 'list_map') {
|
|
131
|
+
images.push(...collectEmbedImages(node.body));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return images;
|
|
135
|
+
}
|
|
136
|
+
function generateEmbedHeaders(images, sourceDir, outputDir) {
|
|
137
|
+
for (const img of images) {
|
|
138
|
+
if (!img.embedKey)
|
|
139
|
+
continue;
|
|
140
|
+
const rawSrc = img.src.replace(/^"|"$/g, '');
|
|
141
|
+
const imagePath = path.resolve(sourceDir, rawSrc);
|
|
142
|
+
const headerPath = path.join(outputDir, `${img.embedKey}.embed.h`);
|
|
143
|
+
// Mtime caching: skip if header exists and is newer than image
|
|
144
|
+
if (fs.existsSync(headerPath) && fs.existsSync(imagePath)) {
|
|
145
|
+
const imgStat = fs.statSync(imagePath);
|
|
146
|
+
const hdrStat = fs.statSync(headerPath);
|
|
147
|
+
if (hdrStat.mtimeMs >= imgStat.mtimeMs) {
|
|
148
|
+
continue; // Header is up to date
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!fs.existsSync(imagePath)) {
|
|
152
|
+
console.warn(` warning: embedded image not found: ${imagePath}`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const imageData = fs.readFileSync(imagePath);
|
|
156
|
+
const bytes = Array.from(imageData)
|
|
157
|
+
.map(b => `0x${b.toString(16).padStart(2, '0')}`)
|
|
158
|
+
.join(', ');
|
|
159
|
+
const header = [
|
|
160
|
+
`// Generated from ${rawSrc} by imxc`,
|
|
161
|
+
`#pragma once`,
|
|
162
|
+
`static const unsigned char ${img.embedKey}_data[] = { ${bytes} };`,
|
|
163
|
+
`static const unsigned int ${img.embedKey}_size = ${imageData.length};`,
|
|
164
|
+
'',
|
|
165
|
+
].join('\n');
|
|
166
|
+
fs.writeFileSync(headerPath, header);
|
|
167
|
+
console.log(` ${rawSrc} -> ${headerPath} (embed)`);
|
|
168
|
+
}
|
|
169
|
+
}
|
package/dist/components.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
export const HOST_COMPONENTS = {
|
|
2
2
|
Window: {
|
|
3
|
-
props: {
|
|
3
|
+
props: {
|
|
4
|
+
title: { type: 'string', required: true },
|
|
5
|
+
open: { type: 'boolean', required: false },
|
|
6
|
+
onClose: { type: 'callback', required: false },
|
|
7
|
+
noTitleBar: { type: 'boolean', required: false },
|
|
8
|
+
noResize: { type: 'boolean', required: false },
|
|
9
|
+
noMove: { type: 'boolean', required: false },
|
|
10
|
+
noCollapse: { type: 'boolean', required: false },
|
|
11
|
+
noDocking: { type: 'boolean', required: false },
|
|
12
|
+
noScrollbar: { type: 'boolean', required: false },
|
|
13
|
+
style: { type: 'style', required: false },
|
|
14
|
+
},
|
|
4
15
|
hasChildren: true, isContainer: true,
|
|
5
16
|
},
|
|
6
17
|
View: {
|
|
@@ -202,6 +213,243 @@ export const HOST_COMPONENTS = {
|
|
|
202
213
|
},
|
|
203
214
|
hasChildren: false, isContainer: false,
|
|
204
215
|
},
|
|
216
|
+
BulletText: {
|
|
217
|
+
props: { style: { type: 'style', required: false } },
|
|
218
|
+
hasChildren: true, isContainer: false,
|
|
219
|
+
},
|
|
220
|
+
LabelText: {
|
|
221
|
+
props: {
|
|
222
|
+
label: { type: 'string', required: true },
|
|
223
|
+
value: { type: 'string', required: true },
|
|
224
|
+
},
|
|
225
|
+
hasChildren: false, isContainer: false,
|
|
226
|
+
},
|
|
227
|
+
Selectable: {
|
|
228
|
+
props: {
|
|
229
|
+
label: { type: 'string', required: true },
|
|
230
|
+
selected: { type: 'boolean', required: false },
|
|
231
|
+
onSelect: { type: 'callback', required: false },
|
|
232
|
+
style: { type: 'style', required: false },
|
|
233
|
+
},
|
|
234
|
+
hasChildren: false, isContainer: false,
|
|
235
|
+
},
|
|
236
|
+
Radio: {
|
|
237
|
+
props: {
|
|
238
|
+
label: { type: 'string', required: true },
|
|
239
|
+
value: { type: 'number', required: true },
|
|
240
|
+
index: { type: 'number', required: true },
|
|
241
|
+
onChange: { type: 'callback', required: false },
|
|
242
|
+
style: { type: 'style', required: false },
|
|
243
|
+
},
|
|
244
|
+
hasChildren: false, isContainer: false,
|
|
245
|
+
},
|
|
246
|
+
InputTextMultiline: {
|
|
247
|
+
props: {
|
|
248
|
+
label: { type: 'string', required: true },
|
|
249
|
+
value: { type: 'string', required: true },
|
|
250
|
+
style: { type: 'style', required: false },
|
|
251
|
+
},
|
|
252
|
+
hasChildren: false, isContainer: false,
|
|
253
|
+
},
|
|
254
|
+
ColorPicker: {
|
|
255
|
+
props: {
|
|
256
|
+
label: { type: 'string', required: true },
|
|
257
|
+
value: { type: 'string', required: true },
|
|
258
|
+
style: { type: 'style', required: false },
|
|
259
|
+
},
|
|
260
|
+
hasChildren: false, isContainer: false,
|
|
261
|
+
},
|
|
262
|
+
PlotLines: {
|
|
263
|
+
props: {
|
|
264
|
+
label: { type: 'string', required: true },
|
|
265
|
+
values: { type: 'string', required: true },
|
|
266
|
+
overlay: { type: 'string', required: false },
|
|
267
|
+
style: { type: 'style', required: false },
|
|
268
|
+
},
|
|
269
|
+
hasChildren: false, isContainer: false,
|
|
270
|
+
},
|
|
271
|
+
PlotHistogram: {
|
|
272
|
+
props: {
|
|
273
|
+
label: { type: 'string', required: true },
|
|
274
|
+
values: { type: 'string', required: true },
|
|
275
|
+
overlay: { type: 'string', required: false },
|
|
276
|
+
style: { type: 'style', required: false },
|
|
277
|
+
},
|
|
278
|
+
hasChildren: false, isContainer: false,
|
|
279
|
+
},
|
|
280
|
+
Modal: {
|
|
281
|
+
props: {
|
|
282
|
+
title: { type: 'string', required: true },
|
|
283
|
+
open: { type: 'boolean', required: false },
|
|
284
|
+
onClose: { type: 'callback', required: false },
|
|
285
|
+
style: { type: 'style', required: false },
|
|
286
|
+
},
|
|
287
|
+
hasChildren: true, isContainer: true,
|
|
288
|
+
},
|
|
289
|
+
Image: {
|
|
290
|
+
props: {
|
|
291
|
+
src: { type: 'string', required: true },
|
|
292
|
+
embed: { type: 'boolean', required: false },
|
|
293
|
+
width: { type: 'number', required: false },
|
|
294
|
+
height: { type: 'number', required: false },
|
|
295
|
+
},
|
|
296
|
+
hasChildren: false, isContainer: false,
|
|
297
|
+
},
|
|
298
|
+
DockLayout: {
|
|
299
|
+
props: {},
|
|
300
|
+
hasChildren: true, isContainer: true,
|
|
301
|
+
},
|
|
302
|
+
DockSplit: {
|
|
303
|
+
props: { direction: { type: 'string', required: true }, size: { type: 'number', required: true } },
|
|
304
|
+
hasChildren: true, isContainer: true,
|
|
305
|
+
},
|
|
306
|
+
DockPanel: {
|
|
307
|
+
props: {},
|
|
308
|
+
hasChildren: true, isContainer: true,
|
|
309
|
+
},
|
|
310
|
+
Theme: {
|
|
311
|
+
props: {
|
|
312
|
+
preset: { type: 'string', required: true },
|
|
313
|
+
accentColor: { type: 'style', required: false },
|
|
314
|
+
backgroundColor: { type: 'style', required: false },
|
|
315
|
+
textColor: { type: 'style', required: false },
|
|
316
|
+
borderColor: { type: 'style', required: false },
|
|
317
|
+
surfaceColor: { type: 'style', required: false },
|
|
318
|
+
rounding: { type: 'number', required: false },
|
|
319
|
+
borderSize: { type: 'number', required: false },
|
|
320
|
+
spacing: { type: 'number', required: false },
|
|
321
|
+
},
|
|
322
|
+
hasChildren: true, isContainer: true,
|
|
323
|
+
},
|
|
324
|
+
Group: {
|
|
325
|
+
props: {
|
|
326
|
+
style: { type: 'style', required: false },
|
|
327
|
+
},
|
|
328
|
+
hasChildren: true, isContainer: true,
|
|
329
|
+
},
|
|
330
|
+
ID: {
|
|
331
|
+
props: {
|
|
332
|
+
scope: { type: 'string', required: true },
|
|
333
|
+
},
|
|
334
|
+
hasChildren: true, isContainer: true,
|
|
335
|
+
},
|
|
336
|
+
StyleColor: {
|
|
337
|
+
props: {
|
|
338
|
+
text: { type: 'style', required: false },
|
|
339
|
+
textDisabled: { type: 'style', required: false },
|
|
340
|
+
windowBg: { type: 'style', required: false },
|
|
341
|
+
frameBg: { type: 'style', required: false },
|
|
342
|
+
frameBgHovered: { type: 'style', required: false },
|
|
343
|
+
frameBgActive: { type: 'style', required: false },
|
|
344
|
+
titleBg: { type: 'style', required: false },
|
|
345
|
+
titleBgActive: { type: 'style', required: false },
|
|
346
|
+
button: { type: 'style', required: false },
|
|
347
|
+
buttonHovered: { type: 'style', required: false },
|
|
348
|
+
buttonActive: { type: 'style', required: false },
|
|
349
|
+
header: { type: 'style', required: false },
|
|
350
|
+
headerHovered: { type: 'style', required: false },
|
|
351
|
+
headerActive: { type: 'style', required: false },
|
|
352
|
+
separator: { type: 'style', required: false },
|
|
353
|
+
checkMark: { type: 'style', required: false },
|
|
354
|
+
sliderGrab: { type: 'style', required: false },
|
|
355
|
+
border: { type: 'style', required: false },
|
|
356
|
+
popupBg: { type: 'style', required: false },
|
|
357
|
+
tab: { type: 'style', required: false },
|
|
358
|
+
},
|
|
359
|
+
hasChildren: true, isContainer: true,
|
|
360
|
+
},
|
|
361
|
+
StyleVar: {
|
|
362
|
+
props: {
|
|
363
|
+
alpha: { type: 'number', required: false },
|
|
364
|
+
windowPadding: { type: 'style', required: false },
|
|
365
|
+
windowRounding: { type: 'number', required: false },
|
|
366
|
+
framePadding: { type: 'style', required: false },
|
|
367
|
+
frameRounding: { type: 'number', required: false },
|
|
368
|
+
frameBorderSize: { type: 'number', required: false },
|
|
369
|
+
itemSpacing: { type: 'style', required: false },
|
|
370
|
+
itemInnerSpacing: { type: 'style', required: false },
|
|
371
|
+
indentSpacing: { type: 'number', required: false },
|
|
372
|
+
cellPadding: { type: 'style', required: false },
|
|
373
|
+
tabRounding: { type: 'number', required: false },
|
|
374
|
+
},
|
|
375
|
+
hasChildren: true, isContainer: true,
|
|
376
|
+
},
|
|
377
|
+
DragDropSource: {
|
|
378
|
+
props: {
|
|
379
|
+
type: { type: 'string', required: true },
|
|
380
|
+
payload: { type: 'number', required: true },
|
|
381
|
+
},
|
|
382
|
+
hasChildren: true, isContainer: true,
|
|
383
|
+
},
|
|
384
|
+
DragDropTarget: {
|
|
385
|
+
props: {
|
|
386
|
+
type: { type: 'string', required: true },
|
|
387
|
+
onDrop: { type: 'callback', required: true },
|
|
388
|
+
},
|
|
389
|
+
hasChildren: true, isContainer: true,
|
|
390
|
+
},
|
|
391
|
+
Canvas: {
|
|
392
|
+
props: {
|
|
393
|
+
width: { type: 'number', required: true },
|
|
394
|
+
height: { type: 'number', required: true },
|
|
395
|
+
style: { type: 'style', required: false },
|
|
396
|
+
},
|
|
397
|
+
hasChildren: true, isContainer: true,
|
|
398
|
+
},
|
|
399
|
+
DrawLine: {
|
|
400
|
+
props: {
|
|
401
|
+
p1: { type: 'style', required: true },
|
|
402
|
+
p2: { type: 'style', required: true },
|
|
403
|
+
color: { type: 'style', required: true },
|
|
404
|
+
thickness: { type: 'number', required: false },
|
|
405
|
+
},
|
|
406
|
+
hasChildren: false, isContainer: false,
|
|
407
|
+
},
|
|
408
|
+
DrawRect: {
|
|
409
|
+
props: {
|
|
410
|
+
min: { type: 'style', required: true },
|
|
411
|
+
max: { type: 'style', required: true },
|
|
412
|
+
color: { type: 'style', required: true },
|
|
413
|
+
filled: { type: 'boolean', required: false },
|
|
414
|
+
thickness: { type: 'number', required: false },
|
|
415
|
+
rounding: { type: 'number', required: false },
|
|
416
|
+
},
|
|
417
|
+
hasChildren: false, isContainer: false,
|
|
418
|
+
},
|
|
419
|
+
DrawCircle: {
|
|
420
|
+
props: {
|
|
421
|
+
center: { type: 'style', required: true },
|
|
422
|
+
radius: { type: 'number', required: true },
|
|
423
|
+
color: { type: 'style', required: true },
|
|
424
|
+
filled: { type: 'boolean', required: false },
|
|
425
|
+
thickness: { type: 'number', required: false },
|
|
426
|
+
},
|
|
427
|
+
hasChildren: false, isContainer: false,
|
|
428
|
+
},
|
|
429
|
+
DrawText: {
|
|
430
|
+
props: {
|
|
431
|
+
pos: { type: 'style', required: true },
|
|
432
|
+
text: { type: 'string', required: true },
|
|
433
|
+
color: { type: 'style', required: true },
|
|
434
|
+
},
|
|
435
|
+
hasChildren: false, isContainer: false,
|
|
436
|
+
},
|
|
437
|
+
Disabled: {
|
|
438
|
+
props: {
|
|
439
|
+
disabled: { type: 'boolean', required: false },
|
|
440
|
+
},
|
|
441
|
+
hasChildren: true, isContainer: true,
|
|
442
|
+
},
|
|
443
|
+
Child: {
|
|
444
|
+
props: {
|
|
445
|
+
id: { type: 'string', required: true },
|
|
446
|
+
width: { type: 'number', required: false },
|
|
447
|
+
height: { type: 'number', required: false },
|
|
448
|
+
border: { type: 'boolean', required: false },
|
|
449
|
+
style: { type: 'style', required: false },
|
|
450
|
+
},
|
|
451
|
+
hasChildren: true, isContainer: true,
|
|
452
|
+
},
|
|
205
453
|
};
|
|
206
454
|
export function isHostComponent(name) {
|
|
207
455
|
return name in HOST_COMPONENTS;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a diagnostic error with source context, caret underline, and color.
|
|
3
|
+
*/
|
|
4
|
+
export function formatDiagnostic(error, source) {
|
|
5
|
+
const lines = source.split('\n');
|
|
6
|
+
const lineIdx = error.line - 1;
|
|
7
|
+
// Header: file:line:col - error: message
|
|
8
|
+
const header = `${error.file}:${error.line}:${error.col} - error: ${error.message}`;
|
|
9
|
+
if (lineIdx < 0 || lineIdx >= lines.length) {
|
|
10
|
+
return header;
|
|
11
|
+
}
|
|
12
|
+
const sourceLine = lines[lineIdx];
|
|
13
|
+
const lineNum = String(error.line);
|
|
14
|
+
const gutter = lineNum.length + 1;
|
|
15
|
+
// Source line with gutter
|
|
16
|
+
const sourceDisplay = `${' '.repeat(gutter)}|${sourceLine}`;
|
|
17
|
+
// Caret underline: point from error column to end of meaningful content
|
|
18
|
+
const col = error.col - 1;
|
|
19
|
+
const contentEnd = sourceLine.trimEnd().length;
|
|
20
|
+
const caretLen = Math.max(1, contentEnd - col);
|
|
21
|
+
const caretDisplay = `${' '.repeat(gutter)}|${' '.repeat(col)}${'^'.repeat(caretLen)}`;
|
|
22
|
+
return `${header}\n\n${lineNum} ${sourceDisplay}\n${' '.repeat(lineNum.length)} ${caretDisplay}`;
|
|
23
|
+
}
|
package/dist/emitter.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ import type { IRComponent } from './ir.js';
|
|
|
3
3
|
* Emit a .gen.h header for a component that has props.
|
|
4
4
|
* Contains the props struct and function forward declaration.
|
|
5
5
|
*/
|
|
6
|
-
export declare function emitComponentHeader(comp: IRComponent): string;
|
|
6
|
+
export declare function emitComponentHeader(comp: IRComponent, sourceFile?: string): string;
|
|
7
7
|
export interface ImportInfo {
|
|
8
8
|
name: string;
|
|
9
9
|
headerFile: string;
|
|
10
10
|
}
|
|
11
|
-
export declare function emitComponent(comp: IRComponent, imports?: ImportInfo[]): string;
|
|
12
|
-
export declare function emitRoot(rootName: string, stateCount: number, bufferCount: number): string;
|
|
11
|
+
export declare function emitComponent(comp: IRComponent, imports?: ImportInfo[], sourceFile?: string): string;
|
|
12
|
+
export declare function emitRoot(rootName: string, stateCount: number, bufferCount: number, sourceFile?: string): string;
|