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 +28 -0
- package/dist/compile.js +66 -4
- package/dist/components.js +10 -10
- package/dist/emitter.d.ts +1 -1
- package/dist/emitter.js +171 -47
- package/dist/init.js +409 -388
- package/dist/ir.d.ts +14 -0
- package/dist/lowering.d.ts +3 -2
- package/dist/lowering.js +137 -34
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/components.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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;
|