@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.
- package/README.md +18 -5
- package/package.json +1 -1
- package/src/core/ast.ts +40 -2
- package/src/core/check.ts +178 -67
- package/src/core/cli.ts +332 -51
- package/src/core/context.ts +213 -36
- package/src/core/emit-javascript.ts +124 -0
- package/src/core/emit-typescript.ts +38 -5
- package/src/core/format.ts +4 -102
- package/src/core/incremental.ts +53 -0
- package/src/core/index.ts +5 -0
- package/src/core/lexer.ts +11 -6
- package/src/core/parser.ts +11 -612
- package/src/core/semantic-source.ts +26 -0
- package/src/core/serialize.ts +18 -0
- package/src/core/test-only/core-text-parser.ts +415 -0
- package/src/core/test-only/format-core.ts +120 -0
- package/src/core/test-only/index.ts +3 -0
- package/src/core/test-only/legacy-lowering.ts +1047 -0
- package/src/semantic/ast.ts +230 -0
- package/src/semantic/callables.ts +51 -0
- package/src/semantic/context.ts +347 -0
- package/src/semantic/desugar.ts +665 -0
- package/src/semantic/expressions.ts +347 -0
- package/src/semantic/format.ts +222 -0
- package/src/semantic/index.ts +10 -0
- package/src/semantic/metadata.ts +37 -0
- package/src/semantic/naming.ts +33 -0
- package/src/semantic/parse.ts +945 -0
- package/src/semantic/serialize.ts +18 -0
package/src/core/context.ts
CHANGED
|
@@ -11,7 +11,25 @@ import type {
|
|
|
11
11
|
PointCoreDiagnostic,
|
|
12
12
|
} from "./check.ts";
|
|
13
13
|
|
|
14
|
-
export type PointCoreSymbolKind =
|
|
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
|
-
|
|
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)
|
|
72
|
-
|
|
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 === "
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
190
|
-
|
|
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 (
|
|
199
|
-
|
|
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 `
|
|
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,15 +50,27 @@ 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(", ")}): ${
|
|
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)];
|
|
@@ -61,21 +78,32 @@ function emitStatement(statement: PointCoreStatement): string[] {
|
|
|
61
78
|
if (statement.kind === "if") {
|
|
62
79
|
const lines = [
|
|
63
80
|
`if (${emitCondition(statement.condition)}) {`,
|
|
64
|
-
...indentLines(statement.thenBody.flatMap((child) => emitStatement(child))),
|
|
81
|
+
...indentLines(statement.thenBody.flatMap((child) => emitStatement(child, semanticKind))),
|
|
65
82
|
"}",
|
|
66
83
|
];
|
|
67
84
|
if (statement.elseBody.length > 0) {
|
|
68
85
|
lines.push(
|
|
69
86
|
"else {",
|
|
70
|
-
...indentLines(statement.elseBody.flatMap((child) => emitStatement(child))),
|
|
87
|
+
...indentLines(statement.elseBody.flatMap((child) => emitStatement(child, semanticKind))),
|
|
71
88
|
"}",
|
|
72
89
|
);
|
|
73
90
|
}
|
|
74
91
|
return lines;
|
|
75
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
|
+
}
|
|
76
100
|
return [`${emitExpression(statement.value)};`];
|
|
77
101
|
}
|
|
78
102
|
|
|
103
|
+
function escapeJsxText(value: string): string {
|
|
104
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
function emitValue(declaration: PointCoreValueDeclaration, exported: boolean): string {
|
|
80
108
|
const prefix = exported ? "export " : "";
|
|
81
109
|
const keyword = declaration.mutable ? "let" : "const";
|
|
@@ -88,6 +116,9 @@ function emitParam(param: PointCoreParameter): string {
|
|
|
88
116
|
|
|
89
117
|
function emitTypeExpression(type: PointCoreTypeExpression): string {
|
|
90
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 }";
|
|
91
122
|
if (isPrimitiveType(type.name)) return emitPrimitiveType(type.name);
|
|
92
123
|
return type.name;
|
|
93
124
|
}
|
|
@@ -106,8 +137,10 @@ function emitExpression(expression: PointCoreExpression): string {
|
|
|
106
137
|
if (expression.kind === "record") {
|
|
107
138
|
return `{ ${expression.fields.map((field) => `${field.name}: ${emitExpression(field.value)}`).join(", ")} }`;
|
|
108
139
|
}
|
|
140
|
+
if (expression.kind === "await") return `await ${emitExpression(expression.value)}`;
|
|
109
141
|
if (expression.kind === "property") return `${emitExpression(expression.target)}.${expression.name}`;
|
|
110
142
|
if (expression.kind === "call") {
|
|
143
|
+
if (expression.callee === "Error") return `{ message: ${expression.args[0] ? emitExpression(expression.args[0]) : JSON.stringify("")} }`;
|
|
111
144
|
return `${expression.callee}(${expression.args.map(emitExpression).join(", ")})`;
|
|
112
145
|
}
|
|
113
146
|
const operator = BINARY_OPERATORS[expression.operator] ?? expression.operator;
|
package/src/core/format.ts
CHANGED
|
@@ -1,104 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
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
|
|
13
|
-
|
|
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 === "assignment") return [`${statement.name} ${statement.operator} ${formatExpression(statement.value)}`];
|
|
55
|
-
if (statement.kind === "if") return formatIf(statement);
|
|
56
|
-
return [formatExpression(statement.value)];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function formatIf(statement: Extract<PointCoreStatement, { kind: "if" }>): string[] {
|
|
60
|
-
const lines = [
|
|
61
|
-
`if ${formatExpression(statement.condition)} {`,
|
|
62
|
-
...indentLines(statement.thenBody.flatMap((child) => formatStatementLines(child))),
|
|
63
|
-
"}",
|
|
64
|
-
];
|
|
65
|
-
if (statement.elseBody.length > 0) {
|
|
66
|
-
lines.push(
|
|
67
|
-
"else {",
|
|
68
|
-
...indentLines(statement.elseBody.flatMap((child) => formatStatementLines(child))),
|
|
69
|
-
"}",
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return lines;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function formatValue(declaration: PointCoreValueDeclaration): string {
|
|
76
|
-
return `${declaration.mutable ? "var" : "let"} ${declaration.name}: ${formatTypeExpression(declaration.type)} = ${formatExpression(declaration.value)}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function formatParam(param: PointCoreParameter): string {
|
|
80
|
-
return `${param.name}: ${formatTypeExpression(param.type)}`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function formatExpression(expression: PointCoreExpression): string {
|
|
84
|
-
if (expression.kind === "literal") return JSON.stringify(expression.value);
|
|
85
|
-
if (expression.kind === "identifier") return expression.name;
|
|
86
|
-
if (expression.kind === "list") return `[${expression.items.map(formatExpression).join(", ")}]`;
|
|
87
|
-
if (expression.kind === "record") {
|
|
88
|
-
return `{ ${expression.fields.map((field) => `${field.name}: ${formatExpression(field.value)}`).join(", ")} }`;
|
|
89
|
-
}
|
|
90
|
-
if (expression.kind === "property") return `${formatExpression(expression.target)}.${expression.name}`;
|
|
91
|
-
if (expression.kind === "binary") {
|
|
92
|
-
return `${formatExpression(expression.left)} ${expression.operator} ${formatExpression(expression.right)}`;
|
|
93
|
-
}
|
|
94
|
-
return `${expression.callee}(${expression.args.map(formatExpression).join(", ")})`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function formatTypeExpression(type: PointCoreParameter["type"]): string {
|
|
98
|
-
if (type.args.length === 0) return String(type.name);
|
|
99
|
-
return `${type.name}<${type.args.map(formatTypeExpression).join(", ")}>`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function indentLines(lines: string[]): string[] {
|
|
103
|
-
return lines.map((line) => ` ${line}`);
|
|
4
|
+
export function formatPointSource(source: string): string {
|
|
5
|
+
return formatSemanticProgram(parseSemanticSource(source));
|
|
104
6
|
}
|