@hatchingpoint/point 0.0.3 → 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.
@@ -11,7 +11,25 @@ import type {
11
11
  PointCoreDiagnostic,
12
12
  } from "./check.ts";
13
13
 
14
- export type PointCoreSymbolKind = "module" | "import" | "value" | "function" | "param" | "type" | "field";
14
+ export type PointCoreSymbolKind =
15
+ | "module"
16
+ | "import"
17
+ | "value"
18
+ | "function"
19
+ | "param"
20
+ | "type"
21
+ | "field"
22
+ | "record"
23
+ | "calculation"
24
+ | "rule"
25
+ | "label"
26
+ | "external"
27
+ | "action"
28
+ | "policy"
29
+ | "view"
30
+ | "route"
31
+ | "workflow"
32
+ | "command";
15
33
 
16
34
  export interface PointCoreSymbol {
17
35
  ref: string;
@@ -21,8 +39,9 @@ export interface PointCoreSymbol {
21
39
  module: string;
22
40
  type?: string;
23
41
  mutable?: boolean;
24
- from?: string;
25
- span: PointSourceSpan | null;
42
+ from?: string;
43
+ effects?: string[];
44
+ span: PointSourceSpan | null;
26
45
  }
27
46
 
28
47
  export interface PointCoreIndex {
@@ -56,8 +75,8 @@ export interface PointCoreRepairStep {
56
75
  relatedRefs: string[];
57
76
  }
58
77
 
59
- export function createPointCoreIndex(program: PointCoreProgram): PointCoreIndex {
60
- const moduleName = program.module ?? "anonymous";
78
+ export function createPointCoreIndex(program: PointCoreProgram): PointCoreIndex {
79
+ const moduleName = program.module ?? "anonymous";
61
80
  const refs: PointCoreSymbol[] = [
62
81
  {
63
82
  ref: refFor(moduleName, "module"),
@@ -67,10 +86,13 @@ export function createPointCoreIndex(program: PointCoreProgram): PointCoreIndex
67
86
  module: moduleName,
68
87
  span: program.span ?? null,
69
88
  },
70
- ];
71
- for (const declaration of program.declarations) refs.push(...symbolsForDeclaration(moduleName, declaration));
72
- return { schemaVersion: "point.core.index.v1", module: moduleName, refs };
73
- }
89
+ ];
90
+ for (const declaration of program.declarations) {
91
+ refs.push(...symbolsForDeclaration(moduleName, declaration));
92
+ refs.push(...semanticSymbolsForDeclaration(moduleName, declaration));
93
+ }
94
+ return { schemaVersion: "point.core.index.v1", module: moduleName, refs };
95
+ }
74
96
 
75
97
  export function explainPointCoreRef(program: PointCoreProgram, ref: string): PointCoreExplanation {
76
98
  const index = createPointCoreIndex(program);
@@ -95,7 +117,7 @@ export function explainPointCoreRef(program: PointCoreProgram, ref: string): Poi
95
117
  };
96
118
  }
97
119
 
98
- export function createPointCoreRepairPlan(diagnostics: PointCoreDiagnostic[]): PointCoreRepairPlan {
120
+ export function createPointCoreRepairPlan(diagnostics: PointCoreDiagnostic[]): PointCoreRepairPlan {
99
121
  return {
100
122
  schemaVersion: "point.core.repair-plan.v1",
101
123
  ok: diagnostics.length === 0,
@@ -109,23 +131,45 @@ export function createPointCoreRepairPlan(diagnostics: PointCoreDiagnostic[]): P
109
131
  relatedRefs: diagnostic.relatedRefs ?? [],
110
132
  })),
111
133
  };
112
- }
134
+ }
135
+
136
+ export function mapDiagnosticsToSemanticRefs(program: PointCoreProgram, diagnostics: PointCoreDiagnostic[]): PointCoreDiagnostic[] {
137
+ return diagnostics.map((diagnostic) => ({
138
+ ...diagnostic,
139
+ ref: semanticRefForDiagnosticPath(program, diagnostic.path) ?? diagnostic.ref,
140
+ relatedRefs: diagnostic.relatedRefs?.map((ref) => semanticRefForCoreRef(program, ref) ?? ref),
141
+ }));
142
+ }
113
143
 
