imxc 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # imxc
2
+
3
+ Compiler for IMX. Compiles React-like `.tsx` to native Dear ImGui C++ apps.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ # Scaffold a new project
9
+ npx imxc init myapp
10
+ cd myapp
11
+ cmake -B build
12
+ cmake --build build --config Release
13
+
14
+ # Add to existing CMake project
15
+ npx imxc add
16
+
17
+ # Compile TSX manually
18
+ imxc App.tsx -o build/generated
19
+
20
+ # Watch mode
21
+ imxc watch src -o build/generated
22
+ ```
23
+
24
+ 54 components, 5-prop theme system, custom C++ widgets, canvas drawing, drag-drop.
25
+
26
+ Requires: Node.js, CMake 3.25+, C++20 compiler.
27
+
28
+ [GitHub](https://github.com/bgocumlu/imx) | [API Reference](https://github.com/bgocumlu/imx/blob/main/docs/api-reference.md)
package/dist/compile.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import ts from 'typescript';
3
4
  import { parseFile, extractImports } from './parser.js';
4
5
  import { validate } from './validator.js';
5
6
  import { lowerComponent } from './lowering.js';
@@ -35,7 +36,9 @@ export function compile(files, outputDir) {
35
36
  hasErrors = true;
36
37
  continue;
37
38
  }
38
- const ir = lowerComponent(parsed, validation);
39
+ // Load external interface definitions from imx.d.ts in the same directory
40
+ const externalInterfaces = loadExternalInterfaces(path.dirname(path.resolve(file)));
41
+ const ir = lowerComponent(parsed, validation, externalInterfaces);
39
42
  const imports = extractImports(parsed.sourceFile);
40
43
  compiled.push({
41
44
  name: ir.name,
@@ -45,7 +48,7 @@ export function compile(files, outputDir) {
45
48
  bufferCount: ir.bufferCount,
46
49
  ir,
47
50
  imports,
48
- hasProps: ir.params.length > 0,
51
+ hasProps: ir.params.length > 0 || !!ir.namedPropsType,
49
52
  });
50
53
  }
51
54
  if (hasErrors) {
@@ -80,7 +83,9 @@ export function compile(files, outputDir) {
80
83
  const sourceDir = path.dirname(path.resolve(comp.sourcePath));
81
84
  generateEmbedHeaders(embedImages, sourceDir, outputDir);
82
85
  }
83
- if (comp.hasProps) {
86
+ // Only generate a header for inline props (not for named interface types —
87
+ // those are declared in the user's main.cpp)
88
+ if (comp.hasProps && !comp.ir.namedPropsType) {
84
89
  const headerOutput = emitComponentHeader(comp.ir, comp.sourceFile);
85
90
  const headerPath = path.join(outputDir, `${baseName}.gen.h`);
86
91
  fs.writeFileSync(headerPath, headerOutput);
@@ -90,7 +95,12 @@ export function compile(files, outputDir) {
90
95
  // Phase 4: Emit root entry point
91
96
  if (compiled.length > 0) {
92
97
  const root = compiled[0];
93
- const rootOutput = emitRoot(root.name, root.stateCount, root.bufferCount, root.sourceFile);
98
+ // Use namedPropsType if available (e.g. "AppState"), else ComponentNameProps for inline props
99
+ const isNamedPropsType = !!root.ir.namedPropsType;
100
+ const propsType = root.ir.namedPropsType
101
+ ? root.ir.namedPropsType
102
+ : root.hasProps ? root.name + 'Props' : undefined;
103
+ const rootOutput = emitRoot(root.name, root.stateCount, root.bufferCount, root.sourceFile, propsType, isNamedPropsType);
94
104
  const rootPath = path.join(outputDir, 'app_root.gen.cpp');
95
105
  fs.writeFileSync(rootPath, rootOutput);
96
106
  console.log(` -> ${rootPath} (root entry point)`);
@@ -167,3 +177,55 @@ function generateEmbedHeaders(images, sourceDir, outputDir) {
167
177
  console.log(` ${rawSrc} -> ${headerPath} (embed)`);
168
178
  }
169
179
  }
180
+ /**
181
+ * Parse the imx.d.ts in the given directory (if present) and extract
182
+ * all interface declarations as a map from interface name -> field name -> type.
183
+ */
184
+ function loadExternalInterfaces(dir) {
185
+ const result = new Map();
186
+ const dtsPath = path.join(dir, 'imx.d.ts');
187
+ if (!fs.existsSync(dtsPath))
188
+ return result;
189
+ const source = fs.readFileSync(dtsPath, 'utf-8');
190
+ const sf = ts.createSourceFile('imx.d.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
191
+ for (const stmt of sf.statements) {
192
+ if (ts.isInterfaceDeclaration(stmt)) {
193
+ const ifName = stmt.name.text;
194
+ const fields = new Map();
195
+ for (const member of stmt.members) {
196
+ if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
197
+ const fieldName = member.name.text;
198
+ if (!member.type) {
199
+ fields.set(fieldName, 'string');
200
+ continue;
201
+ }
202
+ if (ts.isFunctionTypeNode(member.type)) {
203
+ fields.set(fieldName, 'callback');
204
+ continue;
205
+ }
206
+ const typeText = member.type.getText(sf);
207
+ if (typeText === 'number') {
208
+ fields.set(fieldName, 'float');
209
+ }
210
+ else if (typeText === 'boolean') {
211
+ fields.set(fieldName, 'bool');
212
+ }
213
+ else if (typeText === 'string') {
214
+ fields.set(fieldName, 'string');
215
+ }
216
+ else {
217
+ // Arrays, nested interfaces, etc. treated as opaque (non-scalar)
218
+ fields.set(fieldName, 'string');
219
+ }
220
+ }
221
+ else if (ts.isMethodSignature(member)) {
222
+ const mName = ts.isIdentifier(member.name) ? member.name.text : '';
223
+ if (mName)
224
+ fields.set(mName, 'callback');
225
+ }
226
+ }
227
+ result.set(ifName, fields);
228
+ }
229
+ }
230
+ return result;
231
+ }
@@ -52,7 +52,7 @@ export const HOST_COMPONENTS = {
52
52
  Checkbox: {
53
53
  props: {
54
54
  value: { type: 'boolean', required: true },
55
- onChange: { type: 'callback', required: true },
55
+ onChange: { type: 'callback', required: false },
56
56
  label: { type: 'string', required: false },
57
57
  style: { type: 'style', required: false },
58
58
  },
@@ -87,7 +87,7 @@ export const HOST_COMPONENTS = {
87
87
  hasChildren: false, isContainer: false,
88
88
  },
89
89
  Table: {
90
- props: { columns: { type: 'string', required: true }, style: { type: 'style', required: false } },
90
+ props: { columns: { type: 'string', required: true }, scrollY: { type: 'boolean', required: false }, noBorders: { type: 'boolean', required: false }, noRowBg: { type: 'boolean', required: false }, style: { type: 'style', required: false } },
91
91
  hasChildren: true, isContainer: true,
92
92
  },
93
93
  TableRow: {
@@ -114,7 +114,7 @@ export const HOST_COMPONENTS = {
114
114
  props: {
115
115
  label: { type: 'string', required: true },
116
116
  value: { type: 'number', required: true },
117
- onChange: { type: 'callback', required: true },
117
+ onChange: { type: 'callback', required: false },
118
118
  min: { type: 'number', required: true },
119
119
  max: { type: 'number', required: true },
120
120
  style: { type: 'style', required: false },
@@ -125,7 +125,7 @@ export const HOST_COMPONENTS = {
125
125
  props: {
126
126
  label: { type: 'string', required: true },
127
127
  value: { type: 'number', required: true },
128
- onChange: { type: 'callback', required: true },
128
+ onChange: { type: 'callback', required: false },
129
129
  min: { type: 'number', required: true },
130
130
  max: { type: 'number', required: true },
131
131
  style: { type: 'style', required: false },
@@ -136,7 +136,7 @@ export const HOST_COMPONENTS = {
136
136
  props: {
137
137
  label: { type: 'string', required: true },
138
138
  value: { type: 'number', required: true },
139
- onChange: { type: 'callback', required: true },
139
+ onChange: { type: 'callback', required: false },
140
140
  speed: { type: 'number', required: false },
141
141
  style: { type: 'style', required: false },
142
142
  },
@@ -146,7 +146,7 @@ export const HOST_COMPONENTS = {
146
146
  props: {
147
147
  label: { type: 'string', required: true },
148
148
  value: { type: 'number', required: true },
149
- onChange: { type: 'callback', required: true },
149
+ onChange: { type: 'callback', required: false },
150
150
  speed: { type: 'number', required: false },
151
151
  style: { type: 'style', required: false },
152
152
  },
@@ -156,7 +156,7 @@ export const HOST_COMPONENTS = {
156
156
  props: {
157
157
  label: { type: 'string', required: true },
158
158
  value: { type: 'number', required: true },
159
- onChange: { type: 'callback', required: true },
159
+ onChange: { type: 'callback', required: false },
160
160
  items: { type: 'string', required: true },
161
161
  style: { type: 'style', required: false },
162
162
  },
@@ -166,7 +166,7 @@ export const HOST_COMPONENTS = {
166
166
  props: {
167
167
  label: { type: 'string', required: true },
168
168
  value: { type: 'number', required: true },
169
- onChange: { type: 'callback', required: true },
169
+ onChange: { type: 'callback', required: false },
170
170
  style: { type: 'style', required: false },
171
171
  },
172
172
  hasChildren: false, isContainer: false,
@@ -175,7 +175,7 @@ export const HOST_COMPONENTS = {
175
175
  props: {
176
176
  label: { type: 'string', required: true },
177
177
  value: { type: 'number', required: true },
178
- onChange: { type: 'callback', required: true },
178
+ onChange: { type: 'callback', required: false },
179
179
  style: { type: 'style', required: false },
180
180
  },
181
181
  hasChildren: false, isContainer: false,
@@ -193,7 +193,7 @@ export const HOST_COMPONENTS = {
193
193
  props: {
194
194
  label: { type: 'string', required: true },
195
195
  value: { type: 'number', required: true },
196
- onChange: { type: 'callback', required: true },
196
+ onChange: { type: 'callback', required: false },
197
197
  items: { type: 'string', required: true },
198
198
  style: { type: 'style', required: false },
199
199
  },
package/dist/emitter.d.ts CHANGED
@@ -9,4 +9,4 @@ export interface ImportInfo {
9
9
  headerFile: string;
10
10
  }
11
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;
12
+ export declare function emitRoot(rootName: string, stateCount: number, bufferCount: number, sourceFile?: string, propsType?: string, namedPropsType?: boolean): string;