@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.
- package/README.md +18 -5
- package/package.json +1 -1
- package/src/core/ast.ts +46 -1
- package/src/core/check.ts +217 -69
- package/src/core/cli.ts +332 -39
- package/src/core/context.ts +213 -36
- package/src/core/emit-javascript.ts +124 -0
- package/src/core/emit-typescript.ts +39 -5
- package/src/core/format.ts +4 -101
- package/src/core/incremental.ts +53 -0
- package/src/core/index.ts +5 -0
- package/src/core/lexer.ts +15 -5
- package/src/core/parser.ts +11 -362
- 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/cli.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import type { PointCoreDeclaration, PointCoreProgram } from "./ast.ts";
|
|
2
4
|
import { checkPointCore } from "./check.ts";
|
|
3
5
|
import { createPointCoreIndex, createPointCoreRepairPlan, explainPointCoreRef } from "./context.ts";
|
|
6
|
+
import { createSemanticIndex, explainSemanticRef, mapPublicDiagnostics } from "../semantic/context.ts";
|
|
4
7
|
import { emitPointCoreTypeScript } from "./emit-typescript.ts";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
8
|
+
import { emitPointCoreJavaScript } from "./emit-javascript.ts";
|
|
9
|
+
import { formatPointSource } from "./format.ts";
|
|
10
|
+
import { isCacheHit, isIncrementalEnabled, readBuildCache, recordCacheEntry, writeBuildCache } from "./incremental.ts";
|
|
11
|
+
import { parsePointSource } from "./parser.ts";
|
|
7
12
|
|
|
8
13
|
const DEFAULT_INPUT = "examples/math.point";
|
|
9
14
|
const DEFAULT_OUTPUT = "generated/math.ast.json";
|
|
10
15
|
const DEFAULT_TS_OUTPUT = "generated/math.ts";
|
|
11
|
-
const
|
|
16
|
+
const DEFAULT_PATTERNS = ["examples/**/*.point", "std/**/*.point", "compiler/**/*.point"];
|
|
12
17
|
const GENERATED_DIR = "generated";
|
|
13
18
|
|
|
14
19
|
export async function main() {
|
|
@@ -18,24 +23,28 @@ export async function main() {
|
|
|
18
23
|
return;
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
if (command === "repl") {
|
|
27
|
+
await runRepl(Bun.argv.slice(3).join(" "));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
const inputPath = resolve(process.cwd(), input);
|
|
22
32
|
const source = await Bun.file(inputPath).text();
|
|
23
|
-
const program =
|
|
33
|
+
const program = parsePointSource(source);
|
|
24
34
|
const diagnostics = checkPointCore(program);
|
|
25
35
|
|
|
26
36
|
if (command === "fmt") {
|
|
27
|
-
await Bun.write(inputPath,
|
|
28
|
-
console.log(`Point
|
|
37
|
+
await Bun.write(inputPath, formatPointSource(source));
|
|
38
|
+
console.log(`Point fmt wrote ${input}`);
|
|
29
39
|
return;
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
if (command === "fmt-check") {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.error(`Point core fmt check failed: ${input}`);
|
|
43
|
+
if (source !== formatPointSource(source)) {
|
|
44
|
+
console.error(`Point fmt check failed: ${input}`);
|
|
36
45
|
process.exit(1);
|
|
37
46
|
}
|
|
38
|
-
console.log(`Point
|
|
47
|
+
console.log(`Point fmt check passed: ${input}`);
|
|
39
48
|
return;
|
|
40
49
|
}
|
|
41
50
|
|
|
@@ -49,24 +58,34 @@ export async function main() {
|
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
if (command === "check-json") {
|
|
52
|
-
|
|
61
|
+
const outputDiagnostics = mapPublicDiagnostics(program, diagnostics);
|
|
62
|
+
console.log(JSON.stringify({ schemaVersion: "point.core.check.v1", ok: diagnostics.length === 0, diagnostics: outputDiagnostics }, null, 2));
|
|
53
63
|
if (diagnostics.length > 0) process.exit(1);
|
|
54
64
|
return;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
if (command === "index") {
|
|
68
|
+
if (program.semanticSource) {
|
|
69
|
+
console.log(JSON.stringify(createSemanticIndex(program.semanticSource), null, 2));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
58
72
|
console.log(JSON.stringify(createPointCoreIndex(program), null, 2));
|
|
59
73
|
return;
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
if (command === "explain") {
|
|
63
77
|
const ref = output;
|
|
78
|
+
if (program.semanticSource && ref.startsWith("point://semantic/")) {
|
|
79
|
+
console.log(JSON.stringify(explainSemanticRef(program.semanticSource, ref), null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
64
82
|
console.log(JSON.stringify(explainPointCoreRef(program, ref), null, 2));
|
|
65
83
|
return;
|
|
66
84
|
}
|
|
67
85
|
|
|
68
86
|
if (command === "repair-plan") {
|
|
69
|
-
|
|
87
|
+
const outputDiagnostics = mapPublicDiagnostics(program, diagnostics);
|
|
88
|
+
console.log(JSON.stringify(createPointCoreRepairPlan(outputDiagnostics), null, 2));
|
|
70
89
|
if (diagnostics.length > 0) process.exit(1);
|
|
71
90
|
return;
|
|
72
91
|
}
|
|
@@ -100,26 +119,70 @@ export async function main() {
|
|
|
100
119
|
return;
|
|
101
120
|
}
|
|
102
121
|
|
|
122
|
+
if (command === "build-js") {
|
|
123
|
+
if (diagnostics.length > 0) {
|
|
124
|
+
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const outputPath = resolve(process.cwd(), output === DEFAULT_OUTPUT ? DEFAULT_TS_OUTPUT.replace(/\.ts$/, ".js") : output);
|
|
128
|
+
await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
|
|
129
|
+
await Bun.write(outputPath, emitPointCoreJavaScript(program));
|
|
130
|
+
console.log(`Point core JavaScript build wrote ${outputPath.replaceAll("\\", "/")}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (command === "run") {
|
|
135
|
+
if (diagnostics.length > 0) {
|
|
136
|
+
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const runOutput = resolve(tmpdir(), `point-run-${Date.now()}.ts`);
|
|
140
|
+
await Bun.write(runOutput, emitPointCoreTypeScript(program));
|
|
141
|
+
let entryName: string | null = null;
|
|
142
|
+
try {
|
|
143
|
+
const mod = await import(pathToFileUrl(runOutput));
|
|
144
|
+
entryName = findRunEntryName(program);
|
|
145
|
+
if (!entryName) throw new Error("No zero-argument entrypoint found. Define an action or calculation with no inputs.");
|
|
146
|
+
const entry = mod[entryName];
|
|
147
|
+
if (typeof entry !== "function") throw new Error(`Entrypoint ${entryName} was not exported.`);
|
|
148
|
+
const value = await entry();
|
|
149
|
+
if (value !== undefined) console.log(typeof value === "string" ? value : JSON.stringify(value));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(`Runtime error in ${runtimeSourceLocation(program, input, entryName)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (command === "test") {
|
|
158
|
+
if (diagnostics.length > 0) {
|
|
159
|
+
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
const result = await runPointTests(program, input);
|
|
163
|
+
console.log(JSON.stringify(result, null, 2));
|
|
164
|
+
if (!result.ok) process.exit(1);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
103
168
|
throw new Error(`Unknown point core command: ${command}`);
|
|
104
169
|
}
|
|
105
170
|
|
|
106
171
|
async function runProjectCommand(command: string) {
|
|
107
172
|
const inputs = await discoverInputs();
|
|
108
|
-
if (inputs.length === 0) throw new Error(`No Point core files matched ${
|
|
173
|
+
if (inputs.length === 0) throw new Error(`No Point core files matched ${DEFAULT_PATTERNS.join(", ")}`);
|
|
109
174
|
const results = await Promise.all(inputs.map((input) => loadCoreFile(input)));
|
|
175
|
+
const graph = createModuleGraph(results);
|
|
176
|
+
const orderedResults = orderByDependencies(results, graph);
|
|
110
177
|
|
|
111
178
|
if (command === "fmt-all") {
|
|
112
|
-
await Promise.all(
|
|
113
|
-
|
|
114
|
-
Bun.write(resolve(process.cwd(), result.input), formatPointCore(result.program)),
|
|
115
|
-
),
|
|
116
|
-
);
|
|
117
|
-
console.log(`Point core fmt wrote ${results.length} files`);
|
|
179
|
+
await Promise.all(results.map((result) => Bun.write(resolve(process.cwd(), result.input), formatPointSource(result.source))));
|
|
180
|
+
console.log(`Point fmt wrote ${results.length} files`);
|
|
118
181
|
return;
|
|
119
182
|
}
|
|
120
183
|
|
|
121
184
|
if (command === "fmt-check-all") {
|
|
122
|
-
const unformatted = results.filter((result) => result.source !==
|
|
185
|
+
const unformatted = results.filter((result) => result.source !== formatPointSource(result.source));
|
|
123
186
|
if (unformatted.length > 0) {
|
|
124
187
|
console.error(JSON.stringify({ ok: false, unformatted: unformatted.map((result) => result.input) }, null, 2));
|
|
125
188
|
process.exit(1);
|
|
@@ -129,26 +192,40 @@ async function runProjectCommand(command: string) {
|
|
|
129
192
|
}
|
|
130
193
|
|
|
131
194
|
if (command === "check-all") {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
195
|
+
const cache = isIncrementalEnabled() ? await readBuildCache() : null;
|
|
196
|
+
let manifest = cache ?? { schemaVersion: "point.cache.v1" as const, entries: {} };
|
|
197
|
+
const diagnostics = [];
|
|
198
|
+
let skipped = 0;
|
|
199
|
+
for (const result of orderedResults) {
|
|
200
|
+
if (cache && isCacheHit(manifest, result.input, result.source)) {
|
|
201
|
+
skipped += 1;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const fileDiagnostics = checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({
|
|
205
|
+
...diagnostic,
|
|
206
|
+
file: result.input,
|
|
207
|
+
}));
|
|
208
|
+
diagnostics.push(...fileDiagnostics);
|
|
209
|
+
if (cache) manifest = recordCacheEntry(manifest, result.input, result.source, fileDiagnostics.length === 0);
|
|
210
|
+
}
|
|
211
|
+
if (cache) await writeBuildCache(manifest);
|
|
135
212
|
if (diagnostics.length > 0) {
|
|
136
213
|
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
137
214
|
process.exit(1);
|
|
138
215
|
}
|
|
139
|
-
console.log(`Point core check passed: ${results.length} files`);
|
|
216
|
+
console.log(`Point core check passed: ${results.length} files${skipped ? ` (${skipped} cached)` : ""}`);
|
|
140
217
|
return;
|
|
141
218
|
}
|
|
142
219
|
|
|
143
220
|
if (command === "build-all") {
|
|
144
|
-
const diagnostics =
|
|
145
|
-
checkPointCore(result
|
|
221
|
+
const diagnostics = orderedResults.flatMap((result) =>
|
|
222
|
+
checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({ ...diagnostic, file: result.input })),
|
|
146
223
|
);
|
|
147
224
|
if (diagnostics.length > 0) {
|
|
148
225
|
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
149
226
|
process.exit(1);
|
|
150
227
|
}
|
|
151
|
-
for (const result of
|
|
228
|
+
for (const result of orderedResults) {
|
|
152
229
|
const output = outputFor(result.input);
|
|
153
230
|
const outputPath = resolve(process.cwd(), output);
|
|
154
231
|
await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
|
|
@@ -159,46 +236,262 @@ async function runProjectCommand(command: string) {
|
|
|
159
236
|
}
|
|
160
237
|
|
|
161
238
|
if (command === "build-ts-all") {
|
|
162
|
-
const diagnostics =
|
|
163
|
-
checkPointCore(result
|
|
239
|
+
const diagnostics = orderedResults.flatMap((result) =>
|
|
240
|
+
checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({ ...diagnostic, file: result.input })),
|
|
164
241
|
);
|
|
165
242
|
if (diagnostics.length > 0) {
|
|
166
243
|
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
167
244
|
process.exit(1);
|
|
168
245
|
}
|
|
169
|
-
for (const result of
|
|
246
|
+
for (const result of orderedResults) {
|
|
170
247
|
const output = tsOutputFor(result.input);
|
|
171
248
|
const outputPath = resolve(process.cwd(), output);
|
|
172
249
|
await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
|
|
173
|
-
await Bun.write(outputPath, emitPointCoreTypeScript(result
|
|
250
|
+
await Bun.write(outputPath, emitPointCoreTypeScript(programWithTypeScriptImports(result, graph)));
|
|
174
251
|
}
|
|
175
252
|
console.log(`Point core TypeScript build wrote ${results.length} files`);
|
|
176
253
|
return;
|
|
177
254
|
}
|
|
178
255
|
|
|
256
|
+
if (command === "build-js-all") {
|
|
257
|
+
const diagnostics = orderedResults.flatMap((result) =>
|
|
258
|
+
checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({ ...diagnostic, file: result.input })),
|
|
259
|
+
);
|
|
260
|
+
if (diagnostics.length > 0) {
|
|
261
|
+
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
for (const result of orderedResults) {
|
|
265
|
+
const output = jsOutputFor(result.input);
|
|
266
|
+
const outputPath = resolve(process.cwd(), output);
|
|
267
|
+
await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
|
|
268
|
+
await Bun.write(outputPath, emitPointCoreJavaScript(programWithTypeScriptImports(result, graph)));
|
|
269
|
+
}
|
|
270
|
+
console.log(`Point core JavaScript build wrote ${results.length} files`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (command === "test-all") {
|
|
275
|
+
const diagnostics = orderedResults.flatMap((result) =>
|
|
276
|
+
checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({ ...diagnostic, file: result.input })),
|
|
277
|
+
);
|
|
278
|
+
if (diagnostics.length > 0) {
|
|
279
|
+
console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
const results = await Promise.all(orderedResults.map((result) => runPointTests(programWithTypeScriptImports(result, graph), result.input)));
|
|
283
|
+
const failed = results.filter((result) => !result.ok);
|
|
284
|
+
if (failed.length > 0) {
|
|
285
|
+
console.error(JSON.stringify({ ok: false, files: failed }, null, 2));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
console.log(`Point tests passed: ${results.reduce((total, result) => total + result.tests.length, 0)} tests`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
179
292
|
throw new Error(`Unknown point core command: ${command}`);
|
|
180
293
|
}
|
|
181
294
|
|
|
182
295
|
async function discoverInputs(): Promise<string[]> {
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
296
|
+
const inputs = new Set<string>();
|
|
297
|
+
for (const pattern of DEFAULT_PATTERNS) {
|
|
298
|
+
const glob = new Bun.Glob(pattern);
|
|
299
|
+
for await (const input of glob.scan({ cwd: process.cwd(), onlyFiles: true })) {
|
|
300
|
+
if (!input.includes("/generated/")) inputs.add(input.replaceAll("\\", "/"));
|
|
301
|
+
}
|
|
187
302
|
}
|
|
188
|
-
return inputs.sort((a, b) => a.localeCompare(b));
|
|
303
|
+
return [...inputs].sort((a, b) => a.localeCompare(b));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function runtimeSourceLocation(program: PointCoreProgram, input: string, entryName: string | null): string {
|
|
307
|
+
const declaration = program.declarations.find((candidate) => candidate.kind === "function" && candidate.name === entryName);
|
|
308
|
+
const line = declaration?.span?.start.line;
|
|
309
|
+
return line ? `${input}:${line}` : input;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function runRepl(inlineSource: string) {
|
|
313
|
+
const source = inlineSource ? inlineSource.replaceAll("\\n", "\n") : await Bun.stdin.text();
|
|
314
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
315
|
+
const line = rawLine.trim();
|
|
316
|
+
if (!line) continue;
|
|
317
|
+
if (line === ".exit" || line === "exit") return;
|
|
318
|
+
try {
|
|
319
|
+
const value = Function(`"use strict"; return (${line.replace(/\band\b/g, "&&").replace(/\bor\b/g, "||")});`)();
|
|
320
|
+
console.log(`${formatReplValue(value)}: ${pointTypeOfRuntimeValue(value)}`);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error(`REPL error: ${error instanceof Error ? error.message : String(error)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function formatReplValue(value: unknown): string {
|
|
328
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function pointTypeOfRuntimeValue(value: unknown): string {
|
|
332
|
+
if (typeof value === "string") return "Text";
|
|
333
|
+
if (typeof value === "boolean") return "Bool";
|
|
334
|
+
if (typeof value === "number") return Number.isInteger(value) ? "Int" : "Float";
|
|
335
|
+
if (value === null || value === undefined) return "Void";
|
|
336
|
+
if (Array.isArray(value)) return "List";
|
|
337
|
+
return "Record";
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
interface PointTestResult {
|
|
341
|
+
file: string;
|
|
342
|
+
ok: boolean;
|
|
343
|
+
tests: Array<{ name: string; ok: boolean; error?: string }>;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function runPointTests(program: PointCoreProgram, input: string): Promise<PointTestResult> {
|
|
347
|
+
const tests = program.declarations.filter(
|
|
348
|
+
(declaration) =>
|
|
349
|
+
declaration.kind === "function" &&
|
|
350
|
+
declaration.params.length === 0 &&
|
|
351
|
+
declaration.returnType.name === "Bool" &&
|
|
352
|
+
(declaration.semantic?.name.startsWith("test") || declaration.name.startsWith("test")),
|
|
353
|
+
);
|
|
354
|
+
if (tests.length === 0) return { file: input, ok: true, tests: [] };
|
|
355
|
+
const testOutput = resolve(tmpdir(), `point-test-${Date.now()}-${Math.random().toString(16).slice(2)}.ts`);
|
|
356
|
+
await Bun.write(testOutput, emitPointCoreTypeScript(program));
|
|
357
|
+
const mod = await import(pathToFileUrl(testOutput));
|
|
358
|
+
const results = [];
|
|
359
|
+
for (const test of tests) {
|
|
360
|
+
try {
|
|
361
|
+
const candidate = mod[test.name];
|
|
362
|
+
if (typeof candidate !== "function") throw new Error(`Test ${test.name} was not exported.`);
|
|
363
|
+
const value = await candidate();
|
|
364
|
+
results.push({ name: test.semantic?.name ?? test.name, ok: value === true, error: value === true ? undefined : "Expected true." });
|
|
365
|
+
} catch (error) {
|
|
366
|
+
results.push({ name: test.semantic?.name ?? test.name, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return { file: input, ok: results.every((result) => result.ok), tests: results };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function pathToFileUrl(path: string): string {
|
|
373
|
+
return `file://${path.replaceAll("\\", "/")}`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function findRunEntryName(program: PointCoreProgram): string | null {
|
|
377
|
+
const zeroArgFunctions = program.declarations.filter((declaration) => declaration.kind === "function" && declaration.params.length === 0);
|
|
378
|
+
const preferred =
|
|
379
|
+
zeroArgFunctions.find((declaration) => declaration.semantic?.kind === "command") ??
|
|
380
|
+
zeroArgFunctions.find((declaration) => declaration.name === "main") ??
|
|
381
|
+
zeroArgFunctions[0];
|
|
382
|
+
return preferred?.name ?? null;
|
|
189
383
|
}
|
|
190
384
|
|
|
191
385
|
async function loadCoreFile(input: string) {
|
|
192
386
|
const source = await Bun.file(resolve(process.cwd(), input)).text();
|
|
193
|
-
return { input, source, program:
|
|
387
|
+
return { input, source, program: parsePointSource(source), uses: parseUseDeclarations(source, input) };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
type CoreFile = Awaited<ReturnType<typeof loadCoreFile>>;
|
|
391
|
+
type ModuleGraph = Map<string, { result: CoreFile; dependencies: CoreFile[] }>;
|
|
392
|
+
|
|
393
|
+
interface UseDeclaration {
|
|
394
|
+
moduleName: string;
|
|
395
|
+
from: string;
|
|
396
|
+
input: string;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function parseUseDeclarations(source: string, input: string): UseDeclaration[] {
|
|
400
|
+
return source
|
|
401
|
+
.split(/\r?\n/)
|
|
402
|
+
.map((line) => line.trim().match(/^use\s+([A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)*)(?:\s+from\s+"([^"]+)")?$/))
|
|
403
|
+
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
404
|
+
.map((match) => ({ moduleName: match[1]!, from: match[2] ?? stdPathFor(match[1]!), input }));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function createModuleGraph(results: CoreFile[]): ModuleGraph {
|
|
408
|
+
const byInput = new Map(results.map((result) => [normalizeInput(result.input), result]));
|
|
409
|
+
const graph: ModuleGraph = new Map();
|
|
410
|
+
for (const result of results) {
|
|
411
|
+
const dependencies = result.uses.map((use) => {
|
|
412
|
+
const resolved = normalizeInput(resolveDependencyInput(use.input, use.from));
|
|
413
|
+
const dependency = byInput.get(resolved);
|
|
414
|
+
if (!dependency) throw new Error(`Cannot resolve Point module ${use.moduleName} from ${use.from} in ${use.input}`);
|
|
415
|
+
return dependency;
|
|
416
|
+
});
|
|
417
|
+
graph.set(normalizeInput(result.input), { result, dependencies });
|
|
418
|
+
}
|
|
419
|
+
return graph;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function orderByDependencies(results: CoreFile[], graph: ModuleGraph): CoreFile[] {
|
|
423
|
+
const ordered: CoreFile[] = [];
|
|
424
|
+
const visiting = new Set<string>();
|
|
425
|
+
const visited = new Set<string>();
|
|
426
|
+
const visit = (result: CoreFile) => {
|
|
427
|
+
const key = normalizeInput(result.input);
|
|
428
|
+
if (visited.has(key)) return;
|
|
429
|
+
if (visiting.has(key)) throw new Error(`Cyclic Point module dependency involving ${result.input}`);
|
|
430
|
+
visiting.add(key);
|
|
431
|
+
for (const dependency of graph.get(key)?.dependencies ?? []) visit(dependency);
|
|
432
|
+
visiting.delete(key);
|
|
433
|
+
visited.add(key);
|
|
434
|
+
ordered.push(result);
|
|
435
|
+
};
|
|
436
|
+
for (const result of results) visit(result);
|
|
437
|
+
return ordered;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function programWithDependencyDeclarations(result: CoreFile, graph: ModuleGraph): PointCoreProgram {
|
|
441
|
+
const dependencies = graph.get(normalizeInput(result.input))?.dependencies ?? [];
|
|
442
|
+
return {
|
|
443
|
+
...result.program,
|
|
444
|
+
declarations: [...dependencies.flatMap((dependency) => publicDeclarations(dependency.program)), ...result.program.declarations],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function programWithTypeScriptImports(result: CoreFile, graph: ModuleGraph): PointCoreProgram {
|
|
449
|
+
const dependencies = graph.get(normalizeInput(result.input))?.dependencies ?? [];
|
|
450
|
+
const imports: PointCoreDeclaration[] = dependencies.map((dependency) => ({
|
|
451
|
+
kind: "import",
|
|
452
|
+
names: publicDeclarations(dependency.program).map((declaration) => declaration.name).filter(Boolean),
|
|
453
|
+
from: `./${outputBaseName(dependency.input)}`,
|
|
454
|
+
}));
|
|
455
|
+
return { ...result.program, declarations: [...imports.filter((declaration) => declaration.kind !== "import" || declaration.names.length > 0), ...result.program.declarations] };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function publicDeclarations(program: PointCoreProgram): Array<Extract<PointCoreDeclaration, { kind: "type" | "function" | "value" | "external" }>> {
|
|
459
|
+
return program.declarations.filter(
|
|
460
|
+
(declaration): declaration is Extract<PointCoreDeclaration, { kind: "type" | "function" | "value" | "external" }> =>
|
|
461
|
+
declaration.kind === "type" || declaration.kind === "function" || declaration.kind === "value" || declaration.kind === "external",
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function resolveDependencyInput(input: string, from: string): string {
|
|
466
|
+
if (from.startsWith("std/")) return from;
|
|
467
|
+
const base = dirname(resolve(process.cwd(), input));
|
|
468
|
+
return resolve(base, from).replace(resolve(process.cwd()), "").replace(/^[/\\]/, "");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function stdPathFor(moduleName: string): string {
|
|
472
|
+
if (!moduleName.startsWith("std.")) throw new Error(`Use declarations without from must target std modules: ${moduleName}`);
|
|
473
|
+
return `${moduleName.replace(/^std\./, "std/").replaceAll(".", "/")}.point`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function normalizeInput(input: string): string {
|
|
477
|
+
return input.replaceAll("\\", "/");
|
|
194
478
|
}
|
|
195
479
|
|
|
196
480
|
function outputFor(input: string): string {
|
|
197
|
-
const name = input
|
|
481
|
+
const name = outputBaseName(input);
|
|
198
482
|
return `${GENERATED_DIR}/${name}.ast.json`;
|
|
199
483
|
}
|
|
200
484
|
|
|
201
485
|
function tsOutputFor(input: string): string {
|
|
202
|
-
const name = input
|
|
486
|
+
const name = outputBaseName(input);
|
|
203
487
|
return `${GENERATED_DIR}/${name}.ts`;
|
|
204
488
|
}
|
|
489
|
+
|
|
490
|
+
function jsOutputFor(input: string): string {
|
|
491
|
+
const name = outputBaseName(input);
|
|
492
|
+
return `${GENERATED_DIR}/${name}.js`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function outputBaseName(input: string): string {
|
|
496
|
+
return normalizeInput(input).split("/").pop()?.replace(/\.point$/, "") ?? "program";
|
|
497
|
+
}
|