114
144
  function symbolsForDeclaration(moduleName: string, declaration: PointCoreDeclaration): PointCoreSymbol[] {
115
- if (declaration.kind === "import") {
116
- return declaration.names.map((name) => ({
145
+ if (declaration.kind === "import") {
146
+ return declaration.names.map((name) => ({
117
147
  ref: refFor(moduleName, `import.${name}`),
118
148
  path: `import.${name}`,
119
149
  kind: "import",
120
150
  name,
121
151
  module: moduleName,
122
152
  from: declaration.from,
123
- span: declaration.span ?? null,
124
- }));
125
- }
126
- if (declaration.kind === "value") return [valueSymbol(moduleName, declaration, `value.${declaration.name}`)];
127
- if (declaration.kind === "type") return typeSymbols(moduleName, declaration);
128
- return functionSymbols(moduleName, declaration);
153
+ span: declaration.span ?? null,
154
+ }));
155
+ }
156
+ if (declaration.kind === "external") {
157
+ return [
158
+ {
159
+ ref: refFor(moduleName, `external.${declaration.name}`),
160
+ path: `external.${declaration.name}`,
161
+ kind: "external",
162
+ name: declaration.name,
163
+ module: moduleName,
164
+ type: formatType(declaration.returnType),
165
+ from: declaration.from,
166
+ span: declaration.span ?? null,
167
+ },
168
+ ];
169
+ }
170
+ if (declaration.kind === "value") return [valueSymbol(moduleName, declaration, `value.${declaration.name}`)];
171
+ if (declaration.kind === "type") return typeSymbols(moduleName, declaration);
172
+ return functionSymbols(moduleName, declaration);
129
173
  }
130
174
 
131
175
  function valueSymbol(moduleName: string, declaration: PointCoreValueDeclaration, path: string): PointCoreSymbol {
@@ -163,7 +207,7 @@ function typeSymbols(moduleName: string, declaration: PointCoreTypeDeclaration):
163
207
  ];
164
208
  }
165
209
 
