@zenithbuild/compiler 1.0.2

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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/build-analyzer.d.ts +44 -0
  4. package/dist/build-analyzer.js +87 -0
  5. package/dist/bundler.d.ts +31 -0
  6. package/dist/bundler.js +86 -0
  7. package/dist/core/components/index.d.ts +9 -0
  8. package/dist/core/components/index.js +13 -0
  9. package/dist/core/config/index.d.ts +11 -0
  10. package/dist/core/config/index.js +10 -0
  11. package/dist/core/config/loader.d.ts +17 -0
  12. package/dist/core/config/loader.js +60 -0
  13. package/dist/core/config/types.d.ts +98 -0
  14. package/dist/core/config/types.js +32 -0
  15. package/dist/core/index.d.ts +7 -0
  16. package/dist/core/index.js +6 -0
  17. package/dist/core/lifecycle/index.d.ts +16 -0
  18. package/dist/core/lifecycle/index.js +19 -0
  19. package/dist/core/lifecycle/zen-mount.d.ts +66 -0
  20. package/dist/core/lifecycle/zen-mount.js +151 -0
  21. package/dist/core/lifecycle/zen-unmount.d.ts +54 -0
  22. package/dist/core/lifecycle/zen-unmount.js +76 -0
  23. package/dist/core/plugins/bridge.d.ts +116 -0
  24. package/dist/core/plugins/bridge.js +121 -0
  25. package/dist/core/plugins/index.d.ts +6 -0
  26. package/dist/core/plugins/index.js +6 -0
  27. package/dist/core/plugins/registry.d.ts +67 -0
  28. package/dist/core/plugins/registry.js +113 -0
  29. package/dist/core/reactivity/index.d.ts +30 -0
  30. package/dist/core/reactivity/index.js +33 -0
  31. package/dist/core/reactivity/tracking.d.ts +74 -0
  32. package/dist/core/reactivity/tracking.js +136 -0
  33. package/dist/core/reactivity/zen-batch.d.ts +45 -0
  34. package/dist/core/reactivity/zen-batch.js +54 -0
  35. package/dist/core/reactivity/zen-effect.d.ts +48 -0
  36. package/dist/core/reactivity/zen-effect.js +98 -0
  37. package/dist/core/reactivity/zen-memo.d.ts +43 -0
  38. package/dist/core/reactivity/zen-memo.js +100 -0
  39. package/dist/core/reactivity/zen-ref.d.ts +44 -0
  40. package/dist/core/reactivity/zen-ref.js +34 -0
  41. package/dist/core/reactivity/zen-signal.d.ts +48 -0
  42. package/dist/core/reactivity/zen-signal.js +84 -0
  43. package/dist/core/reactivity/zen-state.d.ts +35 -0
  44. package/dist/core/reactivity/zen-state.js +147 -0
  45. package/dist/core/reactivity/zen-untrack.d.ts +38 -0
  46. package/dist/core/reactivity/zen-untrack.js +41 -0
  47. package/dist/css/index.d.ts +73 -0
  48. package/dist/css/index.js +246 -0
  49. package/dist/discovery/componentDiscovery.d.ts +42 -0
  50. package/dist/discovery/componentDiscovery.js +56 -0
  51. package/dist/discovery/layouts.d.ts +13 -0
  52. package/dist/discovery/layouts.js +41 -0
  53. package/dist/errors/compilerError.d.ts +31 -0
  54. package/dist/errors/compilerError.js +51 -0
  55. package/dist/finalize/finalizeOutput.d.ts +32 -0
  56. package/dist/finalize/finalizeOutput.js +62 -0
  57. package/dist/finalize/generateFinalBundle.d.ts +24 -0
  58. package/dist/finalize/generateFinalBundle.js +68 -0
  59. package/dist/index.d.ts +36 -0
  60. package/dist/index.js +51 -0
  61. package/dist/ir/types.d.ts +181 -0
  62. package/dist/ir/types.js +8 -0
  63. package/dist/output/types.d.ts +30 -0
  64. package/dist/output/types.js +6 -0
  65. package/dist/parse/detectMapExpressions.d.ts +45 -0
  66. package/dist/parse/detectMapExpressions.js +77 -0
  67. package/dist/parse/parseScript.d.ts +8 -0
  68. package/dist/parse/parseScript.js +36 -0
  69. package/dist/parse/parseTemplate.d.ts +11 -0
  70. package/dist/parse/parseTemplate.js +487 -0
  71. package/dist/parse/parseZenFile.d.ts +11 -0
  72. package/dist/parse/parseZenFile.js +50 -0
  73. package/dist/parse/scriptAnalysis.d.ts +25 -0
  74. package/dist/parse/scriptAnalysis.js +60 -0
  75. package/dist/parse/trackLoopContext.d.ts +20 -0
  76. package/dist/parse/trackLoopContext.js +62 -0
  77. package/dist/parseZenFile.d.ts +10 -0
  78. package/dist/parseZenFile.js +55 -0
  79. package/dist/runtime/analyzeAndEmit.d.ts +20 -0
  80. package/dist/runtime/analyzeAndEmit.js +70 -0
  81. package/dist/runtime/build.d.ts +6 -0
  82. package/dist/runtime/build.js +13 -0
  83. package/dist/runtime/bundle-generator.d.ts +27 -0
  84. package/dist/runtime/bundle-generator.js +1263 -0
  85. package/dist/runtime/client-runtime.d.ts +41 -0
  86. package/dist/runtime/client-runtime.js +397 -0
  87. package/dist/runtime/dataExposure.d.ts +52 -0
  88. package/dist/runtime/dataExposure.js +227 -0
  89. package/dist/runtime/generateDOM.d.ts +21 -0
  90. package/dist/runtime/generateDOM.js +194 -0
  91. package/dist/runtime/generateHydrationBundle.d.ts +15 -0
  92. package/dist/runtime/generateHydrationBundle.js +399 -0
  93. package/dist/runtime/hydration.d.ts +53 -0
  94. package/dist/runtime/hydration.js +271 -0
  95. package/dist/runtime/navigation.d.ts +58 -0
  96. package/dist/runtime/navigation.js +372 -0
  97. package/dist/runtime/serve.d.ts +13 -0
  98. package/dist/runtime/serve.js +76 -0
  99. package/dist/runtime/thinRuntime.d.ts +23 -0
  100. package/dist/runtime/thinRuntime.js +158 -0
  101. package/dist/runtime/transformIR.d.ts +19 -0
  102. package/dist/runtime/transformIR.js +285 -0
  103. package/dist/runtime/wrapExpression.d.ts +24 -0
  104. package/dist/runtime/wrapExpression.js +76 -0
  105. package/dist/runtime/wrapExpressionWithLoop.d.ts +17 -0
  106. package/dist/runtime/wrapExpressionWithLoop.js +75 -0
  107. package/dist/spa-build.d.ts +26 -0
  108. package/dist/spa-build.js +866 -0
  109. package/dist/ssg-build.d.ts +32 -0
  110. package/dist/ssg-build.js +408 -0
  111. package/dist/test/analyze-emit.test.d.ts +1 -0
  112. package/dist/test/analyze-emit.test.js +88 -0
  113. package/dist/test/bundler-contract.test.d.ts +1 -0
  114. package/dist/test/bundler-contract.test.js +137 -0
  115. package/dist/test/compiler-authority.test.d.ts +1 -0
  116. package/dist/test/compiler-authority.test.js +90 -0
  117. package/dist/test/component-instance-test.d.ts +1 -0
  118. package/dist/test/component-instance-test.js +115 -0
  119. package/dist/test/error-native-bridge.test.d.ts +1 -0
  120. package/dist/test/error-native-bridge.test.js +51 -0
  121. package/dist/test/error-serialization.test.d.ts +1 -0
  122. package/dist/test/error-serialization.test.js +38 -0
  123. package/dist/test/macro-inlining.test.d.ts +1 -0
  124. package/dist/test/macro-inlining.test.js +178 -0
  125. package/dist/test/validate-test.d.ts +6 -0
  126. package/dist/test/validate-test.js +95 -0
  127. package/dist/transform/classifyExpression.d.ts +46 -0
  128. package/dist/transform/classifyExpression.js +354 -0
  129. package/dist/transform/componentResolver.d.ts +15 -0
  130. package/dist/transform/componentResolver.js +30 -0
  131. package/dist/transform/expressionTransformer.d.ts +19 -0
  132. package/dist/transform/expressionTransformer.js +333 -0
  133. package/dist/transform/fragmentLowering.d.ts +25 -0
  134. package/dist/transform/fragmentLowering.js +468 -0
  135. package/dist/transform/layoutProcessor.d.ts +5 -0
  136. package/dist/transform/layoutProcessor.js +34 -0
  137. package/dist/transform/transformTemplate.d.ts +11 -0
  138. package/dist/transform/transformTemplate.js +33 -0
  139. package/dist/validate/invariants.d.ts +23 -0
  140. package/dist/validate/invariants.js +55 -0
  141. package/native/compiler-native/compiler-native.node +0 -0
  142. package/native/compiler-native/index.d.ts +113 -0
  143. package/native/compiler-native/index.js +19 -0
  144. package/native/compiler-native/package.json +19 -0
  145. package/package.json +49 -0
