@hatchingpoint/point 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
+ }