166
- function functionSymbols(moduleName: string, declaration: PointCoreFunctionDeclaration): PointCoreSymbol[] {
210
+ function functionSymbols(moduleName: string, declaration: PointCoreFunctionDeclaration): PointCoreSymbol[] {
167
211
  return [
168
212
  {
169
213
  ref: refFor(moduleName, `fn.${declaration.name}`),
@@ -171,9 +215,10 @@ function functionSymbols(moduleName: string, declaration: PointCoreFunctionDecla
171
215
  kind: "function",
172
216
  name: declaration.name,
173
217
  module: moduleName,
174
- type: formatType(declaration.returnType),
175
- span: declaration.span ?? null,
176
- },
218
+ type: formatType(declaration.returnType),
219
+ effects: declaration.semantic?.effects,
220
+ span: declaration.span ?? null,
221
+ },
177
222
  ...declaration.params.map((param) => ({
178
223
  ref: refFor(moduleName, `fn.${declaration.name}.param.${param.name}`),
179
224
  path: `fn.${declaration.name}.param.${param.name}`,
@@ -184,32 +229,164 @@ function functionSymbols(moduleName: string, declaration: PointCoreFunctionDecla
184
229
  span: param.span ?? null,
185
230
  })),
186
231
  ];
187
- }
188
-
189
- function refFor(moduleName: string, path: string): string {
190
- return `point://core/${moduleName}/${path}`;
191
- }
232
+ }
233
+
234
+ function semanticSymbolsForDeclaration(moduleName: string, declaration: PointCoreDeclaration): PointCoreSymbol[] {
235
+ if (declaration.kind === "type" && declaration.semantic?.kind === "record") {
236
+ const recordPath = `record.${declaration.semantic.name}`;
237
+ return [
238
+ {
239
+ ref: semanticRefFor(moduleName, recordPath),
240
+ path: recordPath,
241
+ kind: "record",
242
+ name: declaration.semantic.name,
243
+ module: moduleName,
244
+ span: declaration.span ?? null,
245
+ },
246
+ ...declaration.fields.map((field) => {
247
+ const fieldName = field.semanticName ?? field.name;
248
+ const path = `${recordPath}.field.${fieldName}`;
249
+ return {
250
+ ref: semanticRefFor(moduleName, path),
251
+ path,
252
+ kind: "field" as const,
253
+ name: fieldName,
254
+ module: moduleName,
255
+ type: formatType(field.type),
256
+ span: field.span ?? null,
257
+ };
258
+ }),
259
+ ];
260
+ }
261
+ if (declaration.kind === "function" && declaration.semantic) {
262
+ const semanticPath = `${declaration.semantic.kind}.${declaration.semantic.name}`;
263
+ return [
264
+ {
265
+ ref: semanticRefFor(moduleName, semanticPath),
266
+ path: semanticPath,
267
+ kind: declaration.semantic.kind,
268
+ name: declaration.semantic.name,
269
+ module: moduleName,
270
+ type: formatType(declaration.returnType),
271
+ effects: declaration.semantic.effects,
272
+ span: declaration.span ?? null,
273
+ },
274
+ ...declaration.params.map((param) => {
275
+ const paramName = param.semanticName ?? param.name;
276
+ const path = `${semanticPath}.input.${paramName}`;
277
+ return {
278
+ ref: semanticRefFor(moduleName, path),
279
+ path,
280
+ kind: "param" as const,
281
+ name: paramName,
282
+ module: moduleName,
283
+ type: formatType(param.type),
284
+ span: param.span ?? null,
285
+ };
286
+ }),
287
+ ];
288
+ }
289
+ if (declaration.kind === "external" && declaration.semantic) {
290
+ const semanticPath = `external.${declaration.semantic.name}`;
291
+ return [
292
+ {
293
+ ref: semanticRefFor(moduleName, semanticPath),
294
+ path: semanticPath,
295
+ kind: "external",
296
+ name: declaration.semantic.name,
297
+ module: moduleName,
298
+ type: formatType(declaration.returnType),
299
+ from: declaration.from,
300
+ span: declaration.span ?? null,
301
+ },
302
+ ];
303
+ }
304
+ return [];
305
+ }
306
+
307
+ function refFor(moduleName: string, path: string): string {
308
+ return `point://core/${moduleName}/${path}`;
309
+ }
310
+
311
+ function semanticRefFor(moduleName: string, path: string): string {
312
+ return `point://semantic/${moduleName}/${path}`;
313
+ }
192
314
 
193
315
  function relatedRefsFor(symbol: PointCoreSymbol, index: PointCoreIndex): string[] {
194
316
  if (symbol.kind === "field") {
195
317
  const ownerPath = symbol.path.split(".").slice(0, 2).join(".");
196
318
  return index.refs.filter((candidate) => candidate.path.startsWith(`${ownerPath}.`) && candidate.ref !== symbol.ref).map((candidate) => candidate.ref);
197
319
  }
198
- if (symbol.kind === "function") {
199
- return index.refs.filter((candidate) => candidate.path.startsWith(`${symbol.path}.param.`)).map((candidate) => candidate.ref);
200
- }
320
+ if (
321
+ symbol.kind === "function" ||
322
+ symbol.kind === "calculation" ||
323
+ symbol.kind === "rule" ||
324
+ symbol.kind === "label" ||
325
+ symbol.kind === "action" ||
326
+ symbol.kind === "policy" ||
327
+ symbol.kind === "view" ||
328
+ symbol.kind === "route" ||
329
+ symbol.kind === "workflow" ||
330
+ symbol.kind === "command"
331
+ ) {
332
+ return index.refs
333
+ .filter((candidate) => candidate.path.startsWith(`${symbol.path}.param.`) || candidate.path.startsWith(`${symbol.path}.input.`))
334
+ .map((candidate) => candidate.ref);
335
+ }
201
336
  return [];
202
337
  }
203
338
 
204
- function summaryFor(symbol: PointCoreSymbol): string {
339
+ function summaryFor(symbol: PointCoreSymbol): string {
205
340
  if (symbol.kind === "module") return `Module ${symbol.name}.`;
206
341
  if (symbol.kind === "import") return `Import ${symbol.name} from ${symbol.from}.`;
207
342
  if (symbol.kind === "value") return `${symbol.mutable ? "Mutable" : "Immutable"} value ${symbol.name}: ${symbol.type}.`;
208
- if (symbol.kind === "function") return `Function ${symbol.name} returns ${symbol.type}.`;
343
+ if (symbol.kind === "function") return `Function ${symbol.name} returns ${symbol.type}.`;
344
+ if (symbol.kind === "external") return `External function ${symbol.name} from ${symbol.from} returns ${symbol.type}.`;
209
345
  if (symbol.kind === "param") return `Parameter ${symbol.name}: ${symbol.type}.`;
210
- if (symbol.kind === "type") return `Named type ${symbol.name}.`;
211
- return `Field ${symbol.name}: ${symbol.type}.`;
212
- }
346
+ if (symbol.kind === "type") return `Named type ${symbol.name}.`;
347
+ if (symbol.kind === "record") return `Semantic record ${symbol.name}.`;
348
+ if (symbol.kind === "calculation") return `Semantic calculation ${symbol.name} returns ${symbol.type}.`;
349
+ if (symbol.kind === "rule") return `Semantic rule ${symbol.name} returns ${symbol.type}.`;
350
+ if (symbol.kind === "label") return `Semantic label ${symbol.name} returns ${symbol.type}.`;
351
+ if (symbol.kind === "action") return `Semantic action ${symbol.name} returns ${symbol.type}; effects: ${(symbol.effects ?? []).join(", ") || "none"}.`;
352
+ if (symbol.kind === "policy") return `Semantic policy ${symbol.name} returns ${symbol.type}.`;
353
+ if (symbol.kind === "view") return `Semantic view ${symbol.name} returns React JSX.`;
354
+ if (symbol.kind === "route") return `Semantic route ${symbol.name} returns ${symbol.type}.`;
355
+ if (symbol.kind === "workflow") return `Semantic workflow ${symbol.name} returns ${symbol.type}.`;
356
+ if (symbol.kind === "command") return `Semantic command ${symbol.name} returns ${symbol.type}.`;
357
+ return `Field ${symbol.name}: ${symbol.type}.`;
358
+ }
359
+
360
+ function semanticRefForCoreRef(program: PointCoreProgram, ref: string): string | null {
361
+ const prefix = `point://core/${program.module ?? "anonymous"}/`;
362
+ if (!ref.startsWith(prefix)) return null;
363
+ return semanticRefForDiagnosticPath(program, ref.slice(prefix.length));
364
+ }
365
+
366
+ function semanticRefForDiagnosticPath(program: PointCoreProgram, path: string): string | null {
367
+ const moduleName = program.module ?? "anonymous";
368
+ for (const declaration of program.declarations) {
369
+ if (declaration.kind === "type" && declaration.semantic?.kind === "record") {
370
+ const recordPath = `type.${declaration.name}`;
371
+ if (path === recordPath || path.startsWith(`${recordPath}.`)) {
372
+ const parts = path.split(".");
373
+ const field = parts.length >= 3 ? declaration.fields.find((candidate) => candidate.name === parts[2]) : undefined;
374
+ const semanticPath = field
375
+ ? `record.${declaration.semantic.name}.field.${field.semanticName ?? field.name}`
376
+ : `record.${declaration.semantic.name}`;
377
+ return semanticRefFor(moduleName, semanticPath);
378
+ }
379
+ }
380
+ if ((declaration.kind === "function" || declaration.kind === "external") && declaration.semantic) {
381
+ const fnPath = `fn.${declaration.name}`;
382
+ if (path === fnPath || path.startsWith(`${fnPath}.`)) {
383
+ const semanticPath = `${declaration.semantic.kind}.${declaration.semantic.name}`;
384
+ return semanticRefFor(moduleName, semanticPath);
385
+ }
386
+ }
387
+ }
388
+ return null;
389
+ }
213
390
 
214
391
  function formatType(type: PointCoreTypeExpression): string {
215
392
  if (type.args.length === 0) return String(type.name);
@@ -0,0 +1,124 @@
1
+ import type {
2
+ PointCoreDeclaration,
3
+ PointCoreExpression,
4
+ PointCoreFunctionDeclaration,
5
+ PointCoreParameter,
6
+ PointCoreProgram,
7
+ PointCoreStatement,
8
+ PointCoreTypeDeclaration,
9
+ PointCoreValueDeclaration,
10
+ } from "./ast.ts";
11
+
12
+ const BINARY_OPERATORS: Record<string, string> = {
13
+ and: "&&",
14
+ or: "||",
15
+ };
16
+
17
+ /** Emit JavaScript from a core AST program (no type syntax). Production path: parsePointSource → check → emit. */
18
+ export function emitPointCoreJavaScript(program: PointCoreProgram): string {
19
+ const lines: string[] = [];
20
+ lines.push("// Generated by Point. Do not edit directly.");
21
+ if (program.module) lines.push(`// Point module: ${program.module}`);
22
+ lines.push("");
23
+ for (const declaration of program.declarations) {
24
+ lines.push(...emitDeclaration(declaration), "");
25
+ }
26
+ return `${trimTrailingBlankLines(lines).join("\n")}\n`;
27
+ }
28
+
29
+ function emitDeclaration(declaration: PointCoreDeclaration): string[] {
30
+ if (declaration.kind === "import") {
31
+ return [`import { ${declaration.names.join(", ")} } from ${JSON.stringify(declaration.from)};`];
32
+ }
33
+ if (declaration.kind === "external") {
34
+ const imported = declaration.importName ? `${declaration.importName} as ${declaration.name}` : declaration.name;
35
+ return [`import { ${imported} } from ${JSON.stringify(declaration.from)};`];
36
+ }
37
+ if (declaration.kind === "type") return [];
38
+ if (declaration.kind === "value") return [emitValue(declaration, true)];
39
+ return emitFunction(declaration);
40
+ }
41
+
42
+ function emitFunction(declaration: PointCoreFunctionDeclaration): string[] {
43
+ const asyncPrefix = declaration.semantic?.kind === "action" || declaration.semantic?.kind === "workflow" || declaration.semantic?.kind === "command" ? "async " : "";
44
+ return [
45
+ `export ${asyncPrefix}function ${declaration.name}(${declaration.params.map(emitParam).join(", ")}) {`,
46
+ ...indentLines(declaration.body.flatMap((statement) => emitStatement(statement, declaration.semantic?.kind))),
47
+ "}",
48
+ ];
49
+ }
50
+
51
+ function emitStatement(statement: PointCoreStatement, semanticKind?: string): string[] {
52
+ if (statement.kind === "return") {
53
+ if (semanticKind === "view" && statement.value?.kind === "literal" && typeof statement.value.value === "string") {
54
+ return [`return ${JSON.stringify(statement.value.value)};`];
55
+ }
56
+ return [statement.value ? `return ${emitExpression(statement.value)};` : "return;"];
57
+ }
58
+ if (statement.kind === "value") return [emitValue(statement, false)];
59
+ if (statement.kind === "assignment") return [`${statement.name} ${statement.operator} ${emitExpression(statement.value)};`];
60
+ if (statement.kind === "if") {
61
+ const lines = [
62
+ `if (${emitCondition(statement.condition)}) {`,
63
+ ...indentLines(statement.thenBody.flatMap((child) => emitStatement(child, semanticKind))),
64
+ "}",
65
+ ];
66
+ if (statement.elseBody.length > 0) {
67
+ lines.push(
68
+ "else {",
69
+ ...indentLines(statement.elseBody.flatMap((child) => emitStatement(child, semanticKind))),
70
+ "}",
71
+ );
72
+ }
73
+ return lines;
74
+ }
75
+ if (statement.kind === "for") {
76
+ return [
77
+ `for (const ${statement.itemName} of ${emitExpression(statement.iterable)}) {`,
78
+ ...indentLines(statement.body.flatMap((child) => emitStatement(child, semanticKind))),
79
+ "}",
80
+ ];
81
+ }
82
+ return [`${emitExpression(statement.value)};`];
83
+ }
84
+
85
+ function emitValue(declaration: PointCoreValueDeclaration, exported: boolean): string {
86
+ const prefix = exported ? "export " : "";
87
+ const keyword = declaration.mutable ? "let" : "const";
88
+ return `${prefix}${keyword} ${declaration.name} = ${emitExpression(declaration.value)};`;
89
+ }
90
+
91
+ function emitParam(param: PointCoreParameter): string {
92
+ return param.name;
93
+ }
94
+
95
+ function emitExpression(expression: PointCoreExpression): string {
96
+ if (expression.kind === "literal") return JSON.stringify(expression.value);
97
+ if (expression.kind === "identifier") return expression.name;
98
+ if (expression.kind === "list") return `[${expression.items.map(emitExpression).join(", ")}]`;
99
+ if (expression.kind === "record") {
100
+ return `{ ${expression.fields.map((field) => `${field.name}: ${emitExpression(field.value)}`).join(", ")} }`;
101
+ }
102
+ if (expression.kind === "await") return `await ${emitExpression(expression.value)}`;
103
+ if (expression.kind === "property") return `${emitExpression(expression.target)}.${expression.name}`;
104
+ if (expression.kind === "call") {
105
+ if (expression.callee === "Error") return `{ message: ${expression.args[0] ? emitExpression(expression.args[0]) : JSON.stringify("")} }`;
106
+ return `${expression.callee}(${expression.args.map(emitExpression).join(", ")})`;
107
+ }
108
+ const operator = BINARY_OPERATORS[expression.operator] ?? expression.operator;
109
+ return `(${emitExpression(expression.left)} ${operator} ${emitExpression(expression.right)})`;
110
+ }
111
+
112
+ function emitCondition(expression: PointCoreExpression): string {
113
+ const emitted = emitExpression(expression);
114
+ return emitted.startsWith("(") && emitted.endsWith(")") ? emitted.slice(1, -1) : emitted;
115
+ }
116
+
117
+ function indentLines(lines: string[]): string[] {
118
+ return lines.map((line) => ` ${line}`);
119
+ }
120
+
121
+ function trimTrailingBlankLines(lines: string[]): string[] {
122
+ while (lines.at(-1) === "") lines.pop();
123
+ return lines;
124
+ }
@@ -16,6 +16,7 @@ const BINARY_OPERATORS: Record<string, string> = {
16
16
  or: "||",
17
17
  };
18
18
 
19
+ /** Emit TypeScript from a core AST program. Production path: parsePointSource → check → emit. */
19
20
  export function emitPointCoreTypeScript(program: PointCoreProgram): string {
20
21
  const lines: string[] = [];
21
22
  lines.push("// Generated by Point. Do not edit directly.");
@@ -31,6 +32,10 @@ function emitDeclaration(declaration: PointCoreDeclaration): string[] {
31
32
  if (declaration.kind === "import") {
32
33
  return [`import { ${declaration.names.join(", ")} } from ${JSON.stringify(declaration.from)};`];
33
34
  }
35
+ if (declaration.kind === "external") {
36
+ const imported = declaration.importName ? `${declaration.importName} as ${declaration.name}` : declaration.name;
37
+ return [`import { ${imported} } from ${JSON.stringify(declaration.from)};`];
38
+ }
34
39
  if (declaration.kind === "type") return emitType(declaration);
35
40
  if (declaration.kind === "value") return [emitValue(declaration, true)];
36
41
  return emitFunction(declaration);
@@ -45,36 +50,60 @@ function emitType(declaration: PointCoreTypeDeclaration): string[] {
45
50
  }
46
51
 
47
52
  function emitFunction(declaration: PointCoreFunctionDeclaration): string[] {
53
+ const asyncPrefix = declaration.semantic?.kind === "action" || declaration.semantic?.kind === "workflow" || declaration.semantic?.kind === "command" ? "async " : "";
54
+ const returnType =
55
+ declaration.semantic?.kind === "action" || declaration.semantic?.kind === "workflow" || declaration.semantic?.kind === "command"
56
+ ? `Promise<${emitTypeExpression(declaration.returnType)}>`
57
+ : declaration.semantic?.kind === "view"
58
+ ? "JSX.Element"
59
+ : declaration.semantic?.kind === "route"
60
+ ? "Response | string"
61
+ : emitTypeExpression(declaration.returnType);
48
62
  return [
49
- `export function ${declaration.name}(${declaration.params.map(emitParam).join(", ")}): ${emitTypeExpression(declaration.returnType)} {`,
50
- ...indentLines(declaration.body.flatMap((statement) => emitStatement(statement))),
63
+ `export ${asyncPrefix}function ${declaration.name}(${declaration.params.map(emitParam).join(", ")}): ${returnType} {`,
64
+ ...indentLines(declaration.body.flatMap((statement) => emitStatement(statement, declaration.semantic?.kind))),
51
65
  "}",
52
66
  ];
53
67
  }
54
68
 
55
- function emitStatement(statement: PointCoreStatement): string[] {
69
+ function emitStatement(statement: PointCoreStatement, semanticKind?: string): string[] {
56
70
  if (statement.kind === "return") {
71
+ if (semanticKind === "view" && statement.value?.kind === "literal" && typeof statement.value.value === "string") {
72
+ return [`return <>${escapeJsxText(statement.value.value)}</>;`];
73
+ }
57
74
  return [statement.value ? `return ${emitExpression(statement.value)};` : "return;"];
58
75
  }
59
76
  if (statement.kind === "value") return [emitValue(statement, false)];
77
+ if (statement.kind === "assignment") return [`${statement.name} ${statement.operator} ${emitExpression(statement.value)};`];
60
78
  if (statement.kind === "if") {
61
79
  const lines = [
62
80
  `if (${emitCondition(statement.condition)}) {`,
63
- ...indentLines(statement.thenBody.flatMap((child) => emitStatement(child))),
81
+ ...indentLines(statement.thenBody.flatMap((child) => emitStatement(child, semanticKind))),
64
82
  "}",
65
83
  ];
66
84
  if (statement.elseBody.length > 0) {
67
85
  lines.push(
68
86
  "else {",
69
- ...indentLines(statement.elseBody.flatMap((child) => emitStatement(child))),
87
+ ...indentLines(statement.elseBody.flatMap((child) => emitStatement(child, semanticKind))),
70
88
  "}",
71
89
  );
72
90
  }
73
91
  return lines;
74
92
  }
93
+ if (statement.kind === "for") {
94
+ return [
95
+ `for (const ${statement.itemName} of ${emitExpression(statement.iterable)}) {`,
96
+ ...indentLines(statement.body.flatMap((child) => emitStatement(child, semanticKind))),
97
+ "}",
98
+ ];
99
+ }
75
100
  return [`${emitExpression(statement.value)};`];
76
101
  }
77
102
 
103
+ function escapeJsxText(value: string): string {
104
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
105
+ }
106
+
78
107
  function emitValue(declaration: PointCoreValueDeclaration, exported: boolean): string {
79
108
  const prefix = exported ? "export " : "";
80
109
  const keyword = declaration.mutable ? "let" : "const";
@@ -87,6 +116,9 @@ function emitParam(param: PointCoreParameter): string {
87
116
 
88
117
  function emitTypeExpression(type: PointCoreTypeExpression): string {
89
118
  if (type.name === "List") return `Array<${type.args[0] ? emitTypeExpression(type.args[0]) : "unknown"}>`;
119
+ if (type.name === "Maybe") return `${type.args[0] ? emitTypeExpression(type.args[0]) : "unknown"} | null`;
120
+ if (type.name === "Or") return type.args.map(emitTypeExpression).join(" | ");
121
+ if (type.name === "Error") return "{ message: string }";
90
122
  if (isPrimitiveType(type.name)) return emitPrimitiveType(type.name);
91
123
  return type.name;
92
124
  }
@@ -105,8 +137,10 @@ function emitExpression(expression: PointCoreExpression): string {
105
137
  if (expression.kind === "record") {
106
138
  return `{ ${expression.fields.map((field) => `${field.name}: ${emitExpression(field.value)}`).join(", ")} }`;
107
139
  }
140
+ if (expression.kind === "await") return `await ${emitExpression(expression.value)}`;
108
141
  if (expression.kind === "property") return `${emitExpression(expression.target)}.${expression.name}`;
109
142
  if (expression.kind === "call") {
143
+ if (expression.callee === "Error") return `{ message: ${expression.args[0] ? emitExpression(expression.args[0]) : JSON.stringify("")} }`;
110
144
  return `${expression.callee}(${expression.args.map(emitExpression).join(", ")})`;
111
145
  }
112
146
  const operator = BINARY_OPERATORS[expression.operator] ?? expression.operator;
@@ -1,103 +1,6 @@
1
- import type {
2
- PointCoreDeclaration,
3
- PointCoreExpression,
4
- PointCoreFunctionDeclaration,
5
- PointCoreParameter,
6
- PointCoreProgram,
7
- PointCoreStatement,
8
- PointCoreTypeDeclaration,
9
- PointCoreValueDeclaration,
10
- } from "./ast.ts";
1
+ import { formatSemanticProgram } from "../semantic/format.ts";
2
+ import { parseSemanticSource } from "../semantic/parse.ts";
11
3
 
12
- export function formatPointCore(program: PointCoreProgram): string {
13
- const lines: string[] = [];
14
- if (program.module) {
15
- lines.push(`module ${program.module}`, "");
16
- }
17
- program.declarations.forEach((declaration, index) => {
18
- if (index > 0) lines.push("");
19
- lines.push(...formatDeclaration(declaration));
20
- });
21
- return `${lines.join("\n")}\n`;
22
- }
23
-
24
- function formatDeclaration(declaration: PointCoreDeclaration): string[] {
25
- if (declaration.kind === "import") {
26
- return [`import { ${declaration.names.join(", ")} } from ${JSON.stringify(declaration.from)}`];
27
- }
28
- if (declaration.kind === "value") return [formatValue(declaration)];
29
- if (declaration.kind === "type") return formatType(declaration);
30
- return formatFunction(declaration);
31
- }
32
-
33
- function formatType(declaration: PointCoreTypeDeclaration): string[] {
34
- return [
35
- `type ${declaration.name} {`,
36
- ...declaration.fields.map((field) => ` ${formatParam(field)}`),
37
- "}",
38
- ];
39
- }
40
-
41
- function formatFunction(declaration: PointCoreFunctionDeclaration): string[] {
42
- return [
43
- `fn ${declaration.name}(${declaration.params.map(formatParam).join(", ")}): ${formatTypeExpression(declaration.returnType)} {`,
44
- ...indentLines(declaration.body.flatMap((statement) => formatStatementLines(statement))),
45
- "}",
46
- ];
47
- }
48
-
49
- function formatStatementLines(statement: PointCoreStatement): string[] {
50
- if (statement.kind === "return") {
51
- return [statement.value ? `return ${formatExpression(statement.value)}` : "return"];
52
- }
53
- if (statement.kind === "value") return [formatValue(statement)];
54
- if (statement.kind === "if") return formatIf(statement);
55
- return [formatExpression(statement.value)];
56
- }
57
-
58
- function formatIf(statement: Extract<PointCoreStatement, { kind: "if" }>): string[] {
59
- const lines = [
60
- `if ${formatExpression(statement.condition)} {`,
61
- ...indentLines(statement.thenBody.flatMap((child) => formatStatementLines(child))),
62
- "}",
63
- ];
64
- if (statement.elseBody.length > 0) {
65
- lines.push(
66
- "else {",
67
- ...indentLines(statement.elseBody.flatMap((child) => formatStatementLines(child))),
68
- "}",
69
- );
70
- }
71
- return lines;
72
- }
73
-
74
- function formatValue(declaration: PointCoreValueDeclaration): string {
75
- return `${declaration.mutable ? "var" : "let"} ${declaration.name}: ${formatTypeExpression(declaration.type)} = ${formatExpression(declaration.value)}`;
76
- }
77
-
78
- function formatParam(param: PointCoreParameter): string {
79
- return `${param.name}: ${formatTypeExpression(param.type)}`;
80
- }
81
-
82
- function formatExpression(expression: PointCoreExpression): string {
83
- if (expression.kind === "literal") return JSON.stringify(expression.value);
84
- if (expression.kind === "identifier") return expression.name;
85
- if (expression.kind === "list") return `[${expression.items.map(formatExpression).join(", ")}]`;
86
- if (expression.kind === "record") {
87
- return `{ ${expression.fields.map((field) => `${field.name}: ${formatExpression(field.value)}`).join(", ")} }`;
88
- }
89
- if (expression.kind === "property") return `${formatExpression(expression.target)}.${expression.name}`;
90
- if (expression.kind === "binary") {
91
- return `${formatExpression(expression.left)} ${expression.operator} ${formatExpression(expression.right)}`;
92
- }
93
- return `${expression.callee}(${expression.args.map(formatExpression).join(", ")})`;
94
- }
95
-
96
- function formatTypeExpression(type: PointCoreParameter["type"]): string {
97
- if (type.args.length === 0) return String(type.name);
98
- return `${type.name}<${type.args.map(formatTypeExpression).join(", ")}>`;
99
- }
100
-
101
- function indentLines(lines: string[]): string[] {
102
- return lines.map((line) => ` ${line}`);
4
+ export function formatPointSource(source: string): string {
5
+ return formatSemanticProgram(parseSemanticSource(source));
103
6
  }