0x-lang 0.1.18 → 0.1.19
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/ast.d.ts +7 -2
- package/dist/generators/ai-bridge.js +33 -0
- package/dist/generators/ai-bridge.js.map +1 -1
- package/dist/generators/react.js +48 -7
- package/dist/generators/react.js.map +1 -1
- package/dist/generators/svelte.js +10 -1
- package/dist/generators/svelte.js.map +1 -1
- package/dist/generators/vue.js +10 -1
- package/dist/generators/vue.js.map +1 -1
- package/dist/parser.js +74 -5
- package/dist/parser.js.map +1 -1
- package/dist/tokenizer.js +1 -1
- package/dist/tokenizer.js.map +1 -1
- package/dist/validator.js +4 -0
- package/dist/validator.js.map +1 -1
- package/package.json +1 -1
package/dist/ast.d.ts
CHANGED
|
@@ -136,6 +136,7 @@ export interface ComponentCall extends BaseNode {
|
|
|
136
136
|
type: 'ComponentCall';
|
|
137
137
|
name: string;
|
|
138
138
|
args: Record<string, Expression>;
|
|
139
|
+
children?: UINode[];
|
|
139
140
|
}
|
|
140
141
|
export interface IfBlock extends BaseNode {
|
|
141
142
|
type: 'IfBlock';
|
|
@@ -189,6 +190,10 @@ export interface JsBlock extends BaseNode {
|
|
|
189
190
|
type: 'JsBlock';
|
|
190
191
|
code: string;
|
|
191
192
|
}
|
|
193
|
+
export interface RawBlock extends BaseNode {
|
|
194
|
+
type: 'RawBlock';
|
|
195
|
+
code: string;
|
|
196
|
+
}
|
|
192
197
|
export interface TopLevelVarDecl extends BaseNode {
|
|
193
198
|
type: 'TopLevelVarDecl';
|
|
194
199
|
keyword: 'const' | 'let';
|
|
@@ -909,8 +914,8 @@ export interface RtlNode extends BaseNode {
|
|
|
909
914
|
enabled: boolean;
|
|
910
915
|
props: Record<string, Expression>;
|
|
911
916
|
}
|
|
912
|
-
export type UINode = LayoutNode | TextNode | ButtonNode | InputNode | ImageNode | LinkNode | ToggleNode | SelectNode | IfBlock | ForBlock | ShowBlock | HideBlock | ComponentCall | CommentNode | TableNode | ChartNode | StatNode | NavNode | UploadNode | ModalNode | ToastNode | CrudNode | ListNode | LayoutShellNode | SlideOverNode | DrawerNode | CommandNode | ConfirmNode | PayNode | CartNode | MediaNode | NotificationNode | SearchNode | FilterNode | SocialNode | ProfileNode | HeroNode | FeaturesNode | PricingNode | FaqNode | TestimonialNode | FooterNode | AdminNode | SeoNode | A11yNode | AnimateNode | GestureNode | AiNode | AutomationNode | DevNode | EmitNode | ResponsiveNode | BreadcrumbNode | StatsGridNode | DividerNode | ProgressNode | ErrorNode | LoadingNode | OfflineNode | RetryNode | LogNode;
|
|
913
|
-
export type ASTNode = AppNode | PageNode | ComponentNode | StateDecl | DerivedDecl | PropDecl | TypeDecl | StoreDecl | ApiDecl | FnDecl | OnMount | OnDestroy | WatchBlock | CheckDecl | StyleDecl | JsImport | UseImport | JsBlock | TopLevelVarDecl | ModelNode | DataDecl | FormDecl | AuthDecl | RealtimeDecl | RouteDecl | RoleDecl | AutomationNode | DevNode | DeployNode | EnvNode | DockerNode | CiNode | DomainNode | CdnNode | MonitorNode | BackupNode | EndpointNode | MiddlewareNode | QueueNode | CronNode | CacheNode | MigrateNode | SeedNode | WebhookNode | StorageNode | TestNode | E2eNode | MockNode | FixtureNode | I18nNode | LocaleNode | RtlNode | UINode;
|
|
917
|
+
export type UINode = LayoutNode | TextNode | ButtonNode | InputNode | ImageNode | LinkNode | ToggleNode | SelectNode | IfBlock | ForBlock | ShowBlock | HideBlock | ComponentCall | CommentNode | TableNode | ChartNode | StatNode | NavNode | UploadNode | ModalNode | ToastNode | CrudNode | ListNode | LayoutShellNode | SlideOverNode | DrawerNode | CommandNode | ConfirmNode | PayNode | CartNode | MediaNode | NotificationNode | SearchNode | FilterNode | SocialNode | ProfileNode | HeroNode | FeaturesNode | PricingNode | FaqNode | TestimonialNode | FooterNode | AdminNode | SeoNode | A11yNode | AnimateNode | GestureNode | AiNode | AutomationNode | DevNode | EmitNode | ResponsiveNode | BreadcrumbNode | StatsGridNode | DividerNode | ProgressNode | ErrorNode | LoadingNode | OfflineNode | RetryNode | LogNode | RawBlock;
|
|
918
|
+
export type ASTNode = AppNode | PageNode | ComponentNode | StateDecl | DerivedDecl | PropDecl | TypeDecl | StoreDecl | ApiDecl | FnDecl | OnMount | OnDestroy | WatchBlock | CheckDecl | StyleDecl | JsImport | UseImport | JsBlock | RawBlock | TopLevelVarDecl | ModelNode | DataDecl | FormDecl | AuthDecl | RealtimeDecl | RouteDecl | RoleDecl | AutomationNode | DevNode | DeployNode | EnvNode | DockerNode | CiNode | DomainNode | CdnNode | MonitorNode | BackupNode | EndpointNode | MiddlewareNode | QueueNode | CronNode | CacheNode | MigrateNode | SeedNode | WebhookNode | StorageNode | TestNode | E2eNode | MockNode | FixtureNode | I18nNode | LocaleNode | RtlNode | UINode;
|
|
914
919
|
export interface CodeGenerator {
|
|
915
920
|
target: string;
|
|
916
921
|
generate(ast: PageNode | ComponentNode | AppNode): GeneratedCode;
|
|
@@ -102,6 +102,39 @@ api userApi:
|
|
|
102
102
|
- \`chart\` — data visualization (bar, line, pie)
|
|
103
103
|
- \`upload\` — file upload handler
|
|
104
104
|
|
|
105
|
+
### CSS Classes & Passthrough Props
|
|
106
|
+
\`\`\`
|
|
107
|
+
layout col class="container mx-auto p-4":
|
|
108
|
+
text "Title" class="text-xl font-bold"
|
|
109
|
+
button "Save" -> save() class="btn btn-primary"
|
|
110
|
+
input name class="input" aria-label="Name" data-testid="name-input"
|
|
111
|
+
\`\`\`
|
|
112
|
+
- \`class\` prop adds className (React) or class (Vue/Svelte) — works with Tailwind, Bootstrap, etc.
|
|
113
|
+
- Unknown props (data-*, aria-*, role, etc.) are passed through as HTML attributes.
|
|
114
|
+
|
|
115
|
+
### Raw Block (Framework Escape Hatch)
|
|
116
|
+
\`\`\`
|
|
117
|
+
raw:
|
|
118
|
+
<CustomComponent onSpecialEvent={handler} />
|
|
119
|
+
raw { <div dangerouslySetInnerHTML={{__html: content}} /> }
|
|
120
|
+
\`\`\`
|
|
121
|
+
- \`raw:\` inserts code directly into JSX/template output (different from \`js:\` which goes into script).
|
|
122
|
+
- Use for framework-specific code that 0x doesn't abstract.
|
|
123
|
+
|
|
124
|
+
### Component with Children
|
|
125
|
+
\`\`\`
|
|
126
|
+
component Dialog(title="Settings"):
|
|
127
|
+
text "Content inside dialog"
|
|
128
|
+
button "Close" -> close()
|
|
129
|
+
\`\`\`
|
|
130
|
+
- When \`component\` call has \`:\`, children are wrapped inside the component tags.
|
|
131
|
+
- Without \`:\`, component renders as self-closing: \`component Card(item)\`
|
|
132
|
+
|
|
133
|
+
### Performance (React Target)
|
|
134
|
+
- \`fn\` declarations are automatically wrapped in \`useCallback\` with auto-extracted deps.
|
|
135
|
+
- Components with props are wrapped in \`React.memo\` for render optimization.
|
|
136
|
+
- \`derived\` values use \`useMemo\` with auto-extracted deps.
|
|
137
|
+
|
|
105
138
|
### Styles
|
|
106
139
|
\`\`\`
|
|
107
140
|
style card:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-bridge.js","sourceRoot":"","sources":["../../src/generators/ai-bridge.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,iFAAiF;AAKjF,6FAA6F;AAC7F,MAAM,UAAU,eAAe;IAC7B,OAAO
|
|
1
|
+
{"version":3,"file":"ai-bridge.js","sourceRoot":"","sources":["../../src/generators/ai-bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/generators/react.js
CHANGED
|
@@ -277,6 +277,9 @@ function generateTopLevel(node) {
|
|
|
277
277
|
case 'JsBlock':
|
|
278
278
|
hookLines.push(child.code);
|
|
279
279
|
break;
|
|
280
|
+
case 'RawBlock':
|
|
281
|
+
jsxParts.push(child.code);
|
|
282
|
+
break;
|
|
280
283
|
case 'TopLevelVarDecl': {
|
|
281
284
|
const tlv = child;
|
|
282
285
|
hookLines.push(`${tlv.keyword} ${tlv.name} = ${genExpr(tlv.value, c)};`);
|
|
@@ -299,10 +302,13 @@ function generateTopLevel(node) {
|
|
|
299
302
|
const propsArg = props.length > 0
|
|
300
303
|
? `{ ${props.map(p => p.defaultValue ? `${p.name} = ${genExpr(p.defaultValue, c)}` : p.name).join(', ')} }`
|
|
301
304
|
: '';
|
|
305
|
+
// Build component
|
|
306
|
+
const isComponent = node.type === 'Component';
|
|
307
|
+
const needsMemo = isComponent && props.length > 0;
|
|
302
308
|
// Build import line — only import hooks that are actually used
|
|
303
309
|
const hookNames = Array.from(c.imports).sort();
|
|
304
310
|
// Check if React.Fragment is used in JSX (for loops, show/hide with multiple children)
|
|
305
|
-
const needsReact = jsxParts.some(p => p.includes('React.Fragment'));
|
|
311
|
+
const needsReact = jsxParts.some(p => p.includes('React.Fragment')) || needsMemo;
|
|
306
312
|
const importLine = needsReact && hookNames.length > 0
|
|
307
313
|
? `import React, { ${hookNames.join(', ')} } from 'react';`
|
|
308
314
|
: needsReact
|
|
@@ -327,8 +333,6 @@ function generateTopLevel(node) {
|
|
|
327
333
|
userImports.push(`import ${ui.name} from '${ui.source}';`);
|
|
328
334
|
}
|
|
329
335
|
}
|
|
330
|
-
// Build component
|
|
331
|
-
const isComponent = node.type === 'Component';
|
|
332
336
|
const exportKw = isComponent ? '' : 'export default ';
|
|
333
337
|
// Add "use client" for Next.js SSR when client-side hooks are used
|
|
334
338
|
const needsClient = c.imports.has('useState') || c.imports.has('useEffect') || c.imports.has('useRef') || c.imports.has('useCallback');
|
|
@@ -347,9 +351,14 @@ function generateTopLevel(node) {
|
|
|
347
351
|
' );',
|
|
348
352
|
'}',
|
|
349
353
|
];
|
|
350
|
-
// If component, also export it
|
|
354
|
+
// If component, also export it (with React.memo if it has props)
|
|
351
355
|
if (isComponent) {
|
|
352
|
-
|
|
356
|
+
if (needsMemo) {
|
|
357
|
+
lines.push('', `const Memoized${node.name} = React.memo(${node.name});`, `export { Memoized${node.name} as ${node.name} };`);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
lines.push('', `export { ${node.name} };`);
|
|
361
|
+
}
|
|
353
362
|
}
|
|
354
363
|
return lines.filter(l => l !== undefined).join('\n');
|
|
355
364
|
}
|
|
@@ -395,6 +404,7 @@ function genStore(node, c) {
|
|
|
395
404
|
}
|
|
396
405
|
// ── Functions ───────────────────────────────────────
|
|
397
406
|
function genFunction(node, c) {
|
|
407
|
+
c.imports.add('useCallback');
|
|
398
408
|
const params = node.params.map(p => p.name).join(', ');
|
|
399
409
|
const asyncKw = node.isAsync ? 'async ' : '';
|
|
400
410
|
const body = node.body.map(s => genStatement(s, c)).join('\n ');
|
|
@@ -402,7 +412,11 @@ function genFunction(node, c) {
|
|
|
402
412
|
const requireChecks = node.requires.map(r => `if (!(${genExpr(r, c)})) throw new Error('Precondition failed');`).join('\n ');
|
|
403
413
|
const allBody = [requireChecks, body].filter(Boolean).join('\n ');
|
|
404
414
|
const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
|
|
405
|
-
|
|
415
|
+
// Extract deps from function body (exclude function params)
|
|
416
|
+
const paramNames = new Set(node.params.map(p => p.name));
|
|
417
|
+
const { deps, warning } = extractDepsFromStmts(node.body, c, paramNames);
|
|
418
|
+
const warnComment = warning ? `${warning}\n ` : '';
|
|
419
|
+
return `${sc}${warnComment}const ${node.name} = useCallback(${asyncKw}(${params}) => {\n ${allBody}\n }, [${deps.join(', ')}]);`;
|
|
406
420
|
}
|
|
407
421
|
// ── Lifecycle ───────────────────────────────────────
|
|
408
422
|
function genOnMount(node, c) {
|
|
@@ -451,6 +465,7 @@ function genUINode(node, c) {
|
|
|
451
465
|
case 'ShowBlock': return genShow(node, c);
|
|
452
466
|
case 'HideBlock': return genHide(node, c);
|
|
453
467
|
case 'ComponentCall': return genComponentCall(node, c);
|
|
468
|
+
case 'RawBlock': return node.code;
|
|
454
469
|
case 'Table': return genTableUI(node, c);
|
|
455
470
|
case 'Chart': return genChartUI(node, c);
|
|
456
471
|
case 'Stat': return genStatUI(node, c);
|
|
@@ -862,7 +877,13 @@ function genComponentCall(node, c) {
|
|
|
862
877
|
parts.push(`${key}={${genExpr(val, c)}}`);
|
|
863
878
|
}
|
|
864
879
|
}
|
|
865
|
-
|
|
880
|
+
const propsStr = parts.length > 0 ? ` ${parts.join(' ')}` : '';
|
|
881
|
+
// Component with children: <Name props>{children}</Name>
|
|
882
|
+
if (node.children && node.children.length > 0) {
|
|
883
|
+
const childrenJsx = node.children.map(ch => genUINode(ch, c)).join('\n');
|
|
884
|
+
return `<${node.name}${propsStr}>\n${childrenJsx}\n</${node.name}>`;
|
|
885
|
+
}
|
|
886
|
+
return `<${node.name}${propsStr} />`;
|
|
866
887
|
}
|
|
867
888
|
// ── Control Flow ────────────────────────────────────
|
|
868
889
|
// Wrap ternary branch body in fragment if it contains JS expressions ({...})
|
|
@@ -2583,6 +2604,26 @@ function extractDepsWithWarning(expr, c) {
|
|
|
2583
2604
|
: null;
|
|
2584
2605
|
return { deps: Array.from(deps), warning };
|
|
2585
2606
|
}
|
|
2607
|
+
function extractDepsFromStmts(stmts, c, paramNames) {
|
|
2608
|
+
const deps = new Set();
|
|
2609
|
+
const unknowns = new Set();
|
|
2610
|
+
walkStmts(stmts, e => {
|
|
2611
|
+
if (e.kind === 'identifier') {
|
|
2612
|
+
if (paramNames.has(e.name))
|
|
2613
|
+
return; // Skip function params
|
|
2614
|
+
if (c.states.has(e.name) || c.derivedNames.has(e.name) || c.propNames.has(e.name)) {
|
|
2615
|
+
deps.add(e.name);
|
|
2616
|
+
}
|
|
2617
|
+
else if (!KNOWN_GLOBALS.has(e.name)) {
|
|
2618
|
+
unknowns.add(e.name);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
const warning = unknowns.size > 0
|
|
2623
|
+
? `/* 0x-warn: untracked deps: ${Array.from(unknowns).join(', ')} */`
|
|
2624
|
+
: null;
|
|
2625
|
+
return { deps: Array.from(deps), warning };
|
|
2626
|
+
}
|
|
2586
2627
|
function walkExpr(expr, fn) {
|
|
2587
2628
|
fn(expr);
|
|
2588
2629
|
switch (expr.kind) {
|