@constela/compiler 0.1.0 → 0.3.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/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export { createUndefinedVarError } from '@constela/core';
15
15
  interface AnalysisContext {
16
16
  stateNames: Set<string>;
17
17
  actionNames: Set<string>;
18
+ componentNames: Set<string>;
18
19
  }
19
20
  interface AnalyzePassSuccess {
20
21
  ok: true;
@@ -32,14 +33,18 @@ type AnalyzePassResult = AnalyzePassSuccess | AnalyzePassFailure;
32
33
  *
33
34
  * - Collects state names
34
35
  * - Collects action names
36
+ * - Collects component names
35
37
  * - Validates state references
36
38
  * - Validates action references
37
39
  * - Validates variable scopes
40
+ * - Validates component references and props
41
+ * - Detects component cycles
42
+ * - Validates param references in component definitions
38
43
  *
39
- * @param ast - Validated AST from validate pass
44
+ * @param programAst - Validated AST from validate pass
40
45
  * @returns AnalyzePassResult
41
46
  */
42
- declare function analyzePass(ast: Program): AnalyzePassResult;
47
+ declare function analyzePass(programAst: Program): AnalyzePassResult;
43
48
 
44
49
  /**
45
50
  * Transform Pass - AST to CompiledProgram transformation
@@ -72,6 +77,8 @@ interface CompiledUpdateStep {
72
77
  target: string;
73
78
  operation: string;
74
79
  value?: CompiledExpression;
80
+ index?: CompiledExpression;
81
+ deleteCount?: CompiledExpression;
75
82
  }
76
83
  interface CompiledFetchStep {
77
84
  do: 'fetch';
@@ -107,7 +114,7 @@ interface CompiledEachNode {
107
114
  key?: CompiledExpression;
108
115
  body: CompiledNode;
109
116
  }
110
- type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr;
117
+ type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr;
111
118
  interface CompiledLitExpr {
112
119
  expr: 'lit';
113
120
  value: string | number | boolean | null | unknown[];
@@ -131,6 +138,17 @@ interface CompiledNotExpr {
131
138
  expr: 'not';
132
139
  operand: CompiledExpression;
133
140
  }
141
+ interface CompiledCondExpr {
142
+ expr: 'cond';
143
+ if: CompiledExpression;
144
+ then: CompiledExpression;
145
+ else: CompiledExpression;
146
+ }
147
+ interface CompiledGetExpr {
148
+ expr: 'get';
149
+ base: CompiledExpression;
150
+ path: string;
151
+ }
134
152
  interface CompiledEventHandler {
135
153
  event: string;
136
154
  action: string;
@@ -195,4 +213,4 @@ type ValidatePassResult = ValidatePassSuccess | ValidatePassFailure;
195
213
  */
196
214
  declare function validatePass(input: unknown): ValidatePassResult;
197
215
 
198
- export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledIfNode, type CompiledNode, type CompiledProgram, type CompiledTextNode, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzePass, compile, transformPass, validatePass };
216
+ export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledIfNode, type CompiledNode, type CompiledProgram, type CompiledSetStep, type CompiledTextNode, type CompiledUpdateStep, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzePass, compile, transformPass, validatePass };
package/dist/index.js CHANGED
@@ -20,21 +20,31 @@ import {
20
20
  createUndefinedActionError,
21
21
  createUndefinedVarError,
22
22
  createDuplicateActionError,
23
+ createComponentNotFoundError,
24
+ createComponentPropMissingError,
25
+ createComponentCycleError,
26
+ createUndefinedParamError,
27
+ createSchemaError,
28
+ createOperationInvalidForTypeError,
29
+ createOperationMissingFieldError,
23
30
  isEventHandler
24
31
  } from "@constela/core";
25
32
  function buildPath(base, ...segments) {
26
33
  return segments.reduce((p, s) => `${p}/${s}`, base);
27
34
  }
