@pattern-algebra/core 0.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 (154) hide show
  1. package/README.md +571 -0
  2. package/dist/automaton/complement.d.ts +20 -0
  3. package/dist/automaton/complement.d.ts.map +1 -0
  4. package/dist/automaton/complement.js +36 -0
  5. package/dist/automaton/complement.js.map +1 -0
  6. package/dist/automaton/complement.test.d.ts +2 -0
  7. package/dist/automaton/complement.test.d.ts.map +1 -0
  8. package/dist/automaton/complement.test.js +114 -0
  9. package/dist/automaton/complement.test.js.map +1 -0
  10. package/dist/automaton/determinize.d.ts +41 -0
  11. package/dist/automaton/determinize.d.ts.map +1 -0
  12. package/dist/automaton/determinize.js +310 -0
  13. package/dist/automaton/determinize.js.map +1 -0
  14. package/dist/automaton/determinize.test.d.ts +2 -0
  15. package/dist/automaton/determinize.test.d.ts.map +1 -0
  16. package/dist/automaton/determinize.test.js +134 -0
  17. package/dist/automaton/determinize.test.js.map +1 -0
  18. package/dist/automaton/emptiness.d.ts +41 -0
  19. package/dist/automaton/emptiness.d.ts.map +1 -0
  20. package/dist/automaton/emptiness.js +262 -0
  21. package/dist/automaton/emptiness.js.map +1 -0
  22. package/dist/automaton/emptiness.test.d.ts +2 -0
  23. package/dist/automaton/emptiness.test.d.ts.map +1 -0
  24. package/dist/automaton/emptiness.test.js +154 -0
  25. package/dist/automaton/emptiness.test.js.map +1 -0
  26. package/dist/automaton/index.d.ts +10 -0
  27. package/dist/automaton/index.d.ts.map +1 -0
  28. package/dist/automaton/index.js +11 -0
  29. package/dist/automaton/index.js.map +1 -0
  30. package/dist/automaton/intersect.d.ts +35 -0
  31. package/dist/automaton/intersect.d.ts.map +1 -0
  32. package/dist/automaton/intersect.js +302 -0
  33. package/dist/automaton/intersect.js.map +1 -0
  34. package/dist/automaton/pattern-algebra.d.ts +62 -0
  35. package/dist/automaton/pattern-algebra.d.ts.map +1 -0
  36. package/dist/automaton/pattern-algebra.js +309 -0
  37. package/dist/automaton/pattern-algebra.js.map +1 -0
  38. package/dist/automaton/pattern-algebra.test.d.ts +2 -0
  39. package/dist/automaton/pattern-algebra.test.d.ts.map +1 -0
  40. package/dist/automaton/pattern-algebra.test.js +223 -0
  41. package/dist/automaton/pattern-algebra.test.js.map +1 -0
  42. package/dist/compile/automaton-builder.d.ts +47 -0
  43. package/dist/compile/automaton-builder.d.ts.map +1 -0
  44. package/dist/compile/automaton-builder.js +211 -0
  45. package/dist/compile/automaton-builder.js.map +1 -0
  46. package/dist/compile/compiler.d.ts +32 -0
  47. package/dist/compile/compiler.d.ts.map +1 -0
  48. package/dist/compile/compiler.js +47 -0
  49. package/dist/compile/compiler.js.map +1 -0
  50. package/dist/compile/index.d.ts +8 -0
  51. package/dist/compile/index.d.ts.map +1 -0
  52. package/dist/compile/index.js +8 -0
  53. package/dist/compile/index.js.map +1 -0
  54. package/dist/compile/quick-reject.d.ts +28 -0
  55. package/dist/compile/quick-reject.d.ts.map +1 -0
  56. package/dist/compile/quick-reject.js +147 -0
  57. package/dist/compile/quick-reject.js.map +1 -0
  58. package/dist/containment/analysis.d.ts +60 -0
  59. package/dist/containment/analysis.d.ts.map +1 -0
  60. package/dist/containment/analysis.js +378 -0
  61. package/dist/containment/analysis.js.map +1 -0
  62. package/dist/containment/containment.d.ts +23 -0
  63. package/dist/containment/containment.d.ts.map +1 -0
  64. package/dist/containment/containment.js +681 -0
  65. package/dist/containment/containment.js.map +1 -0
  66. package/dist/containment/containment.test.d.ts +2 -0
  67. package/dist/containment/containment.test.d.ts.map +1 -0
  68. package/dist/containment/containment.test.js +209 -0
  69. package/dist/containment/containment.test.js.map +1 -0
  70. package/dist/containment/index.d.ts +7 -0
  71. package/dist/containment/index.d.ts.map +1 -0
  72. package/dist/containment/index.js +7 -0
  73. package/dist/containment/index.js.map +1 -0
  74. package/dist/core-alpha.d.ts +1253 -0
  75. package/dist/core-beta.d.ts +1253 -0
  76. package/dist/core-public.d.ts +1253 -0
  77. package/dist/core-unstripped.d.ts +1253 -0
  78. package/dist/index.d.ts +32 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +49 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/match/index.d.ts +8 -0
  83. package/dist/match/index.d.ts.map +1 -0
  84. package/dist/match/index.js +8 -0
  85. package/dist/match/index.js.map +1 -0
  86. package/dist/match/matcher.d.ts +40 -0
  87. package/dist/match/matcher.d.ts.map +1 -0
  88. package/dist/match/matcher.js +256 -0
  89. package/dist/match/matcher.js.map +1 -0
  90. package/dist/match/matcher.test.d.ts +2 -0
  91. package/dist/match/matcher.test.d.ts.map +1 -0
  92. package/dist/match/matcher.test.js +185 -0
  93. package/dist/match/matcher.test.js.map +1 -0
  94. package/dist/match/path-utils.d.ts +132 -0
  95. package/dist/match/path-utils.d.ts.map +1 -0
  96. package/dist/match/path-utils.js +223 -0
  97. package/dist/match/path-utils.js.map +1 -0
  98. package/dist/match/path-utils.test.d.ts +2 -0
  99. package/dist/match/path-utils.test.d.ts.map +1 -0
  100. package/dist/match/path-utils.test.js +193 -0
  101. package/dist/match/path-utils.test.js.map +1 -0
  102. package/dist/match/segment-matcher.d.ts +25 -0
  103. package/dist/match/segment-matcher.d.ts.map +1 -0
  104. package/dist/match/segment-matcher.js +267 -0
  105. package/dist/match/segment-matcher.js.map +1 -0
  106. package/dist/parse/brace-expansion.d.ts +34 -0
  107. package/dist/parse/brace-expansion.d.ts.map +1 -0
  108. package/dist/parse/brace-expansion.js +294 -0
  109. package/dist/parse/brace-expansion.js.map +1 -0
  110. package/dist/parse/brace-expansion.test.d.ts +2 -0
  111. package/dist/parse/brace-expansion.test.d.ts.map +1 -0
  112. package/dist/parse/brace-expansion.test.js +105 -0
  113. package/dist/parse/brace-expansion.test.js.map +1 -0
  114. package/dist/parse/index.d.ts +8 -0
  115. package/dist/parse/index.d.ts.map +1 -0
  116. package/dist/parse/index.js +8 -0
  117. package/dist/parse/index.js.map +1 -0
  118. package/dist/parse/parser.d.ts +15 -0
  119. package/dist/parse/parser.d.ts.map +1 -0
  120. package/dist/parse/parser.js +526 -0
  121. package/dist/parse/parser.js.map +1 -0
  122. package/dist/parse/parser.test.d.ts +2 -0
  123. package/dist/parse/parser.test.d.ts.map +1 -0
  124. package/dist/parse/parser.test.js +266 -0
  125. package/dist/parse/parser.test.js.map +1 -0
  126. package/dist/parse/validator.d.ts +30 -0
  127. package/dist/parse/validator.d.ts.map +1 -0
  128. package/dist/parse/validator.js +115 -0
  129. package/dist/parse/validator.js.map +1 -0
  130. package/dist/parse/validator.test.d.ts +2 -0
  131. package/dist/parse/validator.test.d.ts.map +1 -0
  132. package/dist/parse/validator.test.js +45 -0
  133. package/dist/parse/validator.test.js.map +1 -0
  134. package/dist/types/ast.d.ts +158 -0
  135. package/dist/types/ast.d.ts.map +1 -0
  136. package/dist/types/ast.js +2 -0
  137. package/dist/types/ast.js.map +1 -0
  138. package/dist/types/automaton.d.ts +150 -0
  139. package/dist/types/automaton.d.ts.map +1 -0
  140. package/dist/types/automaton.js +2 -0
  141. package/dist/types/automaton.js.map +1 -0
  142. package/dist/types/containment.d.ts +257 -0
  143. package/dist/types/containment.d.ts.map +1 -0
  144. package/dist/types/containment.js +5 -0
  145. package/dist/types/containment.js.map +1 -0
  146. package/dist/types/errors.d.ts +37 -0
  147. package/dist/types/errors.d.ts.map +1 -0
  148. package/dist/types/errors.js +24 -0
  149. package/dist/types/errors.js.map +1 -0
  150. package/dist/types/index.d.ts +10 -0
  151. package/dist/types/index.d.ts.map +1 -0
  152. package/dist/types/index.js +6 -0
  153. package/dist/types/index.js.map +1 -0
  154. package/package.json +48 -0
