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
package/dist/lowering.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { HOST_COMPONENTS, isHostComponent } from './components.js';
|
|
3
|
+
function getLoc(node, ctx) {
|
|
4
|
+
const { line } = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
5
|
+
return { file: ctx.sourceFile.fileName, line: line + 1 };
|
|
6
|
+
}
|
|
3
7
|
export function lowerComponent(parsed, validation) {
|
|
4
8
|
const func = parsed.component;
|
|
5
9
|
const name = func.name.text;
|
|
@@ -43,6 +47,7 @@ export function lowerComponent(parsed, validation) {
|
|
|
43
47
|
propsParam,
|
|
44
48
|
bufferIndex: 0,
|
|
45
49
|
sourceFile: parsed.sourceFile,
|
|
50
|
+
customComponents: validation.customComponents,
|
|
46
51
|
};
|
|
47
52
|
// Find return statement and lower its JSX
|
|
48
53
|
const body = [];
|
|
@@ -137,6 +142,9 @@ export function exprToCpp(node, ctx) {
|
|
|
137
142
|
if (ctx.stateVars.has(name)) {
|
|
138
143
|
return `${name}.get()`;
|
|
139
144
|
}
|
|
145
|
+
if (name === 'resetLayout') {
|
|
146
|
+
return 'imx_reset_layout';
|
|
147
|
+
}
|
|
140
148
|
return name;
|
|
141
149
|
}
|
|
142
150
|
// Property access (e.g., props.name)
|
|
@@ -153,7 +161,12 @@ export function exprToCpp(node, ctx) {
|
|
|
153
161
|
if (ts.isBinaryExpression(node)) {
|
|
154
162
|
const left = exprToCpp(node.left, ctx);
|
|
155
163
|
const right = exprToCpp(node.right, ctx);
|
|
156
|
-
|
|
164
|
+
let op = node.operatorToken.getText();
|
|
165
|
+
// Map TypeScript operators to C++ equivalents
|
|
166
|
+
if (op === '===')
|
|
167
|
+
op = '==';
|
|
168
|
+
else if (op === '!==')
|
|
169
|
+
op = '!=';
|
|
157
170
|
return `${left} ${op} ${right}`;
|
|
158
171
|
}
|
|
159
172
|
// Prefix unary expression (e.g., !show)
|
|
@@ -243,8 +256,13 @@ function extractActionStatements(expr, ctx) {
|
|
|
243
256
|
return [exprToCpp(expr.body, ctx) + ';'];
|
|
244
257
|
}
|
|
245
258
|
}
|
|
246
|
-
// If not an arrow function,
|
|
247
|
-
|
|
259
|
+
// If not an arrow function, call it
|
|
260
|
+
const code = exprToCpp(expr, ctx);
|
|
261
|
+
// Bare identifier (not already a call) needs () to invoke
|
|
262
|
+
if (ts.isIdentifier(expr)) {
|
|
263
|
+
return [code + '();'];
|
|
264
|
+
}
|
|
265
|
+
return [code + ';'];
|
|
248
266
|
}
|
|
249
267
|
/**
|
|
250
268
|
* Lower a JSX expression (possibly wrapped in parenthesized expr) into IR nodes.
|
|
@@ -273,7 +291,7 @@ function lowerJsxExpression(node, body, ctx) {
|
|
|
273
291
|
const condition = exprToCpp(node.left, ctx);
|
|
274
292
|
const condBody = [];
|
|
275
293
|
lowerJsxExpression(node.right, condBody, ctx);
|
|
276
|
-
body.push({ kind: 'conditional', condition, body: condBody });
|
|
294
|
+
body.push({ kind: 'conditional', condition, body: condBody, loc: getLoc(node, ctx) });
|
|
277
295
|
return;
|
|
278
296
|
}
|
|
279
297
|
// Ternary: condition ? <A/> : <B/>
|
|
@@ -283,12 +301,12 @@ function lowerJsxExpression(node, body, ctx) {
|
|
|
283
301
|
const elseBody = [];
|
|
284
302
|
lowerJsxExpression(node.whenTrue, thenBody, ctx);
|
|
285
303
|
lowerJsxExpression(node.whenFalse, elseBody, ctx);
|
|
286
|
-
body.push({ kind: 'conditional', condition, body: thenBody, elseBody });
|
|
304
|
+
body.push({ kind: 'conditional', condition, body: thenBody, elseBody, loc: getLoc(node, ctx) });
|
|
287
305
|
return;
|
|
288
306
|
}
|
|
289
307
|
// items.map(item => <Comp/>)
|
|
290
308
|
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'map') {
|
|
291
|
-
lowerListMap(node, body, ctx);
|
|
309
|
+
lowerListMap(node, body, ctx, getLoc(node, ctx));
|
|
292
310
|
return;
|
|
293
311
|
}
|
|
294
312
|
// JsxExpression wrapper
|
|
@@ -303,7 +321,15 @@ function lowerJsxElement(node, body, ctx) {
|
|
|
303
321
|
return;
|
|
304
322
|
const name = tagName.text;
|
|
305
323
|
if (name === 'Text') {
|
|
306
|
-
lowerTextElement(node, body, ctx);
|
|
324
|
+
lowerTextElement(node, body, ctx, getLoc(node, ctx));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (name === 'BulletText') {
|
|
328
|
+
lowerBulletTextElement(node, body, ctx, getLoc(node, ctx));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (name === 'DockLayout') {
|
|
332
|
+
body.push(lowerDockLayout(node, ctx));
|
|
307
333
|
return;
|
|
308
334
|
}
|
|
309
335
|
if (isHostComponent(name)) {
|
|
@@ -311,7 +337,48 @@ function lowerJsxElement(node, body, ctx) {
|
|
|
311
337
|
const attrs = getAttributes(node.openingElement.attributes, ctx);
|
|
312
338
|
if (def.isContainer) {
|
|
313
339
|
const containerTag = name;
|
|
314
|
-
|
|
340
|
+
// Special handling for DragDropTarget — lower onDrop callback with type info
|
|
341
|
+
if (name === 'DragDropTarget') {
|
|
342
|
+
const rawAttrs = getRawAttributes(node.openingElement.attributes);
|
|
343
|
+
const props = {};
|
|
344
|
+
for (const [attrName, expr] of rawAttrs) {
|
|
345
|
+
if (attrName === 'onDrop' && expr && (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr))) {
|
|
346
|
+
const params = expr.parameters;
|
|
347
|
+
if (params.length > 0) {
|
|
348
|
+
const param = params[0];
|
|
349
|
+
const paramName = ts.isIdentifier(param.name) ? param.name.text : '_p';
|
|
350
|
+
let cppType = 'float';
|
|
351
|
+
if (param.type) {
|
|
352
|
+
const typeText = param.type.getText();
|
|
353
|
+
if (typeText === 'number')
|
|
354
|
+
cppType = 'float';
|
|
355
|
+
else if (typeText === 'boolean')
|
|
356
|
+
cppType = 'bool';
|
|
357
|
+
else if (typeText === 'string')
|
|
358
|
+
cppType = 'std::string';
|
|
359
|
+
}
|
|
360
|
+
const bodyCode = ts.isBlock(expr.body)
|
|
361
|
+
? expr.body.statements.map(s => stmtToCpp(s, ctx)).join(' ')
|
|
362
|
+
: exprToCpp(expr.body, ctx) + ';';
|
|
363
|
+
// Store as structured string: type|paramName|body
|
|
364
|
+
props[attrName] = `${cppType}|${paramName}|${bodyCode}`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (expr) {
|
|
368
|
+
props[attrName] = exprToCpp(expr, ctx);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
props[attrName] = 'true';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
body.push({ kind: 'begin_container', tag: containerTag, props, loc: getLoc(node, ctx) });
|
|
375
|
+
for (const child of node.children) {
|
|
376
|
+
lowerJsxChild(child, body, ctx);
|
|
377
|
+
}
|
|
378
|
+
body.push({ kind: 'end_container', tag: containerTag });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
body.push({ kind: 'begin_container', tag: containerTag, props: attrs, loc: getLoc(node, ctx) });
|
|
315
382
|
for (const child of node.children) {
|
|
316
383
|
lowerJsxChild(child, body, ctx);
|
|
317
384
|
}
|
|
@@ -321,7 +388,7 @@ function lowerJsxElement(node, body, ctx) {
|
|
|
321
388
|
// Popup
|
|
322
389
|
if (name === 'Popup') {
|
|
323
390
|
const id = attrs['id'] ?? '';
|
|
324
|
-
body.push({ kind: 'begin_popup', id });
|
|
391
|
+
body.push({ kind: 'begin_popup', id, loc: getLoc(node, ctx) });
|
|
325
392
|
for (const child of node.children) {
|
|
326
393
|
lowerJsxChild(child, body, ctx);
|
|
327
394
|
}
|
|
@@ -331,7 +398,12 @@ function lowerJsxElement(node, body, ctx) {
|
|
|
331
398
|
}
|
|
332
399
|
// Custom component with children - treat as container-like (not common but handle gracefully)
|
|
333
400
|
if (!isHostComponent(name)) {
|
|
334
|
-
|
|
401
|
+
if (ctx.customComponents && ctx.customComponents.has(name)) {
|
|
402
|
+
lowerCustomComponent(name, node.openingElement.attributes, body, ctx, getLoc(node, ctx));
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
lowerNativeWidget(name, node.openingElement.attributes, body, ctx, getLoc(node, ctx));
|
|
406
|
+
}
|
|
335
407
|
return;
|
|
336
408
|
}
|
|
337
409
|
}
|
|
@@ -341,75 +413,143 @@ function lowerJsxSelfClosing(node, body, ctx) {
|
|
|
341
413
|
return;
|
|
342
414
|
const name = tagName.text;
|
|
343
415
|
if (!isHostComponent(name)) {
|
|
344
|
-
|
|
416
|
+
if (ctx.customComponents && ctx.customComponents.has(name)) {
|
|
417
|
+
lowerCustomComponent(name, node.attributes, body, ctx, getLoc(node, ctx));
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
lowerNativeWidget(name, node.attributes, body, ctx, getLoc(node, ctx));
|
|
421
|
+
}
|
|
345
422
|
return;
|
|
346
423
|
}
|
|
347
424
|
const attrs = getAttributes(node.attributes, ctx);
|
|
348
425
|
const rawAttrs = getRawAttributes(node.attributes);
|
|
426
|
+
const loc = getLoc(node, ctx);
|
|
349
427
|
switch (name) {
|
|
350
428
|
case 'Button':
|
|
351
|
-
lowerButton(attrs, rawAttrs, body, ctx);
|
|
429
|
+
lowerButton(attrs, rawAttrs, body, ctx, loc);
|
|
352
430
|
break;
|
|
353
431
|
case 'TextInput':
|
|
354
|
-
lowerTextInput(attrs, rawAttrs, body, ctx);
|
|
432
|
+
lowerTextInput(attrs, rawAttrs, body, ctx, loc);
|
|
355
433
|
break;
|
|
356
434
|
case 'Checkbox':
|
|
357
|
-
lowerCheckbox(attrs, rawAttrs, body, ctx);
|
|
435
|
+
lowerCheckbox(attrs, rawAttrs, body, ctx, loc);
|
|
358
436
|
break;
|
|
359
437
|
case 'MenuItem':
|
|
360
|
-
lowerMenuItem(attrs, rawAttrs, body, ctx);
|
|
438
|
+
lowerMenuItem(attrs, rawAttrs, body, ctx, loc);
|
|
361
439
|
break;
|
|
362
440
|
case 'SliderFloat':
|
|
363
|
-
lowerSliderFloat(attrs, rawAttrs, body, ctx);
|
|
441
|
+
lowerSliderFloat(attrs, rawAttrs, body, ctx, loc);
|
|
364
442
|
break;
|
|
365
443
|
case 'SliderInt':
|
|
366
|
-
lowerSliderInt(attrs, rawAttrs, body, ctx);
|
|
444
|
+
lowerSliderInt(attrs, rawAttrs, body, ctx, loc);
|
|
367
445
|
break;
|
|
368
446
|
case 'DragFloat':
|
|
369
|
-
lowerDragFloat(attrs, rawAttrs, body, ctx);
|
|
447
|
+
lowerDragFloat(attrs, rawAttrs, body, ctx, loc);
|
|
370
448
|
break;
|
|
371
449
|
case 'DragInt':
|
|
372
|
-
lowerDragInt(attrs, rawAttrs, body, ctx);
|
|
450
|
+
lowerDragInt(attrs, rawAttrs, body, ctx, loc);
|
|
373
451
|
break;
|
|
374
452
|
case 'Combo':
|
|
375
|
-
lowerCombo(attrs, rawAttrs, body, ctx);
|
|
453
|
+
lowerCombo(attrs, rawAttrs, body, ctx, loc);
|
|
376
454
|
break;
|
|
377
455
|
case 'InputInt':
|
|
378
|
-
lowerInputInt(attrs, rawAttrs, body, ctx);
|
|
456
|
+
lowerInputInt(attrs, rawAttrs, body, ctx, loc);
|
|
379
457
|
break;
|
|
380
458
|
case 'InputFloat':
|
|
381
|
-
lowerInputFloat(attrs, rawAttrs, body, ctx);
|
|
459
|
+
lowerInputFloat(attrs, rawAttrs, body, ctx, loc);
|
|
382
460
|
break;
|
|
383
461
|
case 'ColorEdit':
|
|
384
|
-
lowerColorEdit(attrs, rawAttrs, body, ctx);
|
|
462
|
+
lowerColorEdit(attrs, rawAttrs, body, ctx, loc);
|
|
385
463
|
break;
|
|
386
464
|
case 'ListBox':
|
|
387
|
-
lowerListBox(attrs, rawAttrs, body, ctx);
|
|
465
|
+
lowerListBox(attrs, rawAttrs, body, ctx, loc);
|
|
388
466
|
break;
|
|
389
467
|
case 'ProgressBar':
|
|
390
|
-
lowerProgressBar(attrs, rawAttrs, body, ctx);
|
|
468
|
+
lowerProgressBar(attrs, rawAttrs, body, ctx, loc);
|
|
391
469
|
break;
|
|
392
470
|
case 'Tooltip':
|
|
393
|
-
lowerTooltip(attrs, body, ctx);
|
|
471
|
+
lowerTooltip(attrs, body, ctx, loc);
|
|
394
472
|
break;
|
|
395
473
|
case 'Separator':
|
|
396
|
-
body.push({ kind: 'separator' });
|
|
474
|
+
body.push({ kind: 'separator', loc });
|
|
397
475
|
break;
|
|
398
476
|
case 'Text':
|
|
399
477
|
// Self-closing <Text /> - empty text
|
|
400
|
-
body.push({ kind: 'text', format: '', args: [] });
|
|
478
|
+
body.push({ kind: 'text', format: '', args: [], loc });
|
|
479
|
+
break;
|
|
480
|
+
case 'BulletText':
|
|
481
|
+
// Self-closing <BulletText /> - empty bullet
|
|
482
|
+
body.push({ kind: 'bullet_text', format: '', args: [], loc });
|
|
483
|
+
break;
|
|
484
|
+
case 'LabelText':
|
|
485
|
+
lowerLabelText(attrs, body, ctx, loc);
|
|
486
|
+
break;
|
|
487
|
+
case 'Selectable':
|
|
488
|
+
lowerSelectable(attrs, rawAttrs, body, ctx, loc);
|
|
489
|
+
break;
|
|
490
|
+
case 'Radio':
|
|
491
|
+
lowerRadio(attrs, rawAttrs, body, ctx, loc);
|
|
492
|
+
break;
|
|
493
|
+
case 'InputTextMultiline':
|
|
494
|
+
lowerInputTextMultiline(attrs, rawAttrs, body, ctx, loc);
|
|
495
|
+
break;
|
|
496
|
+
case 'ColorPicker':
|
|
497
|
+
lowerColorPicker(attrs, rawAttrs, body, ctx, loc);
|
|
498
|
+
break;
|
|
499
|
+
case 'PlotLines':
|
|
500
|
+
lowerPlotLines(attrs, body, ctx, loc);
|
|
401
501
|
break;
|
|
502
|
+
case 'PlotHistogram':
|
|
503
|
+
lowerPlotHistogram(attrs, body, ctx, loc);
|
|
504
|
+
break;
|
|
505
|
+
case 'Image':
|
|
506
|
+
lowerImage(attrs, body, ctx, loc);
|
|
507
|
+
break;
|
|
508
|
+
case 'DrawLine': {
|
|
509
|
+
const p1 = attrs['p1'] ?? '0, 0';
|
|
510
|
+
const p2 = attrs['p2'] ?? '0, 0';
|
|
511
|
+
const color = attrs['color'] ?? '1, 1, 1, 1';
|
|
512
|
+
const thickness = attrs['thickness'] ?? '1.0';
|
|
513
|
+
body.push({ kind: 'draw_line', p1, p2, color, thickness, loc });
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
case 'DrawRect': {
|
|
517
|
+
const min = attrs['min'] ?? '0, 0';
|
|
518
|
+
const max = attrs['max'] ?? '0, 0';
|
|
519
|
+
const color = attrs['color'] ?? '1, 1, 1, 1';
|
|
520
|
+
const filled = attrs['filled'] ?? 'false';
|
|
521
|
+
const thickness = attrs['thickness'] ?? '1.0';
|
|
522
|
+
const rounding = attrs['rounding'] ?? '0.0';
|
|
523
|
+
body.push({ kind: 'draw_rect', min, max, color, filled, thickness, rounding, loc });
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case 'DrawCircle': {
|
|
527
|
+
const center = attrs['center'] ?? '0, 0';
|
|
528
|
+
const radius = attrs['radius'] ?? '0';
|
|
529
|
+
const color = attrs['color'] ?? '1, 1, 1, 1';
|
|
530
|
+
const filled = attrs['filled'] ?? 'false';
|
|
531
|
+
const thickness = attrs['thickness'] ?? '1.0';
|
|
532
|
+
body.push({ kind: 'draw_circle', center, radius, color, filled, thickness, loc });
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
case 'DrawText': {
|
|
536
|
+
const pos = attrs['pos'] ?? '0, 0';
|
|
537
|
+
const text = attrs['text'] ?? '""';
|
|
538
|
+
const color = attrs['color'] ?? '1, 1, 1, 1';
|
|
539
|
+
body.push({ kind: 'draw_text', pos, text, color, loc });
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
402
542
|
default:
|
|
403
543
|
// Container self-closing (e.g., <Window title="X"/>)
|
|
404
544
|
if (HOST_COMPONENTS[name]?.isContainer) {
|
|
405
545
|
const containerTag = name;
|
|
406
|
-
body.push({ kind: 'begin_container', tag: containerTag, props: attrs });
|
|
546
|
+
body.push({ kind: 'begin_container', tag: containerTag, props: attrs, loc });
|
|
407
547
|
body.push({ kind: 'end_container', tag: containerTag });
|
|
408
548
|
}
|
|
409
549
|
break;
|
|
410
550
|
}
|
|
411
551
|
}
|
|
412
|
-
function lowerButton(attrs, rawAttrs, body, ctx) {
|
|
552
|
+
function lowerButton(attrs, rawAttrs, body, ctx, loc) {
|
|
413
553
|
const title = attrs['title'] ?? '""';
|
|
414
554
|
const onPressExpr = rawAttrs.get('onPress');
|
|
415
555
|
let action = [];
|
|
@@ -417,9 +557,9 @@ function lowerButton(attrs, rawAttrs, body, ctx) {
|
|
|
417
557
|
action = extractActionStatements(onPressExpr, ctx);
|
|
418
558
|
}
|
|
419
559
|
const style = attrs['style'];
|
|
420
|
-
body.push({ kind: 'button', title, action, style });
|
|
560
|
+
body.push({ kind: 'button', title, action, style, loc });
|
|
421
561
|
}
|
|
422
|
-
function lowerTextInput(attrs, rawAttrs, body, ctx) {
|
|
562
|
+
function lowerTextInput(attrs, rawAttrs, body, ctx, loc) {
|
|
423
563
|
const label = attrs['label'] ?? '""';
|
|
424
564
|
const bufferIndex = ctx.bufferIndex++;
|
|
425
565
|
// Detect bound state variable from value prop
|
|
@@ -432,9 +572,9 @@ function lowerTextInput(attrs, rawAttrs, body, ctx) {
|
|
|
432
572
|
}
|
|
433
573
|
}
|
|
434
574
|
const style = attrs['style'];
|
|
435
|
-
body.push({ kind: 'text_input', label, bufferIndex, stateVar, style });
|
|
575
|
+
body.push({ kind: 'text_input', label, bufferIndex, stateVar, style, loc });
|
|
436
576
|
}
|
|
437
|
-
function lowerCheckbox(attrs, rawAttrs, body, ctx) {
|
|
577
|
+
function lowerCheckbox(attrs, rawAttrs, body, ctx, loc) {
|
|
438
578
|
const label = attrs['label'] ?? '""';
|
|
439
579
|
// Detect bound state variable from value prop
|
|
440
580
|
let stateVar = '';
|
|
@@ -467,9 +607,9 @@ function lowerCheckbox(attrs, rawAttrs, body, ctx) {
|
|
|
467
607
|
}
|
|
468
608
|
}
|
|
469
609
|
const style = attrs['style'];
|
|
470
|
-
body.push({ kind: 'checkbox', label, stateVar, valueExpr: valueExprStr, onChangeExpr: onChangeExprStr, style });
|
|
610
|
+
body.push({ kind: 'checkbox', label, stateVar, valueExpr: valueExprStr, onChangeExpr: onChangeExprStr, style, loc });
|
|
471
611
|
}
|
|
472
|
-
function lowerMenuItem(attrs, rawAttrs, body, ctx) {
|
|
612
|
+
function lowerMenuItem(attrs, rawAttrs, body, ctx, loc) {
|
|
473
613
|
const label = attrs['label'] ?? '""';
|
|
474
614
|
const shortcut = attrs['shortcut'];
|
|
475
615
|
const onPressExpr = rawAttrs.get('onPress');
|
|
@@ -477,9 +617,9 @@ function lowerMenuItem(attrs, rawAttrs, body, ctx) {
|
|
|
477
617
|
if (onPressExpr) {
|
|
478
618
|
action = extractActionStatements(onPressExpr, ctx);
|
|
479
619
|
}
|
|
480
|
-
body.push({ kind: 'menu_item', label, shortcut, action });
|
|
620
|
+
body.push({ kind: 'menu_item', label, shortcut, action, loc });
|
|
481
621
|
}
|
|
482
|
-
function lowerTextElement(node, body, ctx) {
|
|
622
|
+
function lowerTextElement(node, body, ctx, loc) {
|
|
483
623
|
let format = '';
|
|
484
624
|
const args = [];
|
|
485
625
|
for (const child of node.children) {
|
|
@@ -531,7 +671,7 @@ function lowerTextElement(node, body, ctx) {
|
|
|
531
671
|
}
|
|
532
672
|
}
|
|
533
673
|
}
|
|
534
|
-
body.push({ kind: 'text', format, args });
|
|
674
|
+
body.push({ kind: 'text', format, args, loc });
|
|
535
675
|
}
|
|
536
676
|
/**
|
|
537
677
|
* Check if an expression will produce a const char* in C++ (not std::string).
|
|
@@ -593,7 +733,7 @@ function inferExprType(expr, ctx) {
|
|
|
593
733
|
}
|
|
594
734
|
return 'int'; // default
|
|
595
735
|
}
|
|
596
|
-
function lowerListMap(node, body, ctx) {
|
|
736
|
+
function lowerListMap(node, body, ctx, loc) {
|
|
597
737
|
const propAccess = node.expression;
|
|
598
738
|
const array = exprToCpp(propAccess.expression, ctx);
|
|
599
739
|
const callback = node.arguments[0];
|
|
@@ -622,9 +762,10 @@ function lowerListMap(node, body, ctx) {
|
|
|
622
762
|
stateCount: 0,
|
|
623
763
|
bufferCount: 0,
|
|
624
764
|
body: mapBody,
|
|
765
|
+
loc,
|
|
625
766
|
});
|
|
626
767
|
}
|
|
627
|
-
function lowerCustomComponent(name, attributes, body, ctx) {
|
|
768
|
+
function lowerCustomComponent(name, attributes, body, ctx, loc) {
|
|
628
769
|
const attrs = getAttributes(attributes, ctx);
|
|
629
770
|
body.push({
|
|
630
771
|
kind: 'custom_component',
|
|
@@ -632,8 +773,111 @@ function lowerCustomComponent(name, attributes, body, ctx) {
|
|
|
632
773
|
props: attrs,
|
|
633
774
|
stateCount: 0,
|
|
634
775
|
bufferCount: 0,
|
|
776
|
+
loc,
|
|
635
777
|
});
|
|
636
778
|
}
|
|
779
|
+
function lowerNativeWidget(name, attributes, body, ctx, loc) {
|
|
780
|
+
const props = {};
|
|
781
|
+
const callbackProps = {};
|
|
782
|
+
const rawAttrs = getRawAttributes(attributes);
|
|
783
|
+
for (const [attrName, expr] of rawAttrs) {
|
|
784
|
+
if (attrName === 'key')
|
|
785
|
+
continue;
|
|
786
|
+
if (!expr) {
|
|
787
|
+
props[attrName] = 'true';
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
|
|
791
|
+
const params = expr.parameters;
|
|
792
|
+
if (params.length > 0) {
|
|
793
|
+
// Parameterized callback: (v: number) => setVol(v)
|
|
794
|
+
const param = params[0];
|
|
795
|
+
const paramName = ts.isIdentifier(param.name) ? param.name.text : '_p';
|
|
796
|
+
let cppType = 'float';
|
|
797
|
+
if (param.type) {
|
|
798
|
+
const typeText = param.type.getText();
|
|
799
|
+
if (typeText === 'number')
|
|
800
|
+
cppType = 'float';
|
|
801
|
+
else if (typeText === 'boolean')
|
|
802
|
+
cppType = 'bool';
|
|
803
|
+
else if (typeText === 'string')
|
|
804
|
+
cppType = 'std::string';
|
|
805
|
+
}
|
|
806
|
+
const bodyCode = ts.isBlock(expr.body)
|
|
807
|
+
? expr.body.statements.map(s => stmtToCpp(s, ctx)).join(' ')
|
|
808
|
+
: exprToCpp(expr.body, ctx) + ';';
|
|
809
|
+
callbackProps[attrName] = `[&](std::any _v) { auto ${paramName} = std::any_cast<${cppType}>(_v); ${bodyCode} }`;
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
// Void callback: () => doSomething()
|
|
813
|
+
const bodyCode = ts.isBlock(expr.body)
|
|
814
|
+
? expr.body.statements.map(s => stmtToCpp(s, ctx)).join(' ')
|
|
815
|
+
: exprToCpp(expr.body, ctx) + ';';
|
|
816
|
+
callbackProps[attrName] = `[&](std::any) { ${bodyCode} }`;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
props[attrName] = exprToCpp(expr, ctx);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const keyAttr = rawAttrs.get('key');
|
|
824
|
+
const key = keyAttr ? exprToCpp(keyAttr, ctx) : undefined;
|
|
825
|
+
body.push({
|
|
826
|
+
kind: 'native_widget',
|
|
827
|
+
name,
|
|
828
|
+
props,
|
|
829
|
+
callbackProps,
|
|
830
|
+
key,
|
|
831
|
+
loc,
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
function lowerDockLayout(node, ctx) {
|
|
835
|
+
const children = [];
|
|
836
|
+
for (const child of node.children) {
|
|
837
|
+
if (ts.isJsxElement(child)) {
|
|
838
|
+
const tag = child.openingElement.tagName;
|
|
839
|
+
if (ts.isIdentifier(tag)) {
|
|
840
|
+
if (tag.text === 'DockSplit')
|
|
841
|
+
children.push(lowerDockSplit(child, ctx));
|
|
842
|
+
else if (tag.text === 'DockPanel')
|
|
843
|
+
children.push(lowerDockPanel(child, ctx));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return { kind: 'dock_layout', children, loc: getLoc(node, ctx) };
|
|
848
|
+
}
|
|
849
|
+
function lowerDockSplit(node, ctx) {
|
|
850
|
+
const attrs = getAttributes(node.openingElement.attributes, ctx);
|
|
851
|
+
const direction = attrs['direction'] ?? '"horizontal"';
|
|
852
|
+
const size = attrs['size'] ?? '0.5';
|
|
853
|
+
const children = [];
|
|
854
|
+
for (const child of node.children) {
|
|
855
|
+
if (ts.isJsxElement(child)) {
|
|
856
|
+
const tag = child.openingElement.tagName;
|
|
857
|
+
if (ts.isIdentifier(tag)) {
|
|
858
|
+
if (tag.text === 'DockSplit')
|
|
859
|
+
children.push(lowerDockSplit(child, ctx));
|
|
860
|
+
else if (tag.text === 'DockPanel')
|
|
861
|
+
children.push(lowerDockPanel(child, ctx));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return { kind: 'dock_split', direction, size, children };
|
|
866
|
+
}
|
|
867
|
+
function lowerDockPanel(node, ctx) {
|
|
868
|
+
const windows = [];
|
|
869
|
+
for (const child of node.children) {
|
|
870
|
+
if (ts.isJsxSelfClosingElement(child)) {
|
|
871
|
+
const tag = child.tagName;
|
|
872
|
+
if (ts.isIdentifier(tag) && tag.text === 'Window') {
|
|
873
|
+
const attrs = getAttributes(child.attributes, ctx);
|
|
874
|
+
if (attrs['title'])
|
|
875
|
+
windows.push(attrs['title']);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return { kind: 'dock_panel', windows };
|
|
880
|
+
}
|
|
637
881
|
function lowerJsxChild(child, body, ctx) {
|
|
638
882
|
if (ts.isJsxElement(child)) {
|
|
639
883
|
lowerJsxElement(child, body, ctx);
|
|
@@ -650,6 +894,99 @@ function lowerJsxChild(child, body, ctx) {
|
|
|
650
894
|
// Standalone text not inside <Text> — usually whitespace, skip
|
|
651
895
|
}
|
|
652
896
|
}
|
|
897
|
+
function lowerBulletTextElement(node, body, ctx, loc) {
|
|
898
|
+
// Same logic as lowerTextElement but produces bullet_text kind
|
|
899
|
+
const children = node.children;
|
|
900
|
+
const parts = [];
|
|
901
|
+
const args = [];
|
|
902
|
+
for (const child of children) {
|
|
903
|
+
if (ts.isJsxText(child)) {
|
|
904
|
+
const trimmed = child.text.trim();
|
|
905
|
+
if (trimmed)
|
|
906
|
+
parts.push(trimmed.replace(/%/g, '%%'));
|
|
907
|
+
}
|
|
908
|
+
else if (ts.isJsxExpression(child) && child.expression) {
|
|
909
|
+
args.push(exprToCpp(child.expression, ctx));
|
|
910
|
+
parts.push('%s');
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const format = parts.join(' ');
|
|
914
|
+
body.push({ kind: 'bullet_text', format, args, loc });
|
|
915
|
+
}
|
|
916
|
+
function lowerLabelText(attrs, body, ctx, loc) {
|
|
917
|
+
const label = attrs['label'] ?? '""';
|
|
918
|
+
const value = attrs['value'] ?? '""';
|
|
919
|
+
body.push({ kind: 'label_text', label, value, loc });
|
|
920
|
+
}
|
|
921
|
+
function lowerSelectable(attrs, rawAttrs, body, ctx, loc) {
|
|
922
|
+
const label = attrs['label'] ?? '""';
|
|
923
|
+
const selected = attrs['selected'] ?? 'false';
|
|
924
|
+
const onSelectExpr = rawAttrs.get('onSelect');
|
|
925
|
+
let action = [];
|
|
926
|
+
if (onSelectExpr) {
|
|
927
|
+
action = extractActionStatements(onSelectExpr, ctx);
|
|
928
|
+
}
|
|
929
|
+
const style = attrs['style'];
|
|
930
|
+
body.push({ kind: 'selectable', label, selected, action, style, loc });
|
|
931
|
+
}
|
|
932
|
+
function lowerRadio(attrs, rawAttrs, body, ctx, loc) {
|
|
933
|
+
const label = attrs['label'] ?? '""';
|
|
934
|
+
const index = attrs['index'] ?? '0';
|
|
935
|
+
const style = attrs['style'];
|
|
936
|
+
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
937
|
+
body.push({ kind: 'radio', label, stateVar, valueExpr, onChangeExpr, index, style, loc });
|
|
938
|
+
}
|
|
939
|
+
function lowerInputTextMultiline(attrs, rawAttrs, body, ctx, loc) {
|
|
940
|
+
const label = attrs['label'] ?? '""';
|
|
941
|
+
const bufferIndex = ctx.bufferIndex++;
|
|
942
|
+
let stateVar = '';
|
|
943
|
+
const valueExpr = rawAttrs.get('value');
|
|
944
|
+
if (valueExpr && ts.isIdentifier(valueExpr)) {
|
|
945
|
+
const varName = valueExpr.text;
|
|
946
|
+
if (ctx.stateVars.has(varName)) {
|
|
947
|
+
stateVar = varName;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const style = attrs['style'];
|
|
951
|
+
body.push({ kind: 'input_text_multiline', label, bufferIndex, stateVar, style, loc });
|
|
952
|
+
}
|
|
953
|
+
function lowerColorPicker(attrs, rawAttrs, body, ctx, loc) {
|
|
954
|
+
const label = attrs['label'] ?? '""';
|
|
955
|
+
const style = attrs['style'];
|
|
956
|
+
let stateVar = '';
|
|
957
|
+
const valueRaw = rawAttrs.get('value');
|
|
958
|
+
if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
|
|
959
|
+
stateVar = valueRaw.text;
|
|
960
|
+
}
|
|
961
|
+
body.push({ kind: 'color_picker', label, stateVar, style, loc });
|
|
962
|
+
}
|
|
963
|
+
function lowerPlotLines(attrs, body, ctx, loc) {
|
|
964
|
+
const label = attrs['label'] ?? '""';
|
|
965
|
+
const values = attrs['values'] ?? '';
|
|
966
|
+
const overlay = attrs['overlay'];
|
|
967
|
+
const style = attrs['style'];
|
|
968
|
+
body.push({ kind: 'plot_lines', label, values, overlay, style, loc });
|
|
969
|
+
}
|
|
970
|
+
function lowerPlotHistogram(attrs, body, ctx, loc) {
|
|
971
|
+
const label = attrs['label'] ?? '""';
|
|
972
|
+
const values = attrs['values'] ?? '';
|
|
973
|
+
const overlay = attrs['overlay'];
|
|
974
|
+
const style = attrs['style'];
|
|
975
|
+
body.push({ kind: 'plot_histogram', label, values, overlay, style, loc });
|
|
976
|
+
}
|
|
977
|
+
function lowerImage(attrs, body, ctx, loc) {
|
|
978
|
+
const src = attrs['src'] ?? '""';
|
|
979
|
+
const embed = attrs['embed'] === 'true';
|
|
980
|
+
const width = attrs['width'];
|
|
981
|
+
const height = attrs['height'];
|
|
982
|
+
let embedKey;
|
|
983
|
+
if (embed) {
|
|
984
|
+
// Derive key from src: strip quotes, replace non-alnum with underscore
|
|
985
|
+
const rawSrc = src.replace(/^"|"$/g, '');
|
|
986
|
+
embedKey = rawSrc.replace(/[^a-zA-Z0-9]/g, '_');
|
|
987
|
+
}
|
|
988
|
+
body.push({ kind: 'image', src, embed, embedKey, width, height, loc });
|
|
989
|
+
}
|
|
653
990
|
function getAttributes(attributes, ctx) {
|
|
654
991
|
const result = {};
|
|
655
992
|
for (const attr of attributes.properties) {
|
|
@@ -709,56 +1046,56 @@ function lowerValueOnChange(rawAttrs, ctx) {
|
|
|
709
1046
|
}
|
|
710
1047
|
return { stateVar, valueExpr, onChangeExpr };
|
|
711
1048
|
}
|
|
712
|
-
function lowerSliderFloat(attrs, rawAttrs, body, ctx) {
|
|
1049
|
+
function lowerSliderFloat(attrs, rawAttrs, body, ctx, loc) {
|
|
713
1050
|
const label = attrs['label'] ?? '""';
|
|
714
1051
|
const min = attrs['min'] ?? '0.0f';
|
|
715
1052
|
const max = attrs['max'] ?? '1.0f';
|
|
716
1053
|
const style = attrs['style'];
|
|
717
1054
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
718
|
-
body.push({ kind: 'slider_float', label, stateVar, valueExpr, onChangeExpr, min, max, style });
|
|
1055
|
+
body.push({ kind: 'slider_float', label, stateVar, valueExpr, onChangeExpr, min, max, style, loc });
|
|
719
1056
|
}
|
|
720
|
-
function lowerSliderInt(attrs, rawAttrs, body, ctx) {
|
|
1057
|
+
function lowerSliderInt(attrs, rawAttrs, body, ctx, loc) {
|
|
721
1058
|
const label = attrs['label'] ?? '""';
|
|
722
1059
|
const min = attrs['min'] ?? '0';
|
|
723
1060
|
const max = attrs['max'] ?? '100';
|
|
724
1061
|
const style = attrs['style'];
|
|
725
1062
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
726
|
-
body.push({ kind: 'slider_int', label, stateVar, valueExpr, onChangeExpr, min, max, style });
|
|
1063
|
+
body.push({ kind: 'slider_int', label, stateVar, valueExpr, onChangeExpr, min, max, style, loc });
|
|
727
1064
|
}
|
|
728
|
-
function lowerDragFloat(attrs, rawAttrs, body, ctx) {
|
|
1065
|
+
function lowerDragFloat(attrs, rawAttrs, body, ctx, loc) {
|
|
729
1066
|
const label = attrs['label'] ?? '""';
|
|
730
1067
|
const speed = attrs['speed'] ?? '1.0f';
|
|
731
1068
|
const style = attrs['style'];
|
|
732
1069
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
733
|
-
body.push({ kind: 'drag_float', label, stateVar, valueExpr, onChangeExpr, speed, style });
|
|
1070
|
+
body.push({ kind: 'drag_float', label, stateVar, valueExpr, onChangeExpr, speed, style, loc });
|
|
734
1071
|
}
|
|
735
|
-
function lowerDragInt(attrs, rawAttrs, body, ctx) {
|
|
1072
|
+
function lowerDragInt(attrs, rawAttrs, body, ctx, loc) {
|
|
736
1073
|
const label = attrs['label'] ?? '""';
|
|
737
1074
|
const speed = attrs['speed'] ?? '1.0f';
|
|
738
1075
|
const style = attrs['style'];
|
|
739
1076
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
740
|
-
body.push({ kind: 'drag_int', label, stateVar, valueExpr, onChangeExpr, speed, style });
|
|
1077
|
+
body.push({ kind: 'drag_int', label, stateVar, valueExpr, onChangeExpr, speed, style, loc });
|
|
741
1078
|
}
|
|
742
|
-
function lowerCombo(attrs, rawAttrs, body, ctx) {
|
|
1079
|
+
function lowerCombo(attrs, rawAttrs, body, ctx, loc) {
|
|
743
1080
|
const label = attrs['label'] ?? '""';
|
|
744
1081
|
const items = attrs['items'] ?? '';
|
|
745
1082
|
const style = attrs['style'];
|
|
746
1083
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
747
|
-
body.push({ kind: 'combo', label, stateVar, valueExpr, onChangeExpr, items, style });
|
|
1084
|
+
body.push({ kind: 'combo', label, stateVar, valueExpr, onChangeExpr, items, style, loc });
|
|
748
1085
|
}
|
|
749
|
-
function lowerInputInt(attrs, rawAttrs, body, ctx) {
|
|
1086
|
+
function lowerInputInt(attrs, rawAttrs, body, ctx, loc) {
|
|
750
1087
|
const label = attrs['label'] ?? '""';
|
|
751
1088
|
const style = attrs['style'];
|
|
752
1089
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
753
|
-
body.push({ kind: 'input_int', label, stateVar, valueExpr, onChangeExpr, style });
|
|
1090
|
+
body.push({ kind: 'input_int', label, stateVar, valueExpr, onChangeExpr, style, loc });
|
|
754
1091
|
}
|
|
755
|
-
function lowerInputFloat(attrs, rawAttrs, body, ctx) {
|
|
1092
|
+
function lowerInputFloat(attrs, rawAttrs, body, ctx, loc) {
|
|
756
1093
|
const label = attrs['label'] ?? '""';
|
|
757
1094
|
const style = attrs['style'];
|
|
758
1095
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
759
|
-
body.push({ kind: 'input_float', label, stateVar, valueExpr, onChangeExpr, style });
|
|
1096
|
+
body.push({ kind: 'input_float', label, stateVar, valueExpr, onChangeExpr, style, loc });
|
|
760
1097
|
}
|
|
761
|
-
function lowerColorEdit(attrs, rawAttrs, body, ctx) {
|
|
1098
|
+
function lowerColorEdit(attrs, rawAttrs, body, ctx, loc) {
|
|
762
1099
|
const label = attrs['label'] ?? '""';
|
|
763
1100
|
const style = attrs['style'];
|
|
764
1101
|
// ColorEdit only supports state-bound values
|
|
@@ -767,22 +1104,22 @@ function lowerColorEdit(attrs, rawAttrs, body, ctx) {
|
|
|
767
1104
|
if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
|
|
768
1105
|
stateVar = valueRaw.text;
|
|
769
1106
|
}
|
|
770
|
-
body.push({ kind: 'color_edit', label, stateVar, style });
|
|
1107
|
+
body.push({ kind: 'color_edit', label, stateVar, style, loc });
|
|
771
1108
|
}
|
|
772
|
-
function lowerListBox(attrs, rawAttrs, body, ctx) {
|
|
1109
|
+
function lowerListBox(attrs, rawAttrs, body, ctx, loc) {
|
|
773
1110
|
const label = attrs['label'] ?? '""';
|
|
774
1111
|
const items = attrs['items'] ?? '';
|
|
775
1112
|
const style = attrs['style'];
|
|
776
1113
|
const { stateVar, valueExpr, onChangeExpr } = lowerValueOnChange(rawAttrs, ctx);
|
|
777
|
-
body.push({ kind: 'list_box', label, stateVar, valueExpr, onChangeExpr, items, style });
|
|
1114
|
+
body.push({ kind: 'list_box', label, stateVar, valueExpr, onChangeExpr, items, style, loc });
|
|
778
1115
|
}
|
|
779
|
-
function lowerProgressBar(attrs, rawAttrs, body, ctx) {
|
|
1116
|
+
function lowerProgressBar(attrs, rawAttrs, body, ctx, loc) {
|
|
780
1117
|
const value = attrs['value'] ?? '0.0f';
|
|
781
1118
|
const overlay = attrs['overlay'];
|
|
782
1119
|
const style = attrs['style'];
|
|
783
|
-
body.push({ kind: 'progress_bar', value, overlay, style });
|
|
1120
|
+
body.push({ kind: 'progress_bar', value, overlay, style, loc });
|
|
784
1121
|
}
|
|
785
|
-
function lowerTooltip(attrs, body, ctx) {
|
|
1122
|
+
function lowerTooltip(attrs, body, ctx, loc) {
|
|
786
1123
|
const text = attrs['text'] ?? '""';
|
|
787
|
-
body.push({ kind: 'tooltip', text });
|
|
1124
|
+
body.push({ kind: 'tooltip', text, loc });
|
|
788
1125
|
}
|