@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.
@@ -0,0 +1,53 @@
1
+ import { createHash } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+
4
+ const CACHE_DIR = ".point-cache";
5
+ const MANIFEST = "manifest.json";
6
+
7
+ export interface PointBuildCacheEntry {
8
+ sourceHash: string;
9
+ checkedAt: string;
10
+ ok: boolean;
11
+ }
12
+
13
+ export interface PointBuildCacheManifest {
14
+ schemaVersion: "point.cache.v1";
15
+ entries: Record<string, PointBuildCacheEntry>;
16
+ }
17
+
18
+ export function hashPointSource(source: string): string {
19
+ return createHash("sha256").update(source).digest("hex");
20
+ }
21
+
22
+ export async function readBuildCache(cwd = process.cwd()): Promise<PointBuildCacheManifest> {
23
+ const path = resolve(cwd, CACHE_DIR, MANIFEST);
24
+ if (!(await Bun.file(path).exists())) {
25
+ return { schemaVersion: "point.cache.v1", entries: {} };
26
+ }
27
+ return Bun.file(path).json();
28
+ }
29
+
30
+ export async function writeBuildCache(manifest: PointBuildCacheManifest, cwd = process.cwd()): Promise<void> {
31
+ const path = resolve(cwd, CACHE_DIR, MANIFEST);
32
+ await Bun.$`mkdir -p ${resolve(cwd, CACHE_DIR)}`.quiet();
33
+ await Bun.write(path, `${JSON.stringify(manifest, null, 2)}\n`);
34
+ }
35
+
36
+ export function isIncrementalEnabled(): boolean {
37
+ return process.env.POINT_INCREMENTAL === "1" || process.env.POINT_INCREMENTAL === "true";
38
+ }
39
+
40
+ export function isCacheHit(manifest: PointBuildCacheManifest, input: string, source: string): boolean {
41
+ const entry = manifest.entries[input];
42
+ return Boolean(entry && entry.sourceHash === hashPointSource(source) && entry.ok);
43
+ }
44
+
45
+ export function recordCacheEntry(manifest: PointBuildCacheManifest, input: string, source: string, ok: boolean): PointBuildCacheManifest {
46
+ return {
47
+ ...manifest,
48
+ entries: {
49
+ ...manifest.entries,
50
+ [input]: { sourceHash: hashPointSource(source), checkedAt: new Date().toISOString(), ok },
51
+ },
52
+ };
53
+ }
package/src/core/index.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  export * from "./ast.ts";
2
2
  export * from "./check.ts";
3
+ export { findRunEntryName } from "./cli.ts";
3
4
  export * from "./context.ts";
4
5
  export * from "./emit-typescript.ts";
6
+ export * from "./emit-javascript.ts";
7
+ export * from "./incremental.ts";
8
+ export * from "./serialize.ts";
9
+ export * from "../semantic/index.ts";
5
10
  export * from "./format.ts";
6
11
  export * from "./lexer.ts";
7
12
  export * from "./parser.ts";
package/src/core/lexer.ts CHANGED
@@ -13,7 +13,9 @@ export type PointCoreTokenType =
13
13
  | "comma"
14
14
  | "colon"
15
15
  | "dot"
16
- | "equals"
16
+ | "equals"
17
+ | "plusEquals"
18
+ | "minusEquals"
17
19
  | "equalsEquals"
18
20
  | "bangEquals"
19
21
  | "less"
@@ -128,13 +130,21 @@ class CoreLexer {
128
130
  continue;
129
131
  }
130
132
  if (char === "+") {
133
+ if (this.peek(1) === "=") {
134
+ this.push("plusEquals", `${this.advance()}${this.advance()}`);
135
+ continue;
136
+ }
131
137
  this.push("plus", this.advance());
132
138
  continue;
133
139
  }
