@atomic-ehr/fhirpath 0.0.1-canary.0c6931e.20250727185306
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 +473 -0
- package/dist/index.d.ts +462 -0
- package/dist/index.js +10307 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
- package/src/analyzer/analyzer.ts +499 -0
- package/src/analyzer/model-provider.ts +244 -0
- package/src/analyzer/schemas/index.ts +2 -0
- package/src/analyzer/schemas/types.ts +40 -0
- package/src/analyzer/types.ts +142 -0
- package/src/api/builder.ts +157 -0
- package/src/api/errors.ts +145 -0
- package/src/api/expression.ts +156 -0
- package/src/api/index.ts +122 -0
- package/src/api/inspect.ts +99 -0
- package/src/api/registry.ts +128 -0
- package/src/api/types.ts +210 -0
- package/src/compiler/compiler.ts +546 -0
- package/src/compiler/index.ts +2 -0
- package/src/compiler/prototype-context-adapter.ts +99 -0
- package/src/compiler/types.ts +24 -0
- package/src/index.ts +107 -0
- package/src/interpreter/README.md +78 -0
- package/src/interpreter/interpreter.ts +475 -0
- package/src/interpreter/types.ts +108 -0
- package/src/lexer/char-tables.ts +37 -0
- package/src/lexer/errors.ts +31 -0
- package/src/lexer/index.ts +5 -0
- package/src/lexer/lexer.ts +745 -0
- package/src/lexer/token.ts +104 -0
- package/src/lexer2/index.md +232 -0
- package/src/lexer2/index.perf.test.ts +68 -0
- package/src/lexer2/index.test.ts +549 -0
- package/src/lexer2/index.ts +1251 -0
- package/src/lexer2/notes.md +173 -0
- package/src/lexer2/optimization-summary.md +718 -0
- package/src/parser/ast-factory.ts +220 -0
- package/src/parser/ast.ts +144 -0
- package/src/parser/collection-parser.ts +89 -0
- package/src/parser/diagnostic-messages.ts +216 -0
- package/src/parser/diagnostics.ts +85 -0
- package/src/parser/error-reporter.ts +230 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/literal-parser.ts +103 -0
- package/src/parser/parse-error.ts +16 -0
- package/src/parser/parser-error-factory.ts +141 -0
- package/src/parser/parser-state.ts +134 -0
- package/src/parser/parser.ts +1272 -0
- package/src/parser/pprint.ts +169 -0
- package/src/parser/precedence-manager.ts +64 -0
- package/src/parser/source-mapper.ts +248 -0
- package/src/parser/special-constructs.ts +142 -0
- package/src/parser/token-navigator.ts +110 -0
- package/src/parser/types.ts +60 -0
- package/src/parser2/index.md +177 -0
- package/src/parser2/index.perf.test.ts +184 -0
- package/src/parser2/index.test.ts +305 -0
- package/src/parser2/index.ts +578 -0
- package/src/parser2/optimization-summary.md +176 -0
- package/src/registry/default-analyzers.ts +257 -0
- package/src/registry/default-compilers.ts +31 -0
- package/src/registry/index.ts +96 -0
- package/src/registry/operations/arithmetic.ts +506 -0
- package/src/registry/operations/collection.ts +425 -0
- package/src/registry/operations/comparison.ts +432 -0
- package/src/registry/operations/existence.ts +703 -0
- package/src/registry/operations/filtering.ts +358 -0
- package/src/registry/operations/literals.ts +341 -0
- package/src/registry/operations/logical.ts +439 -0
- package/src/registry/operations/math.ts +128 -0
- package/src/registry/operations/membership.ts +132 -0
- package/src/registry/operations/navigation.ts +52 -0
- package/src/registry/operations/string.ts +507 -0
- package/src/registry/operations/subsetting.ts +174 -0
- package/src/registry/operations/type-checking.ts +162 -0
- package/src/registry/operations/type-conversion.ts +404 -0
- package/src/registry/operations/type-operators.ts +308 -0
- package/src/registry/operations/utility.ts +644 -0
- package/src/registry/registry.ts +146 -0
- package/src/registry/types.ts +161 -0
- package/src/registry/utils/evaluation-helpers.ts +93 -0
- package/src/registry/utils/index.ts +3 -0
- package/src/registry/utils/type-system.ts +173 -0
- package/src/runtime/context.ts +158 -0
- package/src/runtime/debug-context.ts +135 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
import { Lexer, TokenType } from '../lexer2';
|
|
2
|
+
import type { Token } from '../lexer2';
|
|
3
|
+
|
|
4
|
+
// AST Types
|
|
5
|
+
export interface Position {
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
offset: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export enum NodeType {
|
|
12
|
+
// Navigation
|
|
13
|
+
Identifier,
|
|
14
|
+
TypeOrIdentifier,
|
|
15
|
+
|
|
16
|
+
// Operators
|
|
17
|
+
Binary,
|
|
18
|
+
Unary,
|
|
19
|
+
Union,
|
|
20
|
+
|
|
21
|
+
// Functions
|
|
22
|
+
Function,
|
|
23
|
+
|
|
24
|
+
// Literals
|
|
25
|
+
Literal,
|
|
26
|
+
Variable,
|
|
27
|
+
Collection,
|
|
28
|
+
|
|
29
|
+
// Type operations
|
|
30
|
+
MembershipTest,
|
|
31
|
+
TypeCast,
|
|
32
|
+
TypeReference,
|
|
33
|
+
|
|
34
|
+
// Special
|
|
35
|
+
Index,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ASTNode {
|
|
39
|
+
type: NodeType;
|
|
40
|
+
position: Position;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IdentifierNode extends ASTNode {
|
|
44
|
+
type: NodeType.Identifier;
|
|
45
|
+
name: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface TypeOrIdentifierNode extends ASTNode {
|
|
49
|
+
type: NodeType.TypeOrIdentifier;
|
|
50
|
+
name: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LiteralNode extends ASTNode {
|
|
54
|
+
type: NodeType.Literal;
|
|
55
|
+
value: any;
|
|
56
|
+
valueType: 'string' | 'number' | 'boolean' | 'date' | 'time' | 'datetime' | 'null';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface BinaryNode extends ASTNode {
|
|
60
|
+
type: NodeType.Binary;
|
|
61
|
+
operator: TokenType;
|
|
62
|
+
left: ASTNode;
|
|
63
|
+
right: ASTNode;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface UnaryNode extends ASTNode {
|
|
67
|
+
type: NodeType.Unary;
|
|
68
|
+
operator: TokenType;
|
|
69
|
+
operand: ASTNode;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FunctionNode extends ASTNode {
|
|
73
|
+
type: NodeType.Function;
|
|
74
|
+
name: ASTNode;
|
|
75
|
+
arguments: ASTNode[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface VariableNode extends ASTNode {
|
|
79
|
+
type: NodeType.Variable;
|
|
80
|
+
name: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface IndexNode extends ASTNode {
|
|
84
|
+
type: NodeType.Index;
|
|
85
|
+
expression: ASTNode;
|
|
86
|
+
index: ASTNode;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface UnionNode extends ASTNode {
|
|
90
|
+
type: NodeType.Union;
|
|
91
|
+
operands: ASTNode[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface MembershipTestNode extends ASTNode {
|
|
95
|
+
type: NodeType.MembershipTest;
|
|
96
|
+
expression: ASTNode;
|
|
97
|
+
targetType: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface TypeCastNode extends ASTNode {
|
|
101
|
+
type: NodeType.TypeCast;
|
|
102
|
+
expression: ASTNode;
|
|
103
|
+
targetType: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface CollectionNode extends ASTNode {
|
|
107
|
+
type: NodeType.Collection;
|
|
108
|
+
elements: ASTNode[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface TypeReferenceNode extends ASTNode {
|
|
112
|
+
type: NodeType.TypeReference;
|
|
113
|
+
typeName: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class Parser {
|
|
117
|
+
private lexer: Lexer;
|
|
118
|
+
private tokens: Token[] = [];
|
|
119
|
+
private current = 0;
|
|
120
|
+
|
|
121
|
+
constructor(input: string) {
|
|
122
|
+
this.lexer = new Lexer(input);
|
|
123
|
+
this.tokens = this.lexer.tokenize();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
parse(): ASTNode {
|
|
127
|
+
const expr = this.expression();
|
|
128
|
+
if (!this.isAtEnd()) {
|
|
129
|
+
throw new Error(`Unexpected token: ${this.lexer.getTokenValue(this.peek())}`);
|
|
130
|
+
}
|
|
131
|
+
return expr;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private expression(): ASTNode {
|
|
135
|
+
return this.parseExpressionWithPrecedence(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private parseExpressionWithPrecedence(minPrecedence: number): ASTNode {
|
|
139
|
+
let left = this.parsePrimary();
|
|
140
|
+
|
|
141
|
+
// Inline isAtEnd() and peek() for hot path
|
|
142
|
+
while (this.current < this.tokens.length) {
|
|
143
|
+
const token = this.tokens[this.current];
|
|
144
|
+
if (!token || token.type === TokenType.EOF) break;
|
|
145
|
+
|
|
146
|
+
const precedence = this.getPrecedence(token.type);
|
|
147
|
+
|
|
148
|
+
if (precedence < minPrecedence) break;
|
|
149
|
+
|
|
150
|
+
if (token.type === TokenType.DOT) {
|
|
151
|
+
this.current++; // inline advance()
|
|
152
|
+
const right = this.parseInvocation();
|
|
153
|
+
left = this.createBinaryNode(token, left, right);
|
|
154
|
+
} else if (this.isBinaryOperator(token.type)) {
|
|
155
|
+
this.current++; // inline advance()
|
|
156
|
+
const associativity = this.getAssociativity(token.type);
|
|
157
|
+
const nextMinPrecedence = associativity === 'left' ? precedence + 1 : precedence;
|
|
158
|
+
const right = this.parseExpressionWithPrecedence(nextMinPrecedence);
|
|
159
|
+
|
|
160
|
+
if (token.type === TokenType.PIPE && left.type === NodeType.Union) {
|
|
161
|
+
(left as UnionNode).operands.push(right);
|
|
162
|
+
} else if (token.type === TokenType.PIPE) {
|
|
163
|
+
left = this.createUnionNode([left, right], this.getPosition(token));
|
|
164
|
+
} else {
|
|
165
|
+
left = this.createBinaryNode(token, left, right);
|
|
166
|
+
}
|
|
167
|
+
} else if (token.type === TokenType.IS) {
|
|
168
|
+
this.current++; // inline advance()
|
|
169
|
+
const typeName = this.parseTypeName();
|
|
170
|
+
left = this.createMembershipTestNode(left, typeName, this.getPosition(token));
|
|
171
|
+
} else if (token.type === TokenType.AS) {
|
|
172
|
+
this.current++; // inline advance()
|
|
173
|
+
const typeName = this.parseTypeName();
|
|
174
|
+
left = this.createTypeCastNode(left, typeName, this.getPosition(token));
|
|
175
|
+
} else if (token.type === TokenType.LBRACKET) {
|
|
176
|
+
this.current++; // inline advance()
|
|
177
|
+
const index = this.expression();
|
|
178
|
+
this.consume(TokenType.RBRACKET, "Expected ']'");
|
|
179
|
+
left = this.createIndexNode(left, index, this.getPosition(token));
|
|
180
|
+
} else if (token.type === TokenType.LPAREN && this.isFunctionCall(left)) {
|
|
181
|
+
const parenToken = token;
|
|
182
|
+
this.current++; // inline advance()
|
|
183
|
+
const args = this.parseArgumentList();
|
|
184
|
+
this.consume(TokenType.RPAREN, "Expected ')'");
|
|
185
|
+
left = this.createFunctionNode(left, args, left.position);
|
|
186
|
+
} else {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return left;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private parsePrimary(): ASTNode {
|
|
195
|
+
// Inline peek() for hot path
|
|
196
|
+
const token = this.current < this.tokens.length ? this.tokens[this.current] : { type: TokenType.EOF, start: 0, end: 0, line: 1, column: 1 };
|
|
197
|
+
|
|
198
|
+
if (token.type === TokenType.NUMBER) {
|
|
199
|
+
this.current++; // inline advance()
|
|
200
|
+
return this.createLiteralNode(parseFloat(this.lexer.getTokenValue(token)), 'number', token);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (token.type === TokenType.STRING) {
|
|
204
|
+
this.current++; // inline advance()
|
|
205
|
+
const value = this.parseStringValue(this.lexer.getTokenValue(token));
|
|
206
|
+
return this.createLiteralNode(value, 'string', token);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (token.type === TokenType.TRUE || token.type === TokenType.FALSE) {
|
|
210
|
+
this.advance();
|
|
211
|
+
return this.createLiteralNode(token.type === TokenType.TRUE, 'boolean', token);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (token.type === TokenType.NULL || (token.type === TokenType.IDENTIFIER && this.lexer.getTokenValue(token) === 'null')) {
|
|
215
|
+
this.advance();
|
|
216
|
+
return this.createLiteralNode(null, 'null', token);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (token.type === TokenType.DATETIME) {
|
|
220
|
+
this.advance();
|
|
221
|
+
const value = this.lexer.getTokenValue(token).substring(1); // Remove @
|
|
222
|
+
return this.createLiteralNode(value, 'datetime', token);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (token.type === TokenType.TIME) {
|
|
226
|
+
this.advance();
|
|
227
|
+
const value = this.lexer.getTokenValue(token).substring(1); // Remove @
|
|
228
|
+
return this.createLiteralNode(value, 'time', token);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (token.type === TokenType.THIS || token.type === TokenType.INDEX || token.type === TokenType.TOTAL) {
|
|
232
|
+
this.advance();
|
|
233
|
+
return this.createVariableNode(this.lexer.getTokenValue(token), token);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (token.type === TokenType.ENV_VAR) {
|
|
237
|
+
this.advance();
|
|
238
|
+
const value = this.lexer.getTokenValue(token);
|
|
239
|
+
return this.createVariableNode(value, token);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (token.type === TokenType.IDENTIFIER ||
|
|
243
|
+
token.type === TokenType.DELIMITED_IDENTIFIER ||
|
|
244
|
+
this.isKeywordAllowedAsIdentifier(token.type)) {
|
|
245
|
+
this.advance();
|
|
246
|
+
const name = this.parseIdentifierValue(this.lexer.getTokenValue(token));
|
|
247
|
+
return this.createIdentifierNode(name, token);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (token.type === TokenType.LPAREN) {
|
|
251
|
+
this.advance();
|
|
252
|
+
const expr = this.expression();
|
|
253
|
+
this.consume(TokenType.RPAREN, "Expected ')'");
|
|
254
|
+
return expr;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (token.type === TokenType.LBRACE) {
|
|
258
|
+
this.advance();
|
|
259
|
+
const elements = this.parseCollectionElements();
|
|
260
|
+
this.consume(TokenType.RBRACE, "Expected '}'");
|
|
261
|
+
return this.createCollectionNode(elements, this.getPosition(token));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (token.type === TokenType.PLUS || token.type === TokenType.MINUS) {
|
|
265
|
+
this.advance();
|
|
266
|
+
const operand = this.parseExpressionWithPrecedence(this.getPrecedence(TokenType.MULTIPLY));
|
|
267
|
+
return this.createUnaryNode(token, operand);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
throw new Error(`Unexpected token: ${this.lexer.getTokenValue(token)}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private parseInvocation(): ASTNode {
|
|
274
|
+
const token = this.peek();
|
|
275
|
+
|
|
276
|
+
// Allow identifiers and keywords that can be used as member names
|
|
277
|
+
if (token.type === TokenType.IDENTIFIER ||
|
|
278
|
+
token.type === TokenType.DELIMITED_IDENTIFIER ||
|
|
279
|
+
this.isKeywordAllowedAsMember(token.type)) {
|
|
280
|
+
this.advance();
|
|
281
|
+
const name = this.parseIdentifierValue(this.lexer.getTokenValue(token));
|
|
282
|
+
const node = this.createIdentifierNode(name, token);
|
|
283
|
+
|
|
284
|
+
// Check if this is a function call
|
|
285
|
+
if (this.check(TokenType.LPAREN)) {
|
|
286
|
+
this.advance();
|
|
287
|
+
const args = this.parseArgumentList();
|
|
288
|
+
this.consume(TokenType.RPAREN, "Expected ')'");
|
|
289
|
+
return this.createFunctionNode(node, args, node.position);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return node;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Allow environment variables after dot (like .%resource)
|
|
296
|
+
if (token.type === TokenType.ENV_VAR) {
|
|
297
|
+
this.advance();
|
|
298
|
+
const value = this.lexer.getTokenValue(token);
|
|
299
|
+
return this.createVariableNode(value, token);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
throw new Error(`Expected identifier after '.', got: ${this.lexer.getTokenValue(token)}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private isKeywordAllowedAsMember(type: TokenType): boolean {
|
|
306
|
+
// Keywords that can be used as member names
|
|
307
|
+
return [
|
|
308
|
+
TokenType.CONTAINS,
|
|
309
|
+
TokenType.AND,
|
|
310
|
+
TokenType.OR,
|
|
311
|
+
TokenType.XOR,
|
|
312
|
+
TokenType.IMPLIES,
|
|
313
|
+
TokenType.AS,
|
|
314
|
+
TokenType.IS,
|
|
315
|
+
TokenType.DIV,
|
|
316
|
+
TokenType.MOD,
|
|
317
|
+
TokenType.IN,
|
|
318
|
+
TokenType.TRUE,
|
|
319
|
+
TokenType.FALSE
|
|
320
|
+
].includes(type);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private isKeywordAllowedAsIdentifier(type: TokenType): boolean {
|
|
324
|
+
// Keywords that can be used as identifiers in certain contexts
|
|
325
|
+
return this.isKeywordAllowedAsMember(type);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private parseArgumentList(): ASTNode[] {
|
|
329
|
+
const args: ASTNode[] = [];
|
|
330
|
+
|
|
331
|
+
if (this.peek().type === TokenType.RPAREN) {
|
|
332
|
+
return args;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
args.push(this.expression());
|
|
336
|
+
|
|
337
|
+
while (this.match(TokenType.COMMA)) {
|
|
338
|
+
args.push(this.expression());
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return args;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private parseCollectionElements(): ASTNode[] {
|
|
345
|
+
const elements: ASTNode[] = [];
|
|
346
|
+
|
|
347
|
+
if (this.peek().type === TokenType.RBRACE) {
|
|
348
|
+
return elements;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
elements.push(this.expression());
|
|
352
|
+
|
|
353
|
+
while (this.match(TokenType.COMMA)) {
|
|
354
|
+
elements.push(this.expression());
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return elements;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private parseTypeName(): string {
|
|
361
|
+
const token = this.advance();
|
|
362
|
+
if (token.type !== TokenType.IDENTIFIER && token.type !== TokenType.DELIMITED_IDENTIFIER) {
|
|
363
|
+
throw new Error(`Expected type name, got: ${this.lexer.getTokenValue(token)}`);
|
|
364
|
+
}
|
|
365
|
+
return this.parseIdentifierValue(this.lexer.getTokenValue(token));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private parseStringValue(raw: string): string {
|
|
369
|
+
// Remove quotes and handle escape sequences
|
|
370
|
+
const content = raw.slice(1, -1);
|
|
371
|
+
return content.replace(/\\(.)/g, (_, char) => {
|
|
372
|
+
switch (char) {
|
|
373
|
+
case 'n': return '\n';
|
|
374
|
+
case 'r': return '\r';
|
|
375
|
+
case 't': return '\t';
|
|
376
|
+
case 'f': return '\f';
|
|
377
|
+
case '\\': return '\\';
|
|
378
|
+
case "'": return "'";
|
|
379
|
+
case '"': return '"';
|
|
380
|
+
case '`': return '`';
|
|
381
|
+
case '/': return '/';
|
|
382
|
+
default: return char;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private parseIdentifierValue(raw: string): string {
|
|
388
|
+
if (raw.startsWith('`')) {
|
|
389
|
+
// Delimited identifier - remove backticks and handle escapes
|
|
390
|
+
return raw.slice(1, -1).replace(/\\(.)/g, '$1');
|
|
391
|
+
}
|
|
392
|
+
return raw;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private isFunctionCall(node: ASTNode): boolean {
|
|
396
|
+
return node.type === NodeType.Identifier || node.type === NodeType.TypeOrIdentifier;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private isBinaryOperator(type: TokenType): boolean {
|
|
400
|
+
return [
|
|
401
|
+
TokenType.PLUS, TokenType.MINUS, TokenType.MULTIPLY, TokenType.DIVIDE,
|
|
402
|
+
TokenType.DIV, TokenType.MOD, TokenType.AMPERSAND, TokenType.PIPE,
|
|
403
|
+
TokenType.LT, TokenType.GT, TokenType.LTE, TokenType.GTE,
|
|
404
|
+
TokenType.EQ, TokenType.NEQ, TokenType.SIMILAR, TokenType.NOT_SIMILAR,
|
|
405
|
+
TokenType.AND, TokenType.OR, TokenType.XOR, TokenType.IMPLIES,
|
|
406
|
+
TokenType.IN, TokenType.CONTAINS
|
|
407
|
+
].includes(type);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private getPrecedence(type: TokenType): number {
|
|
411
|
+
// Extract precedence from high byte using bit shift
|
|
412
|
+
return type >>> 8;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private getAssociativity(type: TokenType): 'left' | 'right' {
|
|
416
|
+
// Most operators are left associative
|
|
417
|
+
// Only implies is right associative
|
|
418
|
+
return type === TokenType.IMPLIES ? 'right' : 'left';
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private getPosition(token: Token): Position {
|
|
422
|
+
return {
|
|
423
|
+
line: token.line,
|
|
424
|
+
column: token.column,
|
|
425
|
+
offset: token.start
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private createIdentifierNode(name: string, token: Token): ASTNode {
|
|
430
|
+
const position = this.getPosition(token);
|
|
431
|
+
|
|
432
|
+
// Check if it's a type (starts with uppercase)
|
|
433
|
+
if (name[0] && name[0] >= 'A' && name[0] <= 'Z') {
|
|
434
|
+
return {
|
|
435
|
+
type: NodeType.TypeOrIdentifier,
|
|
436
|
+
name,
|
|
437
|
+
position
|
|
438
|
+
} as TypeOrIdentifierNode;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
type: NodeType.Identifier,
|
|
443
|
+
name,
|
|
444
|
+
position
|
|
445
|
+
} as IdentifierNode;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private createLiteralNode(value: any, valueType: LiteralNode['valueType'], token: Token): LiteralNode {
|
|
449
|
+
return {
|
|
450
|
+
type: NodeType.Literal,
|
|
451
|
+
value,
|
|
452
|
+
valueType,
|
|
453
|
+
position: this.getPosition(token)
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private createBinaryNode(token: Token, left: ASTNode, right: ASTNode): BinaryNode {
|
|
458
|
+
return {
|
|
459
|
+
type: NodeType.Binary,
|
|
460
|
+
operator: token.type,
|
|
461
|
+
left,
|
|
462
|
+
right,
|
|
463
|
+
position: this.getPosition(token)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private createUnaryNode(token: Token, operand: ASTNode): UnaryNode {
|
|
468
|
+
return {
|
|
469
|
+
type: NodeType.Unary,
|
|
470
|
+
operator: token.type,
|
|
471
|
+
operand,
|
|
472
|
+
position: this.getPosition(token)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private createFunctionNode(name: ASTNode, args: ASTNode[], position: Position): FunctionNode {
|
|
477
|
+
return {
|
|
478
|
+
type: NodeType.Function,
|
|
479
|
+
name,
|
|
480
|
+
arguments: args,
|
|
481
|
+
position
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private createVariableNode(name: string, token: Token): VariableNode {
|
|
486
|
+
return {
|
|
487
|
+
type: NodeType.Variable,
|
|
488
|
+
name,
|
|
489
|
+
position: this.getPosition(token)
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private createIndexNode(expression: ASTNode, index: ASTNode, position: Position): IndexNode {
|
|
494
|
+
return {
|
|
495
|
+
type: NodeType.Index,
|
|
496
|
+
expression,
|
|
497
|
+
index,
|
|
498
|
+
position
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private createUnionNode(operands: ASTNode[], position: Position): UnionNode {
|
|
503
|
+
return {
|
|
504
|
+
type: NodeType.Union,
|
|
505
|
+
operands,
|
|
506
|
+
position
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private createMembershipTestNode(expression: ASTNode, targetType: string, position: Position): MembershipTestNode {
|
|
511
|
+
return {
|
|
512
|
+
type: NodeType.MembershipTest,
|
|
513
|
+
expression,
|
|
514
|
+
targetType,
|
|
515
|
+
position
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private createTypeCastNode(expression: ASTNode, targetType: string, position: Position): TypeCastNode {
|
|
520
|
+
return {
|
|
521
|
+
type: NodeType.TypeCast,
|
|
522
|
+
expression,
|
|
523
|
+
targetType,
|
|
524
|
+
position
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private createCollectionNode(elements: ASTNode[], position: Position): CollectionNode {
|
|
529
|
+
return {
|
|
530
|
+
type: NodeType.Collection,
|
|
531
|
+
elements,
|
|
532
|
+
position
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Helper methods
|
|
537
|
+
private peek(): Token {
|
|
538
|
+
return this.tokens[this.current] || { type: TokenType.EOF, start: 0, end: 0, line: 1, column: 1 };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private previous(): Token {
|
|
542
|
+
return this.tokens[this.current - 1] || { type: TokenType.EOF, start: 0, end: 0, line: 1, column: 1 };
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private isAtEnd(): boolean {
|
|
546
|
+
return this.current >= this.tokens.length || this.peek().type === TokenType.EOF;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private advance(): Token {
|
|
550
|
+
if (!this.isAtEnd()) this.current++;
|
|
551
|
+
return this.previous();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private check(type: TokenType): boolean {
|
|
555
|
+
if (this.isAtEnd()) return false;
|
|
556
|
+
return this.peek().type === type;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private match(...types: TokenType[]): boolean {
|
|
560
|
+
for (const type of types) {
|
|
561
|
+
if (this.check(type)) {
|
|
562
|
+
this.advance();
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private consume(type: TokenType, message: string): Token {
|
|
570
|
+
if (this.check(type)) return this.advance();
|
|
571
|
+
throw new Error(message + ` at token: ${this.lexer.getTokenValue(this.peek())}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export function parse(input: string): ASTNode {
|
|
576
|
+
const parser = new Parser(input);
|
|
577
|
+
return parser.parse();
|
|
578
|
+
}
|