@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/LICENSE +21 -21
- package/README.md +34 -34
- package/package.json +34 -30
- package/src/cli.ts +7 -7
- package/src/core/ast.ts +162 -162
- package/src/core/check.ts +412 -412
- package/src/core/cli.ts +497 -497
- package/src/core/context.ts +181 -181
- package/src/core/emit-javascript.ts +124 -124
- package/src/core/emit-typescript.ts +166 -166
- package/src/core/format.ts +6 -6
- package/src/core/incremental.ts +53 -53
- package/src/core/index.ts +12 -12
- package/src/core/lexer.ts +234 -234
- package/src/core/semantic-source.ts +26 -26
- package/src/core/serialize.ts +18 -18
- package/src/core/test-only/core-text-parser.ts +400 -400
- package/src/core/test-only/format-core.ts +120 -120
- package/src/core/test-only/index.ts +3 -3
- package/src/core/test-only/legacy-lowering.ts +1030 -1030
- package/src/index.ts +1 -1
- package/src/semantic/ast.ts +230 -230
- package/src/semantic/callables.ts +51 -51
- package/src/semantic/context.ts +347 -347
- package/src/semantic/desugar.ts +665 -665
- package/src/semantic/expressions.ts +347 -347
- package/src/semantic/format.ts +222 -222
- package/src/semantic/index.ts +10 -10
- package/src/semantic/metadata.ts +37 -37
- package/src/semantic/naming.ts +33 -33
- package/src/semantic/parse.ts +945 -945
- package/src/semantic/serialize.ts +18 -18
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
|
+
}
|
package/src/core/serialize.ts
CHANGED
|
@@ -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
|
+
}
|