134
- if (char === "-") {
135
- this.push("minus", this.advance());
136
- continue;
137
- }
140
+ if (char === "-") {
141
+ if (this.peek(1) === "=") {
142
+ this.push("minusEquals", `${this.advance()}${this.advance()}`);
143
+ continue;
144
+ }
145
+ this.push("minus", this.advance());
146
+ continue;
147
+ }
138
148
  if (char === "*") {
139
149
  this.push("star", this.advance());
140
150
  continue;
@@ -1,362 +1,11 @@
1
- import type {
2
- PointCoreBinaryOperator,
3
- PointCoreDeclaration,
4
- PointCoreExpression,
5
- PointCoreFunctionDeclaration,
6
- PointCoreParameter,
7
- PointCoreProgram,
8
- PointCoreRecordField,
9
- PointCoreStatement,
10
- PointCoreTypeExpression,
11
- PointCoreValueDeclaration,
12
- PointSourceSpan,
13
- } from "./ast.ts";
14
- import { lexPointCore, type PointCoreToken, type PointCoreTokenType } from "./lexer.ts";
15
-
16
- export class PointCoreParserError extends Error {
17
- constructor(message: string, public readonly token: PointCoreToken) {
18
- super(`${message} at ${token.span.start.line}:${token.span.start.column}`);
19
- this.name = "PointCoreParserError";
20
- }
21
- }
22
-
23
- export function parsePointCore(source: string): PointCoreProgram {
24
- const parser = new CoreParser(lexPointCore(source));
25
- return parser.parseProgram();
26
- }
27
-
28
- class CoreParser {
29
- private current = 0;
30
-
31
- constructor(private readonly tokens: PointCoreToken[]) {}
32
-
33
- parseProgram(): PointCoreProgram {
34
- let moduleName: string | undefined;
35
- const declarations: PointCoreDeclaration[] = [];
36
- const start = this.peek().span.start;
37
- while (!this.check("eof")) {
38
- if (this.matchKeyword("module")) {
39
- moduleName = this.consume("identifier", "Expected module name").value;
40
- continue;
41
- }
42
- declarations.push(this.parseDeclaration());
43
- }
44
- return {
45
- kind: "coreProgram",
46
- module: moduleName,
47
- declarations,
48
- span: { start, end: this.peek().span.end },
49
- };
50
- }
51
-
52
- private parseDeclaration(): PointCoreDeclaration {
53
- if (this.matchKeyword("import")) return this.parseImport();
54
- if (this.checkKeyword("let") || this.checkKeyword("var")) return this.parseValueDeclaration();
55
- if (this.matchKeyword("fn")) return this.parseFunction();
56
- if (this.matchKeyword("type")) return this.parseTypeDeclaration();
57
- throw new PointCoreParserError("Expected import, let, var, fn, or type declaration", this.peek());
58
- }
59
-
60
- private parseImport(): PointCoreDeclaration {
61
- const start = this.previous().span.start;
62
- this.consume("leftBrace", "Expected import list");
63
- const names: string[] = [];
64
- do {
65
- names.push(this.consume("identifier", "Expected imported name").value);
66
- } while (this.match("comma"));
67
- this.consume("rightBrace", "Expected end of import list");
68
- this.consumeKeyword("from");
69
- const from = this.consume("string", "Expected import source").value;
70
- return { kind: "import", names, from, span: { start, end: this.previous().span.end } };
71
- }
72
-
73
- private parseValueDeclaration(): PointCoreValueDeclaration {
74
- const keyword = this.advance();
75
- const name = this.consume("identifier", "Expected value name");
76
- this.consume("colon", `Expected type annotation for ${name.value}`);
77
- const type = this.parseTypeExpression();
78
- this.consume("equals", `Expected initializer for ${name.value}`);
79
- const value = this.parseExpression();
80
- return {
81
- kind: "value",
82
- mutable: keyword.value === "var",
83
- name: name.value,
84
- type,
85
- value,
86
- span: { start: keyword.span.start, end: value.span?.end ?? this.previous().span.end },
87
- };
88
- }
89
-
90
- private parseFunction(): PointCoreFunctionDeclaration {
91
- const start = this.previous().span.start;
92
- const name = this.consume("identifier", "Expected function name").value;
93
- this.consume("leftParen", "Expected function parameters");
94
- const params = this.parseParameters();
95
- this.consume("rightParen", "Expected end of function parameters");
96
- this.consume("colon", "Expected function return type");
97
- const returnType = this.parseTypeExpression();
98
- this.consume("leftBrace", "Expected function body");
99
- const body: PointCoreStatement[] = [];
100
- while (!this.check("rightBrace")) body.push(this.parseStatement());
101
- this.consume("rightBrace", "Expected end of function body");
102
- return { kind: "function", name, params, returnType, body, span: { start, end: this.previous().span.end } };
103
- }
104
-
105
- private parseTypeDeclaration(): PointCoreDeclaration {
106
- const start = this.previous().span.start;
107
- const name = this.consume("identifier", "Expected type name").value;
108
- this.consume("leftBrace", "Expected type body");
109
- const fields: PointCoreParameter[] = [];
110
- while (!this.check("rightBrace")) {
111
- fields.push(this.parseParameter());
112
- }
113
- this.consume("rightBrace", "Expected end of type body");
114
- return { kind: "type", name, fields, span: { start, end: this.previous().span.end } };
115
- }
116
-
117
- private parseStatement(): PointCoreStatement {
118
- if (this.matchKeyword("return")) {
119
- const start = this.previous().span.start;
120
- if (this.check("rightBrace")) return { kind: "return", span: { start, end: this.previous().span.end } };
121
- const value = this.parseExpression();
122
- return { kind: "return", value, span: { start, end: value.span?.end ?? this.previous().span.end } };
123
- }
124
- if (this.matchKeyword("if")) return this.parseIfStatement();
125
- if (this.checkKeyword("let") || this.checkKeyword("var")) return this.parseValueDeclaration();
126
- const value = this.parseExpression();
127
- return { kind: "expression", value, span: value.span };
128
- }
129
-
130
- private parseIfStatement(): PointCoreStatement {
131
- const start = this.previous().span.start;
132
- const condition = this.parseExpression();
133
- const thenBody = this.parseBlockStatements("Expected if body");
134
- let elseBody: PointCoreStatement[] = [];
135
- if (this.matchKeyword("else")) {
136
- elseBody = this.parseBlockStatements("Expected else body");
137
- }
138
- return {
139
- kind: "if",
140
- condition,
141
- thenBody,
142
- elseBody,
143
- span: { start, end: this.previous().span.end },
144
- };
145
- }
146
-
147
- private parseBlockStatements(message: string): PointCoreStatement[] {
148
- this.consume("leftBrace", message);
149
- const statements: PointCoreStatement[] = [];
150
- while (!this.check("rightBrace")) statements.push(this.parseStatement());
151
- this.consume("rightBrace", "Expected end of block");
152
- return statements;
153
- }
154
-
155
- private parseParameters(): PointCoreParameter[] {
156
- const params: PointCoreParameter[] = [];
157
- if (this.check("rightParen")) return params;
158
- do {
159
- params.push(this.parseParameter());
160
- } while (this.match("comma"));
161
- return params;
162
- }
163
-
164
- private parseParameter(): PointCoreParameter {
165
- const name = this.consume("identifier", "Expected parameter name");
166
- this.consume("colon", `Expected type annotation for ${name.value}`);
167
- const type = this.parseTypeExpression();
168
- return { name: name.value, type, span: { start: name.span.start, end: type.span?.end ?? name.span.end } };
169
- }
170
-
171
- private parseTypeExpression(): PointCoreTypeExpression {
172
- const token = this.consume("identifier", "Expected type name");
173
- const args: PointCoreTypeExpression[] = [];
174
- if (this.match("less")) {
175
- do {
176
- args.push(this.parseTypeExpression());
177
- } while (this.match("comma"));
178
- this.consume("greater", "Expected end of type arguments");
179
- }
180
- return { kind: "typeRef", name: token.value, args, span: { start: token.span.start, end: this.previous().span.end } };
181
- }
182
-
183
- private parseExpression(): PointCoreExpression {
184
- return this.parseBinaryExpression(0);
185
- }
186
-
187
- private parseBinaryExpression(minPrecedence: number): PointCoreExpression {
188
- let left = this.parsePrimaryExpression();
189
- while (true) {
190
- const operator = this.peekBinaryOperator();
191
- if (!operator) break;
192
- const precedence = precedenceFor(operator);
193
- if (precedence < minPrecedence) break;
194
- this.advance();
195
- const right = this.parseBinaryExpression(precedence + 1);
196
- left = {
197
- kind: "binary",
198
- operator,
199
- left,
200
- right,
201
- span: { start: left.span?.start ?? this.previous().span.start, end: right.span?.end ?? this.previous().span.end },
202
- };
203
- }
204
- return left;
205
- }
206
-
207
- private parsePrimaryExpression(): PointCoreExpression {
208
- let expression = this.parseAtomExpression();
209
- while (this.match("dot")) {
210
- const name = this.consume("identifier", "Expected property name");
211
- expression = {
212
- kind: "property",
213
- target: expression,
214
- name: name.value,
215
- span: { start: expression.span?.start ?? name.span.start, end: name.span.end },
216
- };
217
- }
218
- return expression;
219
- }
220
-
221
- private parseAtomExpression(): PointCoreExpression {
222
- if (this.check("string")) {
223
- const token = this.advance();
224
- return { kind: "literal", value: token.value, span: token.span };
225
- }
226
- if (this.check("number")) {
227
- const token = this.advance();
228
- return { kind: "literal", value: Number(token.value), span: token.span };
229
- }
230
- if (this.match("leftParen")) {
231
- const start = this.previous().span.start;
232
- const expression = this.parseExpression();
233
- this.consume("rightParen", "Expected end of grouped expression");
234
- return { ...expression, span: { start, end: this.previous().span.end } };
235
- }
236
- if (this.match("leftBracket")) return this.parseListExpression();
237
- if (this.match("leftBrace")) return this.parseRecordExpression();
238
- const identifier = this.consume("identifier", "Expected expression");
239
- if (identifier.value === "true") return { kind: "literal", value: true, span: identifier.span };
240
- if (identifier.value === "false") return { kind: "literal", value: false, span: identifier.span };
241
- if (this.match("leftParen")) {
242
- const args: PointCoreExpression[] = [];
243
- if (!this.check("rightParen")) {
244
- do {
245
- args.push(this.parseExpression());
246
- } while (this.match("comma"));
247
- }
248
- this.consume("rightParen", "Expected end of call arguments");
249
- return {
250
- kind: "call",
251
- callee: identifier.value,
252
- args,
253
- span: { start: identifier.span.start, end: this.previous().span.end },
254
- };
255
- }
256
- return { kind: "identifier", name: identifier.value, span: identifier.span };
257
- }
258
-
259
- private parseListExpression(): PointCoreExpression {
260
- const start = this.previous().span.start;
261
- const items: PointCoreExpression[] = [];
262
- if (!this.check("rightBracket")) {
263
- do {
264
- items.push(this.parseExpression());
265
- } while (this.match("comma"));
266
- }
267
- this.consume("rightBracket", "Expected end of list");
268
- return { kind: "list", items, span: { start, end: this.previous().span.end } };
269
- }
270
-
271
- private parseRecordExpression(): PointCoreExpression {
272
- const start = this.previous().span.start;
273
- const fields: PointCoreRecordField[] = [];
274
- if (!this.check("rightBrace")) {
275
- do {
276
- const name = this.consume("identifier", "Expected record field name");
277
- this.consume("colon", `Expected value for record field ${name.value}`);
278
- const value = this.parseExpression();
279
- fields.push({
280
- name: name.value,
281
- value,
282
- span: { start: name.span.start, end: value.span?.end ?? name.span.end },
283
- });
284
- } while (this.match("comma"));
285
- }
286
- this.consume("rightBrace", "Expected end of record");
287
- return { kind: "record", fields, span: { start, end: this.previous().span.end } };
288
- }
289
-
290
- private peekBinaryOperator(): PointCoreBinaryOperator | null {
291
- const token = this.peek();
292
- if (token.type === "plus") return "+";
293
- if (token.type === "minus") return "-";
294
- if (token.type === "star") return "*";
295
- if (token.type === "slash") return "/";
296
- if (token.type === "equalsEquals") return "==";
297
- if (token.type === "bangEquals") return "!=";
298
- if (token.type === "less") return "<";
299
- if (token.type === "lessEquals") return "<=";
300
- if (token.type === "greater") return ">";
301
- if (token.type === "greaterEquals") return ">=";
302
- if (token.type === "identifier" && (token.value === "and" || token.value === "or")) return token.value;
303
- return null;
304
- }
305
-
306
- private consumeKeyword(keyword: string) {
307
- const token = this.consume("identifier", `Expected ${keyword}`);
308
- if (token.value !== keyword) throw new PointCoreParserError(`Expected ${keyword}`, token);
309
- return token;
310
- }
311
-
312
- private matchKeyword(keyword: string) {
313
- if (!this.checkKeyword(keyword)) return false;
314
- this.advance();
315
- return true;
316
- }
317
-
318
- private checkKeyword(keyword: string) {
319
- return this.check("identifier") && this.peek().value === keyword;
320
- }
321
-
322
- private consume(type: PointCoreTokenType, message: string) {
323
- if (this.check(type)) return this.advance();
324
- throw new PointCoreParserError(message, this.peek());
325
- }
326
-
327
- private match(type: PointCoreTokenType) {
328
- if (!this.check(type)) return false;
329
- this.advance();
330
- return true;
331
- }
332
-
333
- private check(type: PointCoreTokenType) {
334
- return this.peek().type === type;
335
- }
336
-
337
- private advance() {
338
- if (!this.check("eof")) this.current += 1;
339
- return this.previous();
340
- }
341
-
342
- private peek() {
343
- return this.tokens[this.current] ?? this.tokens[this.tokens.length - 1]!;
344
- }
345
-
346
- private previous() {
347
- return this.tokens[this.current - 1] ?? this.tokens[0]!;
348
- }
349
- }
350
-
351
- function precedenceFor(operator: PointCoreBinaryOperator): number {
352
- if (operator === "or") return 1;
353
- if (operator === "and") return 2;
354
- if (operator === "==" || operator === "!=") return 3;
355
- if (operator === "<" || operator === "<=" || operator === ">" || operator === ">=") return 4;
356
- if (operator === "+" || operator === "-") return 5;
357
- return 6;
358
- }
359
-
360
- export function mergeSpans(start: PointSourceSpan, end: PointSourceSpan): PointSourceSpan {
361
- return { start: start.start, end: end.end };
362
- }
1
+ import type { PointCoreProgram } from "./ast.ts";
2
+ import { desugarSemanticProgram } from "../semantic/desugar.ts";
3
+ import { parseSemanticSource } from "../semantic/parse.ts";
4
+ import { assertSemanticPointSource } from "./semantic-source.ts";
5
+
6
+ export { isSemanticPointSyntax } from "./semantic-source.ts";
7
+
8
+ export function parsePointSource(source: string): PointCoreProgram {
9
+ assertSemanticPointSource(source);
10
+ return desugarSemanticProgram(parseSemanticSource(source));
11
+ }
@@ -0,0 +1,26 @@
1
+ export function isSemanticPointSyntax(source: string): boolean {
2
+ return source
3
+ .split(/\r?\n/)
4
+ .some((line) => /^(use|record|calculation|rule|label|external|action|policy|view|route|workflow|command)\s+/.test(line.trim()));
5
+ }
6
+
7
+ export function assertSemanticPointSource(source: string) {
8
+ const oldStyleTopLevel = /^(import|type|let|var|fn)\s+/;
9
+ const lines = source.split(/\r?\n/);
10
+ let hasSemanticDeclaration = false;
11
+
12
+ for (const [index, line] of lines.entries()) {
13
+ const trimmed = line.trim();
14
+ if (!trimmed || trimmed.startsWith("//")) continue;
15
+ if (/^(record|calculation|rule|label|external|action|policy|view|route|workflow|command)\s+/.test(trimmed)) hasSemanticDeclaration = true;
16
+ if (oldStyleTopLevel.test(trimmed)) {
17
+ throw new Error(
18
+ `Point source uses internal core syntax at ${index + 1}:1. Use record, calculation, rule, or label instead.`,
19
+ );
20
+ }
21
+ }
22
+
23
+ if (!hasSemanticDeclaration) {
24
+ throw new Error("Point source must contain at least one semantic declaration: record, calculation, rule, or label.");
25
+ }
26
+ }
@@ -0,0 +1,18 @@
1
+ import type { PointCoreProgram } from "./ast.ts";
2
+
3
+ export function serializeCoreProgram(program: PointCoreProgram): string {
4
+ return `${JSON.stringify(stripSpans(program), null, 2)}\n`;
5
+ }
6
+
7
+ export function stripSpans(value: unknown): unknown {
8
+ if (Array.isArray(value)) return value.map(stripSpans);
9
+ if (value && typeof value === "object") {
10
+ const output: Record<string, unknown> = {};
11
+ for (const [key, child] of Object.entries(value)) {
12
+ if (key === "span" || key === "semanticSource") continue;
13
+ output[key] = stripSpans(child);
14
+ }
15
+ return output;
16
+ }
17
+ return value;
18
+ }