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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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 {};