@hatchingpoint/point 0.0.6 → 0.0.8

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.
@@ -1,665 +1,665 @@
1
- import type {
2
- PointCoreDeclaration,
3
- PointCoreExpression,
4
- PointCoreFunctionDeclaration,
5
- PointCoreExternalDeclaration,
6
- PointCoreImportDeclaration,
7
- PointCoreParameter,
8
- PointCoreProgram,
9
- PointCoreStatement,
10
- PointCoreTypeDeclaration,
11
- PointCoreTypeExpression,
12
- PointCoreValueDeclaration,
13
- PointSourceSpan,
14
- } from "../core/ast.ts";
15
- import type {
16
- PointSemanticBinding,
17
- PointSemanticCalculationDeclaration,
18
- PointSemanticCalculationStatement,
19
- PointSemanticCommandDeclaration,
20
- PointSemanticDeclaration,
21
- PointSemanticExpression,
22
- PointSemanticExternalDeclaration,
23
- PointSemanticExternalFunction,
24
- PointSemanticLabelDeclaration,
25
- PointSemanticLabelStatement,
26
- PointSemanticMutationStatement,
27
- PointSemanticOutputBinding,
28
- PointSemanticPolicyDeclaration,
29
- PointSemanticPolicyStatement,
30
- PointSemanticProgram,
31
- PointSemanticRecordDeclaration,
32
- PointSemanticRouteDeclaration,
33
- PointSemanticRuleDeclaration,
34
- PointSemanticRuleStatement,
35
- PointSemanticTypeExpression,
36
- PointSemanticUseDeclaration,
37
- PointSemanticViewDeclaration,
38
- PointSemanticWorkflowDeclaration,
39
- PointSemanticWorkflowStatement,
40
- PointSemanticActionDeclaration,
41
- } from "./ast.ts";
42
- import { semanticFunctionName, toIdentifier, toPascalCase } from "./naming.ts";
43
- import { semanticDeclarationMetadata } from "./metadata.ts";
44
-
45
- interface DesugarContext {
46
- records: Map<string, Map<string, string>>;
47
- callables: Map<string, string>;
48
- bindings: Map<string, string>;
49
- outputName: string;
50
- outputType: PointCoreTypeExpression;
51
- }
52
-
53
- export function desugarSemanticProgram(program: PointSemanticProgram): PointCoreProgram {
54
- const records = new Map<string, Map<string, string>>();
55
- const callables = buildCallableMap(program, records);
56
- const declarations: PointCoreDeclaration[] = [];
57
-
58
- for (const declaration of program.declarations) {
59
- if (declaration.kind === "record") {
60
- declarations.push(desugarRecord(declaration));
61
- continue;
62
- }
63
- declarations.push(...desugarDeclaration(declaration, records, callables));
64
- }
65
-
66
- return {
67
- kind: "coreProgram",
68
- module: program.module,
69
- declarations,
70
- span: program.span,
71
- semantic: { source: "semantic" },
72
- semanticSource: program,
73
- };
74
- }
75
-
76
- export function desugarSemanticImports(
77
- uses: PointSemanticUseDeclaration[],
78
- resolve: (use: PointSemanticUseDeclaration) => { from: string; names: string[] },
79
- ): PointCoreImportDeclaration[] {
80
- return uses
81
- .map((use) => ({
82
- kind: "import" as const,
83
- names: resolve(use).names,
84
- from: resolve(use).from,
85
- span: use.span,
86
- }))
87
- .filter((declaration) => declaration.names.length > 0);
88
- }
89
-
90
- function buildCallableMap(
91
- program: PointSemanticProgram,
92
- records: Map<string, Map<string, string>>,
93
- ): Map<string, string> {
94
- const callables = new Map<string, string>();
95
- for (const declaration of program.declarations) {
96
- if (declaration.kind === "record") registerRecord(declaration, records);
97
- if (declaration.kind === "external") {
98
- for (const fn of declaration.functions) callables.set(fn.label, toIdentifier(fn.label));
99
- }
100
- if (
101
- declaration.kind === "calculation" ||
102
- declaration.kind === "rule" ||
103
- declaration.kind === "label" ||
104
- declaration.kind === "action" ||
105
- declaration.kind === "policy" ||
106
- declaration.kind === "view" ||
107
- declaration.kind === "route" ||
108
- declaration.kind === "workflow" ||
109
- declaration.kind === "command"
110
- ) {
111
- const outputName = defaultOutputName(declaration);
112
- callables.set(declaration.name, semanticFunctionName(declaration.name, outputName, declaration.kind));
113
- }
114
- }
115
- return callables;
116
- }
117
-
118
- function defaultOutputName(declaration: PointSemanticDeclaration): string {
119
- if (declaration.kind === "label") return "label";
120
- if (declaration.kind === "policy") return "policy";
121
- if (declaration.kind === "view") return "view";
122
- if (declaration.kind === "route") return "route";
123
- if ("output" in declaration) return toIdentifier(declaration.output.name);
124
- return "result";
125
- }
126
-
127
- function registerRecord(declaration: PointSemanticRecordDeclaration, records: Map<string, Map<string, string>>): void {
128
- const fields = new Map<string, string>();
129
- for (const field of declaration.fields) fields.set(field.label, toIdentifier(field.label));
130
- records.set(toPascalCase(declaration.name), fields);
131
- }
132
-
133
- function desugarDeclaration(
134
- declaration: PointSemanticDeclaration,
135
- records: Map<string, Map<string, string>>,
136
- callables: Map<string, string>,
137
- ): PointCoreDeclaration[] {
138
- switch (declaration.kind) {
139
- case "record":
140
- return [desugarRecord(declaration)];
141
- case "calculation":
142
- return [desugarCalculation(declaration, records, callables)];
143
- case "rule":
144
- return [desugarRule(declaration, records, callables)];
145
- case "label":
146
- return [desugarLabel(declaration, records, callables)];
147
- case "external":
148
- return desugarExternal(declaration);
149
- case "action":
150
- return [desugarAction(declaration, records, callables)];
151
- case "policy":
152
- return [desugarPolicy(declaration, records, callables)];
153
- case "view":
154
- return [desugarView(declaration, records, callables)];
155
- case "route":
156
- return [desugarRoute(declaration, records, callables)];
157
- case "workflow":
158
- return [desugarWorkflow(declaration, records, callables)];
159
- case "command":
160
- return [desugarCommand(declaration, records, callables)];
161
- }
162
- }
163
-
164
- function desugarRecord(declaration: PointSemanticRecordDeclaration): PointCoreTypeDeclaration {
165
- return {
166
- kind: "type",
167
- name: toPascalCase(declaration.name),
168
- fields: declaration.fields.map((field) => ({
169
- name: toIdentifier(field.label),
170
- type: desugarType(field.type),
171
- semanticName: field.label,
172
- span: field.span,
173
- })),
174
- semantic: semanticDeclarationMetadata(declaration),
175
- span: declaration.span,
176
- };
177
- }
178
-
179
- function desugarExternal(declaration: PointSemanticExternalDeclaration): PointCoreExternalDeclaration[] {
180
- return declaration.functions.map((fn) => desugarExternalFunction(fn));
181
- }
182
-
183
- function desugarExternalFunction(fn: PointSemanticExternalFunction): PointCoreExternalDeclaration {
184
- return {
185
- kind: "external",
186
- name: toIdentifier(fn.label),
187
- params: fn.params.map((param) => desugarParameter(param)),
188
- returnType: desugarType(fn.returnType),
189
- from: fn.from,
190
- importName: fn.importAs,
191
- semantic: { kind: "external", name: fn.label, outputName: undefined, effects: undefined },
192
- span: fn.span,
193
- };
194
- }
195
-
196
- function desugarCalculation(
197
- declaration: PointSemanticCalculationDeclaration,
198
- records: Map<string, Map<string, string>>,
199
- callables: Map<string, string>,
200
- ): PointCoreFunctionDeclaration {
201
- const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
202
- const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
203
- return {
204
- kind: "function",
205
- name: semanticFunctionName(declaration.name, outputName, "calculation"),
206
- params,
207
- returnType: outputType,
208
- body: desugarCalculationBody(declaration.body, ctx),
209
- semantic: semanticDeclarationMetadata(declaration),
210
- span: declaration.span,
211
- };
212
- }
213
-
214
- function desugarRule(
215
- declaration: PointSemanticRuleDeclaration,
216
- records: Map<string, Map<string, string>>,
217
- callables: Map<string, string>,
218
- ): PointCoreFunctionDeclaration {
219
- const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
220
- const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
221
- return {
222
- kind: "function",
223
- name: semanticFunctionName(declaration.name, outputName, "rule"),
224
- params,
225
- returnType: outputType,
226
- body: desugarRuleBody(declaration.body, ctx),
227
- semantic: semanticDeclarationMetadata(declaration),
228
- span: declaration.span,
229
- };
230
- }
231
-
232
- function desugarLabel(
233
- declaration: PointSemanticLabelDeclaration,
234
- records: Map<string, Map<string, string>>,
235
- callables: Map<string, string>,
236
- ): PointCoreFunctionDeclaration {
237
- const outputType = desugarType(declaration.output.type);
238
- const { params, bindings } = collectBindings(declaration.inputs, declaration.output);
239
- const ctx: DesugarContext = { records, callables, bindings, outputName: "result", outputType };
240
- return {
241
- kind: "function",
242
- name: semanticFunctionName(declaration.name, "label", "label"),
243
- params,
244
- returnType: outputType,
245
- body: desugarLabelBody(declaration.body, ctx),
246
- semantic: semanticDeclarationMetadata(declaration),
247
- span: declaration.span,
248
- };
249
- }
250
-
251
- function desugarAction(
252
- declaration: PointSemanticActionDeclaration,
253
- records: Map<string, Map<string, string>>,
254
- callables: Map<string, string>,
255
- ): PointCoreFunctionDeclaration {
256
- const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
257
- const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
258
- return {
259
- kind: "function",
260
- name: semanticFunctionName(declaration.name, outputName, "action"),
261
- params,
262
- returnType: outputType,
263
- body: declaration.body.map((statement) => ({
264
- kind: "return" as const,
265
- value: desugarExpression(statement.value, ctx),
266
- })),
267
- semantic: semanticDeclarationMetadata(declaration),
268
- span: declaration.span,
269
- };
270
- }
271
-
272
- function desugarPolicy(
273
- declaration: PointSemanticPolicyDeclaration,
274
- records: Map<string, Map<string, string>>,
275
- callables: Map<string, string>,
276
- ): PointCoreFunctionDeclaration {
277
- const outputType: PointCoreTypeExpression = { kind: "typeRef", name: "Bool", args: [] };
278
- const { params, bindings } = collectBindings(declaration.inputs, { name: "result", type: { kind: "typeRef", name: "Bool", args: [] } });
279
- const ctx: DesugarContext = { records, callables, bindings, outputName: "result", outputType };
280
- return {
281
- kind: "function",
282
- name: semanticFunctionName(declaration.name, "policy", "policy"),
283
- params,
284
- returnType: outputType,
285
- body: desugarPolicyBody(declaration.body, ctx),
286
- semantic: semanticDeclarationMetadata(declaration),
287
- span: declaration.span,
288
- };
289
- }
290
-
291
- function desugarView(
292
- declaration: PointSemanticViewDeclaration,
293
- records: Map<string, Map<string, string>>,
294
- callables: Map<string, string>,
295
- ): PointCoreFunctionDeclaration {
296
- const outputType: PointCoreTypeExpression = { kind: "typeRef", name: "Text", args: [] };
297
- const { params, bindings } = collectBindings(declaration.inputs, declaration.output);
298
- const ctx: DesugarContext = { records, callables, bindings, outputName: "page", outputType };
299
- return {
300
- kind: "function",
301
- name: semanticFunctionName(declaration.name, "view", "view"),
302
- params,
303
- returnType: outputType,
304
- body: desugarViewBody(declaration.body, ctx),
305
- semantic: semanticDeclarationMetadata(declaration),
306
- span: declaration.span,
307
- };
308
- }
309
-
310
- function desugarRoute(
311
- declaration: PointSemanticRouteDeclaration,
312
- records: Map<string, Map<string, string>>,
313
- callables: Map<string, string>,
314
- ): PointCoreFunctionDeclaration {
315
- const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
316
- const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
317
- return {
318
- kind: "function",
319
- name: semanticFunctionName(declaration.name, "route", "route"),
320
- params,
321
- returnType: outputType,
322
- body: declaration.body.map((statement) => ({
323
- kind: "return" as const,
324
- value: desugarExpression(statement.value, ctx),
325
- })),
326
- semantic: semanticDeclarationMetadata(declaration),
327
- span: declaration.span,
328
- };
329
- }
330
-
331
- function desugarWorkflow(
332
- declaration: PointSemanticWorkflowDeclaration,
333
- records: Map<string, Map<string, string>>,
334
- callables: Map<string, string>,
335
- ): PointCoreFunctionDeclaration {
336
- const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
337
- const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
338
- return {
339
- kind: "function",
340
- name: semanticFunctionName(declaration.name, "workflow", "workflow"),
341
- params,
342
- returnType: outputType,
343
- body: desugarWorkflowBody(declaration.body, ctx),
344
- semantic: semanticDeclarationMetadata(declaration),
345
- span: declaration.span,
346
- };
347
- }
348
-
349
- function desugarCommand(
350
- declaration: PointSemanticCommandDeclaration,
351
- records: Map<string, Map<string, string>>,
352
- callables: Map<string, string>,
353
- ): PointCoreFunctionDeclaration {
354
- const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
355
- const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
356
- return {
357
- kind: "function",
358
- name: semanticFunctionName(declaration.name, "command", "command"),
359
- params,
360
- returnType: outputType,
361
- body: declaration.body.map((statement) => ({
362
- kind: "return" as const,
363
- value: desugarExpression(statement.value, ctx),
364
- })),
365
- semantic: semanticDeclarationMetadata(declaration),
366
- span: declaration.span,
367
- };
368
- }
369
-
370
- function collectBindings(inputs: PointSemanticBinding[], output: PointSemanticOutputBinding) {
371
- const bindings = new Map<string, string>();
372
- const params: PointCoreParameter[] = inputs.map((input) => {
373
- const name = toIdentifier(input.label);
374
- bindings.set(input.label, name);
375
- return { name, type: desugarType(input.type), semanticName: input.label, span: input.span };
376
- });
377
- const outputName = toIdentifier(output.name);
378
- bindings.set(output.name, outputName);
379
- const outputType = desugarType(output.type);
380
- return { params, bindings, outputName, outputType };
381
- }
382
-
383
- function desugarParameter(binding: PointSemanticBinding): PointCoreParameter {
384
- return {
385
- name: toIdentifier(binding.label),
386
- type: desugarType(binding.type),
387
- semanticName: binding.label,
388
- span: binding.span,
389
- };
390
- }
391
-
392
- function desugarType(type: PointSemanticTypeExpression): PointCoreTypeExpression {
393
- if (type.name === "List" || type.name === "Maybe" || type.name === "Or") {
394
- return { kind: "typeRef", name: type.name, args: type.args.map(desugarType) };
395
- }
396
- const primitives = new Set(["Text", "Int", "Float", "Bool", "Void", "Error", "Page"]);
397
- if (primitives.has(type.name)) return { kind: "typeRef", name: type.name, args: [] };
398
- return { kind: "typeRef", name: toPascalCase(type.name), args: [] };
399
- }
400
-
401
- function desugarCalculationBody(statements: PointSemanticCalculationStatement[], ctx: DesugarContext): PointCoreStatement[] {
402
- const body: PointCoreStatement[] = [];
403
- for (const statement of statements) {
404
- if (statement.kind === "assignIs") {
405
- const name = toIdentifier(statement.name);
406
- if (name !== ctx.outputName) throw new Error(`Calculation can only assign its output ${ctx.outputName}`);
407
- body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
408
- continue;
409
- }
410
- if (statement.kind === "startsAt" || statement.kind === "startsAs") {
411
- const name = toIdentifier(statement.name);
412
- ctx.bindings.set(statement.name, name);
413
- body.push(mutableValue(name, ctx.outputType, desugarExpression(statement.value, ctx), true, statement.span));
414
- continue;
415
- }
416
- if (statement.kind === "forEach") {
417
- body.push(...desugarForEach(statement, ctx));
418
- continue;
419
- }
420
- if (statement.kind === "return") {
421
- body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
422
- continue;
423
- }
424
- body.push(...desugarMutation(statement, ctx));
425
- }
426
- return body;
427
- }
428
-
429
- function desugarRuleBody(statements: PointSemanticRuleStatement[], ctx: DesugarContext): PointCoreStatement[] {
430
- const body: PointCoreStatement[] = [];
431
- for (const statement of statements) {
432
- if (statement.kind === "startsAt") {
433
- const name = toIdentifier(statement.name);
434
- ctx.bindings.set(statement.name, name);
435
- body.push(mutableValue(name, ctx.outputType, desugarExpression(statement.value, ctx), true, statement.span));
436
- continue;
437
- }
438
- if (statement.kind === "addWhen") {
439
- body.push({
440
- kind: "if",
441
- condition: desugarExpression(statement.condition, ctx),
442
- thenBody: [
443
- {
444
- kind: "assignment",
445
- name: ctx.outputName,
446
- operator: "+=",
447
- value: desugarExpression(statement.amount, ctx),
448
- span: statement.span,
449
- },
450
- ],
451
- elseBody: [],
452
- span: statement.span,
453
- });
454
- continue;
455
- }
456
- if (statement.kind === "forEach") {
457
- body.push(...desugarForEach(statement, ctx));
458
- continue;
459
- }
460
- if (statement.kind === "return") {
461
- body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
462
- continue;
463
- }
464
- body.push(...desugarMutation(statement, ctx));
465
- }
466
- return body;
467
- }
468
-
469
- function desugarLabelBody(statements: PointSemanticLabelStatement[], ctx: DesugarContext): PointCoreStatement[] {
470
- const body: PointCoreStatement[] = [];
471
- for (const statement of statements) {
472
- if (statement.kind === "whenReturn") {
473
- body.push({
474
- kind: "if",
475
- condition: desugarExpression(statement.condition, ctx),
476
- thenBody: [{ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span }],
477
- elseBody: [],
478
- span: statement.span,
479
- });
480
- continue;
481
- }
482
- body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
483
- }
484
- return body;
485
- }
486
-
487
- function desugarPolicyBody(statements: PointSemanticPolicyStatement[], ctx: DesugarContext): PointCoreStatement[] {
488
- return statements.map((statement) => {
489
- if (statement.kind === "deny") {
490
- return {
491
- kind: "return" as const,
492
- span: statement.span,
493
- value: {
494
- kind: "binary" as const,
495
- operator: "==" as const,
496
- left: desugarExpression(statement.condition, ctx),
497
- right: { kind: "literal" as const, value: false },
498
- span: statement.span,
499
- },
500
- };
501
- }
502
- return { kind: "return" as const, value: desugarExpression(statement.condition, ctx), span: statement.span };
503
- });
504
- }
505
-
506
- function desugarViewBody(statements: PointSemanticViewStatement[], ctx: DesugarContext): PointCoreStatement[] {
507
- const body: PointCoreStatement[] = [];
508
- for (const statement of statements) {
509
- if (statement.kind === "whenRender") {
510
- body.push({
511
- kind: "if",
512
- condition: desugarExpression(statement.condition, ctx),
513
- thenBody: [{ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span }],
514
- elseBody: [],
515
- span: statement.span,
516
- });
517
- continue;
518
- }
519
- body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
520
- }
521
- return body;
522
- }
523
-
524
- function desugarWorkflowBody(statements: PointSemanticWorkflowStatement[], ctx: DesugarContext): PointCoreStatement[] {
525
- const body: PointCoreStatement[] = [];
526
- for (const statement of statements) {
527
- if (statement.kind === "step") {
528
- const name = toIdentifier(statement.name);
529
- ctx.bindings.set(statement.name, name);
530
- body.push(mutableValue(name, ctx.outputType, desugarExpression(statement.value, ctx), false, statement.span));
531
- continue;
532
- }
533
- body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
534
- }
535
- return body;
536
- }
537
-
538
- function desugarForEach(
539
- statement: Extract<PointSemanticCalculationStatement | PointSemanticRuleStatement, { kind: "forEach" }>,
540
- ctx: DesugarContext,
541
- ): PointCoreStatement[] {
542
- const itemName = toIdentifier(statement.item);
543
- const loopCtx: DesugarContext = {
544
- ...ctx,
545
- bindings: new Map(ctx.bindings),
546
- };
547
- loopCtx.bindings.set(statement.item, itemName);
548
- return [
549
- {
550
- kind: "for",
551
- itemName,
552
- iterable: desugarExpression(statement.iterable, ctx),
553
- body: statement.body.flatMap((mutation) => desugarMutation(mutation, loopCtx)),
554
- span: statement.span,
555
- },
556
- ];
557
- }
558
-
559
- function desugarMutation(statement: PointSemanticMutationStatement, ctx: DesugarContext): PointCoreStatement[] {
560
- if (statement.kind === "addTo") {
561
- return [
562
- {
563
- kind: "assignment",
564
- name: toIdentifier(statement.target),
565
- operator: "+=",
566
- value: desugarExpression(statement.amount, ctx),
567
- span: statement.span,
568
- },
569
- ];
570
- }
571
- if (statement.kind === "subtractFrom") {
572
- return [
573
- {
574
- kind: "assignment",
575
- name: toIdentifier(statement.target),
576
- operator: "-=",
577
- value: desugarExpression(statement.amount, ctx),
578
- span: statement.span,
579
- },
580
- ];
581
- }
582
- return [
583
- {
584
- kind: "assignment",
585
- name: toIdentifier(statement.target),
586
- operator: "=",
587
- value: desugarExpression(statement.value, ctx),
588
- span: statement.span,
589
- },
590
- ];
591
- }
592
-
593
- function mutableValue(
594
- name: string,
595
- type: PointCoreTypeExpression,
596
- value: PointCoreExpression,
597
- mutable: boolean,
598
- span?: PointSourceSpan,
599
- ): PointCoreValueDeclaration {
600
- return { kind: "value", name, type, value, mutable, span };
601
- }
602
-
603
- function desugarExpression(expression: PointSemanticExpression, ctx: DesugarContext): PointCoreExpression {
604
- switch (expression.kind) {
605
- case "literal":
606
- return { kind: "literal", value: expression.value, span: expression.span };
607
- case "name":
608
- return { kind: "identifier", name: resolveName(expression.label, ctx.bindings), span: expression.span };
609
- case "property":
610
- return {
611
- kind: "property",
612
- target: desugarExpression(expression.target, ctx),
613
- name: toIdentifier(expression.label),
614
- span: expression.span,
615
- };
616
- case "binary":
617
- return {
618
- kind: "binary",
619
- operator: expression.operator,
620
- left: desugarExpression(expression.left, ctx),
621
- right: desugarExpression(expression.right, ctx),
622
- span: expression.span,
623
- };
624
- case "call":
625
- return {
626
- kind: "call",
627
- callee: resolveCallable(expression.callee, ctx.callables),
628
- args: expression.args.map((arg) => desugarExpression(arg, ctx)),
629
- span: expression.span,
630
- };
631
- case "await":
632
- return { kind: "await", value: desugarExpression(expression.value, ctx), span: expression.span };
633
- case "list":
634
- return { kind: "list", items: expression.items.map((item) => desugarExpression(item, ctx)), span: expression.span };
635
- case "record":
636
- return {
637
- kind: "record",
638
- fields: expression.fields.map((field) => ({
639
- name: toIdentifier(field.label),
640
- value: desugarExpression(field.value, ctx),
641
- span: field.span,
642
- })),
643
- span: expression.span,
644
- };
645
- case "error":
646
- return {
647
- kind: "call",
648
- callee: "Error",
649
- args: [{ kind: "literal", value: expression.message, span: expression.span }],
650
- span: expression.span,
651
- };
652
- }
653
- }
654
-
655
- function resolveName(label: string, bindings: Map<string, string>): string {
656
- if (bindings.has(label)) return bindings.get(label)!;
657
- if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(label)) return label;
658
- return toIdentifier(label);
659
- }
660
-
661
- function resolveCallable(label: string, callables: Map<string, string>): string {
662
- if (callables.has(label)) return callables.get(label)!;
663
- if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(label)) return label;
664
- return toIdentifier(label);
665
- }
1
+ import type {
2
+ PointCoreDeclaration,
3
+ PointCoreExpression,
4
+ PointCoreFunctionDeclaration,
5
+ PointCoreExternalDeclaration,
6
+ PointCoreImportDeclaration,
7
+ PointCoreParameter,
8
+ PointCoreProgram,
9
+ PointCoreStatement,
10
+ PointCoreTypeDeclaration,
11
+ PointCoreTypeExpression,
12
+ PointCoreValueDeclaration,
13
+ PointSourceSpan,
14
+ } from "../core/ast.ts";
15
+ import type {
16
+ PointSemanticBinding,
17
+ PointSemanticCalculationDeclaration,
18
+ PointSemanticCalculationStatement,
19
+ PointSemanticCommandDeclaration,
20
+ PointSemanticDeclaration,
21
+ PointSemanticExpression,
22
+ PointSemanticExternalDeclaration,
23
+ PointSemanticExternalFunction,
24
+ PointSemanticLabelDeclaration,
25
+ PointSemanticLabelStatement,
26
+ PointSemanticMutationStatement,
27
+ PointSemanticOutputBinding,
28
+ PointSemanticPolicyDeclaration,
29
+ PointSemanticPolicyStatement,
30
+ PointSemanticProgram,
31
+ PointSemanticRecordDeclaration,
32
+ PointSemanticRouteDeclaration,
33
+ PointSemanticRuleDeclaration,
34
+ PointSemanticRuleStatement,
35
+ PointSemanticTypeExpression,
36
+ PointSemanticUseDeclaration,
37
+ PointSemanticViewDeclaration,
38
+ PointSemanticWorkflowDeclaration,
39
+ PointSemanticWorkflowStatement,
40
+ PointSemanticActionDeclaration,
41
+ } from "./ast.ts";
42
+ import { semanticFunctionName, toIdentifier, toPascalCase } from "./naming.ts";
43
+ import { semanticDeclarationMetadata } from "./metadata.ts";
44
+
45
+ interface DesugarContext {
46
+ records: Map<string, Map<string, string>>;
47
+ callables: Map<string, string>;
48
+ bindings: Map<string, string>;
49
+ outputName: string;
50
+ outputType: PointCoreTypeExpression;
51
+ }
52
+
53
+ export function desugarSemanticProgram(program: PointSemanticProgram): PointCoreProgram {
54
+ const records = new Map<string, Map<string, string>>();
55
+ const callables = buildCallableMap(program, records);
56
+ const declarations: PointCoreDeclaration[] = [];
57
+
58
+ for (const declaration of program.declarations) {
59
+ if (declaration.kind === "record") {
60
+ declarations.push(desugarRecord(declaration));
61
+ continue;
62
+ }
63
+ declarations.push(...desugarDeclaration(declaration, records, callables));
64
+ }
65
+
66
+ return {
67
+ kind: "coreProgram",
68
+ module: program.module,
69
+ declarations,
70
+ span: program.span,
71
+ semantic: { source: "semantic" },
72
+ semanticSource: program,
73
+ };
74
+ }
75
+
76
+ export function desugarSemanticImports(
77
+ uses: PointSemanticUseDeclaration[],
78
+ resolve: (use: PointSemanticUseDeclaration) => { from: string; names: string[] },
79
+ ): PointCoreImportDeclaration[] {
80
+ return uses
81
+ .map((use) => ({
82
+ kind: "import" as const,
83
+ names: resolve(use).names,
84
+ from: resolve(use).from,
85
+ span: use.span,
86
+ }))
87
+ .filter((declaration) => declaration.names.length > 0);
88
+ }
89
+
90
+ function buildCallableMap(
91
+ program: PointSemanticProgram,
92
+ records: Map<string, Map<string, string>>,
93
+ ): Map<string, string> {
94
+ const callables = new Map<string, string>();
95
+ for (const declaration of program.declarations) {
96
+ if (declaration.kind === "record") registerRecord(declaration, records);
97
+ if (declaration.kind === "external") {
98
+ for (const fn of declaration.functions) callables.set(fn.label, toIdentifier(fn.label));
99
+ }
100
+ if (
101
+ declaration.kind === "calculation" ||
102
+ declaration.kind === "rule" ||
103
+ declaration.kind === "label" ||
104
+ declaration.kind === "action" ||
105
+ declaration.kind === "policy" ||
106
+ declaration.kind === "view" ||
107
+ declaration.kind === "route" ||
108
+ declaration.kind === "workflow" ||
109
+ declaration.kind === "command"
110
+ ) {
111
+ const outputName = defaultOutputName(declaration);
112
+ callables.set(declaration.name, semanticFunctionName(declaration.name, outputName, declaration.kind));
113
+ }
114
+ }
115
+ return callables;
116
+ }
117
+
118
+ function defaultOutputName(declaration: PointSemanticDeclaration): string {
119
+ if (declaration.kind === "label") return "label";
120
+ if (declaration.kind === "policy") return "policy";
121
+ if (declaration.kind === "view") return "view";
122
+ if (declaration.kind === "route") return "route";
123
+ if ("output" in declaration) return toIdentifier(declaration.output.name);
124
+ return "result";
125
+ }
126
+
127
+ function registerRecord(declaration: PointSemanticRecordDeclaration, records: Map<string, Map<string, string>>): void {
128
+ const fields = new Map<string, string>();
129
+ for (const field of declaration.fields) fields.set(field.label, toIdentifier(field.label));
130
+ records.set(toPascalCase(declaration.name), fields);
131
+ }
132
+
133
+ function desugarDeclaration(
134
+ declaration: PointSemanticDeclaration,
135
+ records: Map<string, Map<string, string>>,
136
+ callables: Map<string, string>,
137
+ ): PointCoreDeclaration[] {
138
+ switch (declaration.kind) {
139
+ case "record":
140
+ return [desugarRecord(declaration)];
141
+ case "calculation":
142
+ return [desugarCalculation(declaration, records, callables)];
143
+ case "rule":
144
+ return [desugarRule(declaration, records, callables)];
145
+ case "label":
146
+ return [desugarLabel(declaration, records, callables)];
147
+ case "external":
148
+ return desugarExternal(declaration);
149
+ case "action":
150
+ return [desugarAction(declaration, records, callables)];
151
+ case "policy":
152
+ return [desugarPolicy(declaration, records, callables)];
153
+ case "view":
154
+ return [desugarView(declaration, records, callables)];
155
+ case "route":
156
+ return [desugarRoute(declaration, records, callables)];
157
+ case "workflow":
158
+ return [desugarWorkflow(declaration, records, callables)];
159
+ case "command":
160
+ return [desugarCommand(declaration, records, callables)];
161
+ }
162
+ }
163
+
164
+ function desugarRecord(declaration: PointSemanticRecordDeclaration): PointCoreTypeDeclaration {
165
+ return {
166
+ kind: "type",
167
+ name: toPascalCase(declaration.name),
168
+ fields: declaration.fields.map((field) => ({
169
+ name: toIdentifier(field.label),
170
+ type: desugarType(field.type),
171
+ semanticName: field.label,
172
+ span: field.span,
173
+ })),
174
+ semantic: semanticDeclarationMetadata(declaration),
175
+ span: declaration.span,
176
+ };
177
+ }
178
+
179
+ function desugarExternal(declaration: PointSemanticExternalDeclaration): PointCoreExternalDeclaration[] {
180
+ return declaration.functions.map((fn) => desugarExternalFunction(fn));
181
+ }
182
+
183
+ function desugarExternalFunction(fn: PointSemanticExternalFunction): PointCoreExternalDeclaration {
184
+ return {
185
+ kind: "external",
186
+ name: toIdentifier(fn.label),
187
+ params: fn.params.map((param) => desugarParameter(param)),
188
+ returnType: desugarType(fn.returnType),
189
+ from: fn.from,
190
+ importName: fn.importAs,
191
+ semantic: { kind: "external", name: fn.label, outputName: undefined, effects: undefined },
192
+ span: fn.span,
193
+ };
194
+ }
195
+
196
+ function desugarCalculation(
197
+ declaration: PointSemanticCalculationDeclaration,
198
+ records: Map<string, Map<string, string>>,
199
+ callables: Map<string, string>,
200
+ ): PointCoreFunctionDeclaration {
201
+ const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
202
+ const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
203
+ return {
204
+ kind: "function",
205
+ name: semanticFunctionName(declaration.name, outputName, "calculation"),
206
+ params,
207
+ returnType: outputType,
208
+ body: desugarCalculationBody(declaration.body, ctx),
209
+ semantic: semanticDeclarationMetadata(declaration),
210
+ span: declaration.span,
211
+ };
212
+ }
213
+
214
+ function desugarRule(
215
+ declaration: PointSemanticRuleDeclaration,
216
+ records: Map<string, Map<string, string>>,
217
+ callables: Map<string, string>,
218
+ ): PointCoreFunctionDeclaration {
219
+ const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
220
+ const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
221
+ return {
222
+ kind: "function",
223
+ name: semanticFunctionName(declaration.name, outputName, "rule"),
224
+ params,
225
+ returnType: outputType,
226
+ body: desugarRuleBody(declaration.body, ctx),
227
+ semantic: semanticDeclarationMetadata(declaration),
228
+ span: declaration.span,
229
+ };
230
+ }
231
+
232
+ function desugarLabel(
233
+ declaration: PointSemanticLabelDeclaration,
234
+ records: Map<string, Map<string, string>>,
235
+ callables: Map<string, string>,
236
+ ): PointCoreFunctionDeclaration {
237
+ const outputType = desugarType(declaration.output.type);
238
+ const { params, bindings } = collectBindings(declaration.inputs, declaration.output);
239
+ const ctx: DesugarContext = { records, callables, bindings, outputName: "result", outputType };
240
+ return {
241
+ kind: "function",
242
+ name: semanticFunctionName(declaration.name, "label", "label"),
243
+ params,
244
+ returnType: outputType,
245
+ body: desugarLabelBody(declaration.body, ctx),
246
+ semantic: semanticDeclarationMetadata(declaration),
247
+ span: declaration.span,
248
+ };
249
+ }
250
+
251
+ function desugarAction(
252
+ declaration: PointSemanticActionDeclaration,
253
+ records: Map<string, Map<string, string>>,
254
+ callables: Map<string, string>,
255
+ ): PointCoreFunctionDeclaration {
256
+ const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
257
+ const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
258
+ return {
259
+ kind: "function",
260
+ name: semanticFunctionName(declaration.name, outputName, "action"),
261
+ params,
262
+ returnType: outputType,
263
+ body: declaration.body.map((statement) => ({
264
+ kind: "return" as const,
265
+ value: desugarExpression(statement.value, ctx),
266
+ })),
267
+ semantic: semanticDeclarationMetadata(declaration),
268
+ span: declaration.span,
269
+ };
270
+ }
271
+
272
+ function desugarPolicy(
273
+ declaration: PointSemanticPolicyDeclaration,
274
+ records: Map<string, Map<string, string>>,
275
+ callables: Map<string, string>,
276
+ ): PointCoreFunctionDeclaration {
277
+ const outputType: PointCoreTypeExpression = { kind: "typeRef", name: "Bool", args: [] };
278
+ const { params, bindings } = collectBindings(declaration.inputs, { name: "result", type: { kind: "typeRef", name: "Bool", args: [] } });
279
+ const ctx: DesugarContext = { records, callables, bindings, outputName: "result", outputType };
280
+ return {
281
+ kind: "function",
282
+ name: semanticFunctionName(declaration.name, "policy", "policy"),
283
+ params,
284
+ returnType: outputType,
285
+ body: desugarPolicyBody(declaration.body, ctx),
286
+ semantic: semanticDeclarationMetadata(declaration),
287
+ span: declaration.span,
288
+ };
289
+ }
290
+
291
+ function desugarView(
292
+ declaration: PointSemanticViewDeclaration,
293
+ records: Map<string, Map<string, string>>,
294
+ callables: Map<string, string>,
295
+ ): PointCoreFunctionDeclaration {
296
+ const outputType: PointCoreTypeExpression = { kind: "typeRef", name: "Text", args: [] };
297
+ const { params, bindings } = collectBindings(declaration.inputs, declaration.output);
298
+ const ctx: DesugarContext = { records, callables, bindings, outputName: "page", outputType };
299
+ return {
300
+ kind: "function",
301
+ name: semanticFunctionName(declaration.name, "view", "view"),
302
+ params,
303
+ returnType: outputType,
304
+ body: desugarViewBody(declaration.body, ctx),
305
+ semantic: semanticDeclarationMetadata(declaration),
306
+ span: declaration.span,
307
+ };
308
+ }
309
+
310
+ function desugarRoute(
311
+ declaration: PointSemanticRouteDeclaration,
312
+ records: Map<string, Map<string, string>>,
313
+ callables: Map<string, string>,
314
+ ): PointCoreFunctionDeclaration {
315
+ const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
316
+ const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
317
+ return {
318
+ kind: "function",
319
+ name: semanticFunctionName(declaration.name, "route", "route"),
320
+ params,
321
+ returnType: outputType,
322
+ body: declaration.body.map((statement) => ({
323
+ kind: "return" as const,
324
+ value: desugarExpression(statement.value, ctx),
325
+ })),
326
+ semantic: semanticDeclarationMetadata(declaration),
327
+ span: declaration.span,
328
+ };
329
+ }
330
+
331
+ function desugarWorkflow(
332
+ declaration: PointSemanticWorkflowDeclaration,
333
+ records: Map<string, Map<string, string>>,
334
+ callables: Map<string, string>,
335
+ ): PointCoreFunctionDeclaration {
336
+ const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
337
+ const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
338
+ return {
339
+ kind: "function",
340
+ name: semanticFunctionName(declaration.name, "workflow", "workflow"),
341
+ params,
342
+ returnType: outputType,
343
+ body: desugarWorkflowBody(declaration.body, ctx),
344
+ semantic: semanticDeclarationMetadata(declaration),
345
+ span: declaration.span,
346
+ };
347
+ }
348
+
349
+ function desugarCommand(
350
+ declaration: PointSemanticCommandDeclaration,
351
+ records: Map<string, Map<string, string>>,
352
+ callables: Map<string, string>,
353
+ ): PointCoreFunctionDeclaration {
354
+ const { params, bindings, outputName, outputType } = collectBindings(declaration.inputs, declaration.output);
355
+ const ctx: DesugarContext = { records, callables, bindings, outputName, outputType };
356
+ return {
357
+ kind: "function",
358
+ name: semanticFunctionName(declaration.name, "command", "command"),
359
+ params,
360
+ returnType: outputType,
361
+ body: declaration.body.map((statement) => ({
362
+ kind: "return" as const,
363
+ value: desugarExpression(statement.value, ctx),
364
+ })),
365
+ semantic: semanticDeclarationMetadata(declaration),
366
+ span: declaration.span,
367
+ };
368
+ }
369
+
370
+ function collectBindings(inputs: PointSemanticBinding[], output: PointSemanticOutputBinding) {
371
+ const bindings = new Map<string, string>();
372
+ const params: PointCoreParameter[] = inputs.map((input) => {
373
+ const name = toIdentifier(input.label);
374
+ bindings.set(input.label, name);
375
+ return { name, type: desugarType(input.type), semanticName: input.label, span: input.span };
376
+ });
377
+ const outputName = toIdentifier(output.name);
378
+ bindings.set(output.name, outputName);
379
+ const outputType = desugarType(output.type);
380
+ return { params, bindings, outputName, outputType };
381
+ }
382
+
383
+ function desugarParameter(binding: PointSemanticBinding): PointCoreParameter {
384
+ return {
385
+ name: toIdentifier(binding.label),
386
+ type: desugarType(binding.type),
387
+ semanticName: binding.label,
388
+ span: binding.span,
389
+ };
390
+ }
391
+
392
+ function desugarType(type: PointSemanticTypeExpression): PointCoreTypeExpression {
393
+ if (type.name === "List" || type.name === "Maybe" || type.name === "Or") {
394
+ return { kind: "typeRef", name: type.name, args: type.args.map(desugarType) };
395
+ }
396
+ const primitives = new Set(["Text", "Int", "Float", "Bool", "Void", "Error", "Page"]);
397
+ if (primitives.has(type.name)) return { kind: "typeRef", name: type.name, args: [] };
398
+ return { kind: "typeRef", name: toPascalCase(type.name), args: [] };
399
+ }
400
+
401
+ function desugarCalculationBody(statements: PointSemanticCalculationStatement[], ctx: DesugarContext): PointCoreStatement[] {
402
+ const body: PointCoreStatement[] = [];
403
+ for (const statement of statements) {
404
+ if (statement.kind === "assignIs") {
405
+ const name = toIdentifier(statement.name);
406
+ if (name !== ctx.outputName) throw new Error(`Calculation can only assign its output ${ctx.outputName}`);
407
+ body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
408
+ continue;
409
+ }
410
+ if (statement.kind === "startsAt" || statement.kind === "startsAs") {
411
+ const name = toIdentifier(statement.name);
412
+ ctx.bindings.set(statement.name, name);
413
+ body.push(mutableValue(name, ctx.outputType, desugarExpression(statement.value, ctx), true, statement.span));
414
+ continue;
415
+ }
416
+ if (statement.kind === "forEach") {
417
+ body.push(...desugarForEach(statement, ctx));
418
+ continue;
419
+ }
420
+ if (statement.kind === "return") {
421
+ body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
422
+ continue;
423
+ }
424
+ body.push(...desugarMutation(statement, ctx));
425
+ }
426
+ return body;
427
+ }
428
+
429
+ function desugarRuleBody(statements: PointSemanticRuleStatement[], ctx: DesugarContext): PointCoreStatement[] {
430
+ const body: PointCoreStatement[] = [];
431
+ for (const statement of statements) {
432
+ if (statement.kind === "startsAt") {
433
+ const name = toIdentifier(statement.name);
434
+ ctx.bindings.set(statement.name, name);
435
+ body.push(mutableValue(name, ctx.outputType, desugarExpression(statement.value, ctx), true, statement.span));
436
+ continue;
437
+ }
438
+ if (statement.kind === "addWhen") {
439
+ body.push({
440
+ kind: "if",
441
+ condition: desugarExpression(statement.condition, ctx),
442
+ thenBody: [
443
+ {
444
+ kind: "assignment",
445
+ name: ctx.outputName,
446
+ operator: "+=",
447
+ value: desugarExpression(statement.amount, ctx),
448
+ span: statement.span,
449
+ },
450
+ ],
451
+ elseBody: [],
452
+ span: statement.span,
453
+ });
454
+ continue;
455
+ }
456
+ if (statement.kind === "forEach") {
457
+ body.push(...desugarForEach(statement, ctx));
458
+ continue;
459
+ }
460
+ if (statement.kind === "return") {
461
+ body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
462
+ continue;
463
+ }
464
+ body.push(...desugarMutation(statement, ctx));
465
+ }
466
+ return body;
467
+ }
468
+
469
+ function desugarLabelBody(statements: PointSemanticLabelStatement[], ctx: DesugarContext): PointCoreStatement[] {
470
+ const body: PointCoreStatement[] = [];
471
+ for (const statement of statements) {
472
+ if (statement.kind === "whenReturn") {
473
+ body.push({
474
+ kind: "if",
475
+ condition: desugarExpression(statement.condition, ctx),
476
+ thenBody: [{ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span }],
477
+ elseBody: [],
478
+ span: statement.span,
479
+ });
480
+ continue;
481
+ }
482
+ body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
483
+ }
484
+ return body;
485
+ }
486
+
487
+ function desugarPolicyBody(statements: PointSemanticPolicyStatement[], ctx: DesugarContext): PointCoreStatement[] {
488
+ return statements.map((statement) => {
489
+ if (statement.kind === "deny") {
490
+ return {
491
+ kind: "return" as const,
492
+ span: statement.span,
493
+ value: {
494
+ kind: "binary" as const,
495
+ operator: "==" as const,
496
+ left: desugarExpression(statement.condition, ctx),
497
+ right: { kind: "literal" as const, value: false },
498
+ span: statement.span,
499
+ },
500
+ };
501
+ }
502
+ return { kind: "return" as const, value: desugarExpression(statement.condition, ctx), span: statement.span };
503
+ });
504
+ }
505
+
506
+ function desugarViewBody(statements: PointSemanticViewStatement[], ctx: DesugarContext): PointCoreStatement[] {
507
+ const body: PointCoreStatement[] = [];
508
+ for (const statement of statements) {
509
+ if (statement.kind === "whenRender") {
510
+ body.push({
511
+ kind: "if",
512
+ condition: desugarExpression(statement.condition, ctx),
513
+ thenBody: [{ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span }],
514
+ elseBody: [],
515
+ span: statement.span,
516
+ });
517
+ continue;
518
+ }
519
+ body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
520
+ }
521
+ return body;
522
+ }
523
+
524
+ function desugarWorkflowBody(statements: PointSemanticWorkflowStatement[], ctx: DesugarContext): PointCoreStatement[] {
525
+ const body: PointCoreStatement[] = [];
526
+ for (const statement of statements) {
527
+ if (statement.kind === "step") {
528
+ const name = toIdentifier(statement.name);
529
+ ctx.bindings.set(statement.name, name);
530
+ body.push(mutableValue(name, ctx.outputType, desugarExpression(statement.value, ctx), false, statement.span));
531
+ continue;
532
+ }
533
+ body.push({ kind: "return", value: desugarExpression(statement.value, ctx), span: statement.span });
534
+ }
535
+ return body;
536
+ }
537
+
538
+ function desugarForEach(
539
+ statement: Extract<PointSemanticCalculationStatement | PointSemanticRuleStatement, { kind: "forEach" }>,
540
+ ctx: DesugarContext,
541
+ ): PointCoreStatement[] {
542
+ const itemName = toIdentifier(statement.item);
543
+ const loopCtx: DesugarContext = {
544
+ ...ctx,
545
+ bindings: new Map(ctx.bindings),
546
+ };
547
+ loopCtx.bindings.set(statement.item, itemName);
548
+ return [
549
+ {
550
+ kind: "for",
551
+ itemName,
552
+ iterable: desugarExpression(statement.iterable, ctx),
553
+ body: statement.body.flatMap((mutation) => desugarMutation(mutation, loopCtx)),
554
+ span: statement.span,
555
+ },
556
+ ];
557
+ }
558
+
559
+ function desugarMutation(statement: PointSemanticMutationStatement, ctx: DesugarContext): PointCoreStatement[] {
560
+ if (statement.kind === "addTo") {
561
+ return [
562
+ {
563
+ kind: "assignment",
564
+ name: toIdentifier(statement.target),
565
+ operator: "+=",
566
+ value: desugarExpression(statement.amount, ctx),
567
+ span: statement.span,
568
+ },
569
+ ];
570
+ }
571
+ if (statement.kind === "subtractFrom") {
572
+ return [
573
+ {
574
+ kind: "assignment",
575
+ name: toIdentifier(statement.target),
576
+ operator: "-=",
577
+ value: desugarExpression(statement.amount, ctx),
578
+ span: statement.span,
579
+ },
580
+ ];
581
+ }
582
+ return [
583
+ {
584
+ kind: "assignment",
585
+ name: toIdentifier(statement.target),
586
+ operator: "=",
587
+ value: desugarExpression(statement.value, ctx),
588
+ span: statement.span,
589
+ },
590
+ ];
591
+ }
592
+
593
+ function mutableValue(
594
+ name: string,
595
+ type: PointCoreTypeExpression,
596
+ value: PointCoreExpression,
597
+ mutable: boolean,
598
+ span?: PointSourceSpan,
599
+ ): PointCoreValueDeclaration {
600
+ return { kind: "value", name, type, value, mutable, span };
601
+ }
602
+
603
+ function desugarExpression(expression: PointSemanticExpression, ctx: DesugarContext): PointCoreExpression {
604
+ switch (expression.kind) {
605
+ case "literal":
606
+ return { kind: "literal", value: expression.value, span: expression.span };
607
+ case "name":
608
+ return { kind: "identifier", name: resolveName(expression.label, ctx.bindings), span: expression.span };
609
+ case "property":
610
+ return {
611
+ kind: "property",
612
+ target: desugarExpression(expression.target, ctx),
613
+ name: toIdentifier(expression.label),
614
+ span: expression.span,
615
+ };
616
+ case "binary":
617
+ return {
618
+ kind: "binary",
619
+ operator: expression.operator,
620
+ left: desugarExpression(expression.left, ctx),
621
+ right: desugarExpression(expression.right, ctx),
622
+ span: expression.span,
623
+ };
624
+ case "call":
625
+ return {
626
+ kind: "call",
627
+ callee: resolveCallable(expression.callee, ctx.callables),
628
+ args: expression.args.map((arg) => desugarExpression(arg, ctx)),
629
+ span: expression.span,
630
+ };
631
+ case "await":
632
+ return { kind: "await", value: desugarExpression(expression.value, ctx), span: expression.span };
633
+ case "list":
634
+ return { kind: "list", items: expression.items.map((item) => desugarExpression(item, ctx)), span: expression.span };
635
+ case "record":
636
+ return {
637
+ kind: "record",
638
+ fields: expression.fields.map((field) => ({
639
+ name: toIdentifier(field.label),
640
+ value: desugarExpression(field.value, ctx),
641
+ span: field.span,
642
+ })),
643
+ span: expression.span,
644
+ };
645
+ case "error":
646
+ return {
647
+ kind: "call",
648
+ callee: "Error",
649
+ args: [{ kind: "literal", value: expression.message, span: expression.span }],
650
+ span: expression.span,
651
+ };
652
+ }
653
+ }
654
+
655
+ function resolveName(label: string, bindings: Map<string, string>): string {
656
+ if (bindings.has(label)) return bindings.get(label)!;
657
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(label)) return label;
658
+ return toIdentifier(label);
659
+ }
660
+
661
+ function resolveCallable(label: string, callables: Map<string, string>): string {
662
+ if (callables.has(label)) return callables.get(label)!;
663
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(label)) return label;
664
+ return toIdentifier(label);
665
+ }