imxc 0.6.8 → 0.6.10

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/emitter.js CHANGED
@@ -30,20 +30,121 @@ function cppPropType(t) {
30
30
  }
31
31
  return t;
32
32
  }
33
+ function unwrapOuterParens(expr) {
34
+ let current = expr.trim();
35
+ while (current.startsWith('(') && current.endsWith(')')) {
36
+ let depth = 0;
37
+ let balanced = true;
38
+ for (let i = 0; i < current.length; i++) {
39
+ const ch = current[i];
40
+ if (ch === '(') {
41
+ depth++;
42
+ }
43
+ else if (ch === ')') {
44
+ depth--;
45
+ if (depth < 0) {
46
+ balanced = false;
47
+ break;
48
+ }
49
+ if (depth === 0 && i < current.length - 1) {
50
+ balanced = false;
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ if (!balanced || depth !== 0) {
56
+ break;
57
+ }
58
+ current = current.slice(1, -1).trim();
59
+ }
60
+ return current;
61
+ }
62
+ function findTopLevelTernary(expr) {
63
+ let depthParen = 0;
64
+ let depthBracket = 0;
65
+ let depthBrace = 0;
66
+ let inString = false;
67
+ let stringQuote = '';
68
+ let question = -1;
69
+ for (let i = 0; i < expr.length; i++) {
70
+ const ch = expr[i];
71
+ const prev = i > 0 ? expr[i - 1] : '';
72
+ if (inString) {
73
+ if (ch === stringQuote && prev !== '\\') {
74
+ inString = false;
75
+ stringQuote = '';
76
+ }
77
+ continue;
78
+ }
79
+ if (ch === '"' || ch === '\'') {
80
+ inString = true;
81
+ stringQuote = ch;
82
+ continue;
83
+ }
84
+ if (ch === '(') {
85
+ depthParen++;
86
+ continue;
87
+ }
88
+ if (ch === ')') {
89
+ depthParen--;
90
+ continue;
91
+ }
92
+ if (ch === '[') {
93
+ depthBracket++;
94
+ continue;
95
+ }
96
+ if (ch === ']') {
97
+ depthBracket--;
98
+ continue;
99
+ }
100
+ if (ch === '{') {
101
+ depthBrace++;
102
+ continue;
103
+ }
104
+ if (ch === '}') {
105
+ depthBrace--;
106
+ continue;
107
+ }
108
+ if (depthParen !== 0 || depthBracket !== 0 || depthBrace !== 0) {
109
+ continue;
110
+ }
111
+ if (ch === '?' && question === -1) {
112
+ question = i;
113
+ continue;
114
+ }
115
+ if (ch === ':' && question !== -1) {
116
+ return { question, colon: i };
117
+ }
118
+ }
119
+ return undefined;
120
+ }
121
+ function isStringLiteral(expr) {
122
+ const trimmed = expr.trim();
123
+ return trimmed.startsWith('"') && trimmed.endsWith('"');
124
+ }
125
+ function isCharPtrExpression(expr) {
126
+ const trimmed = unwrapOuterParens(expr);
127
+ if (isStringLiteral(trimmed) || trimmed.endsWith('.c_str()')) {
128
+ return true;
129
+ }
130
+ const ternary = findTopLevelTernary(trimmed);
131
+ if (!ternary) {
132
+ return false;
133
+ }
134
+ const whenTrue = trimmed.slice(ternary.question + 1, ternary.colon).trim();
135
+ const whenFalse = trimmed.slice(ternary.colon + 1).trim();
136
+ return isCharPtrExpression(whenTrue) && isCharPtrExpression(whenFalse);
137
+ }
33
138
  /**
34
139
  * Ensure a string expression is a const char*.
35
140
  * String literals (quoted) are already const char*.
36
141
  * Expressions like props.title (std::string) need .c_str().
37
142
  */
38
143
  function asCharPtr(expr) {
39
- // Already a string literal — "hello" is const char*
40
- if (expr.startsWith('"'))
41
- return expr;
42
- // Already has .c_str()
43
- if (expr.endsWith('.c_str()'))
44
- return expr;
45
- // Expression — assume std::string, add .c_str()
46
- return `${expr}.c_str()`;
144
+ const trimmed = expr.trim();
145
+ if (isCharPtrExpression(trimmed))
146
+ return trimmed;
147
+ return `(${trimmed}).c_str()`;
47
148
  }
48
149
  /**
49
150
  * For directBind emitters: if the valueExpr references a bound prop (pointer),
@@ -92,6 +193,24 @@ function emitFloat(val) {
92
193
  return trimmed;
93
194
  return trimmed.includes('.') ? `${trimmed}F` : `${trimmed}.0F`;
94
195
  }
196
+ function emitStringItemsSource(itemsExpr, dynamicItems, prefix, counter, indent, lines) {
197
+ if (!dynamicItems) {
198
+ const itemsList = itemsExpr.split(',').map(s => s.trim()).filter(s => s.length > 0);
199
+ const itemsVar = `${prefix}_${counter}`;
200
+ lines.push(`${indent}const char* ${itemsVar}[] = {${itemsList.join(', ')}};`);
201
+ return { itemsArg: itemsVar, countExpr: `${itemsList.length}` };
202
+ }
203
+ const sourceVar = `${prefix}_src_${counter}`;
204
+ const itemsVar = `${prefix}_ptrs_${counter}`;
205
+ const itemVar = `${prefix}_item_${counter}`;
206
+ lines.push(`${indent}const auto& ${sourceVar} = ${itemsExpr};`);
207
+ lines.push(`${indent}std::vector<const char*> ${itemsVar};`);
208
+ lines.push(`${indent}${itemsVar}.reserve(${sourceVar}.size());`);
209
+ lines.push(`${indent}for (const auto& ${itemVar} : ${sourceVar}) {`);
210
+ lines.push(`${indent}${INDENT}${itemsVar}.push_back(${itemVar}.c_str());`);
211
+ lines.push(`${indent}}`);
212
+ return { itemsArg: `${itemsVar}.data()`, countExpr: `static_cast<int>(${itemsVar}.size())` };
213
+ }
95
214
  function findDockLayout(nodes) {
96
215
  for (const node of nodes) {
97
216
  if (node.kind === 'dock_layout')
@@ -207,6 +326,7 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
207
326
  // Named interface: include runtime + renderer, plus user header for the struct definition
208
327
  lines.push('#include <imx/runtime.h>');
209
328
  lines.push('#include <imx/renderer.h>');
329
+ lines.push('#include <vector>');
210
330
  lines.push(`#include "${comp.namedPropsType}.h"`);
211
331
  if (hasColorType) {
212
332
  lines.push('#include <array>');
@@ -227,6 +347,7 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
227
347
  else {
228
348
  // Component with inline props: include its own header instead of redeclaring struct
229
349
  lines.push(`#include "${comp.name}.gen.h"`);
350
+ lines.push('#include <vector>');
230
351
  if (hasColorType) {
231
352
  lines.push('#include <array>');
232
353
  }
@@ -248,6 +369,7 @@ export function emitComponent(comp, imports, sourceFile, boundProps, boundPropsM
248
369
  // No props: standard headers
249
370
  lines.push('#include <imx/runtime.h>');
250
371
  lines.push('#include <imx/renderer.h>');
372
+ lines.push('#include <vector>');
251
373
  if (hasColorType) {
252
374
  lines.push('#include <array>');
253
375
  }
@@ -2182,18 +2304,16 @@ function emitDragInt(node, lines, indent) {
2182
2304
  function emitCombo(node, lines, indent) {
2183
2305
  emitLocComment(node.loc, 'Combo', lines, indent);
2184
2306
  const label = asCharPtr(node.label);
2185
- const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
2186
- const count = itemsList.length;
2187
- const varName = `combo_items_${comboCounter++}`;
2307
+ const counter = comboCounter++;
2188
2308
  if (node.stateVar) {
2189
2309
  lines.push(`${indent}{`);
2190
2310
  const innerIndent = indent + INDENT;
2191
2311
  const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2192
2312
  const styleArg = styleVar ? `, ${styleVar}` : '';
2193
- lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2313
+ const { itemsArg, countExpr } = emitStringItemsSource(node.items, node.dynamicItems, 'combo_items', counter, innerIndent, lines);
2194
2314
  lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2195
2315
  const changedVar = nextWidgetTemp('combo_changed');
2196
- const resultVar = emitBoolWidgetCall(`imx::renderer::combo(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2316
+ const resultVar = emitBoolWidgetCall(`imx::renderer::combo(${label}, &val, ${itemsArg}, ${countExpr}${styleArg})`, node.item, lines, innerIndent, changedVar);
2197
2317
  lines.push(`${innerIndent}if (${resultVar}) {`);
2198
2318
  lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2199
2319
  lines.push(`${innerIndent}}`);
@@ -2204,8 +2324,8 @@ function emitCombo(node, lines, indent) {
2204
2324
  const innerIndent = indent + INDENT;
2205
2325
  const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2206
2326
  const styleArg = styleVar ? `, ${styleVar}` : '';
2207
- lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2208
- emitBoolWidgetCall(`imx::renderer::combo(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent);
2327
+ const { itemsArg, countExpr } = emitStringItemsSource(node.items, node.dynamicItems, 'combo_items', counter, innerIndent, lines);
2328
+ emitBoolWidgetCall(`imx::renderer::combo(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${itemsArg}, ${countExpr}${styleArg})`, node.item, lines, innerIndent);
2209
2329
  lines.push(`${indent}}`);
2210
2330
  }
2211
2331
  else if (node.valueExpr !== undefined) {
@@ -2213,13 +2333,13 @@ function emitCombo(node, lines, indent) {
2213
2333
  const innerIndent = indent + INDENT;
2214
2334
  const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2215
2335
  const styleArg = styleVar ? `, ${styleVar}` : '';
2216
- lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2336
+ const { itemsArg, countExpr } = emitStringItemsSource(node.items, node.dynamicItems, 'combo_items', counter, innerIndent, lines);
2217
2337
  lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2218
2338
  const changedVar = nextWidgetTemp('combo_changed');
2219
- const resultVar = emitBoolWidgetCall(`imx::renderer::combo(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2339
+ const resultVar = emitBoolWidgetCall(`imx::renderer::combo(${label}, &val, ${itemsArg}, ${countExpr}${styleArg})`, node.item, lines, innerIndent, changedVar);
2220
2340
  lines.push(`${innerIndent}if (${resultVar}) {`);
2221
2341
  if (node.onChangeExpr) {
2222
- lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2342
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr}`);
2223
2343
  }
2224
2344
  lines.push(`${innerIndent}}`);
2225
2345
  lines.push(`${indent}}`);
@@ -2378,18 +2498,16 @@ function emitColorEdit3(node, lines, indent) {
2378
2498
  function emitListBox(node, lines, indent) {
2379
2499
  emitLocComment(node.loc, 'ListBox', lines, indent);
2380
2500
  const label = asCharPtr(node.label);
2381
- const itemsList = node.items.split(',').map(s => s.trim()).filter(s => s.length > 0);
2382
- const count = itemsList.length;
2383
- const varName = `listbox_items_${listBoxCounter++}`;
2501
+ const counter = listBoxCounter++;
2384
2502
  if (node.stateVar) {
2385
2503
  lines.push(`${indent}{`);
2386
2504
  const innerIndent = indent + INDENT;
2387
2505
  const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2388
2506
  const styleArg = styleVar ? `, ${styleVar}` : '';
2389
- lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2507
+ const { itemsArg, countExpr } = emitStringItemsSource(node.items, node.dynamicItems, 'listbox_items', counter, innerIndent, lines);
2390
2508
  lines.push(`${innerIndent}int val = ${node.stateVar}.get();`);
2391
2509
  const changedVar = nextWidgetTemp('list_box_changed');
2392
- const resultVar = emitBoolWidgetCall(`imx::renderer::list_box(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2510
+ const resultVar = emitBoolWidgetCall(`imx::renderer::list_box(${label}, &val, ${itemsArg}, ${countExpr}${styleArg})`, node.item, lines, innerIndent, changedVar);
2393
2511
  lines.push(`${innerIndent}if (${resultVar}) {`);
2394
2512
  lines.push(`${innerIndent}${INDENT}${node.stateVar}.set(val);`);
2395
2513
  lines.push(`${innerIndent}}`);
@@ -2400,8 +2518,8 @@ function emitListBox(node, lines, indent) {
2400
2518
  const innerIndent = indent + INDENT;
2401
2519
  const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2402
2520
  const styleArg = styleVar ? `, ${styleVar}` : '';
2403
- lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2404
- emitBoolWidgetCall(`imx::renderer::list_box(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent);
2521
+ const { itemsArg, countExpr } = emitStringItemsSource(node.items, node.dynamicItems, 'listbox_items', counter, innerIndent, lines);
2522
+ emitBoolWidgetCall(`imx::renderer::list_box(${label}, ${emitDirectBindPtr(node.valueExpr)}, ${itemsArg}, ${countExpr}${styleArg})`, node.item, lines, innerIndent);
2405
2523
  lines.push(`${indent}}`);
2406
2524
  }
2407
2525
  else if (node.valueExpr !== undefined) {
@@ -2409,13 +2527,13 @@ function emitListBox(node, lines, indent) {
2409
2527
  const innerIndent = indent + INDENT;
2410
2528
  const styleVar = buildWidgetStyleVar(node.style, node.width, innerIndent, lines);
2411
2529
  const styleArg = styleVar ? `, ${styleVar}` : '';
2412
- lines.push(`${innerIndent}const char* ${varName}[] = {${itemsList.join(', ')}};`);
2530
+ const { itemsArg, countExpr } = emitStringItemsSource(node.items, node.dynamicItems, 'listbox_items', counter, innerIndent, lines);
2413
2531
  lines.push(`${innerIndent}int val = ${node.valueExpr};`);
2414
2532
  const changedVar = nextWidgetTemp('list_box_changed');
2415
- const resultVar = emitBoolWidgetCall(`imx::renderer::list_box(${label}, &val, ${varName}, ${count}${styleArg})`, node.item, lines, innerIndent, changedVar);
2533
+ const resultVar = emitBoolWidgetCall(`imx::renderer::list_box(${label}, &val, ${itemsArg}, ${countExpr}${styleArg})`, node.item, lines, innerIndent, changedVar);
2416
2534
  lines.push(`${innerIndent}if (${resultVar}) {`);
2417
2535
  if (node.onChangeExpr) {
2418
- lines.push(`${innerIndent}${INDENT}${node.onChangeExpr};`);
2536
+ lines.push(`${innerIndent}${INDENT}${node.onChangeExpr}`);
2419
2537
  }
2420
2538
  lines.push(`${innerIndent}}`);
2421
2539
  lines.push(`${indent}}`);
package/dist/init.js CHANGED
@@ -39,7 +39,7 @@ export function addToProject(projectDir) {
39
39
  console.log(' include(FetchContent)');
40
40
  console.log(' FetchContent_Declare(imx');
41
41
  console.log(' GIT_REPOSITORY https://github.com/bgocumlu/imx.git');
42
- console.log(' GIT_TAG v0.6.8');
42
+ console.log(' GIT_TAG v0.6.10');
43
43
  console.log(' )');
44
44
  console.log(' FetchContent_MakeAvailable(imx)');
45
45
  console.log(' include(ImxCompile)');
package/dist/ir.d.ts CHANGED
@@ -310,6 +310,7 @@ export interface IRCombo {
310
310
  onChangeExpr?: string;
311
311
  directBind?: boolean;
312
312
  items: string;
313
+ dynamicItems?: boolean;
313
314
  width?: string;
314
315
  style?: string;
315
316
  item?: IRItemInteraction;
@@ -359,6 +360,7 @@ export interface IRListBox {
359
360
  onChangeExpr?: string;
360
361
  directBind?: boolean;
361
362
  items: string;
363
+ dynamicItems?: boolean;
362
364
  width?: string;
363
365
  style?: string;
364
366
  item?: IRItemInteraction;
package/dist/lowering.js CHANGED
@@ -1876,6 +1876,40 @@ function lowerValueOnChange(rawAttrs, ctx) {
1876
1876
  }
1877
1877
  return { stateVar, valueExpr, onChangeExpr, directBind };
1878
1878
  }
1879
+ function lowerIndexedValueOnChange(rawAttrs, ctx, valueName) {
1880
+ let stateVar = '';
1881
+ let valueExpr;
1882
+ let onChangeExpr;
1883
+ let directBind;
1884
+ const valueRaw = rawAttrs.get('value');
1885
+ if (valueRaw && ts.isIdentifier(valueRaw) && ctx.stateVars.has(valueRaw.text)) {
1886
+ stateVar = valueRaw.text;
1887
+ }
1888
+ else if (valueRaw) {
1889
+ valueExpr = exprToCpp(valueRaw, ctx);
1890
+ const onChangeRaw = rawAttrs.get('onChange');
1891
+ if (onChangeRaw) {
1892
+ const { paramName, bodyCode } = lowerParameterizedCallback(onChangeRaw, ctx, valueName);
1893
+ onChangeExpr = paramName === valueName
1894
+ ? bodyCode
1895
+ : `auto ${paramName} = ${valueName}; ${bodyCode}`;
1896
+ }
1897
+ else if (ts.isPropertyAccessExpression(valueRaw)) {
1898
+ directBind = true;
1899
+ }
1900
+ }
1901
+ return { stateVar, valueExpr, onChangeExpr, directBind };
1902
+ }
1903
+ function lowerItemsSource(attrs, rawAttrs, ctx) {
1904
+ const itemsRaw = rawAttrs.get('items');
1905
+ if (!itemsRaw) {
1906
+ return { items: attrs['items'] ?? '' };
1907
+ }
1908
+ if (ts.isArrayLiteralExpression(itemsRaw)) {
1909
+ return { items: exprToCpp(itemsRaw, ctx) };
1910
+ }
1911
+ return { items: exprToCpp(itemsRaw, ctx), dynamicItems: true };
1912
+ }
1879
1913
  function lowerSliderFloat(attrs, rawAttrs, body, ctx, loc) {
1880
1914
  const label = attrs['label'] ?? '""';
1881
1915
  const min = attrs['min'] ?? '0.0f';
@@ -1948,12 +1982,12 @@ function lowerDragInt(attrs, rawAttrs, body, ctx, loc) {
1948
1982
  }
1949
1983
  function lowerCombo(attrs, rawAttrs, body, ctx, loc) {
1950
1984
  const label = attrs['label'] ?? '""';
1951
- const items = attrs['items'] ?? '';
1952
1985
  const width = attrs['width'];
1953
1986
  const style = attrs['style'];
1954
- const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
1987
+ const { items, dynamicItems } = lowerItemsSource(attrs, rawAttrs, ctx);
1988
+ const { stateVar, valueExpr, onChangeExpr, directBind } = lowerIndexedValueOnChange(rawAttrs, ctx, 'val');
1955
1989
  const item = lowerItemInteraction(attrs, rawAttrs, ctx);
1956
- body.push({ kind: 'combo', label, stateVar, valueExpr, onChangeExpr, directBind, items, width, style, item, loc });
1990
+ body.push({ kind: 'combo', label, stateVar, valueExpr, onChangeExpr, directBind, items, dynamicItems, width, style, item, loc });
1957
1991
  }
1958
1992
  function lowerInputInt(attrs, rawAttrs, body, ctx, loc) {
1959
1993
  const label = attrs['label'] ?? '""';
@@ -2035,12 +2069,12 @@ function lowerColorEdit3(attrs, rawAttrs, body, ctx, loc) {
2035
2069
  }
2036
2070
  function lowerListBox(attrs, rawAttrs, body, ctx, loc) {
2037
2071
  const label = attrs['label'] ?? '""';
2038
- const items = attrs['items'] ?? '';
2039
2072
  const width = attrs['width'];
2040
2073
  const style = attrs['style'];
2041
- const { stateVar, valueExpr, onChangeExpr, directBind } = lowerValueOnChange(rawAttrs, ctx);
2074
+ const { items, dynamicItems } = lowerItemsSource(attrs, rawAttrs, ctx);
2075
+ const { stateVar, valueExpr, onChangeExpr, directBind } = lowerIndexedValueOnChange(rawAttrs, ctx, 'val');
2042
2076
  const item = lowerItemInteraction(attrs, rawAttrs, ctx);
2043
- body.push({ kind: 'list_box', label, stateVar, valueExpr, onChangeExpr, directBind, items, width, style, item, loc });
2077
+ body.push({ kind: 'list_box', label, stateVar, valueExpr, onChangeExpr, directBind, items, dynamicItems, width, style, item, loc });
2044
2078
  }
2045
2079
  function lowerProgressBar(attrs, rawAttrs, body, ctx, loc) {
2046
2080
  const value = attrs['value'] ?? '0.0f';
@@ -428,7 +428,7 @@ set(FETCHCONTENT_QUIET OFF)
428
428
  FetchContent_Declare(
429
429
  imx
430
430
  GIT_REPOSITORY https://github.com/bgocumlu/imx.git
431
- GIT_TAG v0.6.8
431
+ GIT_TAG v0.6.10
432
432
  GIT_SHALLOW TRUE
433
433
  GIT_PROGRESS TRUE
434
434
  )
@@ -313,7 +313,7 @@ set(FETCHCONTENT_QUIET OFF)
313
313
  FetchContent_Declare(
314
314
  imx
315
315
  GIT_REPOSITORY https://github.com/bgocumlu/imx.git
316
- GIT_TAG v0.6.8
316
+ GIT_TAG v0.6.10
317
317
  GIT_SHALLOW TRUE
318
318
  GIT_PROGRESS TRUE
319
319
  )
@@ -402,7 +402,7 @@ set(FETCHCONTENT_QUIET OFF)
402
402
  FetchContent_Declare(
403
403
  imx
404
404
  GIT_REPOSITORY ${repoUrl}
405
- GIT_TAG v0.6.8
405
+ GIT_TAG v0.6.10
406
406
  GIT_SHALLOW TRUE
407
407
  GIT_PROGRESS TRUE
408
408
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imxc",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "Compiler for IMX — compiles React-like .tsx to native Dear ImGui C++",
5
5
  "type": "module",
6
6
  "bin": {