@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 CHANGED
@@ -2,20 +2,33 @@
2
2
 
3
3
  Point is an AI-first general-purpose language core for coding-agent-native software engineering.
4
4
 
5
+ ## Install
6
+
7
+ Requires [Bun](https://bun.sh) on PATH.
8
+
9
+ ```bash
10
+ npm install -g @hatchingpoint/point
11
+ point check examples/math.point
12
+ ```
13
+
14
+ Pair with the [Point Language](https://marketplace.visualstudio.com/items?itemName=hatchingpoint.point) extension in VS Code or Cursor.
15
+
16
+ Point's public source language is semantic product logic. The compiler lowers that source into an internal typed core and emits TypeScript for existing JavaScript infrastructure.
17
+
5
18
  This package is the source of truth for Point. It exposes:
6
19
 
7
20
  - `point` CLI through `src/cli.ts`
8
21
  - core language APIs through `@hatchingpoint/point/core`
9
- - core parser, formatter, checker, and TypeScript emitter APIs
22
+ - semantic parser/lowering, core checker, formatter, and TypeScript emitter APIs
10
23
 
11
24
  Core workflow:
12
25
 
13
26
  ```bash
14
- bun run point:fmt-check:all
15
- bun run point:check:all
16
- bun run point:build:all
27
+ bun run fmt-check
28
+ bun run check
29
+ bun run build
17
30
  ```
18
31
 
19
- `point:build:all` emits TypeScript that can be imported by React, Vue, Bun, Node, and Vite projects.
32
+ `bun run build` emits TypeScript into `generated/` for React, Vue, Bun, Node, and Vite projects.
20
33
 
21
34
  When Point is extracted, this package can move into a standalone repo with the same package name and public entrypoints.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchingpoint/point",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Point language compiler and CLI.",
package/src/core/ast.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { PointSemanticProgram } from "../semantic/ast.ts";
2
+
1
3
  export type PointCorePrimitiveType = "Text" | "Int" | "Float" | "Bool" | "Void";
2
4
 
3
5
  export interface PointSourcePosition {
@@ -16,10 +18,13 @@ export interface PointCoreProgram {
16
18
  module?: string;
17
19
  declarations: PointCoreDeclaration[];
18
20
  span?: PointSourceSpan;
21
+ semantic?: PointSemanticProgramMetadata;
22
+ semanticSource?: PointSemanticProgram;
19
23
  }
20
24
 
21
25
  export type PointCoreDeclaration =
22
26
  | PointCoreImportDeclaration
27
+ | PointCoreExternalDeclaration
23
28
  | PointCoreValueDeclaration
24
29
  | PointCoreFunctionDeclaration
25
30
  | PointCoreTypeDeclaration;
@@ -47,6 +52,18 @@ export interface PointCoreFunctionDeclaration {
47
52
  returnType: PointCoreTypeExpression;
48
53
  body: PointCoreStatement[];
49
54
  span?: PointSourceSpan;
55
+ semantic?: PointSemanticDeclarationMetadata;
56
+ }
57
+
58
+ export interface PointCoreExternalDeclaration {
59
+ kind: "external";
60
+ name: string;
61
+ params: PointCoreParameter[];
62
+ returnType: PointCoreTypeExpression;
63
+ from: string;
64
+ importName?: string;
65
+ span?: PointSourceSpan;
66
+ semantic?: PointSemanticDeclarationMetadata;
50
67
  }
51
68
 
52
69
  export interface PointCoreTypeDeclaration {
@@ -54,12 +71,25 @@ export interface PointCoreTypeDeclaration {
54
71
  name: string;
55
72
  fields: PointCoreParameter[];
56
73
  span?: PointSourceSpan;
74
+ semantic?: PointSemanticDeclarationMetadata;
57
75
  }
58
76
 
59
77
  export interface PointCoreParameter {
60
78
  name: string;
61
79
  type: PointCoreTypeExpression;
62
80
  span?: PointSourceSpan;
81
+ semanticName?: string;
82
+ }
83
+
84
+ export interface PointSemanticProgramMetadata {
85
+ source: "semantic";
86
+ }
87
+
88
+ export interface PointSemanticDeclarationMetadata {
89
+ kind: "record" | "calculation" | "rule" | "label" | "external" | "action" | "policy" | "view" | "route" | "workflow" | "command";
90
+ name: string;
91
+ outputName?: string;
92
+ effects?: string[];
63
93
  }
64
94
 
65
95
  export interface PointCoreTypeExpression {
@@ -81,7 +111,7 @@ export type PointCoreStatement =
81
111
  | {
82
112
  kind: "assignment";
83
113
  name: string;
84
- operator: "=" | "+=";
114
+ operator: "=" | "+=" | "-=";
85
115
  value: PointCoreExpression;
86
116
  span?: PointSourceSpan;
87
117
  }
@@ -92,14 +122,22 @@ export type PointCoreStatement =
92
122
  elseBody: PointCoreStatement[];
93
123
  span?: PointSourceSpan;
94
124
  }
125
+ | {
126
+ kind: "for";
127
+ itemName: string;
128
+ iterable: PointCoreExpression;
129
+ body: PointCoreStatement[];
130
+ span?: PointSourceSpan;
131
+ }
95
132
  | { kind: "expression"; value: PointCoreExpression; span?: PointSourceSpan };
96
133
 
97
134
  export type PointCoreExpression =
98
- | { kind: "literal"; value: string | number | boolean; span?: PointSourceSpan }
135
+ | { kind: "literal"; value: string | number | boolean | null; span?: PointSourceSpan }
99
136
  | { kind: "identifier"; name: string; span?: PointSourceSpan }
100
137
  | { kind: "list"; items: PointCoreExpression[]; span?: PointSourceSpan }
101
138
  | { kind: "record"; fields: PointCoreRecordField[]; span?: PointSourceSpan }
102
139
  | { kind: "property"; target: PointCoreExpression; name: string; span?: PointSourceSpan }
140
+ | { kind: "await"; value: PointCoreExpression; span?: PointSourceSpan }
103
141
  | {
104
142
  kind: "binary";
105
143
  operator: PointCoreBinaryOperator;
package/src/core/check.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type {
2
2
  PointCoreDeclaration,
3
- PointCoreExpression,
4
- PointCoreFunctionDeclaration,
3
+ PointCoreExpression,
4
+ PointCoreExternalDeclaration,
5
+ PointCoreFunctionDeclaration,
5
6
  PointCoreProgram,
6
7
  PointCoreStatement,
7
8
  PointCoreTypeDeclaration,
@@ -27,7 +28,7 @@ type DiagnosticMetadata = Partial<Pick<PointCoreDiagnostic, "expected" | "actual
27
28
  type ScopeEntry = { type: PointCoreTypeExpression; mutable: boolean };
28
29
  type Scope = Map<string, ScopeEntry>;
29
30
 
30
- const PRIMITIVE_TYPES = new Set(["Text", "Int", "Float", "Bool", "Void", "List"]);
31
+ const PRIMITIVE_TYPES = new Set(["Text", "Int", "Float", "Bool", "Void", "List", "Maybe", "Error", "Or"]);
31
32
 
32
33
  export function checkPointCore(program: PointCoreProgram): PointCoreDiagnostic[] {
33
34
  const checker = new CoreChecker(program);
@@ -39,7 +40,7 @@ class CoreChecker {
39
40
  private readonly types = new Set(PRIMITIVE_TYPES);
40
41
  private readonly typeDeclarations = new Map<string, PointCoreTypeDeclaration>();
41
42
  private readonly globals: Scope = new Map();
42
- private readonly functions = new Map<string, PointCoreFunctionDeclaration>();
43
+ private readonly functions = new Map<string, PointCoreFunctionDeclaration | PointCoreExternalDeclaration>();
43
44
 
44
45
  constructor(private readonly program: PointCoreProgram) {}
45
46
 
@@ -59,12 +60,18 @@ class CoreChecker {
59
60
  this.typeDeclarations.set(declaration.name, declaration);
60
61
  }
61
62
  if (declaration.kind === "value") this.addGlobal(declaration);
62
- if (declaration.kind === "function") {
63
+ if (declaration.kind === "function") {
63
64
  if (this.functions.has(declaration.name)) {
64
65
  this.push("duplicate-function", `Duplicate function ${declaration.name}`, `fn.${declaration.name}`, declaration.span);
65
66
  }
66
- this.functions.set(declaration.name, declaration);
67
- }
67
+ this.functions.set(declaration.name, declaration);
68
+ }
69
+ if (declaration.kind === "external") {
70
+ if (this.functions.has(declaration.name)) {
71
+ this.push("duplicate-function", `Duplicate function ${declaration.name}`, `external.${declaration.name}`, declaration.span);
72
+ }
73
+ this.functions.set(declaration.name, declaration);
74
+ }
68
75
  }
69
76
  }
70
77
 
@@ -76,7 +83,12 @@ class CoreChecker {
76
83
  }
77
84
 
78
85
  private checkDeclaration(declaration: PointCoreDeclaration) {
79
- if (declaration.kind === "import") return;
86
+ if (declaration.kind === "import") return;
87
+ if (declaration.kind === "external") {
88
+ for (const param of declaration.params) this.checkType(param.type, `external.${declaration.name}.${param.name}.type`);
89
+ this.checkType(declaration.returnType, `external.${declaration.name}.return`);
90
+ return;
91
+ }
80
92
  if (declaration.kind === "type") {
81
93
  for (const field of declaration.fields) this.checkType(field.type, `type.${declaration.name}.${field.name}`);
82
94
  return;
@@ -126,16 +138,41 @@ class CoreChecker {
126
138
  this.checkAssignment(statement, fn, locals);
127
139
  return;
128
140
  }
129
- if (statement.kind === "if") {
130
- this.checkExpressionAssignable(statement.condition, typeRef("Bool"), `fn.${fn.name}.if.condition`, locals);
131
- const thenLocals = new Map(locals);
132
- for (const child of statement.thenBody) this.checkStatement(child, fn, thenLocals);
133
- const elseLocals = new Map(locals);
134
- for (const child of statement.elseBody) this.checkStatement(child, fn, elseLocals);
135
- return;
136
- }
137
- this.typeOfExpression(statement.value, locals, `fn.${fn.name}.expression`);
138
- }
141
+ if (statement.kind === "if") {
142
+ this.checkExpressionAssignable(statement.condition, typeRef("Bool"), `fn.${fn.name}.if.condition`, locals);
143
+ const thenLocals = new Map(locals);
144
+ for (const child of statement.thenBody) this.checkStatement(child, fn, thenLocals);
145
+ const elseLocals = new Map(locals);
146
+ for (const child of statement.elseBody) this.checkStatement(child, fn, elseLocals);
147
+ return;
148
+ }
149
+ if (statement.kind === "for") {
150
+ this.checkForStatement(statement, fn, locals);
151
+ return;
152
+ }
153
+ this.typeOfExpression(statement.value, locals, `fn.${fn.name}.expression`);
154
+ }
155
+
156
+ private checkForStatement(
157
+ statement: Extract<PointCoreStatement, { kind: "for" }>,
158
+ fn: PointCoreFunctionDeclaration,
159
+ locals: Scope,
160
+ ) {
161
+ const path = `fn.${fn.name}.for.${statement.itemName}`;
162
+ const iterableType = this.typeOfExpression(statement.iterable, locals, `${path}.iterable`);
163
+ if (!iterableType) return;
164
+ if (iterableType.name !== "List" || iterableType.args.length !== 1) {
165
+ this.push("iteration-type-mismatch", "for requires a List<T> iterable", path, statement.span, {
166
+ expected: "List<T>",
167
+ actual: formatType(iterableType),
168
+ repair: "Iterate over a List<T> value or change this expression to a list.",
169
+ });
170
+ return;
171
+ }
172
+ const loopLocals = new Map(locals);
173
+ loopLocals.set(statement.itemName, { type: iterableType.args[0]!, mutable: false });
174
+ for (const child of statement.body) this.checkStatement(child, fn, loopLocals);
175
+ }
139
176
 
140
177
  private checkAssignment(
141
178
  statement: Extract<PointCoreStatement, { kind: "assignment" }>,
@@ -158,25 +195,48 @@ class CoreChecker {
158
195
  repair: `Declare ${statement.name} with var if it needs to change.`,
159
196
  });
160
197
  }
161
- if (statement.operator === "+=" && !isNumeric(String(target.type.name))) {
162
- this.push("operator-type-mismatch", "+= requires a numeric target", path, statement.span, {
163
- expected: "Int or Float target",
164
- actual: formatType(target.type),
165
- repair: "Use += only with Int or Float values.",
166
- });
167
- }
198
+ if ((statement.operator === "+=" || statement.operator === "-=") && !isNumeric(String(target.type.name))) {
199
+ this.push("operator-type-mismatch", `${statement.operator} requires a numeric target`, path, statement.span, {
200
+ expected: "Int or Float target",
201
+ actual: formatType(target.type),
202
+ repair: `Use ${statement.operator} only with Int or Float values.`,
203
+ });
204
+ }
168
205
  this.checkExpressionAssignable(statement.value, target.type, `${path}.value`, locals);
169
206
  }
170
207
 
171
- private checkExpressionAssignable(
172
- expression: PointCoreExpression,
173
- expected: PointCoreTypeExpression,
174
- path: string,
175
- scope: Scope,
176
- ) {
177
- if (expression.kind === "list") {
178
- this.checkListAssignable(expression, expected, path, scope);
179
- return;
208
+ private checkExpressionAssignable(
209
+ expression: PointCoreExpression,
210
+ expected: PointCoreTypeExpression,
211
+ path: string,
212
+ scope: Scope,
213
+ ) {
214
+ if (expected.name === "Maybe" && expected.args.length === 1) {
215
+ if (expression.kind === "literal" && expression.value === null) return;
216
+ if (expression.kind !== "record" && expression.kind !== "list") {
217
+ const actual = this.typeOfExpression(expression, scope, path);
218
+ if (actual && sameType(actual, expected)) return;
219
+ }
220
+ this.checkExpressionAssignable(expression, expected.args[0]!, path, scope);
221
+ return;
222
+ }
223
+ if (expected.name === "Or" && expected.args.length > 0) {
224
+ const diagnosticsBefore = this.diagnostics.length;
225
+ const actual = this.typeOfExpression(expression, scope, path);
226
+ if (!actual) return;
227
+ if (sameType(actual, expected)) return;
228
+ if (expected.args.some((candidate) => sameType(candidate, actual))) return;
229
+ this.push("type-mismatch", `Expected ${formatType(expected)}, got ${formatType(actual)}`, path, expression.span, {
230
+ expected: formatType(expected),
231
+ actual: formatType(actual),
232
+ repair: `Return or assign one of: ${expected.args.map(formatType).join(", ")}.`,
233
+ });
234
+ if (this.diagnostics.length > diagnosticsBefore + 1) return;
235
+ return;
236
+ }
237
+ if (expression.kind === "list") {
238
+ this.checkListAssignable(expression, expected, path, scope);
239
+ return;
180
240
  }
181
241
  if (expression.kind === "record") {
182
242
  this.checkRecordAssignable(expression, expected, path, scope);
@@ -251,15 +311,17 @@ class CoreChecker {
251
311
  }
252
312
  }
253
313
 
254
- private typeOfExpression(
255
- expression: PointCoreExpression,
256
- scope: Scope,
257
- path: string,
258
- ): PointCoreTypeExpression | null {
259
- if (expression.kind === "literal") {
260
- const valueType =
261
- typeof expression.value === "string"
262
- ? "Text"
314
+ private typeOfExpression(
315
+ expression: PointCoreExpression,
316
+ scope: Scope,
317
+ path: string,
318
+ awaitedCall = false,
319
+ ): PointCoreTypeExpression | null {
320
+ if (expression.kind === "literal") {
321
+ if (expression.value === null) return { kind: "typeRef", name: "Void", args: [], span: expression.span };
322
+ const valueType =
323
+ typeof expression.value === "string"
324
+ ? "Text"
263
325
  : typeof expression.value === "boolean"
264
326
  ? "Bool"
265
327
  : Number.isInteger(expression.value)
@@ -286,19 +348,45 @@ class CoreChecker {
286
348
  if (expression.kind === "binary") {
287
349
  return this.typeOfBinaryExpression(expression, scope, path);
288
350
  }
289
- if (expression.kind === "property") {
290
- return this.typeOfPropertyExpression(expression, scope, path);
291
- }
292
- const target = this.functions.get(expression.callee);
293
- if (!target) {
294
- this.push("unknown-function", `Unknown function ${expression.callee}`, path, expression.span, {
295
- actual: expression.callee,
296
- expected: [...this.functions.keys()],
297
- repair: `Define fn ${expression.callee}(...) or call an existing function.`,
298
- });
299
- return null;
300
- }
301
- if (target.params.length !== expression.args.length) {
351
+ if (expression.kind === "property") {
352
+ return this.typeOfPropertyExpression(expression, scope, path);
353
+ }
354
+ if (expression.kind === "await") {
355
+ return this.typeOfExpression(expression.value, scope, path, true);
356
+ }
357
+ if (expression.callee === "Error") {
358
+ if (expression.args.length !== 1) {
359
+ this.push("arity-mismatch", "Error expects 1 message argument", path, expression.span, {
360
+ expected: "1 arg",
361
+ actual: `${expression.args.length} args`,
362
+ repair: "Construct errors as Error(\"message\").",
363
+ });
364
+ }
365
+ const message = expression.args[0];
366
+ if (message) this.checkExpressionAssignable(message, typeRef("Text"), `${path}.message`, scope);
367
+ return typeRef("Error", [], expression.span);
368
+ }
369
+ if (expression.callee === "Ok") {
370
+ return expression.args[0] ? this.typeOfExpression(expression.args[0], scope, `${path}.value`) : typeRef("Void", [], expression.span);
371
+ }
372
+ const target = this.functions.get(expression.callee);
373
+ if (!target) {
374
+ this.push("unknown-function", `Unknown function ${expression.callee}`, path, expression.span, {
375
+ actual: expression.callee,
376
+ expected: [...this.functions.keys()],
377
+ repair: `Define fn ${expression.callee}(...) or call an existing function.`,
378
+ });
379
+ return null;
380
+ }
381
+ if ((target.semantic?.kind === "action" || target.semantic?.kind === "workflow") && !awaitedCall) {
382
+ this.push("missing-await", `Action ${expression.callee} must be awaited`, path, expression.span, {
383
+ expected: `await ${expression.callee}(...)`,
384
+ actual: `${expression.callee}(...)`,
385
+ repair: "Prefix this action call with await.",
386
+ relatedRefs: [this.refFor(`fn.${target.name}`)],
387
+ });
388
+ }
389
+ if (target.params.length !== expression.args.length) {
302
390
  this.push("arity-mismatch", `Function ${expression.callee} expects ${target.params.length} args`, path, expression.span, {
303
391
  expected: `${target.params.length} args`,
304
392
  actual: `${expression.args.length} args`,
@@ -338,9 +426,17 @@ class CoreChecker {
338
426
  scope: Scope,
339
427
  path: string,
340
428
  ): PointCoreTypeExpression | null {
341
- const targetType = this.typeOfExpression(expression.target, scope, `${path}.target`);
342
- if (!targetType) return null;
343
- const declaration = this.typeDeclarations.get(String(targetType.name));
429
+ const targetType = this.typeOfExpression(expression.target, scope, `${path}.target`);
430
+ if (!targetType) return null;
431
+ if (targetType.name === "Maybe" && targetType.args.length === 1) {
432
+ this.push("nullable-field-access", `Cannot access field ${expression.name} on nullable ${formatType(targetType)}`, path, expression.span, {
433
+ expected: formatType(targetType.args[0]!),
434
+ actual: formatType(targetType),
435
+ repair: "Check that this Maybe value is present before accessing its fields.",
436
+ });
437
+ return null;
438
+ }
439
+ const declaration = this.typeDeclarations.get(String(targetType.name));
344
440
  if (!declaration) {
345
441
  this.push("not-a-record", `${formatType(targetType)} has no fields`, path, expression.span, {
346
442
  actual: formatType(targetType),
@@ -414,14 +510,28 @@ class CoreChecker {
414
510
  repair: `Declare type ${type.name} or use an existing type.`,
415
511
  });
416
512
  }
417
- if (type.name === "List" && type.args.length !== 1) {
513
+ if (type.name === "List" && type.args.length !== 1) {
418
514
  this.push("invalid-type-arity", "List requires one type argument", path, type.span, {
419
515
  expected: "List<T>",
420
516
  actual: formatType(type),
421
517
  repair: "Use List<Text>, List<Int>, or another concrete item type.",
422
518
  });
423
- }
424
- if (type.name !== "List" && type.args.length > 0 && !this.typeDeclarations.has(String(type.name))) {
519
+ }
520
+ if (type.name === "Maybe" && type.args.length !== 1) {
521
+ this.push("invalid-type-arity", "Maybe requires one type argument", path, type.span, {
522
+ expected: "Maybe<T>",
523
+ actual: formatType(type),
524
+ repair: "Use Maybe<Text>, Maybe<User>, or another concrete optional type.",
525
+ });
526
+ }
527
+ if (type.name === "Or" && type.args.length < 2) {
528
+ this.push("invalid-type-arity", "Or requires at least two type arguments", path, type.span, {
529
+ expected: "A or B",
530
+ actual: formatType(type),
531
+ repair: "Use syntax such as User or Error.",
532
+ });
533
+ }
534
+ if (type.name !== "List" && type.name !== "Maybe" && type.name !== "Or" && type.args.length > 0 && !this.typeDeclarations.has(String(type.name))) {
425
535
  this.push("invalid-type-arity", `${type.name} does not accept type arguments`, path, type.span, {
426
536
  expected: String(type.name),
427
537
  actual: formatType(type),
@@ -468,11 +578,12 @@ function sameType(left: PointCoreTypeExpression, right: PointCoreTypeExpression)
468
578
  return left.name === right.name && leftArgs.length === rightArgs.length && leftArgs.every((arg, index) => sameType(arg, rightArgs[index]!));
469
579
  }
470
580
 
471
- function formatType(type: PointCoreTypeExpression): string {
472
- const args = type.args ?? [];
473
- if (args.length === 0) return String(type.name);
474
- return `${type.name}<${args.map(formatType).join(", ")}>`;
475
- }
581
+ function formatType(type: PointCoreTypeExpression): string {
582
+ const args = type.args ?? [];
583
+ if (args.length === 0) return String(type.name);
584
+ if (type.name === "Or") return args.map(formatType).join(" or ");
585
+ return `${type.name}<${args.map(formatType).join(", ")}>`;
586
+ }
476
587
 
477
588
  function typeRef(name: string, args: PointCoreTypeExpression[] = [], span?: PointSourceSpan): PointCoreTypeExpression {
478
589
  return { kind: "typeRef", name, args, span };