@@ -0,0 +1,487 @@
1
+ /**
2
+ * Template Parser
3
+ *
4
+ * Parses HTML template and extracts expressions
5
+ * Phase 1: Only extracts, does not execute
6
+ */
7
+ import { parseFragment } from 'parse5';
8
+ import { CompilerError, InvariantError } from '../errors/compilerError';
9
+ import { shouldAttachLoopContext, mergeLoopContext, extractLoopContextFromExpression } from './trackLoopContext';
10
+ import { INVARIANT } from '../validate/invariants';
11
+ import { lowerFragments } from '../transform/fragmentLowering';
12
+ // Generate stable IDs for expressions
13
+ let expressionIdCounter = 0;
14
+ function generateExpressionId() {
15
+ return `expr_${expressionIdCounter++}`;
16
+ }
17
+ /**
18
+ * Strip script and style blocks from HTML before parsing
19
+ */
20
+ function stripBlocks(html) {
21
+ // Remove script blocks
22
+ let stripped = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '');
23
+ // Remove style blocks
24
+ stripped = stripped.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
25
+ return stripped;
26
+ }
27
+ /**
28
+ * Find the end of a balanced brace expression, handling strings and template literals
29
+ * Returns the index after the closing brace, or -1 if unbalanced
30
+ */
31
+ function findBalancedBraceEnd(html, startIndex) {
32
+ let braceCount = 1;
33
+ let i = startIndex + 1;
34
+ let inString = false;
35
+ let stringChar = '';
36
+ let inTemplate = false;
37
+ while (i < html.length && braceCount > 0) {
38
+ const char = html[i];
39
+ const prevChar = i > 0 ? html[i - 1] : '';
40
+ // Handle escape sequences
41
+ if (prevChar === '\\') {
42
+ i++;
43
+ continue;
44
+ }
45
+ // Handle string literals (not inside template)
46
+ if (!inString && !inTemplate && (char === '"' || char === "'")) {
47
+ inString = true;
48
+ stringChar = char;
49
+ i++;
50
+ continue;
51
+ }
52
+ if (inString && char === stringChar) {
53
+ inString = false;
54
+ stringChar = '';
55
+ i++;
56
+ continue;
57
+ }
58
+ // Handle template literals
59
+ if (!inString && !inTemplate && char === '`') {
60
+ inTemplate = true;
61
+ i++;
62
+ continue;
63
+ }
64
+ if (inTemplate && char === '`') {
65
+ inTemplate = false;
66
+ i++;
67
+ continue;
68
+ }
69
+ // Handle ${} inside template literals - need to track nested braces
70
+ if (inTemplate && char === '$' && html[i + 1] === '{') {
71
+ // Skip the ${ and count as opening brace
72
+ i += 2;
73
+ let templateBraceCount = 1;
74
+ while (i < html.length && templateBraceCount > 0) {
75
+ if (html[i] === '{')
76
+ templateBraceCount++;
77
+ else if (html[i] === '}')
78
+ templateBraceCount--;
79
+ i++;
80
+ }
81
+ continue;
82
+ }
83
+ // Count braces only when not in strings or templates
84
+ if (!inString && !inTemplate) {
85
+ if (char === '{')
86
+ braceCount++;
87
+ else if (char === '}')
88
+ braceCount--;
89
+ }
90
+ i++;
91
+ }
92
+ return braceCount === 0 ? i : -1;
93
+ }
94
+ /**
95
+ * Normalize expressions before parsing
96
+ * Replaces both attr={expr} and {textExpr} with placeholders so parse5 can parse the HTML correctly
97
+ * without being confused by tags or braces inside expressions.
98
+ *
99
+ * Uses balanced brace parsing to correctly handle:
100
+ * - String literals with braces inside
101
+ * - Template literals with ${} interpolations
102
+ * - Arrow functions with object returns
103
+ * - Multi-line JSX expressions
104
+ */
105
+ function normalizeAllExpressions(html) {
106
+ const exprMap = new Map();
107
+ let exprCounter = 0;
108
+ let result = '';
109
+ let lastPos = 0;
110
+ for (let i = 0; i < html.length; i++) {
111
+ // Look for { and check if it's an expression
112
+ // We handle both text expressions and attribute expressions: attr={...}
113
+ if (html[i] === '{') {
114
+ const j = findBalancedBraceEnd(html, i);
115
+ if (j !== -1 && j > i + 1) {
116
+ const expr = html.substring(i + 1, j - 1).trim();
117
+ // Skip empty expressions
118
+ if (expr.length === 0) {
119
+ i++;
120
+ continue;
121
+ }
122
+ const placeholder = `__ZEN_EXPR_${exprCounter++}`;
123
+ exprMap.set(placeholder, expr);
124
+ result += html.substring(lastPos, i);
125
+ result += placeholder;
126
+ lastPos = j;
127
+ i = j - 1;
128
+ }
129
+ }
130
+ }
131
+ result += html.substring(lastPos);
132
+ return { normalized: result, expressions: exprMap };
133
+ }
134
+ /**
135
+ * Calculate source location from parse5 node
136
+ */
137
+ function getLocation(node, originalHtml) {
138
+ // parse5 provides sourceCodeLocation if available
139
+ if (node.sourceCodeLocation) {
140
+ return {
141
+ line: node.sourceCodeLocation.startLine || 1,
142
+ column: node.sourceCodeLocation.startCol || 1
143
+ };
144
+ }
145
+ // Fallback if location info not available
146
+ return { line: 1, column: 1 };
147
+ }
148
+ /**
149
+ * Extract expressions from text content
150
+ * Returns array of { expression, location } and the text with expressions replaced
151
+ * Phase 7: Supports loop context for expressions inside map iterations
152
+ */
153
+ function extractExpressionsFromText(text, baseLocation, expressions, normalizedExprs, loopContext) {
154
+ const nodes = [];
155
+ let processedText = '';
156
+ let currentIndex = 0;
157
+ // Match __ZEN_EXPR_N placeholders
158
+ const expressionRegex = /__ZEN_EXPR_\d+/g;
159
+ let match;
160
+ while ((match = expressionRegex.exec(text)) !== null) {
161
+ const beforeExpr = text.substring(currentIndex, match.index);
162
+ if (beforeExpr) {
163
+ nodes.push({
164
+ type: 'text',
165
+ value: beforeExpr,
166
+ location: {
167
+ line: baseLocation.line,
168
+ column: baseLocation.column + currentIndex
169
+ }
170
+ });
171
+ processedText += beforeExpr;
172
+ }
173
+ // Resolve placeholder to original expression code
174
+ const placeholder = match[0];
175
+ const exprCode = (normalizedExprs.get(placeholder) || '').trim();
176
+ const exprId = generateExpressionId();
177
+ const exprLocation = {
178
+ line: baseLocation.line,
179
+ column: baseLocation.column + match.index
180
+ };
181
+ const exprIR = {
182
+ id: exprId,
183
+ code: exprCode,
184
+ location: exprLocation
185
+ };
186
+ expressions.push(exprIR);
187
+ // Phase 7: Loop context detection and attachment
188
+ const mapLoopContext = extractLoopContextFromExpression(exprIR);
189
+ const activeLoopContext = mergeLoopContext(loopContext, mapLoopContext);
190
+ const attachedLoopContext = shouldAttachLoopContext(exprIR, activeLoopContext);
191
+ nodes.push({
192
+ type: 'expression',
193
+ expression: exprId,
194
+ location: exprLocation,
195
+ loopContext: attachedLoopContext
196
+ });
197
+ processedText += `{${exprCode}}`;
198
+ currentIndex = match.index + match[0].length;
199
+ }
200
+ // Add remaining text
201
+ const remaining = text.substring(currentIndex);
202
+ if (remaining) {
203
+ nodes.push({
204
+ type: 'text',
205
+ value: remaining,
206
+ location: {
207
+ line: baseLocation.line,
208
+ column: baseLocation.column + currentIndex
209
+ }
210
+ });
211
+ processedText += remaining;
212
+ }
213
+ // If no expressions found, return single text node
214
+ if (nodes.length === 0) {
215
+ nodes.push({
216
+ type: 'text',
217
+ value: text,
218
+ location: baseLocation
219
+ });
220
+ processedText = text;
221
+ }
222
+ return { processedText, nodes };
223
+ }
224
+ /**
225
+ * Parse attribute value - may contain expressions
226
+ * Phase 7: Supports loop context for expressions inside map iterations
227
+ */
228
+ function parseAttributeValue(value, baseLocation, expressions, normalizedExprs, loopContext // Phase 7: Loop context from parent map expressions
229
+ ) {
230
+ // Check if this is a normalized expression placeholder
231
+ if (value.startsWith('__ZEN_EXPR_')) {
232
+ const exprCode = normalizedExprs.get(value);
233
+ if (!exprCode) {
234
+ throw new Error(`Normalized expression placeholder not found: ${value}`);
235
+ }
236
+ const exprId = generateExpressionId();
237
+ expressions.push({
238
+ id: exprId,
239
+ code: exprCode,
240
+ location: baseLocation
241
+ });
242
+ return {
243
+ id: exprId,
244
+ code: exprCode,
245
+ location: baseLocation
246
+ };
247
+ }
248
+ // Check if attribute value is an expression { ... } (shouldn't happen after normalization)
249
+ const exprMatch = value.match(/^\{([^}]+)\}$/);
250
+ if (exprMatch && exprMatch[1]) {
251
+ const exprCode = exprMatch[1].trim();
252
+ const exprId = generateExpressionId();
253
+ expressions.push({
254
+ id: exprId,
255
+ code: exprCode,
256
+ location: baseLocation
257
+ });
258
+ return {
259
+ id: exprId,
260
+ code: exprCode,
261
+ location: baseLocation
262
+ };
263
+ }
264
+ // Regular string value
265
+ return value;
266
+ }
267
+ /**
268
+ * Convert parse5 node to TemplateNode
269
+ * Phase 7: Supports loop context propagation for map expressions
270
+ */
271
+ function parseNode(node, originalHtml, expressions, normalizedExprs, parentLoopContext // Phase 7: Loop context from parent map expressions
272
+ ) {
273
+ if (node.nodeName === '#text') {
274
+ const text = node.value || '';
275
+ const location = getLocation(node, originalHtml);
276
+ // Extract expressions from text
277
+ // Phase 7: Pass loop context to detect map expressions and attach context
278
+ const { nodes } = extractExpressionsFromText(node.value, location, expressions, normalizedExprs, parentLoopContext);
279
+ // If single text node with no expressions, return it
280
+ if (nodes.length === 1 && nodes[0] && nodes[0].type === 'text') {
281
+ return nodes[0];
282
+ }
283
+ // Otherwise, we need to handle multiple nodes
284
+ // For Phase 1, we'll flatten to text for now (will be handled in future phases)
285
+ // This is a limitation we accept for Phase 1
286
+ const firstNode = nodes[0];
287
+ if (firstNode) {
288
+ return firstNode;
289
+ }
290
+ return {
291
+ type: 'text',
292
+ value: text,
293
+ location
294
+ };
295
+ }
296
+ if (node.nodeName === '#comment') {
297
+ // Skip comments for Phase 1
298
+ return null;
299
+ }
300
+ if (node.nodeName && node.nodeName !== '#text' && node.nodeName !== '#comment') {
301
+ const location = getLocation(node, originalHtml);
302
+ const tag = node.tagName?.toLowerCase() || node.nodeName;
303
+ // Extract original tag name from source HTML to preserve casing (parse5 lowercases everything)
304
+ let originalTag = node.tagName || node.nodeName;
305
+ if (node.sourceCodeLocation && node.sourceCodeLocation.startOffset !== undefined) {
306
+ const startOffset = node.sourceCodeLocation.startOffset;
307
+ // Find the tag name in original HTML (after '<')
308
+ const tagMatch = originalHtml.slice(startOffset).match(/^<([a-zA-Z][a-zA-Z0-9._-]*)/);
309
+ if (tagMatch && tagMatch[1]) {
310
+ originalTag = tagMatch[1];
311
+ }
312
+ }
313
+ // INV005: <template> tags are forbidden — use compound components instead
314
+ if (tag === 'template') {
315
+ throw new InvariantError(INVARIANT.TEMPLATE_TAG, `<template> tags are forbidden in Zenith. Use compound components (e.g., Card.Header) for named slots.`, 'Named slots use compound component pattern (Card.Header), not <template> tags.', 'unknown', // filePath passed to parseTemplate
316
+ location.line, location.column);
317
+ }
318
+ // Parse attributes
319
+ const attributes = [];
320
+ if (node.attrs) {
321
+ for (const attr of node.attrs) {
322
+ const attrLocation = node.sourceCodeLocation?.attrs?.[attr.name]
323
+ ? {
324
+ line: node.sourceCodeLocation.attrs[attr.name].startLine || location.line,
325
+ column: node.sourceCodeLocation.attrs[attr.name].startCol || location.column
326
+ }
327
+ : location;
328
+ // INV006: slot="" attributes are forbidden — use compound components instead
329
+ if (attr.name === 'slot') {
330
+ throw new InvariantError(INVARIANT.SLOT_ATTRIBUTE, `slot="${attr.value || ''}" attribute is forbidden. Use compound components (e.g., Card.Header) for named slots.`, 'Named slots use compound component pattern (Card.Header), not slot="" attributes.', 'unknown', attrLocation.line, attrLocation.column);
331
+ }
332
+ // Handle :attr="expr" syntax (colon-prefixed reactive attributes)
333
+ let attrName = attr.name;
334
+ let attrValue = attr.value || '';
335
+ let isReactive = false;
336
+ if (attrName.startsWith(':')) {
337
+ // This is a reactive attribute like :class="expr"
338
+ attrName = attrName.slice(1); // Remove the colon
339
+ isReactive = true;
340
+ // The value is already a string expression (not in braces)
341
+ // Treat it as an expression
342
+ const exprId = generateExpressionId();
343
+ const exprCode = attrValue.trim();
344
+ const exprIR = {
345
+ id: exprId,
346
+ code: exprCode,
347
+ location: attrLocation
348
+ };
349
+ expressions.push(exprIR);
350
+ // Phase 7: Attach loop context if expression references loop variables
351
+ const attachedLoopContext = shouldAttachLoopContext(exprIR, parentLoopContext);
352
+ attributes.push({
353
+ name: attrName, // Store without colon (e.g., "class" not ":class")
354
+ value: exprIR,
355
+ location: attrLocation,
356
+ loopContext: attachedLoopContext
357
+ });
358
+ }
359
+ else {
360
+ // Regular attribute or attr={expr} syntax
361
+ const attrValueResult = parseAttributeValue(attrValue, attrLocation, expressions, normalizedExprs, parentLoopContext);
362
+ // Transform event attributes: onclick -> data-zen-click, onchange -> data-zen-change, etc.
363
+ let finalAttrName = attrName;
364
+ if (attrName.startsWith('on') && attrName.length > 2) {
365
+ const eventType = attrName.slice(2); // Remove "on" prefix
366
+ finalAttrName = `data-zen-${eventType}`;
367
+ }
368
+ if (typeof attrValueResult === 'string') {
369
+ // Static attribute value
370
+ attributes.push({
371
+ name: finalAttrName,
372
+ value: attrValueResult,
373
+ location: attrLocation
374
+ });
375
+ }
376
+ else {
377
+ // Expression attribute value
378
+ const exprIR = attrValueResult;
379
+ // Phase 7: Attach loop context if expression references loop variables
380
+ const attachedLoopContext = shouldAttachLoopContext(exprIR, parentLoopContext);
381
+ attributes.push({
382
+ name: finalAttrName,
383
+ value: exprIR,
384
+ location: attrLocation,
385
+ loopContext: attachedLoopContext
386
+ });
387
+ }
388
+ }
389
+ }
390
+ }
391
+ // Parse children
392
+ const children = [];
393
+ if (node.childNodes) {
394
+ for (const child of node.childNodes) {
395
+ if (child.nodeName === '#text') {
396
+ // Handle text nodes that may contain expressions
397
+ const text = child.value || '';
398
+ const location = getLocation(child, originalHtml);
399
+ const { nodes: textNodes } = extractExpressionsFromText(text, location, expressions, normalizedExprs, parentLoopContext);
400
+ // Add all nodes from text (can be multiple: text + expression + text)
401
+ for (const textNode of textNodes) {
402
+ children.push(textNode);
403
+ }
404
+ }
405
+ else {
406
+ const childNode = parseNode(child, originalHtml, expressions, normalizedExprs, parentLoopContext);
407
+ if (childNode) {
408
+ children.push(childNode);
409
+ }
410
+ }
411
+ }
412
+ }
413
+ // Phase 7: Check if any child expression is a map expression and extract its loop context
414
+ // This allows nested loops to work correctly
415
+ let elementLoopContext = parentLoopContext;
416
+ // Check children for map expressions (they create new loop contexts)
417
+ for (const child of children) {
418
+ if (child.type === 'expression' && child.loopContext) {
419
+ // If we find a map expression child, merge its context
420
+ elementLoopContext = mergeLoopContext(elementLoopContext, child.loopContext);
421
+ }
422
+ }
423
+ // Check if this is a custom component (starts with uppercase)
424
+ const isComponent = originalTag.length > 0 && originalTag[0] === originalTag[0].toUpperCase();
425
+ if (isComponent) {
426
+ // This is a component node
427
+ return {
428
+ type: 'component',
429
+ name: originalTag,
430
+ attributes,
431
+ children,
432
+ location,
433
+ loopContext: elementLoopContext
434
+ };
435
+ }
436
+ else {
437
+ // This is a regular HTML element
438
+ return {
439
+ type: 'element',
440
+ tag,
441
+ attributes,
442
+ children,
443
+ location,
444
+ loopContext: elementLoopContext
445
+ };
446
+ }
447
+ }
448
+ return null;
449
+ }
450
+ /**
451
+ * Parse template from HTML string
452
+ */
453
+ export function parseTemplate(html, filePath) {
454
+ // Strip script and style blocks
455
+ let templateHtml = stripBlocks(html);
456
+ // Normalize all expressions so parse5 can parse them safely
457
+ const { normalized, expressions: normalizedExprs } = normalizeAllExpressions(templateHtml);
458
+ templateHtml = normalized;
459
+ try {
460
+ // Parse HTML using parseFragment
461
+ const fragment = parseFragment(templateHtml, {
462
+ sourceCodeLocationInfo: true
463
+ });
464
+ const expressions = [];
465
+ const nodes = [];
466
+ // Parse fragment children
467
+ if (fragment.childNodes) {
468
+ for (const node of fragment.childNodes) {
469
+ const parsed = parseNode(node, templateHtml, expressions, normalizedExprs, undefined);
470
+ if (parsed) {
471
+ nodes.push(parsed);
472
+ }
473
+ }
474
+ }
475
+ // Phase 8: Lower JSX expressions to structural fragments
476
+ // This transforms expressions like {cond ? <A /> : <B />} into ConditionalFragmentNode
477
+ const loweredNodes = lowerFragments(nodes, filePath, expressions);
478
+ return {
479
+ raw: templateHtml,
480
+ nodes: loweredNodes,
481
+ expressions
482
+ };
483
+ }
484
+ catch (error) {
485
+ throw new CompilerError(`Template parsing failed: ${error.message}`, filePath, 1, 1);
486
+ }
487
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Zenith File Parser
3
+ *
4
+ * Main entry point for parsing .zen files
5
+ * Phase 1: Parse & Extract only
6
+ */
7
+ import type { ZenIR } from '../ir/types';
8
+ /**
9
+ * Parse a .zen file into IR
10
+ */
11
+ export declare function parseZenFile(filePath: string): ZenIR;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Zenith File Parser
3
+ *
4
+ * Main entry point for parsing .zen files
5
+ * Phase 1: Parse & Extract only
6
+ */
7
+ import { readFileSync } from 'fs';
8
+ import { parseTemplate } from './parseTemplate';
9
+ import { parseScript } from './parseScript';
10
+ import { CompilerError } from '../errors/compilerError';
11
+ /**
12
+ * Extract style blocks from HTML
13
+ */
14
+ function parseStyles(html) {
15
+ const styles = [];
16
+ const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
17
+ let match;
18
+ while ((match = styleRegex.exec(html)) !== null) {
19
+ if (match[1]) {
20
+ styles.push({
21
+ raw: match[1].trim()
22
+ });
23
+ }
24
+ }
25
+ return styles;
26
+ }
27
+ /**
28
+ * Parse a .zen file into IR
29
+ */
30
+ export function parseZenFile(filePath) {
31
+ let source;
32
+ try {
33
+ source = readFileSync(filePath, 'utf-8');
34
+ }
35
+ catch (error) {
36
+ throw new CompilerError(`Failed to read file: ${error.message}`, filePath, 1, 1);
37
+ }
38
+ // Parse template
39
+ const template = parseTemplate(source, filePath);
40
+ // Parse script
41
+ const script = parseScript(source);
42
+ // Parse styles
43
+ const styles = parseStyles(source);
44
+ return {
45
+ filePath,
46
+ template,
47
+ script,
48
+ styles
49
+ };
50
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Script Analysis Utilities
3
+ *
4
+ * Extracts state and prop declarations from <script> blocks
5
+ */
6
+ export interface StateInfo {
7
+ name: string;
8
+ value: string;
9
+ }
10
+ /**
11
+ * Extract state declarations: state name = value
12
+ */
13
+ export declare function extractStateDeclarations(script: string): Map<string, string>;
14
+ /**
15
+ * Extract prop declarations: export let props: Props;
16
+ */
17
+ export declare function extractProps(script: string): string[];
18
+ /**
19
+ * Transform script by removing state and prop declarations
20
+ */
21
+ export declare function transformStateDeclarations(script: string): string;
22
+ /**
23
+ * Inject props into a setup script as top-level variables
24
+ */
25
+ export declare function injectPropsIntoSetup(script: string, props: Record<string, any>): string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Script Analysis Utilities
3
+ *
4
+ * Extracts state and prop declarations from <script> blocks
5
+ */
6
+ /**
7
+ * Extract state declarations: state name = value
8
+ */
9
+ export function extractStateDeclarations(script) {
10
+ const states = new Map();
11
+ const statePattern = /state\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*([^;]+?)(?:\s*;|\s*$)/gm;
12
+ let match;
13
+ while ((match = statePattern.exec(script)) !== null) {
14
+ if (match[1] && match[2]) {
15
+ states.set(match[1], match[2].trim());
16
+ }
17
+ }
18
+ return states;
19
+ }
20
+ /**
21
+ * Extract prop declarations: export let props: Props;
22
+ */
23
+ export function extractProps(script) {
24
+ const props = [];
25
+ const propPattern = /export\s+let\s+props(?:\s*:\s*([^;]+))?[ \t]*;?/g;
26
+ let match;
27
+ while ((match = propPattern.exec(script)) !== null) {
28
+ if (!props.includes('props')) {
29
+ props.push('props');
30
+ }
31
+ }
32
+ return props;
33
+ }
34
+ /**
35
+ * Transform script by removing state and prop declarations
36
+ */
37
+ export function transformStateDeclarations(script) {
38
+ let transformed = script;
39
+ // Remove state declarations (state count = 0)
40
+ transformed = transformed.replace(/state\s+([a-zA-Z_$][a-zA-Z0-9_$]*)[ \t]*=[ \t]*([^;]+?)(?:[ \t]*;|\s*$)/gm, '');
41
+ // Remove export let props (legacy)
42
+ transformed = transformed.replace(/export\s+let\s+props(?:\s*:\s*([^;]+))?\s*;?[ \t]*/g, '');
43
+ // Remove type/interface Props (carefully handling comments)
44
+ // We search for the start of the word 'type' or 'interface' and match until the closing brace
45
+ transformed = transformed.replace(/(?:type|interface)\s+Props\s*=?\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}[ \t]*;?/gs, '');
46
+ // Remove zenith/runtime imports
47
+ transformed = transformed.replace(/import\s+{[^}]+}\s+from\s+['"]zenith\/runtime['"]\s*;?[ \t]*/g, '');
48
+ // Transform zenith:content imports to global lookups
49
+ transformed = transformed.replace(/import\s*{\s*([^}]+)\s*}\s*from\s*['"]zenith:content['"]\s*;?/g, (_, imports) => `const { ${imports.trim()} } = window.__zenith;`);
50
+ return transformed.trim();
51
+ }
52
+ /**
53
+ * Inject props into a setup script as top-level variables
54
+ */
55
+ export function injectPropsIntoSetup(script, props) {
56
+ const propDeclarations = Object.entries(props)
57
+ .map(([key, value]) => `const ${key} = ${typeof value === 'string' ? `'${value}'` : JSON.stringify(value)};`)
58
+ .join('\n');
59
+ return `${propDeclarations}\n\n${script}`;
60
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Loop Context Tracking
3
+ *
4
+ * Phase 7: Utilities for tracking and propagating loop context through the parse tree
5
+ */
6
+ import type { LoopContext, ExpressionIR } from '../ir/types';
7
+ /**
8
+ * Check if an expression should have loop context attached
9
+ * Returns the loop context if the expression references loop variables
10
+ */
11
+ export declare function shouldAttachLoopContext(expr: ExpressionIR, parentLoopContext?: LoopContext): LoopContext | undefined;
12
+ /**
13
+ * Merge loop contexts for nested loops
14
+ * Inner loops inherit outer loop variables
15
+ */
16
+ export declare function mergeLoopContext(outer?: LoopContext, inner?: LoopContext): LoopContext | undefined;
17
+ /**
18
+ * Detect if an expression is a map expression and extract its loop context
19
+ */
20
+ export declare function extractLoopContextFromExpression(expr: ExpressionIR): LoopContext | undefined;