28
- function collectContext(ast) {
29
- const stateNames = new Set(Object.keys(ast.state));
30
- const actionNames = new Set(ast.actions.map((a) => a.name));
31
- return { stateNames, actionNames };
35
+ function collectContext(ast2) {
36
+ const stateNames = new Set(Object.keys(ast2.state));
37
+ const actionNames = new Set(ast2.actions.map((a) => a.name));
38
+ const componentNames = new Set(
39
+ ast2.components ? Object.keys(ast2.components) : []
40
+ );
41
+ return { stateNames, actionNames, componentNames };
32
42
  }
33
- function checkDuplicateActions(ast) {
43
+ function checkDuplicateActions(ast2) {
34
44
  const errors = [];
35
45
  const seenNames = /* @__PURE__ */ new Set();
36
- for (let i = 0; i < ast.actions.length; i++) {
37
- const action = ast.actions[i];
46
+ for (let i = 0; i < ast2.actions.length; i++) {
47
+ const action = ast2.actions[i];
38
48
  if (action === void 0) continue;
39
49
  if (seenNames.has(action.name)) {
40
50
  errors.push(createDuplicateActionError(action.name, `/actions/${i}`));
@@ -43,7 +53,7 @@ function checkDuplicateActions(ast) {
43
53
  }
44
54
  return errors;
45
55
  }
46
- function validateExpression(expr, path, context, scope) {
56
+ function validateExpression(expr, path, context, scope, paramScope) {
47
57
  const errors = [];
48
58
  switch (expr.expr) {
49
59
  case "state":
@@ -56,15 +66,28 @@ function validateExpression(expr, path, context, scope) {
56
66
  errors.push(createUndefinedVarError(expr.name, path));
57
67
  }
58
68
  break;
69
+ case "param":
70
+ if (!paramScope || !paramScope.params.has(expr.name)) {
71
+ errors.push(createUndefinedParamError(expr.name, path));
72
+ }
73
+ break;
59
74
  case "bin":
60
- errors.push(...validateExpression(expr.left, buildPath(path, "left"), context, scope));
61
- errors.push(...validateExpression(expr.right, buildPath(path, "right"), context, scope));
75
+ errors.push(...validateExpression(expr.left, buildPath(path, "left"), context, scope, paramScope));
76
+ errors.push(...validateExpression(expr.right, buildPath(path, "right"), context, scope, paramScope));
62
77
  break;
63
78
  case "not":
64
- errors.push(...validateExpression(expr.operand, buildPath(path, "operand"), context, scope));
79
+ errors.push(...validateExpression(expr.operand, buildPath(path, "operand"), context, scope, paramScope));
65
80
  break;
66
81
  case "lit":
67
82
  break;
83
+ case "cond":
84
+ errors.push(...validateExpression(expr.if, buildPath(path, "if"), context, scope, paramScope));
85
+ errors.push(...validateExpression(expr.then, buildPath(path, "then"), context, scope, paramScope));
86
+ errors.push(...validateExpression(expr.else, buildPath(path, "else"), context, scope, paramScope));
87
+ break;
88
+ case "get":
89
+ errors.push(...validateExpression(expr.base, buildPath(path, "base"), context, scope, paramScope));
90
+ break;
68
91
  }
69
92
  return errors;
70
93
  }
@@ -79,16 +102,50 @@ function validateActionStep(step, path, context) {
79
102
  ...validateExpressionStateOnly(step.value, buildPath(path, "value"), context)
80
103
  );
81
104
  break;
82
- case "update":
105
+ case "update": {
83
106
  if (!context.stateNames.has(step.target)) {
84
107
  errors.push(createUndefinedStateError(step.target, buildPath(path, "target")));
108
+ } else {
109
+ const stateField = ast.state[step.target];
110
+ if (stateField) {
111
+ const stateType = stateField.type;
112
+ const op = step.operation;
113
+ if (op === "toggle" && stateType !== "boolean") {
114
+ errors.push(createOperationInvalidForTypeError(op, stateType, buildPath(path, "operation")));
115
+ }
116
+ if (op === "merge" && stateType !== "object") {
117
+ errors.push(createOperationInvalidForTypeError(op, stateType, buildPath(path, "operation")));
118
+ }
119
+ if ((op === "increment" || op === "decrement") && stateType !== "number") {
120
+ errors.push(createOperationInvalidForTypeError(op, stateType, buildPath(path, "operation")));
121
+ }
122
+ if ((op === "push" || op === "pop" || op === "remove" || op === "replaceAt" || op === "insertAt" || op === "splice") && stateType !== "list") {
123
+ errors.push(createOperationInvalidForTypeError(op, stateType, buildPath(path, "operation")));
124
+ }
125
+ if (op === "merge" && !step.value) {
126
+ errors.push(createOperationMissingFieldError(op, "value", path));
127
+ }
128
+ if ((op === "replaceAt" || op === "insertAt") && (!step.index || !step.value)) {
129
+ if (!step.index) errors.push(createOperationMissingFieldError(op, "index", path));
130
+ if (!step.value) errors.push(createOperationMissingFieldError(op, "value", path));
131
+ }
132
+ if (op === "splice" && (!step.index || !step.deleteCount)) {
133
+ if (!step.index) errors.push(createOperationMissingFieldError(op, "index", path));
134
+ if (!step.deleteCount) errors.push(createOperationMissingFieldError(op, "deleteCount", path));
135
+ }
136
+ }
85
137
  }
86
138
  if (step.value) {
87
- errors.push(
88
- ...validateExpressionStateOnly(step.value, buildPath(path, "value"), context)
89
- );
139
+ errors.push(...validateExpressionStateOnly(step.value, buildPath(path, "value"), context));
140
+ }
141
+ if (step.index) {
142
+ errors.push(...validateExpressionStateOnly(step.index, buildPath(path, "index"), context));
143
+ }
144
+ if (step.deleteCount) {
145
+ errors.push(...validateExpressionStateOnly(step.deleteCount, buildPath(path, "deleteCount"), context));
90
146
  }
91
147
  break;
148
+ }
92
149
  case "fetch":
93
150
  errors.push(
94
151
  ...validateExpressionStateOnly(step.url, buildPath(path, "url"), context)
@@ -141,6 +198,14 @@ function validateExpressionStateOnly(expr, path, context) {
141
198
  break;
142
199
  case "lit":
143
200
  break;
201
+ case "cond":
202
+ errors.push(...validateExpressionStateOnly(expr.if, buildPath(path, "if"), context));
203
+ errors.push(...validateExpressionStateOnly(expr.then, buildPath(path, "then"), context));
204
+ errors.push(...validateExpressionStateOnly(expr.else, buildPath(path, "else"), context));
205
+ break;
206
+ case "get":
207
+ errors.push(...validateExpressionStateOnly(expr.base, buildPath(path, "base"), context));
208
+ break;
144
209
  }
145
210
  return errors;
146
211
  }
@@ -174,11 +239,28 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
174
239
  break;
175
240
  case "lit":
176
241
  break;
242
+ case "cond":
243
+ errors.push(
244
+ ...validateExpressionInEventPayload(expr.if, buildPath(path, "if"), context, scope)
245
+ );
246
+ errors.push(
247
+ ...validateExpressionInEventPayload(expr.then, buildPath(path, "then"), context, scope)
248
+ );
249
+ errors.push(
250
+ ...validateExpressionInEventPayload(expr.else, buildPath(path, "else"), context, scope)
251
+ );
252
+ break;
253
+ case "get":
254
+ errors.push(
255
+ ...validateExpressionInEventPayload(expr.base, buildPath(path, "base"), context, scope)
256
+ );
257
+ break;
177
258
  }
178
259
  return errors;
179
260
  }
180
- function validateViewNode(node, path, context, scope) {
261
+ function validateViewNode(node, path, context, scope, options = { insideComponent: false }) {
181
262
  const errors = [];
263
+ const { insideComponent, paramScope } = options;
182
264
  switch (node.kind) {
183
265
  case "element":
184
266
  if (node.props) {
@@ -199,7 +281,7 @@ function validateViewNode(node, path, context, scope) {
199
281
  );
200
282
  }
201
283
  } else {
202
- errors.push(...validateExpression(propValue, propPath, context, scope));
284
+ errors.push(...validateExpression(propValue, propPath, context, scope, paramScope));
203
285
  }
204
286
  }
205
287
  }
@@ -208,42 +290,192 @@ function validateViewNode(node, path, context, scope) {
208
290
  const child = node.children[i];
209
291
  if (child === void 0) continue;
210
292
  errors.push(
211
- ...validateViewNode(child, buildPath(path, "children", i), context, scope)
293
+ ...validateViewNode(child, buildPath(path, "children", i), context, scope, options)
212
294
  );
213
295
  }
214
296
  }
215
297
  break;
216
298
  case "text":
217
- errors.push(...validateExpression(node.value, buildPath(path, "value"), context, scope));
299
+ errors.push(...validateExpression(node.value, buildPath(path, "value"), context, scope, paramScope));
218
300
  break;
219
301
  case "if":
220
302
  errors.push(
221
- ...validateExpression(node.condition, buildPath(path, "condition"), context, scope)
303
+ ...validateExpression(node.condition, buildPath(path, "condition"), context, scope, paramScope)
222
304
  );
223
- errors.push(...validateViewNode(node.then, buildPath(path, "then"), context, scope));
305
+ errors.push(...validateViewNode(node.then, buildPath(path, "then"), context, scope, options));
224
306
  if (node.else) {
225
- errors.push(...validateViewNode(node.else, buildPath(path, "else"), context, scope));
307
+ errors.push(...validateViewNode(node.else, buildPath(path, "else"), context, scope, options));
226
308
  }
227
309
  break;
228
- case "each":
229
- errors.push(...validateExpression(node.items, buildPath(path, "items"), context, scope));
310
+ case "each": {
311
+ errors.push(...validateExpression(node.items, buildPath(path, "items"), context, scope, paramScope));
230
312
  const bodyScope = new Set(scope);
231
313
  bodyScope.add(node.as);
232
314
  if (node.index) {
233
315
  bodyScope.add(node.index);
234
316
  }
235
317
  if (node.key) {
236
- errors.push(...validateExpression(node.key, buildPath(path, "key"), context, bodyScope));
318
+ errors.push(...validateExpression(node.key, buildPath(path, "key"), context, bodyScope, paramScope));
319
+ }
320
+ errors.push(...validateViewNode(node.body, buildPath(path, "body"), context, bodyScope, options));
321
+ break;
322
+ }
323
+ case "component": {
324
+ if (!context.componentNames.has(node.name)) {
325
+ errors.push(createComponentNotFoundError(node.name, path));
326
+ } else {
327
+ const componentDef = ast.components?.[node.name];
328
+ if (componentDef) {
329
+ errors.push(
330
+ ...validateComponentProps(node, componentDef, path, context, scope, paramScope)
331
+ );
332
+ }
333
+ }
334
+ if (node.children) {
335
+ for (let i = 0; i < node.children.length; i++) {
336
+ const child = node.children[i];
337
+ if (child === void 0) continue;
338
+ errors.push(
339
+ ...validateViewNode(child, buildPath(path, "children", i), context, scope, options)
340
+ );
341
+ }
342
+ }
343
+ break;
344
+ }
345
+ case "slot":
346
+ if (!insideComponent) {
347
+ errors.push(
348
+ createSchemaError(`Slot can only be used inside component definitions`, path)
349
+ );
350
+ }
351
+ break;
352
+ }
353
+ return errors;
354
+ }
355
+ function validateComponentProps(node, componentDef, path, context, scope, paramScope) {
356
+ const errors = [];
357
+ const params = componentDef.params ?? {};
358
+ const providedProps = node.props ?? {};
359
+ for (const [paramName, paramDef] of Object.entries(params)) {
360
+ const isRequired = paramDef.required !== false;
361
+ if (isRequired && !(paramName in providedProps)) {
362
+ errors.push(
363
+ createComponentPropMissingError(node.name, paramName, buildPath(path, "props"))
364
+ );
365
+ }
366
+ }
367
+ for (const [propName, propValue] of Object.entries(providedProps)) {
368
+ errors.push(
369
+ ...validateExpression(propValue, buildPath(path, "props", propName), context, scope, paramScope)
370
+ );
371
+ }
372
+ return errors;
373
+ }
374
+ var ast;
375
+ function collectComponentCalls(node) {
376
+ const calls = /* @__PURE__ */ new Set();
377
+ switch (node.kind) {
378
+ case "component":
379
+ calls.add(node.name);
380
+ if (node.children) {
381
+ for (const child of node.children) {
382
+ for (const call of collectComponentCalls(child)) {
383
+ calls.add(call);
384
+ }
385
+ }
386
+ }
387
+ break;
388
+ case "element":
389
+ if (node.children) {
390
+ for (const child of node.children) {
391
+ for (const call of collectComponentCalls(child)) {
392
+ calls.add(call);
393
+ }
394
+ }
395
+ }
396
+ break;
397
+ case "if":
398
+ for (const call of collectComponentCalls(node.then)) {
399
+ calls.add(call);
400
+ }
401
+ if (node.else) {
402
+ for (const call of collectComponentCalls(node.else)) {
403
+ calls.add(call);
404
+ }
237
405
  }
238
- errors.push(...validateViewNode(node.body, buildPath(path, "body"), context, bodyScope));
239
406
  break;
407
+ case "each":
408
+ for (const call of collectComponentCalls(node.body)) {
409
+ calls.add(call);
410
+ }
411
+ break;
412
+ case "text":
413
+ case "slot":
414
+ break;
415
+ }
416
+ return calls;
417
+ }
418
+ function detectComponentCycles(programAst, context) {
419
+ if (!programAst.components) return [];
420
+ const errors = [];
421
+ const callGraph = /* @__PURE__ */ new Map();
422
+ for (const [name, def] of Object.entries(programAst.components)) {
423
+ const calls = collectComponentCalls(def.view);
424
+ callGraph.set(name, calls);
425
+ }
426
+ const visited = /* @__PURE__ */ new Set();
427
+ const recStack = /* @__PURE__ */ new Set();
428
+ function dfs(name, path) {
429
+ visited.add(name);
430
+ recStack.add(name);
431
+ const calls = callGraph.get(name) || /* @__PURE__ */ new Set();
432
+ for (const callee of calls) {
433
+ if (!visited.has(callee)) {
434
+ if (dfs(callee, [...path, callee])) return true;
435
+ } else if (recStack.has(callee)) {
436
+ const cycleStart = path.indexOf(callee);
437
+ const cycle = cycleStart >= 0 ? [...path.slice(cycleStart), callee] : [...path, callee];
438
+ errors.push(createComponentCycleError(cycle, `/components/${path[0]}`));
439
+ return true;
440
+ }
441
+ }
442
+ recStack.delete(name);
443
+ return false;
444
+ }
445
+ for (const name of context.componentNames) {
446
+ if (!visited.has(name)) {
447
+ dfs(name, [name]);
448
+ }
449
+ }
450
+ return errors;
451
+ }
452
+ function validateComponents(programAst, context) {
453
+ const errors = [];
454
+ if (!programAst.components) return errors;
455
+ for (const [name, def] of Object.entries(programAst.components)) {
456
+ const paramNames = new Set(
457
+ def.params ? Object.keys(def.params) : []
458
+ );
459
+ const paramScope = {
460
+ params: paramNames,
461
+ componentName: name
462
+ };
463
+ errors.push(
464
+ ...validateViewNode(
465
+ def.view,
466
+ buildPath("", "components", name, "view"),
467
+ context,
468
+ /* @__PURE__ */ new Set(),
469
+ { insideComponent: true, paramScope }
470
+ )
471
+ );
240
472
  }
241
473
  return errors;
242
474
  }
243
- function validateActions(ast, context) {
475
+ function validateActions(programAst, context) {
244
476
  const errors = [];
245
- for (let i = 0; i < ast.actions.length; i++) {
246
- const action = ast.actions[i];
477
+ for (let i = 0; i < programAst.actions.length; i++) {
478
+ const action = programAst.actions[i];
247
479
  if (action === void 0) continue;
248
480
  for (let j = 0; j < action.steps.length; j++) {
249
481
  const step = action.steps[j];
@@ -255,12 +487,19 @@ function validateActions(ast, context) {
255
487
  }
256
488
  return errors;
257
489
  }
258
- function analyzePass(ast) {
259
- const context = collectContext(ast);
490
+ function analyzePass(programAst) {
491
+ ast = programAst;
492
+ const context = collectContext(programAst);
260
493
  const errors = [];
261
- errors.push(...checkDuplicateActions(ast));
262
- errors.push(...validateActions(ast, context));
263
- errors.push(...validateViewNode(ast.view, "/view", context, /* @__PURE__ */ new Set()));
494
+ errors.push(...checkDuplicateActions(programAst));
495
+ errors.push(...validateActions(programAst, context));
496
+ errors.push(...detectComponentCycles(programAst, context));
497
+ errors.push(...validateComponents(programAst, context));
498
+ errors.push(
499
+ ...validateViewNode(programAst.view, "/view", context, /* @__PURE__ */ new Set(), {
500
+ insideComponent: false
501
+ })
502
+ );
264
503
  if (errors.length > 0) {
265
504
  return {
266
505
  ok: false,
@@ -269,14 +508,14 @@ function analyzePass(ast) {
269
508
  }
270
509
  return {
271
510
  ok: true,
272
- ast,
511
+ ast: programAst,
273
512
  context
274
513
  };
275
514
  }
276
515
 
277
516
  // src/passes/transform.ts
278
517
  import { isEventHandler as isEventHandler2 } from "@constela/core";
279
- function transformExpression(expr) {
518
+ function transformExpression(expr, ctx) {
280
519
  switch (expr.expr) {
281
520
  case "lit":
282
521
  return {
@@ -302,33 +541,73 @@ function transformExpression(expr) {
302
541
  return {
303
542
  expr: "bin",
304
543
  op: expr.op,
305
- left: transformExpression(expr.left),
306
- right: transformExpression(expr.right)
544
+ left: transformExpression(expr.left, ctx),
545
+ right: transformExpression(expr.right, ctx)
307
546
  };
308
547
  case "not":
309
548
  return {
310
549
  expr: "not",
311
- operand: transformExpression(expr.operand)
550
+ operand: transformExpression(expr.operand, ctx)
551
+ };
552
+ case "param": {
553
+ const paramValue = ctx.currentParams?.[expr.name];
554
+ if (paramValue !== void 0) {
555
+ if (expr.path) {
556
+ if (paramValue.expr === "var") {
557
+ const existingPath = paramValue.path;
558
+ const resultPath = existingPath ? `${existingPath}.${expr.path}` : expr.path;
559
+ return {
560
+ expr: "var",
561
+ name: paramValue.name,
562
+ path: resultPath
563
+ };
564
+ }
565
+ if (paramValue.expr === "state") {
566
+ return {
567
+ expr: "var",
568
+ name: paramValue.name,
569
+ path: expr.path
570
+ };
571
+ }
572
+ return paramValue;
573
+ }
574
+ return paramValue;
575
+ }
576
+ return { expr: "lit", value: null };
577
+ }
578
+ case "cond":
579
+ return {
580
+ expr: "cond",
581
+ if: transformExpression(expr.if, ctx),
582
+ then: transformExpression(expr.then, ctx),
583
+ else: transformExpression(expr.else, ctx)
584
+ };
585
+ case "get":
586
+ return {
587
+ expr: "get",
588
+ base: transformExpression(expr.base, ctx),
589
+ path: expr.path
312
590
  };
313
591
  }
314
592
  }
315
- function transformEventHandler(handler) {
593
+ function transformEventHandler(handler, ctx) {
316
594
  const result = {
317
595
  event: handler.event,
318
596
  action: handler.action
319
597
  };
320
598
  if (handler.payload) {
321
- result.payload = transformExpression(handler.payload);
599
+ result.payload = transformExpression(handler.payload, ctx);
322
600
  }
323
601
  return result;
324
602
  }
603
+ var emptyContext = { components: {} };
325
604
  function transformActionStep(step) {
326
605
  switch (step.do) {
327
606
  case "set":
328
607
  return {
329
608
  do: "set",
330
609
  target: step.target,
331
- value: transformExpression(step.value)
610
+ value: transformExpression(step.value, emptyContext)
332
611
  };
333
612
  case "update": {
334
613
  const updateStep = {
@@ -337,20 +616,26 @@ function transformActionStep(step) {
337
616
  operation: step.operation
338
617
  };
339
618
  if (step.value) {
340
- updateStep.value = transformExpression(step.value);
619
+ updateStep.value = transformExpression(step.value, emptyContext);
620
+ }
621
+ if (step.index) {
622
+ updateStep.index = transformExpression(step.index, emptyContext);
623
+ }
624
+ if (step.deleteCount) {
625
+ updateStep.deleteCount = transformExpression(step.deleteCount, emptyContext);
341
626
  }
342
627
  return updateStep;
343
628
  }
344
629
  case "fetch": {
345
630
  const fetchStep = {
346
631
  do: "fetch",
347
- url: transformExpression(step.url)
632
+ url: transformExpression(step.url, emptyContext)
348
633
  };
349
634
  if (step.method) {
350
635
  fetchStep.method = step.method;
351
636
  }
352
637
  if (step.body) {
353
- fetchStep.body = transformExpression(step.body);
638
+ fetchStep.body = transformExpression(step.body, emptyContext);
354
639
  }
355
640
  if (step.result) {
356
641
  fetchStep.result = step.result;
@@ -365,7 +650,20 @@ function transformActionStep(step) {
365
650
  }
366
651
  }
367
652
  }
368
- function transformViewNode(node) {
653
+ function flattenSlotChildren(children, ctx) {
654
+ const result = [];
655
+ for (const child of children) {
656
+ if (child.kind === "slot") {
657
+ if (ctx.currentChildren && ctx.currentChildren.length > 0) {
658
+ result.push(...ctx.currentChildren);
659
+ }
660
+ } else {
661
+ result.push(transformViewNode(child, ctx));
662
+ }
663
+ }
664
+ return result;
665
+ }
666
+ function transformViewNode(node, ctx) {
369
667
  switch (node.kind) {
370
668
  case "element": {
371
669
  const compiledElement = {
@@ -376,48 +674,89 @@ function transformViewNode(node) {
376
674
  compiledElement.props = {};
377
675
  for (const [propName, propValue] of Object.entries(node.props)) {
378
676
  if (isEventHandler2(propValue)) {
379
- compiledElement.props[propName] = transformEventHandler(propValue);
677
+ compiledElement.props[propName] = transformEventHandler(propValue, ctx);
380
678
  } else {
381
- compiledElement.props[propName] = transformExpression(propValue);
679
+ compiledElement.props[propName] = transformExpression(propValue, ctx);
382
680
  }
383
681
  }
384
682
  }
385
683
  if (node.children && node.children.length > 0) {
386
- compiledElement.children = node.children.map(transformViewNode);
684
+ const flattenedChildren = flattenSlotChildren(node.children, ctx);
685
+ if (flattenedChildren.length > 0) {
686
+ compiledElement.children = flattenedChildren;
687
+ }
387
688
  }
388
689
  return compiledElement;
389
690
  }
390
691
  case "text":
391
692
  return {
392
693
  kind: "text",
393
- value: transformExpression(node.value)
694
+ value: transformExpression(node.value, ctx)
394
695
  };
395
696
  case "if": {
396
697
  const compiledIf = {
397
698
  kind: "if",
398
- condition: transformExpression(node.condition),
399
- then: transformViewNode(node.then)
699
+ condition: transformExpression(node.condition, ctx),
700
+ then: transformViewNode(node.then, ctx)
400
701
  };
401
702
  if (node.else) {
402
- compiledIf.else = transformViewNode(node.else);
703
+ compiledIf.else = transformViewNode(node.else, ctx);
403
704
  }
404
705
  return compiledIf;
405
706
  }
406
707
  case "each": {
407
708
  const compiledEach = {
408
709
  kind: "each",
409
- items: transformExpression(node.items),
710
+ items: transformExpression(node.items, ctx),
410
711
  as: node.as,
411
- body: transformViewNode(node.body)
712
+ body: transformViewNode(node.body, ctx)
412
713
  };
413
714
  if (node.index) {
414
715
  compiledEach.index = node.index;
415
716
  }
416
717
  if (node.key) {
417
- compiledEach.key = transformExpression(node.key);
718
+ compiledEach.key = transformExpression(node.key, ctx);
418
719
  }
419
720
  return compiledEach;
420
721
  }
722
+ case "component": {
723
+ const def = ctx.components[node.name];
724
+ if (!def) {
725
+ return { kind: "element", tag: "div" };
726
+ }
727
+ const params = {};
728
+ if (node.props) {
729
+ for (const [name, expr] of Object.entries(node.props)) {
730
+ params[name] = transformExpression(expr, ctx);
731
+ }
732
+ }
733
+ const children = [];
734
+ if (node.children && node.children.length > 0) {
735
+ for (const child of node.children) {
736
+ children.push(transformViewNode(child, ctx));
737
+ }
738
+ }
739
+ const newCtx = {
740
+ ...ctx,
741
+ currentParams: params,
742
+ currentChildren: children
743
+ };
744
+ return transformViewNode(def.view, newCtx);
745
+ }
746
+ case "slot": {
747
+ if (ctx.currentChildren && ctx.currentChildren.length > 0) {
748
+ if (ctx.currentChildren.length === 1) {
749
+ const child = ctx.currentChildren[0];
750
+ if (child) return child;
751
+ }
752
+ return {
753
+ kind: "element",
754
+ tag: "span",
755
+ children: ctx.currentChildren
756
+ };
757
+ }
758
+ return { kind: "text", value: { expr: "lit", value: "" } };
759
+ }
421
760
  }
422
761
  }
423
762
  function transformState(state) {
@@ -440,12 +779,15 @@ function transformActions(actions) {
440
779
  }
441
780
  return compiledActions;
442
781
  }
443
- function transformPass(ast, _context) {
782
+ function transformPass(ast2, _context) {
783
+ const ctx = {
784
+ components: ast2.components || {}
785
+ };
444
786
  return {
445
787
  version: "1.0",
446
- state: transformState(ast.state),
447
- actions: transformActions(ast.actions),
448
- view: transformViewNode(ast.view)
788
+ state: transformState(ast2.state),
789
+ actions: transformActions(ast2.actions),
790
+ view: transformViewNode(ast2.view, ctx)
449
791
  };
450
792
  }
451
793
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/compiler",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Compiler for Constela UI framework - AST to Program transformation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@constela/core": "0.1.0"
18
+ "@constela/core": "0.3.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.10.0",