@libraz/coverwise 1.0.0

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.
Files changed (110) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +119 -0
  3. package/README.npm.md +119 -0
  4. package/dist/coverwise.js +2 -0
  5. package/dist/coverwise.wasm +0 -0
  6. package/dist/js/constraint.d.ts +78 -0
  7. package/dist/js/constraint.d.ts.map +1 -0
  8. package/dist/js/constraint.js +213 -0
  9. package/dist/js/constraint.js.map +1 -0
  10. package/dist/js/index.d.ts +94 -0
  11. package/dist/js/index.d.ts.map +1 -0
  12. package/dist/js/index.js +164 -0
  13. package/dist/js/index.js.map +1 -0
  14. package/dist/js/pure/adapter.d.ts +40 -0
  15. package/dist/js/pure/adapter.d.ts.map +1 -0
  16. package/dist/js/pure/adapter.js +207 -0
  17. package/dist/js/pure/adapter.js.map +1 -0
  18. package/dist/js/pure/index.d.ts +83 -0
  19. package/dist/js/pure/index.d.ts.map +1 -0
  20. package/dist/js/pure/index.js +132 -0
  21. package/dist/js/pure/index.js.map +1 -0
  22. package/dist/js/types.d.ts +132 -0
  23. package/dist/js/types.d.ts.map +1 -0
  24. package/dist/js/types.js +3 -0
  25. package/dist/js/types.js.map +1 -0
  26. package/dist/src/ts/algo/greedy.d.ts +9 -0
  27. package/dist/src/ts/algo/greedy.d.ts.map +1 -0
  28. package/dist/src/ts/algo/greedy.js +137 -0
  29. package/dist/src/ts/algo/greedy.js.map +1 -0
  30. package/dist/src/ts/algo/index.d.ts +2 -0
  31. package/dist/src/ts/algo/index.d.ts.map +1 -0
  32. package/dist/src/ts/algo/index.js +2 -0
  33. package/dist/src/ts/algo/index.js.map +1 -0
  34. package/dist/src/ts/core/coverage-engine.d.ts +40 -0
  35. package/dist/src/ts/core/coverage-engine.d.ts.map +1 -0
  36. package/dist/src/ts/core/coverage-engine.js +366 -0
  37. package/dist/src/ts/core/coverage-engine.js.map +1 -0
  38. package/dist/src/ts/core/generator.d.ts +6 -0
  39. package/dist/src/ts/core/generator.d.ts.map +1 -0
  40. package/dist/src/ts/core/generator.js +394 -0
  41. package/dist/src/ts/core/generator.js.map +1 -0
  42. package/dist/src/ts/core/index.d.ts +3 -0
  43. package/dist/src/ts/core/index.d.ts.map +1 -0
  44. package/dist/src/ts/core/index.js +3 -0
  45. package/dist/src/ts/core/index.js.map +1 -0
  46. package/dist/src/ts/model/boundary.d.ts +29 -0
  47. package/dist/src/ts/model/boundary.d.ts.map +1 -0
  48. package/dist/src/ts/model/boundary.js +102 -0
  49. package/dist/src/ts/model/boundary.js.map +1 -0
  50. package/dist/src/ts/model/constraint-ast.d.ts +152 -0
  51. package/dist/src/ts/model/constraint-ast.d.ts.map +1 -0
  52. package/dist/src/ts/model/constraint-ast.js +384 -0
  53. package/dist/src/ts/model/constraint-ast.js.map +1 -0
  54. package/dist/src/ts/model/constraint-parser.d.ts +49 -0
  55. package/dist/src/ts/model/constraint-parser.d.ts.map +1 -0
  56. package/dist/src/ts/model/constraint-parser.js +831 -0
  57. package/dist/src/ts/model/constraint-parser.js.map +1 -0
  58. package/dist/src/ts/model/error.d.ts +19 -0
  59. package/dist/src/ts/model/error.d.ts.map +1 -0
  60. package/dist/src/ts/model/error.js +19 -0
  61. package/dist/src/ts/model/error.js.map +1 -0
  62. package/dist/src/ts/model/generate-options.d.ts +82 -0
  63. package/dist/src/ts/model/generate-options.d.ts.map +1 -0
  64. package/dist/src/ts/model/generate-options.js +52 -0
  65. package/dist/src/ts/model/generate-options.js.map +1 -0
  66. package/dist/src/ts/model/index.d.ts +6 -0
  67. package/dist/src/ts/model/index.d.ts.map +1 -0
  68. package/dist/src/ts/model/index.js +6 -0
  69. package/dist/src/ts/model/index.js.map +1 -0
  70. package/dist/src/ts/model/parameter.d.ts +65 -0
  71. package/dist/src/ts/model/parameter.d.ts.map +1 -0
  72. package/dist/src/ts/model/parameter.js +157 -0
  73. package/dist/src/ts/model/parameter.js.map +1 -0
  74. package/dist/src/ts/model/test-case.d.ts +67 -0
  75. package/dist/src/ts/model/test-case.d.ts.map +1 -0
  76. package/dist/src/ts/model/test-case.js +28 -0
  77. package/dist/src/ts/model/test-case.js.map +1 -0
  78. package/dist/src/ts/util/bitset.d.ts +14 -0
  79. package/dist/src/ts/util/bitset.d.ts.map +1 -0
  80. package/dist/src/ts/util/bitset.js +66 -0
  81. package/dist/src/ts/util/bitset.js.map +1 -0
  82. package/dist/src/ts/util/combinatorics.d.ts +4 -0
  83. package/dist/src/ts/util/combinatorics.d.ts.map +1 -0
  84. package/dist/src/ts/util/combinatorics.js +60 -0
  85. package/dist/src/ts/util/combinatorics.js.map +1 -0
  86. package/dist/src/ts/util/index.d.ts +5 -0
  87. package/dist/src/ts/util/index.d.ts.map +1 -0
  88. package/dist/src/ts/util/index.js +7 -0
  89. package/dist/src/ts/util/index.js.map +1 -0
  90. package/dist/src/ts/util/rng.d.ts +13 -0
  91. package/dist/src/ts/util/rng.d.ts.map +1 -0
  92. package/dist/src/ts/util/rng.js +112 -0
  93. package/dist/src/ts/util/rng.js.map +1 -0
  94. package/dist/src/ts/util/string_util.d.ts +3 -0
  95. package/dist/src/ts/util/string_util.d.ts.map +1 -0
  96. package/dist/src/ts/util/string_util.js +25 -0
  97. package/dist/src/ts/util/string_util.js.map +1 -0
  98. package/dist/src/ts/validator/constraint-validator.d.ts +34 -0
  99. package/dist/src/ts/validator/constraint-validator.d.ts.map +1 -0
  100. package/dist/src/ts/validator/constraint-validator.js +51 -0
  101. package/dist/src/ts/validator/constraint-validator.js.map +1 -0
  102. package/dist/src/ts/validator/coverage-validator.d.ts +42 -0
  103. package/dist/src/ts/validator/coverage-validator.d.ts.map +1 -0
  104. package/dist/src/ts/validator/coverage-validator.js +230 -0
  105. package/dist/src/ts/validator/coverage-validator.js.map +1 -0
  106. package/dist/src/ts/validator/index.d.ts +3 -0
  107. package/dist/src/ts/validator/index.d.ts.map +1 -0
  108. package/dist/src/ts/validator/index.js +3 -0
  109. package/dist/src/ts/validator/index.js.map +1 -0
  110. package/package.json +82 -0
