imxc 0.4.0 → 0.5.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.
@@ -184,7 +184,7 @@ export const HOST_COMPONENTS = {
184
184
  props: {
185
185
  label: { type: 'string', required: true },
186
186
  value: { type: 'string', required: true },
187
- onChange: { type: 'callback', required: true },
187
+ onChange: { type: 'callback', required: false },
188
188
  style: { type: 'style', required: false },
189
189
  },
190
190
  hasChildren: false, isContainer: false,
package/dist/emitter.js CHANGED
@@ -1096,9 +1096,10 @@ function emitListMap(node, lines, indent, depth) {
1096
1096
  if (node.loc) {
1097
1097
  lines.push(`${indent}// ${node.loc.file}:${node.loc.line} .map()`);
1098
1098
  }
1099
- lines.push(`${indent}for (size_t i = 0; i < ${node.array}.size(); i++) {`);
1100
- lines.push(`${indent}${INDENT}auto& ${node.itemVar} = ${node.array}[i];`);
1101
- lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", (int)i, ${node.stateCount}, ${node.bufferCount});`);
1099
+ const idx = node.indexVar;
1100
+ lines.push(`${indent}for (size_t ${idx} = 0; ${idx} < ${node.array}.size(); ${idx}++) {`);
1101
+ lines.push(`${indent}${INDENT}auto& ${node.itemVar} = ${node.array}[${idx}];`);
1102
+ lines.push(`${indent}${INDENT}ctx.begin_instance("${node.componentName}", (int)${idx}, ${node.stateCount}, ${node.bufferCount});`);
1102
1103
  emitNodes(node.body, lines, depth + 2);
1103
1104
  lines.push(`${indent}${INDENT}ctx.end_instance();`);
1104
1105
  lines.push(`${indent}}`);
@@ -1346,6 +1347,19 @@ function emitColorEdit(node, lines, indent) {
1346
1347
  lines.push(`${indent}${INDENT}}`);
1347
1348
  lines.push(`${indent}}`);
1348
1349
  }
1350
+ else if (node.directBind && node.valueExpr) {
1351
+ lines.push(`${indent}imx::renderer::color_edit(${label}, ${node.valueExpr}.data());`);
1352
+ }
1353
+ else if (node.valueExpr !== undefined) {
1354
+ lines.push(`${indent}{`);
1355
+ lines.push(`${indent}${INDENT}auto val = ${node.valueExpr};`);
1356
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_edit(${label}, val.data())) {`);
1357
+ if (node.onChangeExpr) {
1358
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1359
+ }
1360
+ lines.push(`${indent}${INDENT}}`);
1361
+ lines.push(`${indent}}`);
1362
+ }
1349
1363
  }
1350
1364
  function emitListBox(node, lines, indent) {
1351
1365
  emitLocComment(node.loc, 'ListBox', lines, indent);
@@ -1469,44 +1483,79 @@ function emitInputTextMultiline(node, lines, indent) {
1469
1483
  }
1470
1484
  function emitColorPicker(node, lines, indent) {
1471
1485
  emitLocComment(node.loc, 'ColorPicker', lines, indent);
1486
+ const label = asCharPtr(node.label);
1472
1487
  if (node.stateVar) {
1473
1488
  lines.push(`${indent}{`);
1474
1489
  lines.push(`${indent}${INDENT}auto val = ${node.stateVar}.get();`);
1475
- lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${node.label}, val.data())) {`);
1490
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${label}, val.data())) {`);
1476
1491
  lines.push(`${indent}${INDENT}${INDENT}${node.stateVar}.set(val);`);
1477
1492
  lines.push(`${indent}${INDENT}}`);
1478
1493
  lines.push(`${indent}}`);
1479
1494
  }
1495
+ else if (node.directBind && node.valueExpr) {
1496
+ lines.push(`${indent}imx::renderer::color_picker(${label}, ${node.valueExpr}.data());`);
1497
+ }
1498
+ else if (node.valueExpr !== undefined) {
1499
+ lines.push(`${indent}{`);
1500
+ lines.push(`${indent}${INDENT}auto val = ${node.valueExpr};`);
1501
+ lines.push(`${indent}${INDENT}if (imx::renderer::color_picker(${label}, val.data())) {`);
1502
+ if (node.onChangeExpr) {
1503
+ lines.push(`${indent}${INDENT}${INDENT}${node.onChangeExpr};`);
1504
+ }
1505
+ lines.push(`${indent}${INDENT}}`);
1506
+ lines.push(`${indent}}`);
1507
+ }
1480
1508
  }
1481
1509
  function emitPlotLines(node, lines, indent) {
1482
1510
  emitLocComment(node.loc, 'PlotLines', lines, indent);
1483
- const idx = plotCounter++;
1484
- const varName = `_plot_${idx}`;
1485
- const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1486
- const count = values.length;
1487
- lines.push(`${indent}{`);
1488
- const innerIndent = indent + INDENT;
1489
- const styleVar = buildStyleVar(node.style, innerIndent, lines);
1490
- lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1491
1511
  const overlay = node.overlay ? `, ${node.overlay}` : ', nullptr';
1492
- const styleArg = styleVar ? `, ${styleVar}` : '';
1493
- lines.push(`${innerIndent}imx::renderer::plot_lines(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1494
- lines.push(`${indent}}`);
1512
+ // Check if values is a variable/property access (struct binding) vs literal array
1513
+ if (node.values.includes('.') || /^[a-zA-Z_]\w*$/.test(node.values)) {
1514
+ lines.push(`${indent}{`);
1515
+ const innerIndent = indent + INDENT;
1516
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1517
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1518
+ lines.push(`${innerIndent}imx::renderer::plot_lines(${node.label}, ${node.values}.data(), static_cast<int>(${node.values}.size())${overlay}${styleArg});`);
1519
+ lines.push(`${indent}}`);
1520
+ }
1521
+ else {
1522
+ const idx = plotCounter++;
1523
+ const varName = `_plot_${idx}`;
1524
+ const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1525
+ const count = values.length;
1526
+ lines.push(`${indent}{`);
1527
+ const innerIndent = indent + INDENT;
1528
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1529
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1530
+ lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1531
+ lines.push(`${innerIndent}imx::renderer::plot_lines(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1532
+ lines.push(`${indent}}`);
1533
+ }
1495
1534
  }
1496
1535
  function emitPlotHistogram(node, lines, indent) {
1497
1536
  emitLocComment(node.loc, 'PlotHistogram', lines, indent);
1498
- const idx = plotCounter++;
1499
- const varName = `_plot_${idx}`;
1500
- const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1501
- const count = values.length;
1502
- lines.push(`${indent}{`);
1503
- const innerIndent = indent + INDENT;
1504
- const styleVar = buildStyleVar(node.style, innerIndent, lines);
1505
- lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1506
1537
  const overlay = node.overlay ? `, ${node.overlay}` : ', nullptr';
1507
- const styleArg = styleVar ? `, ${styleVar}` : '';
1508
- lines.push(`${innerIndent}imx::renderer::plot_histogram(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1509
- lines.push(`${indent}}`);
1538
+ if (node.values.includes('.') || /^[a-zA-Z_]\w*$/.test(node.values)) {
1539
+ lines.push(`${indent}{`);
1540
+ const innerIndent = indent + INDENT;
1541
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1542
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1543
+ lines.push(`${innerIndent}imx::renderer::plot_histogram(${node.label}, ${node.values}.data(), static_cast<int>(${node.values}.size())${overlay}${styleArg});`);
1544
+ lines.push(`${indent}}`);
1545
+ }
1546
+ else {
1547
+ const idx = plotCounter++;
1548
+ const varName = `_plot_${idx}`;
1549
+ const values = node.values.split(',').map(v => ensureFloatLiteral(v.trim()));
1550
+ const count = values.length;
1551
+ lines.push(`${indent}{`);
1552
+ const innerIndent = indent + INDENT;
1553
+ const styleVar = buildStyleVar(node.style, innerIndent, lines);
1554
+ const styleArg = styleVar ? `, ${styleVar}` : '';
1555
+ lines.push(`${innerIndent}float ${varName}[] = {${values.join(', ')}};`);
1556
+ lines.push(`${innerIndent}imx::renderer::plot_histogram(${node.label}, ${varName}, ${count}${overlay}${styleArg});`);
1557
+ lines.push(`${indent}}`);
1558
+ }
1510
1559
  }
1511
1560
  function emitImage(node, lines, indent) {
1512
1561
  emitLocComment(node.loc, 'Image', lines, indent);
package/dist/ir.d.ts CHANGED
@@ -99,6 +99,7 @@ export interface IRListMap {
99
99
  kind: 'list_map';
100
100
  array: string;
101
101
  itemVar: string;
102
+ indexVar: string;
102
103
  key: string;
103
104
  componentName: string;
104
105
  stateCount: number;
@@ -203,6 +204,8 @@ export interface IRColorEdit {
203
204
  kind: 'color_edit';
204
205
  label: string;
205
206
  stateVar: string;
207
+ valueExpr?: string;
208
+ onChangeExpr?: string;
206
209
  directBind?: boolean;
207
210
  style?: string;
208
211
  loc?: SourceLoc;
@@ -281,6 +284,8 @@ export interface IRColorPicker {
281
284
  kind: 'color_picker';
282
285
  label: string;
283
286
  stateVar: string;
287
+ valueExpr?: string;
288
+ onChangeExpr?: string;
284
289
  directBind?: boolean;
285
290
  style?: string;
286
291
  loc?: SourceLoc;
package/dist/lowering.js CHANGED
@@ -230,10 +230,14 @@ export function exprToCpp(node, ctx) {
230
230
  else if (op === '!==')
231
231
  op = '!=';
232
232
  // String + non-string: JS string concatenation -> C++ std::string + std::to_string
233
- if (op === '+' && (ts.isStringLiteral(node.left) || ts.isNoSubstitutionTemplateLiteral(node.left))) {
234
- const rightType = inferExprType(node.right, ctx);
235
- if (rightType === 'int' || rightType === 'float') {
236
- return `(std::string(${left}) + std::to_string(${right}))`;
233
+ if (op === '+') {
234
+ const leftIsString = ts.isStringLiteral(node.left) || ts.isNoSubstitutionTemplateLiteral(node.left) || inferExprType(node.left, ctx) === 'string';
235
+ const rightIsString = ts.isStringLiteral(node.right) || ts.isNoSubstitutionTemplateLiteral(node.right) || inferExprType(node.right, ctx) === 'string';
236
+ if (leftIsString && !rightIsString) {
237
+ return `(std::string(${left}) + std::to_string(${right})).c_str()`;
238
+ }
239
+ if (!leftIsString && rightIsString) {
240
+ return `(std::to_string(${left}) + std::string(${right})).c_str()`;
237
241
  }
238
242
  }
239
243
  return `${left} ${op} ${right}`;
@@ -677,8 +681,12 @@ function lowerCheckbox(attrs, rawAttrs, body, ctx, loc) {
677
681
  const onChangeRaw = rawAttrs.get('onChange');
678
682
  if (onChangeRaw) {
679
683
  onChangeExprStr = exprToCpp(onChangeRaw, ctx);
680
- // If it's not already a lambda/call, make it a call
681
- if (!onChangeExprStr.startsWith('[') && !onChangeExprStr.endsWith(')')) {
684
+ if (onChangeExprStr.startsWith('[')) {
685
+ // Lambda from arrow function — invoke it (IIFE)
686
+ onChangeExprStr = `(${onChangeExprStr})()`;
687
+ }
688
+ else if (!onChangeExprStr.endsWith(')')) {
689
+ // Plain identifier — make it a call
682
690
  onChangeExprStr = `${onChangeExprStr}()`;
683
691
  }
684
692
  }
@@ -727,8 +735,15 @@ function lowerTextElement(node, body, ctx, loc) {
727
735
  args.push(cppExpr);
728
736
  break;
729
737
  case 'float':
730
- format += '%.2f';
731
- args.push(cppExpr);
738
+ if (cppExpr.startsWith('props.')) {
739
+ // Props fields: number could be int or float in C++, cast to double for safe printf
740
+ format += '%g';
741
+ args.push(`(double)${cppExpr}`);
742
+ }
743
+ else {
744
+ format += '%.2f';
745
+ args.push(cppExpr);
746
+ }
732
747
  break;
733
748
  case 'bool':
734
749
  format += '%s';
@@ -828,11 +843,15 @@ function lowerListMap(node, body, ctx, loc) {
828
843
  const array = exprToCpp(propAccess.expression, ctx);
829
844
  const callback = node.arguments[0];
830
845
  let itemVar = 'item';
846
+ let indexVar = 'i';
831
847
  let mapBody = [];
832
848
  if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
833
849
  if (callback.parameters.length > 0 && ts.isIdentifier(callback.parameters[0].name)) {
834
850
  itemVar = callback.parameters[0].name.text;
835
851
  }
852
+ if (callback.parameters.length > 1 && ts.isIdentifier(callback.parameters[1].name)) {
853
+ indexVar = callback.parameters[1].name.text;
854
+ }
836
855
  if (ts.isBlock(callback.body)) {
837
856
  const ret = callback.body.statements.find(ts.isReturnStatement);
838
857
  if (ret?.expression) {
@@ -847,7 +866,8 @@ function lowerListMap(node, body, ctx, loc) {
847
866
  kind: 'list_map',
848
867
  array,
849
868
  itemVar,
850
- key: 'i',
869
+ indexVar,
870
+ key: indexVar,
851
871
  componentName: 'ListItem',
852
872
  stateCount: 0,
853
873
  bufferCount: 0,
@@ -1044,11 +1064,30 @@ function lowerColorPicker(attrs, rawAttrs, body, ctx, loc) {
1044
1064
  const label = attrs['label'] ?? '""';
1045
1065
  const style = attrs['style'];
1046
1066
  let stateVar = '';
1067
+ let valueExpr;
1068
+ let onChangeExpr;
1069
+ let directBind;
1047
1070
  const valueRaw = rawAttrs.get('value');
1048
1071
  if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1049
1072
  stateVar = valueRaw.text;
1050
1073
  }
1051
- body.push({ kind: 'color_picker', label, stateVar, style, loc });
1074
+ else if (valueRaw) {
1075
+ valueExpr = exprToCpp(valueRaw, ctx);
1076
+ const onChangeRaw = rawAttrs.get('onChange');
1077
+ if (onChangeRaw) {
1078
+ onChangeExpr = exprToCpp(onChangeRaw, ctx);
1079
+ if (onChangeExpr.startsWith('[')) {
1080
+ onChangeExpr = `(${onChangeExpr})()`;
1081
+ }
1082
+ else if (!onChangeExpr.endsWith(')')) {
1083
+ onChangeExpr = `${onChangeExpr}()`;
1084
+ }
1085
+ }
1086
+ else if (valueRaw && ts.isPropertyAccessExpression(valueRaw)) {
1087
+ directBind = true;
1088
+ }
1089
+ }
1090
+ body.push({ kind: 'color_picker', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1052
1091
  }
1053
1092
  function lowerPlotLines(attrs, body, ctx, loc) {
1054
1093
  const label = attrs['label'] ?? '""';
@@ -1130,7 +1169,12 @@ function lowerValueOnChange(rawAttrs, ctx) {
1130
1169
  const onChangeRaw = rawAttrs.get('onChange');
1131
1170
  if (onChangeRaw) {
1132
1171
  onChangeExpr = exprToCpp(onChangeRaw, ctx);
1133
- if (!onChangeExpr.startsWith('[') && !onChangeExpr.endsWith(')')) {
1172
+ if (onChangeExpr.startsWith('[')) {
1173
+ // Lambda from arrow function — invoke it (IIFE)
1174
+ onChangeExpr = `(${onChangeExpr})()`;
1175
+ }
1176
+ else if (!onChangeExpr.endsWith(')')) {
1177
+ // Plain identifier — make it a call
1134
1178
  onChangeExpr = `${onChangeExpr}()`;
1135
1179
  }
1136
1180
  }
@@ -1194,13 +1238,31 @@ function lowerInputFloat(attrs, rawAttrs, body, ctx, loc) {
1194
1238
  function lowerColorEdit(attrs, rawAttrs, body, ctx, loc) {
1195
1239
  const label = attrs['label'] ?? '""';
1196
1240
  const style = attrs['style'];
1197
- // ColorEdit only supports state-bound values
1198
1241
  let stateVar = '';
1242
+ let valueExpr;
1243
+ let onChangeExpr;
1244
+ let directBind;
1199
1245
  const valueRaw = rawAttrs.get('value');
1200
1246
  if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1201
1247
  stateVar = valueRaw.text;
1202
1248
  }
1203
- body.push({ kind: 'color_edit', label, stateVar, style, loc });
1249
+ else if (valueRaw) {
1250
+ valueExpr = exprToCpp(valueRaw, ctx);
1251
+ const onChangeRaw = rawAttrs.get('onChange');
1252
+ if (onChangeRaw) {
1253
+ onChangeExpr = exprToCpp(onChangeRaw, ctx);
1254
+ if (onChangeExpr.startsWith('[')) {
1255
+ onChangeExpr = `(${onChangeExpr})()`;
1256
+ }
1257
+ else if (!onChangeExpr.endsWith(')')) {
1258
+ onChangeExpr = `${onChangeExpr}()`;
1259
+ }
1260
+ }
1261
+ else if (valueRaw && ts.isPropertyAccessExpression(valueRaw)) {
1262
+ directBind = true;
1263
+ }
1264
+ }
1265
+ body.push({ kind: 'color_edit', label, stateVar, valueExpr, onChangeExpr, directBind, style, loc });
1204
1266
  }
1205
1267
  function lowerListBox(attrs, rawAttrs, body, ctx, loc) {
1206
1268
  const label = attrs['label'] ?? '""';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imxc",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Compiler for IMX — compiles React-like .tsx to native Dear ImGui C++",
5
5
  "type": "module",
6
6
  "bin": {