@hatchingpoint/point 0.0.3

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,204 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { checkPointCore } from "./check.ts";
3
+ import { createPointCoreIndex, createPointCoreRepairPlan, explainPointCoreRef } from "./context.ts";
4
+ import { emitPointCoreTypeScript } from "./emit-typescript.ts";
5
+ import { formatPointCore } from "./format.ts";
6
+ import { parsePointCore } from "./parser.ts";
7
+
8
+ const DEFAULT_INPUT = "examples/math.point";
9
+ const DEFAULT_OUTPUT = "generated/math.ast.json";
10
+ const DEFAULT_TS_OUTPUT = "generated/math.ts";
11
+ const DEFAULT_PATTERN = "examples/**/*.point";
12
+ const GENERATED_DIR = "generated";
13
+
14
+ export async function main() {
15
+ const [, , command = "check", input = DEFAULT_INPUT, output = DEFAULT_OUTPUT] = Bun.argv;
16
+ if (command.endsWith("-all")) {
17
+ await runProjectCommand(command);
18
+ return;
19
+ }
20
+
21
+ const inputPath = resolve(process.cwd(), input);
22
+ const source = await Bun.file(inputPath).text();
23
+ const program = parsePointCore(source);
24
+ const diagnostics = checkPointCore(program);
25
+
26
+ if (command === "fmt") {
27
+ await Bun.write(inputPath, formatPointCore(program));
28
+ console.log(`Point core fmt wrote ${input}`);
29
+ return;
30
+ }
31
+
32
+ if (command === "fmt-check") {
33
+ const formatted = formatPointCore(program);
34
+ if (source !== formatted) {
35
+ console.error(`Point core fmt check failed: ${input}`);
36
+ process.exit(1);
37
+ }
38
+ console.log(`Point core fmt check passed: ${input}`);
39
+ return;
40
+ }
41
+
42
+ if (command === "check") {
43
+ if (diagnostics.length > 0) {
44
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
45
+ process.exit(1);
46
+ }
47
+ console.log(`Point core check passed: ${input}`);
48
+ return;
49
+ }
50
+
51
+ if (command === "check-json") {
52
+ console.log(JSON.stringify({ schemaVersion: "point.core.check.v1", ok: diagnostics.length === 0, diagnostics }, null, 2));
53
+ if (diagnostics.length > 0) process.exit(1);
54
+ return;
55
+ }
56
+
57
+ if (command === "index") {
58
+ console.log(JSON.stringify(createPointCoreIndex(program), null, 2));
59
+ return;
60
+ }
61
+
62
+ if (command === "explain") {
63
+ const ref = output;
64
+ console.log(JSON.stringify(explainPointCoreRef(program, ref), null, 2));
65
+ return;
66
+ }
67
+
68
+ if (command === "repair-plan") {
69
+ console.log(JSON.stringify(createPointCoreRepairPlan(diagnostics), null, 2));
70
+ if (diagnostics.length > 0) process.exit(1);
71
+ return;
72
+ }
73
+
74
+ if (command === "print-ast") {
75
+ console.log(JSON.stringify(program, null, 2));
76
+ return;
77
+ }
78
+
79
+ if (command === "build") {
80
+ if (diagnostics.length > 0) {
81
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
82
+ process.exit(1);
83
+ }
84
+ const outputPath = resolve(process.cwd(), output);
85
+ await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
86
+ await Bun.write(outputPath, `${JSON.stringify(program, null, 2)}\n`);
87
+ console.log(`Point core build wrote ${output}`);
88
+ return;
89
+ }
90
+
91
+ if (command === "build-ts") {
92
+ if (diagnostics.length > 0) {
93
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
94
+ process.exit(1);
95
+ }
96
+ const outputPath = resolve(process.cwd(), output === DEFAULT_OUTPUT ? DEFAULT_TS_OUTPUT : output);
97
+ await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
98
+ await Bun.write(outputPath, emitPointCoreTypeScript(program));
99
+ console.log(`Point core TypeScript build wrote ${outputPath.replaceAll("\\", "/")}`);
100
+ return;
101
+ }
102
+
103
+ throw new Error(`Unknown point core command: ${command}`);
104
+ }
105
+
106
+ async function runProjectCommand(command: string) {
107
+ const inputs = await discoverInputs();
108
+ if (inputs.length === 0) throw new Error(`No Point core files matched ${DEFAULT_PATTERN}`);
109
+ const results = await Promise.all(inputs.map((input) => loadCoreFile(input)));
110
+
111
+ if (command === "fmt-all") {
112
+ await Promise.all(
113
+ results.map((result) =>
114
+ Bun.write(resolve(process.cwd(), result.input), formatPointCore(result.program)),
115
+ ),
116
+ );
117
+ console.log(`Point core fmt wrote ${results.length} files`);
118
+ return;
119
+ }
120
+
121
+ if (command === "fmt-check-all") {
122
+ const unformatted = results.filter((result) => result.source !== formatPointCore(result.program));
123
+ if (unformatted.length > 0) {
124
+ console.error(JSON.stringify({ ok: false, unformatted: unformatted.map((result) => result.input) }, null, 2));
125
+ process.exit(1);
126
+ }
127
+ console.log(`Point core fmt check passed: ${results.length} files`);
128
+ return;
129
+ }
130
+
131
+ if (command === "check-all") {
132
+ const diagnostics = results.flatMap((result) =>
133
+ checkPointCore(result.program).map((diagnostic) => ({ ...diagnostic, file: result.input })),
134
+ );
135
+ if (diagnostics.length > 0) {
136
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
137
+ process.exit(1);
138
+ }
139
+ console.log(`Point core check passed: ${results.length} files`);
140
+ return;
141
+ }
142
+
143
+ if (command === "build-all") {
144
+ const diagnostics = results.flatMap((result) =>
145
+ checkPointCore(result.program).map((diagnostic) => ({ ...diagnostic, file: result.input })),
146
+ );
147
+ if (diagnostics.length > 0) {
148
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
149
+ process.exit(1);
150
+ }
151
+ for (const result of results) {
152
+ const output = outputFor(result.input);
153
+ const outputPath = resolve(process.cwd(), output);
154
+ await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
155
+ await Bun.write(outputPath, `${JSON.stringify(result.program, null, 2)}\n`);
156
+ }
157
+ console.log(`Point core build wrote ${results.length} files`);
158
+ return;
159
+ }
160
+
161
+ if (command === "build-ts-all") {
162
+ const diagnostics = results.flatMap((result) =>
163
+ checkPointCore(result.program).map((diagnostic) => ({ ...diagnostic, file: result.input })),
164
+ );
165
+ if (diagnostics.length > 0) {
166
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
167
+ process.exit(1);
168
+ }
169
+ for (const result of results) {
170
+ const output = tsOutputFor(result.input);
171
+ const outputPath = resolve(process.cwd(), output);
172
+ await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
173
+ await Bun.write(outputPath, emitPointCoreTypeScript(result.program));
174
+ }
175
+ console.log(`Point core TypeScript build wrote ${results.length} files`);
176
+ return;
177
+ }
178
+
179
+ throw new Error(`Unknown point core command: ${command}`);
180
+ }
181
+
182
+ async function discoverInputs(): Promise<string[]> {
183
+ const glob = new Bun.Glob(DEFAULT_PATTERN);
184
+ const inputs: string[] = [];
185
+ for await (const input of glob.scan({ cwd: process.cwd(), onlyFiles: true })) {
186
+ if (!input.includes("/generated/")) inputs.push(input.replaceAll("\\", "/"));
187
+ }
188
+ return inputs.sort((a, b) => a.localeCompare(b));
189
+ }
190
+
191
+ async function loadCoreFile(input: string) {
192
+ const source = await Bun.file(resolve(process.cwd(), input)).text();
193
+ return { input, source, program: parsePointCore(source) };
194
+ }
195
+
196
+ function outputFor(input: string): string {
197
+ const name = input.split("/").pop()?.replace(/\.point$/, "") ?? "program";
198
+ return `${GENERATED_DIR}/${name}.ast.json`;
199
+ }
200
+
201
+ function tsOutputFor(input: string): string {
202
+ const name = input.split("/").pop()?.replace(/\.point$/, "") ?? "program";
203
+ return `${GENERATED_DIR}/${name}.ts`;
204
+ }
@@ -0,0 +1,217 @@
1
+ import type {
2
+ PointCoreDeclaration,
3
+ PointCoreFunctionDeclaration,
4
+ PointCoreProgram,
5
+ PointCoreTypeDeclaration,
6
+ PointCoreTypeExpression,
7
+ PointCoreValueDeclaration,
8
+ PointSourceSpan,
9
+ } from "./ast.ts";
10
+ import type {
11
+ PointCoreDiagnostic,
12
+ } from "./check.ts";
13
+
14
+ export type PointCoreSymbolKind = "module" | "import" | "value" | "function" | "param" | "type" | "field";
15
+
16
+ export interface PointCoreSymbol {
17
+ ref: string;
18
+ path: string;
19
+ kind: PointCoreSymbolKind;
20
+ name: string;
21
+ module: string;
22
+ type?: string;
23
+ mutable?: boolean;
24
+ from?: string;
25
+ span: PointSourceSpan | null;
26
+ }
27
+
28
+ export interface PointCoreIndex {
29
+ schemaVersion: "point.core.index.v1";
30
+ module: string;
31
+ refs: PointCoreSymbol[];
32
+ }
33
+
34
+ export interface PointCoreExplanation {
35
+ schemaVersion: "point.core.explain.v1";
36
+ ref: string;
37
+ found: boolean;
38
+ symbol?: PointCoreSymbol;
39
+ relatedRefs: string[];
40
+ summary: string;
41
+ }
42
+
43
+ export interface PointCoreRepairPlan {
44
+ schemaVersion: "point.core.repair-plan.v1";
45
+ ok: boolean;
46
+ steps: PointCoreRepairStep[];
47
+ }
48
+
49
+ export interface PointCoreRepairStep {
50
+ ref: string;
51
+ code: string;
52
+ message: string;
53
+ repair: string;
54
+ expected?: string | string[];
55
+ actual?: string;
56
+ relatedRefs: string[];
57
+ }
58
+
59
+ export function createPointCoreIndex(program: PointCoreProgram): PointCoreIndex {
60
+ const moduleName = program.module ?? "anonymous";
61
+ const refs: PointCoreSymbol[] = [
62
+ {
63
+ ref: refFor(moduleName, "module"),
64
+ path: "module",
65
+ kind: "module",
66
+ name: moduleName,
67
+ module: moduleName,
68
+ span: program.span ?? null,
69
+ },
70
+ ];
71
+ for (const declaration of program.declarations) refs.push(...symbolsForDeclaration(moduleName, declaration));
72
+ return { schemaVersion: "point.core.index.v1", module: moduleName, refs };
73
+ }
74
+
75
+ export function explainPointCoreRef(program: PointCoreProgram, ref: string): PointCoreExplanation {
76
+ const index = createPointCoreIndex(program);
77
+ const symbol = index.refs.find((candidate) => candidate.ref === ref);
78
+ if (!symbol) {
79
+ return {
80
+ schemaVersion: "point.core.explain.v1",
81
+ ref,
82
+ found: false,
83
+ relatedRefs: [],
84
+ summary: `No Point symbol found for ${ref}.`,
85
+ };
86
+ }
87
+ const relatedRefs = relatedRefsFor(symbol, index);
88
+ return {
89
+ schemaVersion: "point.core.explain.v1",
90
+ ref,
91
+ found: true,
92
+ symbol,
93
+ relatedRefs,
94
+ summary: summaryFor(symbol),
95
+ };
96
+ }
97
+
98
+ export function createPointCoreRepairPlan(diagnostics: PointCoreDiagnostic[]): PointCoreRepairPlan {
99
+ return {
100
+ schemaVersion: "point.core.repair-plan.v1",
101
+ ok: diagnostics.length === 0,
102
+ steps: diagnostics.map((diagnostic) => ({
103
+ ref: diagnostic.ref,
104
+ code: diagnostic.code,
105
+ message: diagnostic.message,
106
+ repair: diagnostic.repair ?? "Inspect this ref and update the Point source.",
107
+ expected: diagnostic.expected,
108
+ actual: diagnostic.actual,
109
+ relatedRefs: diagnostic.relatedRefs ?? [],
110
+ })),
111
+ };
112
+ }
113
+
114
+ function symbolsForDeclaration(moduleName: string, declaration: PointCoreDeclaration): PointCoreSymbol[] {
115
+ if (declaration.kind === "import") {
116
+ return declaration.names.map((name) => ({
117
+ ref: refFor(moduleName, `import.${name}`),
118
+ path: `import.${name}`,
119
+ kind: "import",
120
+ name,
121
+ module: moduleName,
122
+ 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);
129
+ }
130
+
131
+ function valueSymbol(moduleName: string, declaration: PointCoreValueDeclaration, path: string): PointCoreSymbol {
132
+ return {
133
+ ref: refFor(moduleName, path),
134
+ path,
135
+ kind: "value",
136
+ name: declaration.name,
137
+ module: moduleName,
138
+ type: formatType(declaration.type),
139
+ mutable: declaration.mutable,
140
+ span: declaration.span ?? null,
141
+ };
142
+ }
143
+
144
+ function typeSymbols(moduleName: string, declaration: PointCoreTypeDeclaration): PointCoreSymbol[] {
145
+ return [
146
+ {
147
+ ref: refFor(moduleName, `type.${declaration.name}`),
148
+ path: `type.${declaration.name}`,
149
+ kind: "type",
150
+ name: declaration.name,
151
+ module: moduleName,
152
+ span: declaration.span ?? null,
153
+ },
154
+ ...declaration.fields.map((field) => ({
155
+ ref: refFor(moduleName, `type.${declaration.name}.${field.name}`),
156
+ path: `type.${declaration.name}.${field.name}`,
157
+ kind: "field" as const,
158
+ name: field.name,
159
+ module: moduleName,
160
+ type: formatType(field.type),
161
+ span: field.span ?? null,
162
+ })),
163
+ ];
164
+ }
165
+
166
+ function functionSymbols(moduleName: string, declaration: PointCoreFunctionDeclaration): PointCoreSymbol[] {
167
+ return [
168
+ {
169
+ ref: refFor(moduleName, `fn.${declaration.name}`),
170
+ path: `fn.${declaration.name}`,
171
+ kind: "function",
172
+ name: declaration.name,
173
+ module: moduleName,
174
+ type: formatType(declaration.returnType),
175
+ span: declaration.span ?? null,
176
+ },
177
+ ...declaration.params.map((param) => ({
178
+ ref: refFor(moduleName, `fn.${declaration.name}.param.${param.name}`),
179
+ path: `fn.${declaration.name}.param.${param.name}`,
180
+ kind: "param" as const,
181
+ name: param.name,
182
+ module: moduleName,
183
+ type: formatType(param.type),
184
+ span: param.span ?? null,
185
+ })),
186
+ ];
187
+ }
188
+
189
+ function refFor(moduleName: string, path: string): string {
190
+ return `point://core/${moduleName}/${path}`;
191
+ }
192
+
193
+ function relatedRefsFor(symbol: PointCoreSymbol, index: PointCoreIndex): string[] {
194
+ if (symbol.kind === "field") {
195
+ const ownerPath = symbol.path.split(".").slice(0, 2).join(".");
196
+ return index.refs.filter((candidate) => candidate.path.startsWith(`${ownerPath}.`) && candidate.ref !== symbol.ref).map((candidate) => candidate.ref);
197
+ }
198
+ if (symbol.kind === "function") {
199
+ return index.refs.filter((candidate) => candidate.path.startsWith(`${symbol.path}.param.`)).map((candidate) => candidate.ref);
200
+ }
201
+ return [];
202
+ }
203
+
204
+ function summaryFor(symbol: PointCoreSymbol): string {
205
+ if (symbol.kind === "module") return `Module ${symbol.name}.`;
206
+ if (symbol.kind === "import") return `Import ${symbol.name} from ${symbol.from}.`;
207
+ 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}.`;
209
+ 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
+ }
213
+
214
+ function formatType(type: PointCoreTypeExpression): string {
215
+ if (type.args.length === 0) return String(type.name);
216
+ return `${type.name}<${type.args.map(formatType).join(", ")}>`;
217
+ }
@@ -0,0 +1,132 @@
1
+ import type {
2
+ PointCoreDeclaration,
3
+ PointCoreExpression,
4
+ PointCoreFunctionDeclaration,
5
+ PointCoreParameter,
6
+ PointCorePrimitiveType,
7
+ PointCoreProgram,
8
+ PointCoreStatement,
9
+ PointCoreTypeDeclaration,
10
+ PointCoreTypeExpression,
11
+ PointCoreValueDeclaration,
12
+ } from "./ast.ts";
13
+
14
+ const BINARY_OPERATORS: Record<string, string> = {
15
+ and: "&&",
16
+ or: "||",
17
+ };
18
+
19
+ export function emitPointCoreTypeScript(program: PointCoreProgram): string {
20
+ const lines: string[] = [];
21
+ lines.push("// Generated by Point. Do not edit directly.");
22
+ if (program.module) lines.push(`// Point module: ${program.module}`);
23
+ lines.push("");
24
+ for (const declaration of program.declarations) {
25
+ lines.push(...emitDeclaration(declaration), "");
26
+ }
27
+ return `${trimTrailingBlankLines(lines).join("\n")}\n`;
28
+ }
29
+
30
+ function emitDeclaration(declaration: PointCoreDeclaration): string[] {
31
+ if (declaration.kind === "import") {
32
+ return [`import { ${declaration.names.join(", ")} } from ${JSON.stringify(declaration.from)};`];
33
+ }
34
+ if (declaration.kind === "type") return emitType(declaration);
35
+ if (declaration.kind === "value") return [emitValue(declaration, true)];
36
+ return emitFunction(declaration);
37
+ }
38
+
39
+ function emitType(declaration: PointCoreTypeDeclaration): string[] {
40
+ return [
41
+ `export interface ${declaration.name} {`,
42
+ ...declaration.fields.map((field) => ` ${field.name}: ${emitTypeExpression(field.type)};`),
43
+ "}",
44
+ ];
45
+ }
46
+
47
+ function emitFunction(declaration: PointCoreFunctionDeclaration): string[] {
48
+ return [
49
+ `export function ${declaration.name}(${declaration.params.map(emitParam).join(", ")}): ${emitTypeExpression(declaration.returnType)} {`,
50
+ ...indentLines(declaration.body.flatMap((statement) => emitStatement(statement))),
51
+ "}",
52
+ ];
53
+ }
54
+
55
+ function emitStatement(statement: PointCoreStatement): string[] {
56
+ if (statement.kind === "return") {
57
+ return [statement.value ? `return ${emitExpression(statement.value)};` : "return;"];
58
+ }
59
+ if (statement.kind === "value") return [emitValue(statement, false)];
60
+ if (statement.kind === "if") {
61
+ const lines = [
62
+ `if (${emitCondition(statement.condition)}) {`,
63
+ ...indentLines(statement.thenBody.flatMap((child) => emitStatement(child))),
64
+ "}",
65
+ ];
66
+ if (statement.elseBody.length > 0) {
67
+ lines.push(
68
+ "else {",
69
+ ...indentLines(statement.elseBody.flatMap((child) => emitStatement(child))),
70
+ "}",
71
+ );
72
+ }
73
+ return lines;
74
+ }
75
+ return [`${emitExpression(statement.value)};`];
76
+ }
77
+
78
+ function emitValue(declaration: PointCoreValueDeclaration, exported: boolean): string {
79
+ const prefix = exported ? "export " : "";
80
+ const keyword = declaration.mutable ? "let" : "const";
81
+ return `${prefix}${keyword} ${declaration.name}: ${emitTypeExpression(declaration.type)} = ${emitExpression(declaration.value)};`;
82
+ }
83
+
84
+ function emitParam(param: PointCoreParameter): string {
85
+ return `${param.name}: ${emitTypeExpression(param.type)}`;
86
+ }
87
+
88
+ function emitTypeExpression(type: PointCoreTypeExpression): string {
89
+ if (type.name === "List") return `Array<${type.args[0] ? emitTypeExpression(type.args[0]) : "unknown"}>`;
90
+ if (isPrimitiveType(type.name)) return emitPrimitiveType(type.name);
91
+ return type.name;
92
+ }
93
+
94
+ function emitPrimitiveType(type: PointCorePrimitiveType): string {
95
+ if (type === "Text") return "string";
96
+ if (type === "Int" || type === "Float") return "number";
97
+ if (type === "Bool") return "boolean";
98
+ return "void";
99
+ }
100
+
101
+ function emitExpression(expression: PointCoreExpression): string {
102
+ if (expression.kind === "literal") return JSON.stringify(expression.value);
103
+ if (expression.kind === "identifier") return expression.name;
104
+ if (expression.kind === "list") return `[${expression.items.map(emitExpression).join(", ")}]`;
105
+ if (expression.kind === "record") {
106
+ return `{ ${expression.fields.map((field) => `${field.name}: ${emitExpression(field.value)}`).join(", ")} }`;
107
+ }
108
+ if (expression.kind === "property") return `${emitExpression(expression.target)}.${expression.name}`;
109
+ if (expression.kind === "call") {
110
+ return `${expression.callee}(${expression.args.map(emitExpression).join(", ")})`;
111
+ }
112
+ const operator = BINARY_OPERATORS[expression.operator] ?? expression.operator;
113
+ return `(${emitExpression(expression.left)} ${operator} ${emitExpression(expression.right)})`;
114
+ }
115
+
116
+ function emitCondition(expression: PointCoreExpression): string {
117
+ const emitted = emitExpression(expression);
118
+ return emitted.startsWith("(") && emitted.endsWith(")") ? emitted.slice(1, -1) : emitted;
119
+ }
120
+
121
+ function isPrimitiveType(type: string): type is PointCorePrimitiveType {
122
+ return type === "Text" || type === "Int" || type === "Float" || type === "Bool" || type === "Void";
123
+ }
124
+
125
+ function indentLines(lines: string[]): string[] {
126
+ return lines.map((line) => ` ${line}`);
127
+ }
128
+
129
+ function trimTrailingBlankLines(lines: string[]): string[] {
130
+ while (lines.at(-1) === "") lines.pop();
131
+ return lines;
132
+ }
@@ -0,0 +1,103 @@
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
+ 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}`);
103
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./ast.ts";
2
+ export * from "./check.ts";
3
+ export * from "./context.ts";
4
+ export * from "./emit-typescript.ts";
5
+ export * from "./format.ts";
6
+ export * from "./lexer.ts";
7
+ export * from "./parser.ts";