@@ -0,0 +1,681 @@
1
+ /**
2
+ * Pattern containment checking.
3
+ * @packageDocumentation
4
+ */
5
+ import { matchPath } from '../match/matcher';
6
+ /**
7
+ * Check if pattern A is contained within pattern B.
8
+ *
9
+ * A ⊆ B means: every path that matches A also matches B.
10
+ *
11
+ * Uses a hybrid approach:
12
+ * 1. Structural analysis for quick checks
13
+ * 2. Sample-based testing for validation
14
+ * 3. Automaton operations for complex cases (when available)
15
+ *
16
+ * @param a - First compiled pattern
17
+ * @param b - Second compiled pattern
18
+ * @returns Containment result with explanation data
19
+ *
20
+ * @public
21
+ */
22
+ export function checkContainment(a, b) {
23
+ // Use structural analysis for containment checking
24
+ const { isSubset, isSuperset, counterexample, reverseCounterexample } = checkContainmentStructural(a, b);
25
+ const isEqual = isSubset && isSuperset;
26
+ const hasOverlap = checkHasOverlap(a, b);
27
+ let relationship;
28
+ if (isEqual) {
29
+ relationship = 'equal';
30
+ }
31
+ else if (isSubset) {
32
+ relationship = 'subset';
33
+ }
34
+ else if (isSuperset) {
35
+ relationship = 'superset';
36
+ }
37
+ else if (!hasOverlap) {
38
+ relationship = 'disjoint';
39
+ }
40
+ else {
41
+ relationship = 'overlapping';
42
+ }
43
+ // Build explanation
44
+ const explanation = buildExplanation(a, b, relationship, counterexample, reverseCounterexample);
45
+ return {
46
+ patternA: a.source,
47
+ patternB: b.source,
48
+ isSubset,
49
+ isSuperset,
50
+ isEqual,
51
+ hasOverlap,
52
+ relationship,
53
+ counterexample,
54
+ reverseCounterexample,
55
+ explanation,
56
+ };
57
+ }
58
+ /**
59
+ * Structural containment check.
60
+ */
61
+ function checkContainmentStructural(a, b) {
62
+ // Generate test paths from pattern A and check if they match B
63
+ const aPaths = generateTestPaths(a, 20);
64
+ const bPaths = generateTestPaths(b, 20);
65
+ let aSubsetB = true;
66
+ let bSubsetA = true;
67
+ let counterexample;
68
+ let reverseCounterexample;
69
+ // Check if all A paths match B
70
+ for (const path of aPaths) {
71
+ if (!matchPath(path, b)) {
72
+ aSubsetB = false;
73
+ counterexample = path;
74
+ break;
75
+ }
76
+ }
77
+ // Check if all B paths match A
78
+ for (const path of bPaths) {
79
+ if (!matchPath(path, a)) {
80
+ bSubsetA = false;
81
+ reverseCounterexample = path;
82
+ break;
83
+ }
84
+ }
85
+ // Additional structural checks
86
+ if (aSubsetB) {
87
+ // Verify with depth analysis
88
+ if (a.isUnbounded && !b.isUnbounded) {
89
+ // A can go deeper than B allows
90
+ aSubsetB = false;
91
+ counterexample = generateDeepPath(a, (b.maxSegments ?? 0) + 1);
92
+ }
93
+ }
94
+ if (bSubsetA) {
95
+ if (b.isUnbounded && !a.isUnbounded) {
96
+ bSubsetA = false;
97
+ reverseCounterexample = generateDeepPath(b, (a.maxSegments ?? 0) + 1);
98
+ }
99
+ }
100
+ return {
101
+ isSubset: aSubsetB,
102
+ isSuperset: bSubsetA,
103
+ counterexample,
104
+ reverseCounterexample,
105
+ };
106
+ }
107
+ /**
108
+ * Check if patterns have any overlap.
109
+ */
110
+ function checkHasOverlap(a, b) {
111
+ // Generate paths from A and check if they match B
112
+ const aPaths = generateTestPaths(a, 10);
113
+ for (const path of aPaths) {
114
+ if (matchPath(path, b)) {
115
+ return true;
116
+ }
117
+ }
118
+ // Generate paths from B and check if they match A
119
+ const bPaths = generateTestPaths(b, 10);
120
+ for (const path of bPaths) {
121
+ if (matchPath(path, a)) {
122
+ return true;
123
+ }
124
+ }
125
+ // Try to generate paths that combine constraints from both patterns
126
+ const combinedPaths = generateCombinedPaths(a, b, 10);
127
+ for (const path of combinedPaths) {
128
+ if (matchPath(path, a) && matchPath(path, b)) {
129
+ return true;
130
+ }
131
+ }
132
+ return false;
133
+ }
134
+ /**
135
+ * Generate paths that might match both patterns by combining their constraints.
136
+ */
137
+ function generateCombinedPaths(a, b, count) {
138
+ const paths = [];
139
+ // Extract constraints from both patterns, removing leading slashes
140
+ const aPrefix = a.quickReject.requiredPrefix?.replace(/^\//, '') ?? '';
141
+ const bPrefix = b.quickReject.requiredPrefix?.replace(/^\//, '') ?? '';
142
+ const aSuffix = a.quickReject.requiredSuffix?.replace(/^\//, '') ?? '';
143
+ const bSuffix = b.quickReject.requiredSuffix?.replace(/^\//, '') ?? '';
144
+ // Extract file extensions and base names from suffixes
145
+ const extractFileInfo = (suffix) => {
146
+ if (!suffix)
147
+ return { basename: '', ext: '' };
148
+ const lastDot = suffix.lastIndexOf('.');
149
+ if (lastDot > 0) {
150
+ return { basename: suffix.slice(0, lastDot), ext: suffix.slice(lastDot) };
151
+ }
152
+ return { basename: suffix, ext: '' };
153
+ };
154
+ // Build candidate paths
155
+ const prefixes = [aPrefix, bPrefix].filter(Boolean);
156
+ if (prefixes.length === 0)
157
+ prefixes.push('');
158
+ // If we have suffixes like "index.ts", extract the components
159
+ const suffixInfoA = extractFileInfo(aSuffix);
160
+ const suffixInfoB = extractFileInfo(bSuffix);
161
+ // File basenames to try
162
+ const basenames = [suffixInfoA.basename || 'index', suffixInfoB.basename || 'index', 'file', 'test'].filter(Boolean);
163
+ // Extensions to try
164
+ const extensions = [suffixInfoA.ext, suffixInfoB.ext, '.ts', '.js'].filter(Boolean);
165
+ for (const prefix of prefixes) {
166
+ for (const basename of basenames) {
167
+ for (const ext of extensions) {
168
+ // Build path: /prefix/basename.ext
169
+ const filename = basename + ext;
170
+ const path = prefix ? `/${prefix}/${filename}` : `/${filename}`;
171
+ if (!paths.includes(path)) {
172
+ paths.push(path);
173
+ }
174
+ // Also try with subdirectory
175
+ if (prefix) {
176
+ const deepPath = `/${prefix}/sub/${filename}`;
177
+ if (!paths.includes(deepPath)) {
178
+ paths.push(deepPath);
179
+ }
180
+ }
181
+ if (paths.length >= count)
182
+ break;
183
+ }
184
+ if (paths.length >= count)
185
+ break;
186
+ }
187
+ if (paths.length >= count)
188
+ break;
189
+ }
190
+ return paths.slice(0, count);
191
+ }
192
+ /**
193
+ * Generate test paths that match a pattern.
194
+ */
195
+ function generateTestPaths(pattern, count) {
196
+ const paths = [];
197
+ const ast = pattern.ast;
198
+ if (ast.root.type === 'alternation') {
199
+ // Generate paths for each branch
200
+ for (const branch of ast.root.branches) {
201
+ paths.push(...generatePathsFromSequence(branch, Math.ceil(count / ast.root.branches.length)));
202
+ }
203
+ }
204
+ else {
205
+ paths.push(...generatePathsFromSequence(ast.root, count));
206
+ }
207
+ return paths.slice(0, count);
208
+ }
209
+ /**
210
+ * Generate paths from a segment sequence.
211
+ */
212
+ function generatePathsFromSequence(sequence, count) {
213
+ const paths = [];
214
+ // Generate base path
215
+ const basePath = generateBasePath(sequence.segments);
216
+ paths.push(basePath);
217
+ // Generate variations
218
+ for (let i = 1; i < count && paths.length < count; i++) {
219
+ const variation = generatePathVariation(sequence.segments, i);
220
+ if (variation && !paths.includes(variation)) {
221
+ paths.push(variation);
222
+ }
223
+ }
224
+ return paths;
225
+ }
226
+ /**
227
+ * Generate a base path from segments.
228
+ */
229
+ function generateBasePath(segments) {
230
+ const parts = [];
231
+ for (const segment of segments) {
232
+ parts.push(generateSegmentValue(segment, 0));
233
+ }
234
+ return '/' + parts.join('/');
235
+ }
236
+ /**
237
+ * Generate a path variation.
238
+ */
239
+ function generatePathVariation(segments, variationIndex) {
240
+ const parts = [];
241
+ let usedVariation = false;
242
+ for (let i = 0; i < segments.length; i++) {
243
+ const segment = segments[i];
244
+ if (segment.type === 'globstar' && !usedVariation) {
245
+ // For globstar, always add at least one segment (globstar requires at least 1)
246
+ // Then optionally add more based on variation
247
+ const extraCount = 1 + (variationIndex % 3);
248
+ for (let j = 0; j < extraCount; j++) {
249
+ parts.push(`dir${j}`);
250
+ }
251
+ usedVariation = true;
252
+ }
253
+ else {
254
+ parts.push(generateSegmentValue(segment, usedVariation ? 0 : variationIndex));
255
+ }
256
+ }
257
+ return '/' + parts.join('/');
258
+ }
259
+ /**
260
+ * Generate a value that matches a segment pattern.
261
+ */
262
+ function generateSegmentValue(segment, variation) {
263
+ switch (segment.type) {
264
+ case 'literal':
265
+ return segment.value;
266
+ case 'globstar':
267
+ return 'subdir';
268
+ case 'wildcard': {
269
+ // Generate based on pattern
270
+ const pattern = segment.pattern;
271
+ if (pattern.endsWith('.ts')) {
272
+ return `file${variation}.ts`;
273
+ }
274
+ if (pattern.endsWith('.js')) {
275
+ return `file${variation}.js`;
276
+ }
277
+ if (pattern.startsWith('test-')) {
278
+ return `test-${variation}`;
279
+ }
280
+ return `match${variation}`;
281
+ }
282
+ case 'charclass':
283
+ // Pick a character from the class
284
+ if (segment.ranges.length > 0) {
285
+ const range = segment.ranges[0];
286
+ return range.start;
287
+ }
288
+ return segment.chars[0] || 'x';
289
+ case 'composite':
290
+ return `composite${variation}`;
291
+ }
292
+ }
293
+ /**
294
+ * Generate a path with a specific depth.
295
+ */
296
+ function generateDeepPath(pattern, depth) {
297
+ const ast = pattern.ast;
298
+ const segments = [];
299
+ if (ast.root.type === 'sequence') {
300
+ for (const seg of ast.root.segments) {
301
+ if (seg.type === 'globstar') {
302
+ // Fill with enough segments to reach target depth
303
+ while (segments.length < depth - 1) {
304
+ segments.push('deep');
305
+ }
306
+ }
307
+ else {
308
+ segments.push(generateSegmentValue(seg, 0));
309
+ }
310
+ }
311
+ }
312
+ // Ensure we reach target depth
313
+ while (segments.length < depth) {
314
+ segments.push('extra');
315
+ }
316
+ return '/' + segments.join('/');
317
+ }
318
+ /**
319
+ * Build a detailed explanation of the containment result.
320
+ */
321
+ function buildExplanation(a, b, relationship, counterexample, reverseCounterexample) {
322
+ const failureReasons = determineFailureReasons(a, b, relationship, counterexample);
323
+ const segmentComparison = buildSegmentComparison(a, b);
324
+ const structuralDiffs = buildStructuralDifferences(a, b);
325
+ const witnesses = buildWitnesses(a, b, counterexample, reverseCounterexample);
326
+ return {
327
+ failureReasons,
328
+ segmentComparison,
329
+ structuralDiffs,
330
+ witnesses,
331
+ };
332
+ }
333
+ /**
334
+ * Determine the high-level reasons why containment fails.
335
+ */
336
+ function determineFailureReasons(a, b, relationship, counterexample) {
337
+ if (relationship === 'subset' || relationship === 'equal') {
338
+ return [];
339
+ }
340
+ const reasons = [];
341
+ // Check depth mismatch
342
+ if (a.isUnbounded !== b.isUnbounded) {
343
+ reasons.push('depth_mismatch');
344
+ }
345
+ else if (!a.isUnbounded && !b.isUnbounded) {
346
+ if (a.maxSegments > b.maxSegments || a.minSegments < b.minSegments) {
347
+ reasons.push('depth_mismatch');
348
+ }
349
+ }
350
+ // Check prefix mismatch
351
+ if (a.quickReject.requiredPrefix !== b.quickReject.requiredPrefix) {
352
+ if (!b.quickReject.requiredPrefix || !a.quickReject.requiredPrefix?.startsWith(b.quickReject.requiredPrefix)) {
353
+ reasons.push('prefix_mismatch');
354
+ }
355
+ }
356
+ // Check suffix mismatch
357
+ if (a.quickReject.requiredSuffix !== b.quickReject.requiredSuffix) {
358
+ if (!b.quickReject.requiredSuffix || !a.quickReject.requiredSuffix?.endsWith(b.quickReject.requiredSuffix)) {
359
+ reasons.push('suffix_mismatch');
360
+ }
361
+ }
362
+ // Check for segment-level mismatches using counterexample
363
+ if (counterexample && reasons.length === 0) {
364
+ // If we have a counterexample but no obvious structural reason,
365
+ // it's likely a segment-level mismatch
366
+ reasons.push('segment_mismatch');
367
+ }
368
+ return reasons;
369
+ }
370
+ /**
371
+ * Build segment-by-segment comparison.
372
+ */
373
+ function buildSegmentComparison(a, b) {
374
+ const entries = [];
375
+ const maxPositions = Math.max(a.maxSegments ?? 10, b.maxSegments ?? 10, a.minSegments, b.minSegments);
376
+ // For simplicity, compare the first few positions
377
+ // A full implementation would analyze the automaton structure
378
+ for (let pos = 0; pos < Math.min(maxPositions, 5); pos++) {
379
+ const constraintA = getConstraintAtPosition(a, pos);
380
+ const constraintB = getConstraintAtPosition(b, pos);
381
+ const aSubsetOfB = isConstraintSubset(constraintA, constraintB);
382
+ const difference = aSubsetOfB ? undefined : describeConstraintDifference(constraintA, constraintB);
383
+ entries.push({
384
+ position: pos,
385
+ patternAAllows: constraintA,
386
+ patternBAllows: constraintB,
387
+ aSubsetOfB,
388
+ difference,
389
+ });
390
+ }
391
+ return entries;
392
+ }
393
+ /**
394
+ * Get the constraint at a given segment position.
395
+ */
396
+ function getConstraintAtPosition(pattern, position) {
397
+ const ast = pattern.ast;
398
+ if (ast.root.type !== 'sequence') {
399
+ // For alternations, return a more general constraint
400
+ return {
401
+ type: 'any',
402
+ optional: true,
403
+ repeatable: false,
404
+ };
405
+ }
406
+ const segments = ast.root.segments;
407
+ if (position >= segments.length) {
408
+ // Past the end of the pattern
409
+ if (pattern.isUnbounded) {
410
+ return { type: 'any_sequence', optional: true, repeatable: true };
411
+ }
412
+ return { type: 'end', optional: false, repeatable: false };
413
+ }
414
+ const segment = segments[position];
415
+ switch (segment.type) {
416
+ case 'literal':
417
+ return {
418
+ type: 'literal',
419
+ literalValue: segment.value,
420
+ optional: false,
421
+ repeatable: false,
422
+ };
423
+ case 'wildcard':
424
+ return {
425
+ type: 'wildcard',
426
+ wildcardPattern: segment.pattern,
427
+ optional: false,
428
+ repeatable: false,
429
+ };
430
+ case 'globstar':
431
+ return {
432
+ type: 'any_sequence',
433
+ optional: true,
434
+ repeatable: true,
435
+ };
436
+ case 'charclass':
437
+ return {
438
+ type: 'charclass',
439
+ charclassDescription: describeCharClass(segment),
440
+ optional: false,
441
+ repeatable: false,
442
+ };
443
+ case 'composite':
444
+ return {
445
+ type: 'wildcard',
446
+ wildcardPattern: 'composite',
447
+ optional: false,
448
+ repeatable: false,
449
+ };
450
+ }
451
+ }
452
+ /**
453
+ * Describe a character class for display.
454
+ */
455
+ function describeCharClass(segment) {
456
+ const desc = segment.negated ? 'not ' : '';
457
+ const parts = [];
458
+ if (segment.chars) {
459
+ parts.push(`[${segment.chars}]`);
460
+ }
461
+ for (const range of segment.ranges) {
462
+ parts.push(`${range.start}-${range.end}`);
463
+ }
464
+ return desc + parts.join(', ');
465
+ }
466
+ /**
467
+ * Check if constraint A is a subset of constraint B.
468
+ */
469
+ function isConstraintSubset(a, b) {
470
+ const aType = a.type;
471
+ const bType = b.type;
472
+ // any_sequence contains everything
473
+ if (bType === 'any_sequence')
474
+ return true;
475
+ if (aType === 'any_sequence')
476
+ return false;
477
+ // any contains any single segment (except any_sequence which is handled above)
478
+ if (bType === 'any')
479
+ return true;
480
+ // end only contains end
481
+ if (bType === 'end')
482
+ return aType === 'end';
483
+ if (aType === 'end')
484
+ return false;
485
+ // literal is only subset if same literal or B is wildcard/any
486
+ if (aType === 'literal') {
487
+ if (bType === 'literal')
488
+ return a.literalValue === b.literalValue;
489
+ if (bType === 'wildcard')
490
+ return true;
491
+ return false;
492
+ }
493
+ // wildcard is subset of wildcard only if patterns align
494
+ if (aType === 'wildcard' && bType === 'wildcard') {
495
+ // Simplified: check if patterns look compatible
496
+ return true; // Would need regex analysis for accuracy
497
+ }
498
+ return false;
499
+ }
500
+ /**
501
+ * Describe the difference between two constraints.
502
+ */
503
+ function describeConstraintDifference(a, b) {
504
+ if (a.type === 'any_sequence' && b.type !== 'any_sequence') {
505
+ return 'A allows unlimited depth, B is bounded';
506
+ }
507
+ if (a.type === 'literal' && b.type === 'literal' && a.literalValue !== b.literalValue) {
508
+ return `A requires "${a.literalValue}", B requires "${b.literalValue}"`;
509
+ }
510
+ if (a.type === 'wildcard' && b.type === 'literal') {
511
+ return `A allows any matching segment, B requires exact "${b.literalValue}"`;
512
+ }
513
+ return 'Constraint mismatch';
514
+ }
515
+ /**
516
+ * Build structural differences summary.
517
+ */
518
+ function buildStructuralDifferences(a, b) {
519
+ const depthDifference = buildDepthComparison(a, b);
520
+ const prefixDifference = buildPrefixComparison(a, b);
521
+ const suffixDifference = buildSuffixComparison(a, b);
522
+ const anchoringDifference = buildAnchoringComparison(a, b);
523
+ return {
524
+ depthDifference,
525
+ prefixDifference,
526
+ suffixDifference,
527
+ anchoringDifference,
528
+ };
529
+ }
530
+ function buildDepthComparison(a, b) {
531
+ const aMax = a.maxSegments ?? 'unbounded';
532
+ const bMax = b.maxSegments ?? 'unbounded';
533
+ const differ = a.minSegments !== b.minSegments || aMax !== bMax;
534
+ let explanation;
535
+ if (differ) {
536
+ if (a.isUnbounded && !b.isUnbounded) {
537
+ explanation = `A can match paths of any depth, B is limited to ${bMax} segments`;
538
+ }
539
+ else if (!a.isUnbounded && b.isUnbounded) {
540
+ explanation = `A is limited to ${aMax} segments, B can match any depth`;
541
+ }
542
+ else if (a.minSegments !== b.minSegments) {
543
+ explanation = `A requires at least ${a.minSegments} segments, B requires ${b.minSegments}`;
544
+ }
545
+ }
546
+ return {
547
+ differ,
548
+ patternAMin: a.minSegments,
549
+ patternAMax: aMax,
550
+ patternBMin: b.minSegments,
551
+ patternBMax: bMax,
552
+ explanation,
553
+ };
554
+ }
555
+ function buildPrefixComparison(a, b) {
556
+ const prefixA = a.quickReject.requiredPrefix;
557
+ const prefixB = b.quickReject.requiredPrefix;
558
+ const differ = prefixA !== prefixB;
559
+ let explanation;
560
+ if (differ) {
561
+ if (prefixA && prefixB) {
562
+ explanation = `A requires prefix "${prefixA}", B requires "${prefixB}"`;
563
+ }
564
+ else if (prefixA) {
565
+ explanation = `A requires prefix "${prefixA}", B has no prefix requirement`;
566
+ }
567
+ else {
568
+ explanation = `A has no prefix requirement, B requires "${prefixB}"`;
569
+ }
570
+ }
571
+ return {
572
+ differ,
573
+ patternAPrefix: prefixA,
574
+ patternBPrefix: prefixB,
575
+ explanation,
576
+ };
577
+ }
578
+ function buildSuffixComparison(a, b) {
579
+ const suffixA = a.quickReject.requiredSuffix;
580
+ const suffixB = b.quickReject.requiredSuffix;
581
+ const differ = suffixA !== suffixB;
582
+ let explanation;
583
+ if (differ) {
584
+ if (suffixA && suffixB) {
585
+ explanation = `A requires suffix "${suffixA}", B requires "${suffixB}"`;
586
+ }
587
+ else if (suffixA) {
588
+ explanation = `A requires suffix "${suffixA}", B has no suffix requirement`;
589
+ }
590
+ else {
591
+ explanation = `A has no suffix requirement, B requires "${suffixB}"`;
592
+ }
593
+ }
594
+ return {
595
+ differ,
596
+ patternASuffix: suffixA,
597
+ patternBSuffix: suffixB,
598
+ explanation,
599
+ };
600
+ }
601
+ function buildAnchoringComparison(a, b) {
602
+ const aAbsolute = a.ast.isAbsolute;
603
+ const bAbsolute = b.ast.isAbsolute;
604
+ const differ = aAbsolute !== bAbsolute;
605
+ let explanation;
606
+ if (differ) {
607
+ explanation = aAbsolute ? 'A is an absolute pattern, B is relative' : 'A is a relative pattern, B is absolute';
608
+ }
609
+ return {
610
+ differ,
611
+ patternAAbsolute: aAbsolute,
612
+ patternBAbsolute: bAbsolute,
613
+ explanation,
614
+ };
615
+ }
616
+ /**
617
+ * Build witness paths for the containment result.
618
+ */
619
+ function buildWitnesses(a, b, counterexample, reverseCounterexample) {
620
+ const witnesses = [];
621
+ // Add counterexample if present
622
+ if (counterexample) {
623
+ witnesses.push({
624
+ path: counterexample,
625
+ matchesA: true,
626
+ matchesB: false,
627
+ category: 'counterexample',
628
+ divergenceIndex: findDivergenceIndex(counterexample, a, b),
629
+ });
630
+ }
631
+ // Add reverse counterexample if present
632
+ if (reverseCounterexample) {
633
+ witnesses.push({
634
+ path: reverseCounterexample,
635
+ matchesA: false,
636
+ matchesB: true,
637
+ category: 'reverse_counterexample',
638
+ divergenceIndex: findDivergenceIndex(reverseCounterexample, a, b),
639
+ });
640
+ }
641
+ // Try to find a shared example
642
+ const sharedExample = findSharedExample(a, b);
643
+ if (sharedExample) {
644
+ witnesses.push({
645
+ path: sharedExample,
646
+ matchesA: true,
647
+ matchesB: true,
648
+ category: 'shared',
649
+ });
650
+ }
651
+ return witnesses;
652
+ }
653
+ /**
654
+ * Find the segment index where patterns diverge for a path.
655
+ */
656
+ function findDivergenceIndex(_path, _a, _b) {
657
+ // This would require tracing through both automata
658
+ // For simplicity, return undefined
659
+ return undefined;
660
+ }
661
+ /**
662
+ * Find an example path that matches both patterns.
663
+ */
664
+ function findSharedExample(a, b) {
665
+ // Generate paths from A and check if any match B
666
+ const aPaths = generateTestPaths(a, 10);
667
+ for (const path of aPaths) {
668
+ if (matchPath(path, b)) {
669
+ return path;
670
+ }
671
+ }
672
+ // Generate paths from B and check if any match A
673
+ const bPaths = generateTestPaths(b, 10);
674
+ for (const path of bPaths) {
675
+ if (matchPath(path, a)) {
676
+ return path;
677
+ }
678
+ }
679
+ return undefined;
680
+ }
681
+ //# sourceMappingURL=containment.js.map