imxc 0.1.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/components.d.ts +12 -0
- package/dist/components.js +208 -0
- package/dist/emitter.d.ts +12 -0
- package/dist/emitter.js +716 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +119 -0
- package/dist/ir.d.ts +193 -0
- package/dist/ir.js +1 -0
- package/dist/lowering.d.ts +17 -0
- package/dist/lowering.js +788 -0
- package/dist/parser.d.ts +15 -0
- package/dist/parser.js +47 -0
- package/dist/validator.d.ts +14 -0
- package/dist/validator.js +138 -0
- package/package.json +27 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { parseIgxFile, extractImports } from './parser.js';
|
|
6
|
+
import { validate } from './validator.js';
|
|
7
|
+
import { lowerComponent } from './lowering.js';
|
|
8
|
+
import { emitComponent, emitComponentHeader, emitRoot } from './emitter.js';
|
|
9
|
+
const { values, positionals } = parseArgs({
|
|
10
|
+
allowPositionals: true,
|
|
11
|
+
options: { output: { type: 'string', short: 'o' } },
|
|
12
|
+
});
|
|
13
|
+
if (positionals.length === 0) {
|
|
14
|
+
console.error('Usage: imxc <input.tsx ...> -o <output-dir>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const outputDir = values.output ?? '.';
|
|
18
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
19
|
+
let hasErrors = false;
|
|
20
|
+
const compiled = [];
|
|
21
|
+
// Phase 1: Parse, validate, and lower all components
|
|
22
|
+
for (const file of positionals) {
|
|
23
|
+
if (!fs.existsSync(file)) {
|
|
24
|
+
console.error(`${file}:1:1 - error: file not found`);
|
|
25
|
+
hasErrors = true;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const source = fs.readFileSync(file, 'utf-8');
|
|
29
|
+
const parsed = parseIgxFile(file, source);
|
|
30
|
+
if (parsed.errors.length > 0) {
|
|
31
|
+
parsed.errors.forEach(e => console.error(`${e.file}:${e.line}:${e.col} - error: ${e.message}`));
|
|
32
|
+
hasErrors = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const validation = validate(parsed);
|
|
36
|
+
if (validation.errors.length > 0) {
|
|
37
|
+
validation.errors.forEach(e => console.error(`${e.file}:${e.line}:${e.col} - error: ${e.message}`));
|
|
38
|
+
hasErrors = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const ir = lowerComponent(parsed, validation);
|
|
42
|
+
const imports = extractImports(parsed.sourceFile);
|
|
43
|
+
compiled.push({
|
|
44
|
+
name: ir.name,
|
|
45
|
+
stateCount: ir.stateSlots.length,
|
|
46
|
+
bufferCount: ir.bufferCount,
|
|
47
|
+
ir,
|
|
48
|
+
imports,
|
|
49
|
+
hasProps: ir.params.length > 0,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (hasErrors)
|
|
53
|
+
process.exit(1);
|
|
54
|
+
// Phase 2: Build lookup of compiled components for cross-file resolution
|
|
55
|
+
const componentMap = new Map();
|
|
56
|
+
for (const comp of compiled) {
|
|
57
|
+
componentMap.set(comp.name, comp);
|
|
58
|
+
}
|
|
59
|
+
// Phase 3: Resolve imported component stateCount/bufferCount in IR, then emit
|
|
60
|
+
for (const comp of compiled) {
|
|
61
|
+
// Resolve cross-file custom component info
|
|
62
|
+
resolveCustomComponents(comp.ir.body, componentMap);
|
|
63
|
+
// Build import info for #include directives
|
|
64
|
+
const importInfos = [];
|
|
65
|
+
for (const [importedName] of comp.imports) {
|
|
66
|
+
const importedComp = componentMap.get(importedName);
|
|
67
|
+
if (importedComp && importedComp.hasProps) {
|
|
68
|
+
importInfos.push({
|
|
69
|
+
name: importedName,
|
|
70
|
+
headerFile: `${importedName}.gen.h`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Emit .gen.cpp
|
|
75
|
+
const cppOutput = emitComponent(comp.ir, importInfos);
|
|
76
|
+
const baseName = comp.name;
|
|
77
|
+
const outPath = path.join(outputDir, `${baseName}.gen.cpp`);
|
|
78
|
+
fs.writeFileSync(outPath, cppOutput);
|
|
79
|
+
console.log(` ${baseName} -> ${outPath}`);
|
|
80
|
+
// Emit .gen.h for components with props
|
|
81
|
+
if (comp.hasProps) {
|
|
82
|
+
const headerOutput = emitComponentHeader(comp.ir);
|
|
83
|
+
const headerPath = path.join(outputDir, `${baseName}.gen.h`);
|
|
84
|
+
fs.writeFileSync(headerPath, headerOutput);
|
|
85
|
+
console.log(` ${baseName} -> ${headerPath} (header)`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Phase 4: Emit root entry point
|
|
89
|
+
if (compiled.length > 0) {
|
|
90
|
+
const root = compiled[0];
|
|
91
|
+
const rootOutput = emitRoot(root.name, root.stateCount, root.bufferCount);
|
|
92
|
+
const rootPath = path.join(outputDir, 'app_root.gen.cpp');
|
|
93
|
+
fs.writeFileSync(rootPath, rootOutput);
|
|
94
|
+
console.log(` -> ${rootPath} (root entry point)`);
|
|
95
|
+
}
|
|
96
|
+
console.log(`imxc: ${compiled.length} component(s) compiled successfully.`);
|
|
97
|
+
/**
|
|
98
|
+
* Walk IR nodes and update custom_component nodes with resolved stateCount/bufferCount
|
|
99
|
+
* from the compiled component map.
|
|
100
|
+
*/
|
|
101
|
+
function resolveCustomComponents(nodes, map) {
|
|
102
|
+
for (const node of nodes) {
|
|
103
|
+
if (node.kind === 'custom_component') {
|
|
104
|
+
const target = map.get(node.name);
|
|
105
|
+
if (target) {
|
|
106
|
+
node.stateCount = target.stateCount;
|
|
107
|
+
node.bufferCount = target.bufferCount;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (node.kind === 'conditional') {
|
|
111
|
+
resolveCustomComponents(node.body, map);
|
|
112
|
+
if (node.elseBody)
|
|
113
|
+
resolveCustomComponents(node.elseBody, map);
|
|
114
|
+
}
|
|
115
|
+
else if (node.kind === 'list_map') {
|
|
116
|
+
resolveCustomComponents(node.body, map);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
package/dist/ir.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
export type IRType = 'int' | 'float' | 'bool' | 'string' | 'color';
|
|
2
|
+
export interface IRExpr {
|
|
3
|
+
code: string;
|
|
4
|
+
type: IRType;
|
|
5
|
+
}
|
|
6
|
+
export interface IRComponent {
|
|
7
|
+
name: string;
|
|
8
|
+
stateSlots: IRStateSlot[];
|
|
9
|
+
bufferCount: number;
|
|
10
|
+
params: IRPropParam[];
|
|
11
|
+
body: IRNode[];
|
|
12
|
+
}
|
|
13
|
+
export interface IRStateSlot {
|
|
14
|
+
name: string;
|
|
15
|
+
setter: string;
|
|
16
|
+
type: IRType;
|
|
17
|
+
initialValue: string;
|
|
18
|
+
index: number;
|
|
19
|
+
}
|
|
20
|
+
export interface IRPropParam {
|
|
21
|
+
name: string;
|
|
22
|
+
type: IRType | 'callback';
|
|
23
|
+
}
|
|
24
|
+
export type IRNode = IRBeginContainer | IREndContainer | IRText | IRButton | IRTextInput | IRCheckbox | IRSeparator | IRBeginPopup | IREndPopup | IROpenPopup | IRConditional | IRListMap | IRCustomComponent | IRMenuItem | IRSliderFloat | IRSliderInt | IRDragFloat | IRDragInt | IRCombo | IRInputInt | IRInputFloat | IRColorEdit | IRListBox | IRProgressBar | IRTooltip;
|
|
25
|
+
export interface IRBeginContainer {
|
|
26
|
+
kind: 'begin_container';
|
|
27
|
+
tag: 'Window' | 'View' | 'Row' | 'Column' | 'DockSpace' | 'MenuBar' | 'Menu' | 'Table' | 'TableRow' | 'TabBar' | 'TabItem' | 'TreeNode' | 'CollapsingHeader';
|
|
28
|
+
props: Record<string, string>;
|
|
29
|
+
style?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface IREndContainer {
|
|
32
|
+
kind: 'end_container';
|
|
33
|
+
tag: 'Window' | 'View' | 'Row' | 'Column' | 'DockSpace' | 'MenuBar' | 'Menu' | 'Table' | 'TableRow' | 'TabBar' | 'TabItem' | 'TreeNode' | 'CollapsingHeader';
|
|
34
|
+
}
|
|
35
|
+
export interface IRText {
|
|
36
|
+
kind: 'text';
|
|
37
|
+
format: string;
|
|
38
|
+
args: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface IRButton {
|
|
41
|
+
kind: 'button';
|
|
42
|
+
title: string;
|
|
43
|
+
action: string[];
|
|
44
|
+
style?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface IRTextInput {
|
|
47
|
+
kind: 'text_input';
|
|
48
|
+
label: string;
|
|
49
|
+
bufferIndex: number;
|
|
50
|
+
stateVar: string;
|
|
51
|
+
style?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface IRCheckbox {
|
|
54
|
+
kind: 'checkbox';
|
|
55
|
+
label: string;
|
|
56
|
+
stateVar: string;
|
|
57
|
+
valueExpr?: string;
|
|
58
|
+
onChangeExpr?: string;
|
|
59
|
+
style?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface IRSeparator {
|
|
62
|
+
kind: 'separator';
|
|
63
|
+
}
|
|
64
|
+
export interface IRBeginPopup {
|
|
65
|
+
kind: 'begin_popup';
|
|
66
|
+
id: string;
|
|
67
|
+
style?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface IREndPopup {
|
|
70
|
+
kind: 'end_popup';
|
|
71
|
+
}
|
|
72
|
+
export interface IROpenPopup {
|
|
73
|
+
kind: 'open_popup';
|
|
74
|
+
id: string;
|
|
75
|
+
}
|
|
76
|
+
export interface IRConditional {
|
|
77
|
+
kind: 'conditional';
|
|
78
|
+
condition: string;
|
|
79
|
+
body: IRNode[];
|
|
80
|
+
elseBody?: IRNode[];
|
|
81
|
+
}
|
|
82
|
+
export interface IRListMap {
|
|
83
|
+
kind: 'list_map';
|
|
84
|
+
array: string;
|
|
85
|
+
itemVar: string;
|
|
86
|
+
key: string;
|
|
87
|
+
componentName: string;
|
|
88
|
+
stateCount: number;
|
|
89
|
+
bufferCount: number;
|
|
90
|
+
body: IRNode[];
|
|
91
|
+
}
|
|
92
|
+
export interface IRCustomComponent {
|
|
93
|
+
kind: 'custom_component';
|
|
94
|
+
name: string;
|
|
95
|
+
props: Record<string, string>;
|
|
96
|
+
key?: string;
|
|
97
|
+
stateCount: number;
|
|
98
|
+
bufferCount: number;
|
|
99
|
+
}
|
|
100
|
+
export interface IRMenuItem {
|
|
101
|
+
kind: 'menu_item';
|
|
102
|
+
label: string;
|
|
103
|
+
shortcut?: string;
|
|
104
|
+
action: string[];
|
|
105
|
+
}
|
|
106
|
+
export interface IRSliderFloat {
|
|
107
|
+
kind: 'slider_float';
|
|
108
|
+
label: string;
|
|
109
|
+
stateVar: string;
|
|
110
|
+
valueExpr?: string;
|
|
111
|
+
onChangeExpr?: string;
|
|
112
|
+
min: string;
|
|
113
|
+
max: string;
|
|
114
|
+
style?: string;
|
|
115
|
+
}
|
|
116
|
+
export interface IRSliderInt {
|
|
117
|
+
kind: 'slider_int';
|
|
118
|
+
label: string;
|
|
119
|
+
stateVar: string;
|
|
120
|
+
valueExpr?: string;
|
|
121
|
+
onChangeExpr?: string;
|
|
122
|
+
min: string;
|
|
123
|
+
max: string;
|
|
124
|
+
style?: string;
|
|
125
|
+
}
|
|
126
|
+
export interface IRDragFloat {
|
|
127
|
+
kind: 'drag_float';
|
|
128
|
+
label: string;
|
|
129
|
+
stateVar: string;
|
|
130
|
+
valueExpr?: string;
|
|
131
|
+
onChangeExpr?: string;
|
|
132
|
+
speed: string;
|
|
133
|
+
style?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface IRDragInt {
|
|
136
|
+
kind: 'drag_int';
|
|
137
|
+
label: string;
|
|
138
|
+
stateVar: string;
|
|
139
|
+
valueExpr?: string;
|
|
140
|
+
onChangeExpr?: string;
|
|
141
|
+
speed: string;
|
|
142
|
+
style?: string;
|
|
143
|
+
}
|
|
144
|
+
export interface IRCombo {
|
|
145
|
+
kind: 'combo';
|
|
146
|
+
label: string;
|
|
147
|
+
stateVar: string;
|
|
148
|
+
valueExpr?: string;
|
|
149
|
+
onChangeExpr?: string;
|
|
150
|
+
items: string;
|
|
151
|
+
style?: string;
|
|
152
|
+
}
|
|
153
|
+
export interface IRInputInt {
|
|
154
|
+
kind: 'input_int';
|
|
155
|
+
label: string;
|
|
156
|
+
stateVar: string;
|
|
157
|
+
valueExpr?: string;
|
|
158
|
+
onChangeExpr?: string;
|
|
159
|
+
style?: string;
|
|
160
|
+
}
|
|
161
|
+
export interface IRInputFloat {
|
|
162
|
+
kind: 'input_float';
|
|
163
|
+
label: string;
|
|
164
|
+
stateVar: string;
|
|
165
|
+
valueExpr?: string;
|
|
166
|
+
onChangeExpr?: string;
|
|
167
|
+
style?: string;
|
|
168
|
+
}
|
|
169
|
+
export interface IRColorEdit {
|
|
170
|
+
kind: 'color_edit';
|
|
171
|
+
label: string;
|
|
172
|
+
stateVar: string;
|
|
173
|
+
style?: string;
|
|
174
|
+
}
|
|
175
|
+
export interface IRListBox {
|
|
176
|
+
kind: 'list_box';
|
|
177
|
+
label: string;
|
|
178
|
+
stateVar: string;
|
|
179
|
+
valueExpr?: string;
|
|
180
|
+
onChangeExpr?: string;
|
|
181
|
+
items: string;
|
|
182
|
+
style?: string;
|
|
183
|
+
}
|
|
184
|
+
export interface IRProgressBar {
|
|
185
|
+
kind: 'progress_bar';
|
|
186
|
+
value: string;
|
|
187
|
+
overlay?: string;
|
|
188
|
+
style?: string;
|
|
189
|
+
}
|
|
190
|
+
export interface IRTooltip {
|
|
191
|
+
kind: 'tooltip';
|
|
192
|
+
text: string;
|
|
193
|
+
}
|
package/dist/ir.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import type { ParsedFile } from './parser.js';
|
|
3
|
+
import type { ValidationResult } from './validator.js';
|
|
4
|
+
import type { IRComponent, IRStateSlot } from './ir.js';
|
|
5
|
+
interface LoweringContext {
|
|
6
|
+
stateVars: Map<string, IRStateSlot>;
|
|
7
|
+
setterMap: Map<string, string>;
|
|
8
|
+
propsParam: string | null;
|
|
9
|
+
bufferIndex: number;
|
|
10
|
+
sourceFile: ts.SourceFile;
|
|
11
|
+
}
|
|
12
|
+
export declare function lowerComponent(parsed: ParsedFile, validation: ValidationResult): IRComponent;
|
|
13
|
+
/**
|
|
14
|
+
* Convert a TypeScript expression to C++ code string.
|
|
15
|
+
*/
|
|
16
|
+
export declare function exprToCpp(node: ts.Expression, ctx: LoweringContext): string;
|
|
17
|
+
export {};
|