@@ -0,0 +1,831 @@
1
+ /// Parser for human-readable constraint expressions.
2
+ import { AndNode, EqualsNode, IfThenElseNode, ImpliesNode, InNode, LikeNode, NotEqualsNode, NotNode, OrNode, ParamEqualsNode, ParamNotEqualsNode, RelationalNode, RelOp, } from './constraint-ast.js';
3
+ import { ErrorCode, okError } from './error.js';
4
+ const NOT_FOUND = 0xffffffff;
5
+ // --- Token types ---
6
+ var TokenType;
7
+ (function (TokenType) {
8
+ TokenType[TokenType["Identifier"] = 0] = "Identifier";
9
+ TokenType[TokenType["Number"] = 1] = "Number";
10
+ TokenType[TokenType["Equals"] = 2] = "Equals";
11
+ TokenType[TokenType["NotEquals"] = 3] = "NotEquals";
12
+ TokenType[TokenType["Less"] = 4] = "Less";
13
+ TokenType[TokenType["LessEqual"] = 5] = "LessEqual";
14
+ TokenType[TokenType["Greater"] = 6] = "Greater";
15
+ TokenType[TokenType["GreaterEqual"] = 7] = "GreaterEqual";
16
+ TokenType[TokenType["LParen"] = 8] = "LParen";
17
+ TokenType[TokenType["RParen"] = 9] = "RParen";
18
+ TokenType[TokenType["LBrace"] = 10] = "LBrace";
19
+ TokenType[TokenType["RBrace"] = 11] = "RBrace";
20
+ TokenType[TokenType["Comma"] = 12] = "Comma";
21
+ TokenType[TokenType["And"] = 13] = "And";
22
+ TokenType[TokenType["Or"] = 14] = "Or";
23
+ TokenType[TokenType["Not"] = 15] = "Not";
24
+ TokenType[TokenType["If"] = 16] = "If";
25
+ TokenType[TokenType["Then"] = 17] = "Then";
26
+ TokenType[TokenType["Else"] = 18] = "Else";
27
+ TokenType[TokenType["Implies"] = 19] = "Implies";
28
+ TokenType[TokenType["In"] = 20] = "In";
29
+ TokenType[TokenType["Like"] = 21] = "Like";
30
+ TokenType[TokenType["End"] = 22] = "End";
31
+ })(TokenType || (TokenType = {}));
32
+ // --- Tokenizer ---
33
+ function toUpper(s) {
34
+ return s.toUpperCase();
35
+ }
36
+ function isIdentChar(c) {
37
+ const code = c.charCodeAt(0);
38
+ return ((code >= 0x30 && code <= 0x39) || // 0-9
39
+ (code >= 0x41 && code <= 0x5a) || // A-Z
40
+ (code >= 0x61 && code <= 0x7a) || // a-z
41
+ c === '_' ||
42
+ c === '-' ||
43
+ c === '.' ||
44
+ code >= 0x80);
45
+ }
46
+ function isGlobPatternChar(c) {
47
+ return isIdentChar(c) || c === '*' || c === '?' || c === '.';
48
+ }
49
+ function isDigit(c) {
50
+ const code = c.charCodeAt(0);
51
+ return code >= 0x30 && code <= 0x39;
52
+ }
53
+ function isSpace(c) {
54
+ return c === ' ' || c === '\t' || c === '\n' || c === '\r';
55
+ }
56
+ function classifyKeyword(upper) {
57
+ switch (upper) {
58
+ case 'AND':
59
+ return TokenType.And;
60
+ case 'OR':
61
+ return TokenType.Or;
62
+ case 'NOT':
63
+ return TokenType.Not;
64
+ case 'IF':
65
+ return TokenType.If;
66
+ case 'THEN':
67
+ return TokenType.Then;
68
+ case 'ELSE':
69
+ return TokenType.Else;
70
+ case 'IMPLIES':
71
+ return TokenType.Implies;
72
+ case 'IN':
73
+ return TokenType.In;
74
+ case 'LIKE':
75
+ return TokenType.Like;
76
+ default:
77
+ return TokenType.Identifier;
78
+ }
79
+ }
80
+ function tokenize(expr) {
81
+ const tokens = [];
82
+ let i = 0;
83
+ const len = expr.length;
84
+ let expectPattern = false;
85
+ while (i < len) {
86
+ if (isSpace(expr[i])) {
87
+ i++;
88
+ continue;
89
+ }
90
+ const start = i;
91
+ if (expr[i] === '(') {
92
+ tokens.push({ type: TokenType.LParen, text: '(', position: start });
93
+ i++;
94
+ expectPattern = false;
95
+ continue;
96
+ }
97
+ if (expr[i] === ')') {
98
+ tokens.push({ type: TokenType.RParen, text: ')', position: start });
99
+ i++;
100
+ expectPattern = false;
101
+ continue;
102
+ }
103
+ if (expr[i] === '{') {
104
+ tokens.push({ type: TokenType.LBrace, text: '{', position: start });
105
+ i++;
106
+ expectPattern = false;
107
+ continue;
108
+ }
109
+ if (expr[i] === '}') {
110
+ tokens.push({ type: TokenType.RBrace, text: '}', position: start });
111
+ i++;
112
+ expectPattern = false;
113
+ continue;
114
+ }
115
+ if (expr[i] === ',') {
116
+ tokens.push({ type: TokenType.Comma, text: ',', position: start });
117
+ i++;
118
+ expectPattern = false;
119
+ continue;
120
+ }
121
+ if (expr[i] === '!' && i + 1 < len && expr[i + 1] === '=') {
122
+ tokens.push({ type: TokenType.NotEquals, text: '!=', position: start });
123
+ i += 2;
124
+ expectPattern = false;
125
+ continue;
126
+ }
127
+ if (expr[i] === '<' && i + 1 < len && expr[i + 1] === '=') {
128
+ tokens.push({ type: TokenType.LessEqual, text: '<=', position: start });
129
+ i += 2;
130
+ expectPattern = false;
131
+ continue;
132
+ }
133
+ if (expr[i] === '>' && i + 1 < len && expr[i + 1] === '=') {
134
+ tokens.push({ type: TokenType.GreaterEqual, text: '>=', position: start });
135
+ i += 2;
136
+ expectPattern = false;
137
+ continue;
138
+ }
139
+ if (expr[i] === '<') {
140
+ tokens.push({ type: TokenType.Less, text: '<', position: start });
141
+ i++;
142
+ expectPattern = false;
143
+ continue;
144
+ }
145
+ if (expr[i] === '>') {
146
+ tokens.push({ type: TokenType.Greater, text: '>', position: start });
147
+ i++;
148
+ expectPattern = false;
149
+ continue;
150
+ }
151
+ if (expr[i] === '=') {
152
+ tokens.push({ type: TokenType.Equals, text: '=', position: start });
153
+ i++;
154
+ expectPattern = false;
155
+ continue;
156
+ }
157
+ // Negative number: '-' followed by digit, only if preceded by an operator
158
+ if (expr[i] === '-' && i + 1 < len && isDigit(expr[i + 1])) {
159
+ let isNegativeNum = tokens.length === 0;
160
+ if (!isNegativeNum && tokens.length > 0) {
161
+ const prev = tokens[tokens.length - 1].type;
162
+ isNegativeNum =
163
+ prev === TokenType.Equals ||
164
+ prev === TokenType.NotEquals ||
165
+ prev === TokenType.Less ||
166
+ prev === TokenType.LessEqual ||
167
+ prev === TokenType.Greater ||
168
+ prev === TokenType.GreaterEqual;
169
+ }
170
+ if (isNegativeNum) {
171
+ let j = i + 1;
172
+ while (j < len && (isDigit(expr[j]) || expr[j] === '.')) {
173
+ j++;
174
+ }
175
+ const num = expr.substring(i, j);
176
+ tokens.push({ type: TokenType.Number, text: num, position: start });
177
+ i = j;
178
+ expectPattern = false;
179
+ continue;
180
+ }
181
+ }
182
+ // Number literal
183
+ if (isDigit(expr[i])) {
184
+ let j = i;
185
+ while (j < len && (isDigit(expr[j]) || expr[j] === '.')) {
186
+ j++;
187
+ }
188
+ // If followed by identifier chars, it's actually an identifier (e.g., "3d")
189
+ if (j < len && isIdentChar(expr[j]) && expr[j] !== '-') {
190
+ while (j < len && isIdentChar(expr[j])) {
191
+ j++;
192
+ }
193
+ const word = expr.substring(i, j);
194
+ tokens.push({ type: TokenType.Identifier, text: word, position: start });
195
+ }
196
+ else {
197
+ const num = expr.substring(i, j);
198
+ tokens.push({ type: TokenType.Number, text: num, position: start });
199
+ }
200
+ i = j;
201
+ expectPattern = false;
202
+ continue;
203
+ }
204
+ // LIKE pattern: after LIKE keyword, consume pattern with glob chars
205
+ if (expectPattern) {
206
+ let j = i;
207
+ while (j < len && isGlobPatternChar(expr[j])) {
208
+ j++;
209
+ }
210
+ if (j > i) {
211
+ const pattern = expr.substring(i, j);
212
+ tokens.push({ type: TokenType.Identifier, text: pattern, position: start });
213
+ i = j;
214
+ expectPattern = false;
215
+ continue;
216
+ }
217
+ }
218
+ if (isIdentChar(expr[i])) {
219
+ let j = i;
220
+ while (j < len && isIdentChar(expr[j])) {
221
+ j++;
222
+ }
223
+ const word = expr.substring(i, j);
224
+ const upper = toUpper(word);
225
+ const type = classifyKeyword(upper);
226
+ tokens.push({ type, text: word, position: start });
227
+ i = j;
228
+ expectPattern = type === TokenType.Like;
229
+ continue;
230
+ }
231
+ // Handle glob pattern chars at top level
232
+ if (expr[i] === '*' || expr[i] === '?') {
233
+ let j = i;
234
+ while (j < len && isGlobPatternChar(expr[j])) {
235
+ j++;
236
+ }
237
+ const pattern = expr.substring(i, j);
238
+ tokens.push({ type: TokenType.Identifier, text: pattern, position: start });
239
+ i = j;
240
+ expectPattern = false;
241
+ continue;
242
+ }
243
+ return {
244
+ tokens: [],
245
+ error: {
246
+ code: ErrorCode.ConstraintError,
247
+ message: `Unexpected character '${expr[i]}' at position ${start}`,
248
+ detail: '',
249
+ },
250
+ };
251
+ }
252
+ tokens.push({ type: TokenType.End, text: '', position: len });
253
+ return { tokens, error: okError() };
254
+ }
255
+ // --- Name resolution ---
256
+ function namesEqual(a, b, caseSensitive) {
257
+ if (caseSensitive) {
258
+ return a === b;
259
+ }
260
+ return a.toLowerCase() === b.toLowerCase();
261
+ }
262
+ function resolveParam(paramName, params, caseSensitive) {
263
+ for (let i = 0; i < params.length; i++) {
264
+ if (namesEqual(params[i].name, paramName, caseSensitive)) {
265
+ return { paramIndex: i, error: okError() };
266
+ }
267
+ }
268
+ const available = params.map((p) => p.name).join(', ');
269
+ return {
270
+ paramIndex: 0,
271
+ error: {
272
+ code: ErrorCode.ConstraintError,
273
+ message: `Unknown parameter '${paramName}'`,
274
+ detail: `Available parameters: ${available}`,
275
+ },
276
+ };
277
+ }
278
+ function resolveComparison(paramName, valueName, params, caseSensitive) {
279
+ const rp = resolveParam(paramName, params, caseSensitive);
280
+ if (rp.error.code !== ErrorCode.Ok) {
281
+ return { paramIndex: 0, valueIndex: 0, error: rp.error };
282
+ }
283
+ const paramIdx = rp.paramIndex;
284
+ const valIdx = params[paramIdx].findValueIndex(valueName, caseSensitive);
285
+ if (valIdx === NOT_FOUND) {
286
+ const available = params[paramIdx].values.join(', ');
287
+ return {
288
+ paramIndex: 0,
289
+ valueIndex: 0,
290
+ error: {
291
+ code: ErrorCode.ConstraintError,
292
+ message: `Unknown value '${valueName}' for parameter '${paramName}'`,
293
+ detail: `Available values: ${available}`,
294
+ },
295
+ };
296
+ }
297
+ return { paramIndex: paramIdx, valueIndex: valIdx, error: okError() };
298
+ }
299
+ function resolveValue(paramIndex, valueName, params, caseSensitive) {
300
+ const idx = params[paramIndex].findValueIndex(valueName, caseSensitive);
301
+ if (idx !== NOT_FOUND) {
302
+ return { valueIndex: idx, error: okError() };
303
+ }
304
+ const available = params[paramIndex].values.join(', ');
305
+ return {
306
+ valueIndex: 0,
307
+ error: {
308
+ code: ErrorCode.ConstraintError,
309
+ message: `Unknown value '${valueName}' for parameter '${params[paramIndex].name}'`,
310
+ detail: `Available values: ${available}`,
311
+ },
312
+ };
313
+ }
314
+ function isParameterName(name, params, caseSensitive) {
315
+ for (const p of params) {
316
+ if (namesEqual(p.name, name, caseSensitive)) {
317
+ return true;
318
+ }
319
+ }
320
+ return false;
321
+ }
322
+ function isValueOfParam(paramIndex, name, params, caseSensitive) {
323
+ return params[paramIndex].findValueIndex(name, caseSensitive) !== NOT_FOUND;
324
+ }
325
+ function isNumericString(s) {
326
+ if (s.length === 0) {
327
+ return false;
328
+ }
329
+ const n = Number(s);
330
+ return !Number.isNaN(n) && Number.isFinite(n);
331
+ }
332
+ // --- Recursive descent parser ---
333
+ class Parser {
334
+ constructor(tokens, params, options) {
335
+ this.tokens = tokens;
336
+ this.params = params;
337
+ this.options = options;
338
+ this.pos = 0;
339
+ }
340
+ parse() {
341
+ const result = this.parseExpression();
342
+ if (result.error.code !== ErrorCode.Ok) {
343
+ return result;
344
+ }
345
+ if (this.current().type !== TokenType.End) {
346
+ return {
347
+ constraint: null,
348
+ error: {
349
+ code: ErrorCode.ConstraintError,
350
+ message: `Unexpected token '${this.current().text}' at position ${this.current().position}`,
351
+ detail: 'Expected end of expression',
352
+ },
353
+ };
354
+ }
355
+ return result;
356
+ }
357
+ current() {
358
+ return this.tokens[this.pos];
359
+ }
360
+ advance() {
361
+ const tok = this.tokens[this.pos];
362
+ if (this.pos + 1 < this.tokens.length) {
363
+ this.pos++;
364
+ }
365
+ return tok;
366
+ }
367
+ match(type) {
368
+ if (this.current().type === type) {
369
+ this.advance();
370
+ return true;
371
+ }
372
+ return false;
373
+ }
374
+ parseExpression() {
375
+ return this.parseImpliesExpr();
376
+ }
377
+ parseImpliesExpr() {
378
+ if (this.current().type === TokenType.If) {
379
+ this.advance();
380
+ const antecedent = this.parseOrExpr();
381
+ if (antecedent.error.code !== ErrorCode.Ok) {
382
+ return antecedent;
383
+ }
384
+ if (this.current().type !== TokenType.Then) {
385
+ return {
386
+ constraint: null,
387
+ error: {
388
+ code: ErrorCode.ConstraintError,
389
+ message: `Expected 'THEN' after 'IF' clause at position ${this.current().position}`,
390
+ detail: 'Syntax: IF <condition> THEN <condition>',
391
+ },
392
+ };
393
+ }
394
+ this.advance();
395
+ const consequent = this.parseOrExpr();
396
+ if (consequent.error.code !== ErrorCode.Ok) {
397
+ return consequent;
398
+ }
399
+ // Check for optional ELSE clause
400
+ if (this.current().type === TokenType.Else) {
401
+ this.advance();
402
+ const elseBranch = this.parseOrExpr();
403
+ if (elseBranch.error.code !== ErrorCode.Ok) {
404
+ return elseBranch;
405
+ }
406
+ return {
407
+ constraint: new IfThenElseNode(antecedent.constraint, consequent.constraint, elseBranch.constraint),
408
+ error: okError(),
409
+ };
410
+ }
411
+ return {
412
+ constraint: new ImpliesNode(antecedent.constraint, consequent.constraint),
413
+ error: okError(),
414
+ };
415
+ }
416
+ const left = this.parseOrExpr();
417
+ if (left.error.code !== ErrorCode.Ok) {
418
+ return left;
419
+ }
420
+ if (this.match(TokenType.Implies)) {
421
+ const right = this.parseOrExpr();
422
+ if (right.error.code !== ErrorCode.Ok) {
423
+ return right;
424
+ }
425
+ return {
426
+ constraint: new ImpliesNode(left.constraint, right.constraint),
427
+ error: okError(),
428
+ };
429
+ }
430
+ return left;
431
+ }
432
+ parseOrExpr() {
433
+ let left = this.parseAndExpr();
434
+ if (left.error.code !== ErrorCode.Ok) {
435
+ return left;
436
+ }
437
+ while (this.match(TokenType.Or)) {
438
+ const right = this.parseAndExpr();
439
+ if (right.error.code !== ErrorCode.Ok) {
440
+ return right;
441
+ }
442
+ left = {
443
+ constraint: new OrNode(left.constraint, right.constraint),
444
+ error: okError(),
445
+ };
446
+ }
447
+ return left;
448
+ }
449
+ parseAndExpr() {
450
+ let left = this.parseUnaryExpr();
451
+ if (left.error.code !== ErrorCode.Ok) {
452
+ return left;
453
+ }
454
+ while (this.match(TokenType.And)) {
455
+ const right = this.parseUnaryExpr();
456
+ if (right.error.code !== ErrorCode.Ok) {
457
+ return right;
458
+ }
459
+ left = {
460
+ constraint: new AndNode(left.constraint, right.constraint),
461
+ error: okError(),
462
+ };
463
+ }
464
+ return left;
465
+ }
466
+ parseUnaryExpr() {
467
+ if (this.match(TokenType.Not)) {
468
+ const child = this.parseUnaryExpr();
469
+ if (child.error.code !== ErrorCode.Ok) {
470
+ return child;
471
+ }
472
+ return {
473
+ constraint: new NotNode(child.constraint),
474
+ error: okError(),
475
+ };
476
+ }
477
+ return this.parseAtom();
478
+ }
479
+ isComparisonOp(type) {
480
+ return (type === TokenType.Equals ||
481
+ type === TokenType.NotEquals ||
482
+ type === TokenType.Less ||
483
+ type === TokenType.LessEqual ||
484
+ type === TokenType.Greater ||
485
+ type === TokenType.GreaterEqual);
486
+ }
487
+ parseAtom() {
488
+ if (this.match(TokenType.LParen)) {
489
+ const inner = this.parseExpression();
490
+ if (inner.error.code !== ErrorCode.Ok) {
491
+ return inner;
492
+ }
493
+ if (!this.match(TokenType.RParen)) {
494
+ return {
495
+ constraint: null,
496
+ error: {
497
+ code: ErrorCode.ConstraintError,
498
+ message: `Expected ')' at position ${this.current().position}`,
499
+ detail: 'Mismatched parentheses',
500
+ },
501
+ };
502
+ }
503
+ return inner;
504
+ }
505
+ if (this.current().type === TokenType.Identifier) {
506
+ const paramTok = this.advance();
507
+ // IN operator: ident IN { val1, val2, ... }
508
+ if (this.current().type === TokenType.In) {
509
+ return this.parseInExpr(paramTok);
510
+ }
511
+ // LIKE operator: ident LIKE pattern
512
+ if (this.current().type === TokenType.Like) {
513
+ return this.parseLikeExpr(paramTok);
514
+ }
515
+ if (!this.isComparisonOp(this.current().type)) {
516
+ return {
517
+ constraint: null,
518
+ error: {
519
+ code: ErrorCode.ConstraintError,
520
+ message: `Expected operator after '${paramTok.text}' at position ${this.current().position}`,
521
+ detail: 'Syntax: parameter=value, parameter!=value, parameter>value, ' +
522
+ 'parameter IN {values}, or parameter LIKE pattern',
523
+ },
524
+ };
525
+ }
526
+ const opType = this.current().type;
527
+ this.advance();
528
+ // Relational operators (>, >=, <, <=) with number or param
529
+ if (opType === TokenType.Less ||
530
+ opType === TokenType.LessEqual ||
531
+ opType === TokenType.Greater ||
532
+ opType === TokenType.GreaterEqual) {
533
+ return this.parseRelationalRhs(paramTok, opType);
534
+ }
535
+ // = or != with identifier, number, or param-to-param
536
+ if (this.current().type !== TokenType.Identifier &&
537
+ this.current().type !== TokenType.Number) {
538
+ return {
539
+ constraint: null,
540
+ error: {
541
+ code: ErrorCode.ConstraintError,
542
+ message: `Expected value after operator at position ${this.current().position}`,
543
+ detail: 'Syntax: parameter=value or parameter!=value',
544
+ },
545
+ };
546
+ }
547
+ const valueTok = this.advance();
548
+ const isEquals = opType === TokenType.Equals;
549
+ // Resolve left parameter
550
+ const rp = resolveParam(paramTok.text, this.params, this.options.caseSensitive);
551
+ if (rp.error.code !== ErrorCode.Ok) {
552
+ return { constraint: null, error: rp.error };
553
+ }
554
+ const leftParam = rp.paramIndex;
555
+ // Determine if RHS is a value of the left param or a parameter name
556
+ const rhsIsValue = isValueOfParam(leftParam, valueTok.text, this.params, this.options.caseSensitive);
557
+ const rhsIsParam = isParameterName(valueTok.text, this.params, this.options.caseSensitive);
558
+ // If it's a value of the left param, prefer param=value interpretation
559
+ if (rhsIsValue) {
560
+ const rv = resolveValue(leftParam, valueTok.text, this.params, this.options.caseSensitive);
561
+ if (rv.error.code !== ErrorCode.Ok) {
562
+ return { constraint: null, error: rv.error };
563
+ }
564
+ if (isEquals) {
565
+ return {
566
+ constraint: new EqualsNode(leftParam, rv.valueIndex),
567
+ error: okError(),
568
+ };
569
+ }
570
+ return {
571
+ constraint: new NotEqualsNode(leftParam, rv.valueIndex),
572
+ error: okError(),
573
+ };
574
+ }
575
+ // If it's a parameter name, do param-to-param comparison
576
+ if (rhsIsParam) {
577
+ const rp2 = resolveParam(valueTok.text, this.params, this.options.caseSensitive);
578
+ if (rp2.error.code !== ErrorCode.Ok) {
579
+ return { constraint: null, error: rp2.error };
580
+ }
581
+ if (isEquals) {
582
+ return {
583
+ constraint: new ParamEqualsNode(leftParam, rp2.paramIndex, this.params[leftParam].values, this.params[rp2.paramIndex].values),
584
+ error: okError(),
585
+ };
586
+ }
587
+ return {
588
+ constraint: new ParamNotEqualsNode(leftParam, rp2.paramIndex, this.params[leftParam].values, this.params[rp2.paramIndex].values),
589
+ error: okError(),
590
+ };
591
+ }
592
+ // Neither a value nor a parameter -- error
593
+ const resolved = resolveComparison(paramTok.text, valueTok.text, this.params, this.options.caseSensitive);
594
+ return { constraint: null, error: resolved.error };
595
+ }
596
+ if (this.current().type === TokenType.End) {
597
+ return {
598
+ constraint: null,
599
+ error: {
600
+ code: ErrorCode.ConstraintError,
601
+ message: 'Unexpected end of expression',
602
+ detail: "Expected a comparison or '('",
603
+ },
604
+ };
605
+ }
606
+ return {
607
+ constraint: null,
608
+ error: {
609
+ code: ErrorCode.ConstraintError,
610
+ message: `Unexpected token '${this.current().text}' at position ${this.current().position}`,
611
+ detail: "Expected a comparison (e.g. param=value) or '('",
612
+ },
613
+ };
614
+ }
615
+ parseInExpr(paramTok) {
616
+ this.advance(); // consume IN
617
+ const rp = resolveParam(paramTok.text, this.params, this.options.caseSensitive);
618
+ if (rp.error.code !== ErrorCode.Ok) {
619
+ return { constraint: null, error: rp.error };
620
+ }
621
+ const paramIdx = rp.paramIndex;
622
+ if (!this.match(TokenType.LBrace)) {
623
+ return {
624
+ constraint: null,
625
+ error: {
626
+ code: ErrorCode.ConstraintError,
627
+ message: `Expected '{' after 'IN' at position ${this.current().position}`,
628
+ detail: 'Syntax: parameter IN {value1, value2, ...}',
629
+ },
630
+ };
631
+ }
632
+ const valueIndices = [];
633
+ // Parse first value
634
+ if (this.current().type !== TokenType.Identifier && this.current().type !== TokenType.Number) {
635
+ return {
636
+ constraint: null,
637
+ error: {
638
+ code: ErrorCode.ConstraintError,
639
+ message: `Expected value in set at position ${this.current().position}`,
640
+ detail: 'Syntax: parameter IN {value1, value2, ...}',
641
+ },
642
+ };
643
+ }
644
+ {
645
+ const valTok = this.advance();
646
+ const rv = resolveValue(paramIdx, valTok.text, this.params, this.options.caseSensitive);
647
+ if (rv.error.code !== ErrorCode.Ok) {
648
+ return { constraint: null, error: rv.error };
649
+ }
650
+ valueIndices.push(rv.valueIndex);
651
+ }
652
+ // Parse remaining values
653
+ while (this.match(TokenType.Comma)) {
654
+ if (this.current().type !== TokenType.Identifier &&
655
+ this.current().type !== TokenType.Number) {
656
+ return {
657
+ constraint: null,
658
+ error: {
659
+ code: ErrorCode.ConstraintError,
660
+ message: `Expected value after ',' at position ${this.current().position}`,
661
+ detail: 'Syntax: parameter IN {value1, value2, ...}',
662
+ },
663
+ };
664
+ }
665
+ const valTok = this.advance();
666
+ const rv = resolveValue(paramIdx, valTok.text, this.params, this.options.caseSensitive);
667
+ if (rv.error.code !== ErrorCode.Ok) {
668
+ return { constraint: null, error: rv.error };
669
+ }
670
+ valueIndices.push(rv.valueIndex);
671
+ }
672
+ if (!this.match(TokenType.RBrace)) {
673
+ return {
674
+ constraint: null,
675
+ error: {
676
+ code: ErrorCode.ConstraintError,
677
+ message: `Expected '}' at position ${this.current().position}`,
678
+ detail: 'Syntax: parameter IN {value1, value2, ...}',
679
+ },
680
+ };
681
+ }
682
+ return {
683
+ constraint: new InNode(paramIdx, valueIndices),
684
+ error: okError(),
685
+ };
686
+ }
687
+ parseLikeExpr(paramTok) {
688
+ this.advance(); // consume LIKE
689
+ const rp = resolveParam(paramTok.text, this.params, this.options.caseSensitive);
690
+ if (rp.error.code !== ErrorCode.Ok) {
691
+ return { constraint: null, error: rp.error };
692
+ }
693
+ const paramIdx = rp.paramIndex;
694
+ if (this.current().type !== TokenType.Identifier && this.current().type !== TokenType.Number) {
695
+ return {
696
+ constraint: null,
697
+ error: {
698
+ code: ErrorCode.ConstraintError,
699
+ message: `Expected pattern after 'LIKE' at position ${this.current().position}`,
700
+ detail: 'Syntax: parameter LIKE pattern (wildcards: * = any string, ? = single char)',
701
+ },
702
+ };
703
+ }
704
+ const patternTok = this.advance();
705
+ return {
706
+ constraint: new LikeNode(paramIdx, patternTok.text, this.params[paramIdx].values),
707
+ error: okError(),
708
+ };
709
+ }
710
+ parseRelationalRhs(paramTok, opType) {
711
+ let op;
712
+ switch (opType) {
713
+ case TokenType.Less:
714
+ op = RelOp.Less;
715
+ break;
716
+ case TokenType.LessEqual:
717
+ op = RelOp.LessEqual;
718
+ break;
719
+ case TokenType.Greater:
720
+ op = RelOp.Greater;
721
+ break;
722
+ case TokenType.GreaterEqual:
723
+ op = RelOp.GreaterEqual;
724
+ break;
725
+ default:
726
+ return {
727
+ constraint: null,
728
+ error: {
729
+ code: ErrorCode.ConstraintError,
730
+ message: 'Internal parser error',
731
+ detail: 'Unexpected operator type',
732
+ },
733
+ };
734
+ }
735
+ const rp = resolveParam(paramTok.text, this.params, this.options.caseSensitive);
736
+ if (rp.error.code !== ErrorCode.Ok) {
737
+ return { constraint: null, error: rp.error };
738
+ }
739
+ const leftParam = rp.paramIndex;
740
+ if (this.current().type === TokenType.Number) {
741
+ const numTok = this.advance();
742
+ const literal = Number.parseFloat(numTok.text);
743
+ return {
744
+ constraint: RelationalNode.fromLiteral(leftParam, op, literal, this.params[leftParam].values),
745
+ error: okError(),
746
+ };
747
+ }
748
+ if (this.current().type === TokenType.Identifier) {
749
+ const rhsTok = this.advance();
750
+ // Check if RHS is a parameter name
751
+ if (isParameterName(rhsTok.text, this.params, this.options.caseSensitive)) {
752
+ const rp2 = resolveParam(rhsTok.text, this.params, this.options.caseSensitive);
753
+ if (rp2.error.code !== ErrorCode.Ok) {
754
+ return { constraint: null, error: rp2.error };
755
+ }
756
+ return {
757
+ constraint: RelationalNode.fromParams(leftParam, op, rp2.paramIndex, this.params[leftParam].values, this.params[rp2.paramIndex].values),
758
+ error: okError(),
759
+ };
760
+ }
761
+ // Try parsing as a number
762
+ if (isNumericString(rhsTok.text)) {
763
+ const literal = Number.parseFloat(rhsTok.text);
764
+ return {
765
+ constraint: RelationalNode.fromLiteral(leftParam, op, literal, this.params[leftParam].values),
766
+ error: okError(),
767
+ };
768
+ }
769
+ return {
770
+ constraint: null,
771
+ error: {
772
+ code: ErrorCode.ConstraintError,
773
+ message: `Expected number or parameter after relational operator at position ${rhsTok.position}`,
774
+ detail: 'Relational operators (>, >=, <, <=) require numeric values or parameter names',
775
+ },
776
+ };
777
+ }
778
+ return {
779
+ constraint: null,
780
+ error: {
781
+ code: ErrorCode.ConstraintError,
782
+ message: `Expected value after relational operator at position ${this.current().position}`,
783
+ detail: 'Syntax: parameter > number or parameter > parameter',
784
+ },
785
+ };
786
+ }
787
+ }
788
+ /**
789
+ * Parse a human-readable constraint expression into an AST.
790
+ *
791
+ * Supported syntax examples:
792
+ * "IF os=mac THEN browser!=ie"
793
+ * "IF os=mac THEN browser!=ie ELSE arch!=arm"
794
+ * "NOT (os=win AND browser=safari)"
795
+ * "os=linux IMPLIES arch!=arm"
796
+ * "os=win OR browser=chrome"
797
+ * "NOT os=linux"
798
+ * "version > 3"
799
+ * "env IN {staging, prod}"
800
+ * "browser LIKE chrome*"
801
+ * "start_date < end_date" (parameter-to-parameter comparison)
802
+ *
803
+ * Keywords (case-insensitive): IF, THEN, ELSE, IMPLIES, AND, OR, NOT, IN, LIKE
804
+ * Operators: = != > >= < <=
805
+ * Parentheses: ( )
806
+ * Set literals: { value1, value2, ... }
807
+ *
808
+ * @param expression The constraint string to parse.
809
+ * @param params The parameter definitions (used to resolve names to indices).
810
+ * @param options Parsing options (e.g., case sensitivity). Defaults to case-insensitive.
811
+ * @returns ParseResult with the AST on success, or an error on failure.
812
+ */
813
+ export function parseConstraint(expression, params, options = { caseSensitive: false }) {
814
+ if (expression.length === 0) {
815
+ return {
816
+ constraint: null,
817
+ error: {
818
+ code: ErrorCode.ConstraintError,
819
+ message: 'Empty constraint expression',
820
+ detail: 'Provide a non-empty constraint string',
821
+ },
822
+ };
823
+ }
824
+ const tokResult = tokenize(expression);
825
+ if (tokResult.error.code !== ErrorCode.Ok) {
826
+ return { constraint: null, error: tokResult.error };
827
+ }
828
+ const parser = new Parser(tokResult.tokens, params, options);
829
+ return parser.parse();
830
+ }
831
+ //# sourceMappingURL=constraint-parser.js.map