@hatchingpoint/point 0.0.6 → 0.0.8

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/src/core/lexer.ts CHANGED
@@ -1,142 +1,142 @@
1
- import type { PointSourceSpan } from "./ast.ts";
2
-
3
- export type PointCoreTokenType =
4
- | "identifier"
5
- | "number"
6
- | "string"
7
- | "leftBrace"
8
- | "rightBrace"
9
- | "leftBracket"
10
- | "rightBracket"
11
- | "leftParen"
12
- | "rightParen"
13
- | "comma"
14
- | "colon"
15
- | "dot"
1
+ import type { PointSourceSpan } from "./ast.ts";
2
+
3
+ export type PointCoreTokenType =
4
+ | "identifier"
5
+ | "number"
6
+ | "string"
7
+ | "leftBrace"
8
+ | "rightBrace"
9
+ | "leftBracket"
10
+ | "rightBracket"
11
+ | "leftParen"
12
+ | "rightParen"
13
+ | "comma"
14
+ | "colon"
15
+ | "dot"
16
16
  | "equals"
17
17
  | "plusEquals"
18
18
  | "minusEquals"
19
- | "equalsEquals"
20
- | "bangEquals"
21
- | "less"
22
- | "lessEquals"
23
- | "greater"
24
- | "greaterEquals"
25
- | "plus"
26
- | "minus"
27
- | "star"
28
- | "slash"
29
- | "eof";
30
-
31
- export interface PointCoreToken {
32
- type: PointCoreTokenType;
33
- value: string;
34
- span: PointSourceSpan;
35
- }
36
-
37
- export class PointCoreLexerError extends Error {
38
- constructor(message: string, public readonly span: PointSourceSpan) {
39
- super(`${message} at ${span.start.line}:${span.start.column}`);
40
- this.name = "PointCoreLexerError";
41
- }
42
- }
43
-
44
- export function lexPointCore(source: string): PointCoreToken[] {
45
- const lexer = new CoreLexer(source);
46
- return lexer.lex();
47
- }
48
-
49
- class CoreLexer {
50
- private index = 0;
51
- private line = 1;
52
- private column = 1;
53
- private readonly tokens: PointCoreToken[] = [];
54
-
55
- constructor(private readonly source: string) {}
56
-
57
- lex(): PointCoreToken[] {
58
- while (!this.done()) {
59
- const char = this.peek();
60
- if (/\s/.test(char)) {
61
- this.advance();
62
- continue;
63
- }
64
- if (char === "/" && this.peek(1) === "/") {
65
- while (!this.done() && this.peek() !== "\n") this.advance();
66
- continue;
67
- }
68
- if (char === "{") {
69
- this.push("leftBrace", this.advance());
70
- continue;
71
- }
72
- if (char === "}") {
73
- this.push("rightBrace", this.advance());
74
- continue;
75
- }
76
- if (char === "(") {
77
- this.push("leftParen", this.advance());
78
- continue;
79
- }
80
- if (char === "[") {
81
- this.push("leftBracket", this.advance());
82
- continue;
83
- }
84
- if (char === "]") {
85
- this.push("rightBracket", this.advance());
86
- continue;
87
- }
88
- if (char === ")") {
89
- this.push("rightParen", this.advance());
90
- continue;
91
- }
92
- if (char === ",") {
93
- this.push("comma", this.advance());
94
- continue;
95
- }
96
- if (char === ":") {
97
- this.push("colon", this.advance());
98
- continue;
99
- }
100
- if (char === ".") {
101
- this.push("dot", this.advance());
102
- continue;
103
- }
104
- if (char === "=") {
105
- if (this.peek(1) === "=") {
106
- this.push("equalsEquals", `${this.advance()}${this.advance()}`);
107
- continue;
108
- }
109
- this.push("equals", this.advance());
110
- continue;
111
- }
112
- if (char === "!" && this.peek(1) === "=") {
113
- this.push("bangEquals", `${this.advance()}${this.advance()}`);
114
- continue;
115
- }
116
- if (char === "<") {
117
- if (this.peek(1) === "=") {
118
- this.push("lessEquals", `${this.advance()}${this.advance()}`);
119
- continue;
120
- }
121
- this.push("less", this.advance());
122
- continue;
123
- }
124
- if (char === ">") {
125
- if (this.peek(1) === "=") {
126
- this.push("greaterEquals", `${this.advance()}${this.advance()}`);
127
- continue;
128
- }
129
- this.push("greater", this.advance());
130
- continue;
131
- }
132
- if (char === "+") {
133
- if (this.peek(1) === "=") {
134
- this.push("plusEquals", `${this.advance()}${this.advance()}`);
135
- continue;
136
- }
137
- this.push("plus", this.advance());
138
- continue;
139
- }
19
+ | "equalsEquals"
20
+ | "bangEquals"
21
+ | "less"
22
+ | "lessEquals"
23
+ | "greater"
24
+ | "greaterEquals"
25
+ | "plus"
26
+ | "minus"
27
+ | "star"
28
+ | "slash"
29
+ | "eof";
30
+
31
+ export interface PointCoreToken {
32
+ type: PointCoreTokenType;
33
+ value: string;
34
+ span: PointSourceSpan;
35
+ }
36
+
37
+ export class PointCoreLexerError extends Error {
38
+ constructor(message: string, public readonly span: PointSourceSpan) {
39
+ super(`${message} at ${span.start.line}:${span.start.column}`);
40
+ this.name = "PointCoreLexerError";
41
+ }
42
+ }
43
+
44
+ export function lexPointCore(source: string): PointCoreToken[] {
45
+ const lexer = new CoreLexer(source);
46
+ return lexer.lex();
47
+ }
48
+
49
+ class CoreLexer {
50
+ private index = 0;
51
+ private line = 1;
52
+ private column = 1;
53
+ private readonly tokens: PointCoreToken[] = [];
54
+
55
+ constructor(private readonly source: string) {}
56
+
57
+ lex(): PointCoreToken[] {
58
+ while (!this.done()) {
59
+ const char = this.peek();
60
+ if (/\s/.test(char)) {
61
+ this.advance();
62
+ continue;
63
+ }
64
+ if (char === "/" && this.peek(1) === "/") {
65
+ while (!this.done() && this.peek() !== "\n") this.advance();
66
+ continue;
67
+ }
68
+ if (char === "{") {
69
+ this.push("leftBrace", this.advance());
70
+ continue;
71
+ }
72
+ if (char === "}") {
73
+ this.push("rightBrace", this.advance());
74
+ continue;
75
+ }
76
+ if (char === "(") {
77
+ this.push("leftParen", this.advance());
78
+ continue;
79
+ }
80
+ if (char === "[") {
81
+ this.push("leftBracket", this.advance());
82
+ continue;
83
+ }
84
+ if (char === "]") {
85
+ this.push("rightBracket", this.advance());
86
+ continue;
87
+ }
88
+ if (char === ")") {
89
+ this.push("rightParen", this.advance());
90
+ continue;
91
+ }
92
+ if (char === ",") {
93
+ this.push("comma", this.advance());
94
+ continue;
95
+ }
96
+ if (char === ":") {
97
+ this.push("colon", this.advance());
98
+ continue;
99
+ }
100
+ if (char === ".") {
101
+ this.push("dot", this.advance());
102
+ continue;
103
+ }
104
+ if (char === "=") {
105
+ if (this.peek(1) === "=") {
106
+ this.push("equalsEquals", `${this.advance()}${this.advance()}`);
107
+ continue;
108
+ }
109
+ this.push("equals", this.advance());
110
+ continue;
111
+ }
112
+ if (char === "!" && this.peek(1) === "=") {
113
+ this.push("bangEquals", `${this.advance()}${this.advance()}`);
114
+ continue;
115
+ }
116
+ if (char === "<") {
117
+ if (this.peek(1) === "=") {
118
+ this.push("lessEquals", `${this.advance()}${this.advance()}`);
119
+ continue;
120
+ }
121
+ this.push("less", this.advance());
122
+ continue;
123
+ }
124
+ if (char === ">") {
125
+ if (this.peek(1) === "=") {
126
+ this.push("greaterEquals", `${this.advance()}${this.advance()}`);
127
+ continue;
128
+ }
129
+ this.push("greater", this.advance());
130
+ continue;
131
+ }
132
+ if (char === "+") {
133
+ if (this.peek(1) === "=") {
134
+ this.push("plusEquals", `${this.advance()}${this.advance()}`);
135
+ continue;
136
+ }
137
+ this.push("plus", this.advance());
138
+ continue;
139
+ }
140
140
  if (char === "-") {
141
141
  if (this.peek(1) === "=") {
142
142
  this.push("minusEquals", `${this.advance()}${this.advance()}`);
@@ -145,101 +145,101 @@ class CoreLexer {
145
145
  this.push("minus", this.advance());
146
146
  continue;
147
147
  }
148
- if (char === "*") {
149
- this.push("star", this.advance());
150
- continue;
151
- }
152
- if (char === "/") {
153
- this.push("slash", this.advance());
154
- continue;
155
- }
156
- if (char === "\"") {
157
- this.readString();
158
- continue;
159
- }
160
- if (/[0-9]/.test(char)) {
161
- this.readNumber();
162
- continue;
163
- }
164
- if (/[A-Za-z_]/.test(char)) {
165
- this.readIdentifier();
166
- continue;
167
- }
168
- throw new PointCoreLexerError(`Unexpected character ${JSON.stringify(char)}`, this.spanAt());
169
- }
170
- this.tokens.push({ type: "eof", value: "", span: this.spanAt() });
171
- return this.tokens;
172
- }
173
-
174
- private readString() {
175
- const start = this.position();
176
- this.advance();
177
- let value = "";
178
- while (!this.done() && this.peek() !== "\"") {
179
- const next = this.advance();
180
- if (next === "\\") {
181
- const escaped = this.advance();
182
- value += escaped === "n" ? "\n" : escaped;
183
- } else {
184
- value += next;
185
- }
186
- }
187
- if (this.peek() !== "\"") throw new PointCoreLexerError("Unterminated string", { start, end: this.position() });
188
- this.advance();
189
- this.tokens.push({ type: "string", value, span: { start, end: this.position() } });
190
- }
191
-
192
- private readNumber() {
193
- const start = this.position();
194
- let value = "";
195
- while (/[0-9.]/.test(this.peek())) value += this.advance();
196
- this.tokens.push({ type: "number", value, span: { start, end: this.position() } });
197
- }
198
-
199
- private readIdentifier() {
200
- const start = this.position();
201
- let value = "";
202
- while (/[A-Za-z0-9_]/.test(this.peek())) value += this.advance();
203
- this.tokens.push({ type: "identifier", value, span: { start, end: this.position() } });
204
- }
205
-
206
- private push(type: PointCoreTokenType, value: string) {
207
- const end = this.position();
208
- this.tokens.push({
209
- type,
210
- value,
211
- span: {
212
- start: { line: end.line, column: end.column - value.length, offset: end.offset - value.length },
213
- end,
214
- },
215
- });
216
- }
217
-
218
- private advance() {
219
- const char = this.source[this.index++] ?? "";
220
- if (char === "\n") {
221
- this.line += 1;
222
- this.column = 1;
223
- } else {
224
- this.column += 1;
225
- }
226
- return char;
227
- }
228
-
229
- private peek(offset = 0) {
230
- return this.source[this.index + offset] ?? "";
231
- }
232
-
233
- private done() {
234
- return this.index >= this.source.length;
235
- }
236
-
237
- private position() {
238
- return { line: this.line, column: this.column, offset: this.index };
239
- }
240
-
241
- private spanAt(): PointSourceSpan {
242
- const position = this.position();
243
- return { start: position, end: position };
244
- }
245
- }
148
+ if (char === "*") {
149
+ this.push("star", this.advance());
150
+ continue;
151
+ }
152
+ if (char === "/") {
153
+ this.push("slash", this.advance());
154
+ continue;
155
+ }
156
+ if (char === "\"") {
157
+ this.readString();
158
+ continue;
159
+ }
160
+ if (/[0-9]/.test(char)) {
161
+ this.readNumber();
162
+ continue;
163
+ }
164
+ if (/[A-Za-z_]/.test(char)) {
165
+ this.readIdentifier();
166
+ continue;
167
+ }
168
+ throw new PointCoreLexerError(`Unexpected character ${JSON.stringify(char)}`, this.spanAt());
169
+ }
170
+ this.tokens.push({ type: "eof", value: "", span: this.spanAt() });
171
+ return this.tokens;
172
+ }
173
+
174
+ private readString() {
175
+ const start = this.position();
176
+ this.advance();
177
+ let value = "";
178
+ while (!this.done() && this.peek() !== "\"") {
179
+ const next = this.advance();
180
+ if (next === "\\") {
181
+ const escaped = this.advance();
182
+ value += escaped === "n" ? "\n" : escaped;
183
+ } else {
184
+ value += next;
185
+ }
186
+ }
187
+ if (this.peek() !== "\"") throw new PointCoreLexerError("Unterminated string", { start, end: this.position() });
188
+ this.advance();
189
+ this.tokens.push({ type: "string", value, span: { start, end: this.position() } });
190
+ }
191
+
192
+ private readNumber() {
193
+ const start = this.position();
194
+ let value = "";
195
+ while (/[0-9.]/.test(this.peek())) value += this.advance();
196
+ this.tokens.push({ type: "number", value, span: { start, end: this.position() } });
197
+ }
198
+
199
+ private readIdentifier() {
200
+ const start = this.position();
201
+ let value = "";
202
+ while (/[A-Za-z0-9_]/.test(this.peek())) value += this.advance();
203
+ this.tokens.push({ type: "identifier", value, span: { start, end: this.position() } });
204
+ }
205
+
206
+ private push(type: PointCoreTokenType, value: string) {
207
+ const end = this.position();
208
+ this.tokens.push({
209
+ type,
210
+ value,
211
+ span: {
212
+ start: { line: end.line, column: end.column - value.length, offset: end.offset - value.length },
213
+ end,
214
+ },
215
+ });
216
+ }
217
+
218
+ private advance() {
219
+ const char = this.source[this.index++] ?? "";
220
+ if (char === "\n") {
221
+ this.line += 1;
222
+ this.column = 1;
223
+ } else {
224
+ this.column += 1;
225
+ }
226
+ return char;
227
+ }
228
+
229
+ private peek(offset = 0) {
230
+ return this.source[this.index + offset] ?? "";
231
+ }
232
+
233
+ private done() {
234
+ return this.index >= this.source.length;
235
+ }
236
+
237
+ private position() {
238
+ return { line: this.line, column: this.column, offset: this.index };
239
+ }
240
+
241
+ private spanAt(): PointSourceSpan {
242
+ const position = this.position();
243
+ return { start: position, end: position };
244
+ }
245
+ }
@@ -1,26 +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
- }
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
+ }
@@ -1,18 +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
- }
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
+ }