@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.
- package/README.md +571 -0
- package/dist/automaton/complement.d.ts +20 -0
- package/dist/automaton/complement.d.ts.map +1 -0
- package/dist/automaton/complement.js +36 -0
- package/dist/automaton/complement.js.map +1 -0
- package/dist/automaton/complement.test.d.ts +2 -0
- package/dist/automaton/complement.test.d.ts.map +1 -0
- package/dist/automaton/complement.test.js +114 -0
- package/dist/automaton/complement.test.js.map +1 -0
- package/dist/automaton/determinize.d.ts +41 -0
- package/dist/automaton/determinize.d.ts.map +1 -0
- package/dist/automaton/determinize.js +310 -0
- package/dist/automaton/determinize.js.map +1 -0
- package/dist/automaton/determinize.test.d.ts +2 -0
- package/dist/automaton/determinize.test.d.ts.map +1 -0
- package/dist/automaton/determinize.test.js +134 -0
- package/dist/automaton/determinize.test.js.map +1 -0
- package/dist/automaton/emptiness.d.ts +41 -0
- package/dist/automaton/emptiness.d.ts.map +1 -0
- package/dist/automaton/emptiness.js +262 -0
- package/dist/automaton/emptiness.js.map +1 -0
- package/dist/automaton/emptiness.test.d.ts +2 -0
- package/dist/automaton/emptiness.test.d.ts.map +1 -0
- package/dist/automaton/emptiness.test.js +154 -0
- package/dist/automaton/emptiness.test.js.map +1 -0
- package/dist/automaton/index.d.ts +10 -0
- package/dist/automaton/index.d.ts.map +1 -0
- package/dist/automaton/index.js +11 -0
- package/dist/automaton/index.js.map +1 -0
- package/dist/automaton/intersect.d.ts +35 -0
- package/dist/automaton/intersect.d.ts.map +1 -0
- package/dist/automaton/intersect.js +302 -0
- package/dist/automaton/intersect.js.map +1 -0
- package/dist/automaton/pattern-algebra.d.ts +62 -0
- package/dist/automaton/pattern-algebra.d.ts.map +1 -0
- package/dist/automaton/pattern-algebra.js +309 -0
- package/dist/automaton/pattern-algebra.js.map +1 -0
- package/dist/automaton/pattern-algebra.test.d.ts +2 -0
- package/dist/automaton/pattern-algebra.test.d.ts.map +1 -0
- package/dist/automaton/pattern-algebra.test.js +223 -0
- package/dist/automaton/pattern-algebra.test.js.map +1 -0
- package/dist/compile/automaton-builder.d.ts +47 -0
- package/dist/compile/automaton-builder.d.ts.map +1 -0
- package/dist/compile/automaton-builder.js +211 -0
- package/dist/compile/automaton-builder.js.map +1 -0
- package/dist/compile/compiler.d.ts +32 -0
- package/dist/compile/compiler.d.ts.map +1 -0
- package/dist/compile/compiler.js +47 -0
- package/dist/compile/compiler.js.map +1 -0
- package/dist/compile/index.d.ts +8 -0
- package/dist/compile/index.d.ts.map +1 -0
- package/dist/compile/index.js +8 -0
- package/dist/compile/index.js.map +1 -0
- package/dist/compile/quick-reject.d.ts +28 -0
- package/dist/compile/quick-reject.d.ts.map +1 -0
- package/dist/compile/quick-reject.js +147 -0
- package/dist/compile/quick-reject.js.map +1 -0
- package/dist/containment/analysis.d.ts +60 -0
- package/dist/containment/analysis.d.ts.map +1 -0
- package/dist/containment/analysis.js +378 -0
- package/dist/containment/analysis.js.map +1 -0
- package/dist/containment/containment.d.ts +23 -0
- package/dist/containment/containment.d.ts.map +1 -0
- package/dist/containment/containment.js +681 -0
- package/dist/containment/containment.js.map +1 -0
- package/dist/containment/containment.test.d.ts +2 -0
- package/dist/containment/containment.test.d.ts.map +1 -0
- package/dist/containment/containment.test.js +209 -0
- package/dist/containment/containment.test.js.map +1 -0
- package/dist/containment/index.d.ts +7 -0
- package/dist/containment/index.d.ts.map +1 -0
- package/dist/containment/index.js +7 -0
- package/dist/containment/index.js.map +1 -0
- package/dist/core-alpha.d.ts +1253 -0
- package/dist/core-beta.d.ts +1253 -0
- package/dist/core-public.d.ts +1253 -0
- package/dist/core-unstripped.d.ts +1253 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/match/index.d.ts +8 -0
- package/dist/match/index.d.ts.map +1 -0
- package/dist/match/index.js +8 -0
- package/dist/match/index.js.map +1 -0
- package/dist/match/matcher.d.ts +40 -0
- package/dist/match/matcher.d.ts.map +1 -0
- package/dist/match/matcher.js +256 -0
- package/dist/match/matcher.js.map +1 -0
- package/dist/match/matcher.test.d.ts +2 -0
- package/dist/match/matcher.test.d.ts.map +1 -0
- package/dist/match/matcher.test.js +185 -0
- package/dist/match/matcher.test.js.map +1 -0
- package/dist/match/path-utils.d.ts +132 -0
- package/dist/match/path-utils.d.ts.map +1 -0
- package/dist/match/path-utils.js +223 -0
- package/dist/match/path-utils.js.map +1 -0
- package/dist/match/path-utils.test.d.ts +2 -0
- package/dist/match/path-utils.test.d.ts.map +1 -0
- package/dist/match/path-utils.test.js +193 -0
- package/dist/match/path-utils.test.js.map +1 -0
- package/dist/match/segment-matcher.d.ts +25 -0
- package/dist/match/segment-matcher.d.ts.map +1 -0
- package/dist/match/segment-matcher.js +267 -0
- package/dist/match/segment-matcher.js.map +1 -0
- package/dist/parse/brace-expansion.d.ts +34 -0
- package/dist/parse/brace-expansion.d.ts.map +1 -0
- package/dist/parse/brace-expansion.js +294 -0
- package/dist/parse/brace-expansion.js.map +1 -0
- package/dist/parse/brace-expansion.test.d.ts +2 -0
- package/dist/parse/brace-expansion.test.d.ts.map +1 -0
- package/dist/parse/brace-expansion.test.js +105 -0
- package/dist/parse/brace-expansion.test.js.map +1 -0
- package/dist/parse/index.d.ts +8 -0
- package/dist/parse/index.d.ts.map +1 -0
- package/dist/parse/index.js +8 -0
- package/dist/parse/index.js.map +1 -0
- package/dist/parse/parser.d.ts +15 -0
- package/dist/parse/parser.d.ts.map +1 -0
- package/dist/parse/parser.js +526 -0
- package/dist/parse/parser.js.map +1 -0
- package/dist/parse/parser.test.d.ts +2 -0
- package/dist/parse/parser.test.d.ts.map +1 -0
- package/dist/parse/parser.test.js +266 -0
- package/dist/parse/parser.test.js.map +1 -0
- package/dist/parse/validator.d.ts +30 -0
- package/dist/parse/validator.d.ts.map +1 -0
- package/dist/parse/validator.js +115 -0
- package/dist/parse/validator.js.map +1 -0
- package/dist/parse/validator.test.d.ts +2 -0
- package/dist/parse/validator.test.d.ts.map +1 -0
- package/dist/parse/validator.test.js +45 -0
- package/dist/parse/validator.test.js.map +1 -0
- package/dist/types/ast.d.ts +158 -0
- package/dist/types/ast.d.ts.map +1 -0
- package/dist/types/ast.js +2 -0
- package/dist/types/ast.js.map +1 -0
- package/dist/types/automaton.d.ts +150 -0
- package/dist/types/automaton.d.ts.map +1 -0
- package/dist/types/automaton.js +2 -0
- package/dist/types/automaton.js.map +1 -0
- package/dist/types/containment.d.ts +257 -0
- package/dist/types/containment.d.ts.map +1 -0
- package/dist/types/containment.js +5 -0
- package/dist/types/containment.js.map +1 -0
- package/dist/types/errors.d.ts +37 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +24 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parsePattern } from './parser';
|
|
3
|
+
import { expandBraces, countBraceExpansions } from './brace-expansion';
|
|
4
|
+
describe('expandBraces', () => {
|
|
5
|
+
it('expands simple brace expression', () => {
|
|
6
|
+
const pattern = parsePattern('{src,lib}');
|
|
7
|
+
const expanded = expandBraces(pattern);
|
|
8
|
+
expect(expanded).toHaveLength(2);
|
|
9
|
+
expect(expanded[0].source).toBe('src');
|
|
10
|
+
expect(expanded[1].source).toBe('lib');
|
|
11
|
+
});
|
|
12
|
+
it('expands braces with suffix', () => {
|
|
13
|
+
const pattern = parsePattern('{src,lib}/**/*.ts');
|
|
14
|
+
const expanded = expandBraces(pattern);
|
|
15
|
+
expect(expanded).toHaveLength(2);
|
|
16
|
+
expect(expanded[0].source).toBe('src/**/*.ts');
|
|
17
|
+
expect(expanded[1].source).toBe('lib/**/*.ts');
|
|
18
|
+
});
|
|
19
|
+
it('expands braces with prefix', () => {
|
|
20
|
+
const pattern = parsePattern('packages/{core,cli}');
|
|
21
|
+
const expanded = expandBraces(pattern);
|
|
22
|
+
expect(expanded).toHaveLength(2);
|
|
23
|
+
expect(expanded[0].source).toBe('packages/core');
|
|
24
|
+
expect(expanded[1].source).toBe('packages/cli');
|
|
25
|
+
});
|
|
26
|
+
it('expands multiple braces', () => {
|
|
27
|
+
const pattern = parsePattern('{a,b}/{x,y}');
|
|
28
|
+
const expanded = expandBraces(pattern);
|
|
29
|
+
expect(expanded).toHaveLength(4);
|
|
30
|
+
const sources = expanded.map((p) => p.source);
|
|
31
|
+
expect(sources).toContain('a/x');
|
|
32
|
+
expect(sources).toContain('a/y');
|
|
33
|
+
expect(sources).toContain('b/x');
|
|
34
|
+
expect(sources).toContain('b/y');
|
|
35
|
+
});
|
|
36
|
+
it('expands numeric range', () => {
|
|
37
|
+
const pattern = parsePattern('file{1..5}.txt');
|
|
38
|
+
const expanded = expandBraces(pattern);
|
|
39
|
+
expect(expanded).toHaveLength(5);
|
|
40
|
+
expect(expanded[0].source).toBe('file1.txt');
|
|
41
|
+
expect(expanded[4].source).toBe('file5.txt');
|
|
42
|
+
});
|
|
43
|
+
it('expands descending numeric range', () => {
|
|
44
|
+
const pattern = parsePattern('v{3..1}');
|
|
45
|
+
const expanded = expandBraces(pattern);
|
|
46
|
+
expect(expanded).toHaveLength(3);
|
|
47
|
+
expect(expanded[0].source).toBe('v3');
|
|
48
|
+
expect(expanded[1].source).toBe('v2');
|
|
49
|
+
expect(expanded[2].source).toBe('v1');
|
|
50
|
+
});
|
|
51
|
+
it('returns pattern unchanged if no braces', () => {
|
|
52
|
+
const pattern = parsePattern('src/**/*.ts');
|
|
53
|
+
const expanded = expandBraces(pattern);
|
|
54
|
+
expect(expanded).toHaveLength(1);
|
|
55
|
+
expect(expanded[0].source).toBe('src/**/*.ts');
|
|
56
|
+
});
|
|
57
|
+
it('preserves negation', () => {
|
|
58
|
+
const pattern = parsePattern('!{node_modules,dist}/**');
|
|
59
|
+
const expanded = expandBraces(pattern);
|
|
60
|
+
expect(expanded).toHaveLength(2);
|
|
61
|
+
expect(expanded[0].source).toBe('!node_modules/**');
|
|
62
|
+
expect(expanded[1].source).toBe('!dist/**');
|
|
63
|
+
});
|
|
64
|
+
it('limits expansion count', () => {
|
|
65
|
+
const pattern = parsePattern('{a,b,c,d,e}');
|
|
66
|
+
const expanded = expandBraces(pattern, 3);
|
|
67
|
+
expect(expanded.length).toBeLessThanOrEqual(3);
|
|
68
|
+
expect(expanded.some((p) => p.errors?.some((e) => e.code === 'EXPANSION_LIMIT'))).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it('limits numeric range', () => {
|
|
71
|
+
const pattern = parsePattern('{1..100}');
|
|
72
|
+
const expanded = expandBraces(pattern);
|
|
73
|
+
// Should error because range exceeds 50
|
|
74
|
+
expect(expanded.some((p) => p.errors?.some((e) => e.code === 'EXPANSION_LIMIT'))).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
it('errors on nested braces', () => {
|
|
77
|
+
const pattern = parsePattern('{a,{b,c}}');
|
|
78
|
+
const expanded = expandBraces(pattern);
|
|
79
|
+
expect(expanded.some((p) => p.errors?.some((e) => e.code === 'NESTED_BRACES'))).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
it('handles braces inside brackets (treated as literal)', () => {
|
|
82
|
+
// {a,b} inside [] is not brace expansion
|
|
83
|
+
const pattern = parsePattern('[{a,b}]');
|
|
84
|
+
const expanded = expandBraces(pattern);
|
|
85
|
+
expect(expanded).toHaveLength(1);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('countBraceExpansions', () => {
|
|
89
|
+
it('counts simple expansion', () => {
|
|
90
|
+
expect(countBraceExpansions('{a,b}')).toBe(2);
|
|
91
|
+
expect(countBraceExpansions('{a,b,c}')).toBe(3);
|
|
92
|
+
});
|
|
93
|
+
it('counts multiple braces as multiplication', () => {
|
|
94
|
+
expect(countBraceExpansions('{a,b}/{x,y}')).toBe(4);
|
|
95
|
+
expect(countBraceExpansions('{a,b}/{x,y}/{1,2}')).toBe(8);
|
|
96
|
+
});
|
|
97
|
+
it('returns 1 for no braces', () => {
|
|
98
|
+
expect(countBraceExpansions('src/**/*.ts')).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
it('returns Infinity for excessive expansion', () => {
|
|
101
|
+
// Create a pattern that would expand to > 10000 (10^5 = 100,000)
|
|
102
|
+
expect(countBraceExpansions(`{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}`)).toBe(Infinity);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=brace-expansion.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brace-expansion.test.js","sourceRoot":"","sources":["../../src/parse/brace-expansion.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern parsing utilities.
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
export { parsePattern } from './parser';
|
|
6
|
+
export { validatePattern, isValidPattern } from './validator';
|
|
7
|
+
export { expandBraces, countBraceExpansions } from './brace-expansion';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parse/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern parsing utilities.
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
export { parsePattern } from './parser';
|
|
6
|
+
export { validatePattern, isValidPattern } from './validator';
|
|
7
|
+
export { expandBraces, countBraceExpansions } from './brace-expansion';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parse/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern parser - converts glob pattern strings to AST.
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import type { PathPattern } from '../types';
|
|
6
|
+
/**
|
|
7
|
+
* Parse a pattern string into an AST.
|
|
8
|
+
*
|
|
9
|
+
* @param source - The pattern string to parse
|
|
10
|
+
* @returns Parsed PathPattern with AST and any errors
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export declare function parsePattern(source: string): PathPattern;
|
|
15
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parse/parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,WAAW,EASZ,MAAM,UAAU,CAAA;AAWjB;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CA+CxD"}
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern parser - converts glob pattern strings to AST.
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Parse a pattern string into an AST.
|
|
7
|
+
*
|
|
8
|
+
* @param source - The pattern string to parse
|
|
9
|
+
* @returns Parsed PathPattern with AST and any errors
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export function parsePattern(source) {
|
|
14
|
+
const state = {
|
|
15
|
+
source,
|
|
16
|
+
position: 0,
|
|
17
|
+
errors: [],
|
|
18
|
+
};
|
|
19
|
+
// Handle negation prefix
|
|
20
|
+
let isNegation = false;
|
|
21
|
+
let patternToParse = source;
|
|
22
|
+
if (source.startsWith('!')) {
|
|
23
|
+
isNegation = true;
|
|
24
|
+
patternToParse = source.slice(1);
|
|
25
|
+
state.position = 1;
|
|
26
|
+
}
|
|
27
|
+
// Determine if absolute
|
|
28
|
+
const isAbsolute = patternToParse.startsWith('/') || patternToParse.startsWith('~');
|
|
29
|
+
// Check for brace expansion at top level
|
|
30
|
+
const hasBraces = containsTopLevelBraces(patternToParse);
|
|
31
|
+
let root;
|
|
32
|
+
if (hasBraces) {
|
|
33
|
+
// Parse as alternation
|
|
34
|
+
const branches = expandBracesOnce(patternToParse, state);
|
|
35
|
+
if (branches.length === 1) {
|
|
36
|
+
root = parseSegmentSequence(branches[0], state);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
root = {
|
|
40
|
+
type: 'alternation',
|
|
41
|
+
branches: branches.map((branch) => parseSegmentSequence(branch, state)),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
root = parseSegmentSequence(patternToParse, state);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
source,
|
|
50
|
+
root,
|
|
51
|
+
isAbsolute,
|
|
52
|
+
isNegation,
|
|
53
|
+
errors: state.errors.length > 0 ? state.errors : undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if a pattern contains top-level braces (not nested in brackets).
|
|
58
|
+
*/
|
|
59
|
+
function containsTopLevelBraces(pattern) {
|
|
60
|
+
let inBracket = false;
|
|
61
|
+
let braceDepth = 0;
|
|
62
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
63
|
+
const char = pattern[i];
|
|
64
|
+
const prevChar = i > 0 ? pattern[i - 1] : '';
|
|
65
|
+
// Skip escaped characters
|
|
66
|
+
if (prevChar === '\\')
|
|
67
|
+
continue;
|
|
68
|
+
if (char === '[' && !inBracket) {
|
|
69
|
+
inBracket = true;
|
|
70
|
+
}
|
|
71
|
+
else if (char === ']' && inBracket) {
|
|
72
|
+
inBracket = false;
|
|
73
|
+
}
|
|
74
|
+
else if (char === '{' && !inBracket) {
|
|
75
|
+
braceDepth++;
|
|
76
|
+
}
|
|
77
|
+
else if (char === '}' && !inBracket && braceDepth > 0) {
|
|
78
|
+
braceDepth--;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Check again for actual braces
|
|
82
|
+
braceDepth = 0;
|
|
83
|
+
inBracket = false;
|
|
84
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
85
|
+
const char = pattern[i];
|
|
86
|
+
const prevChar = i > 0 ? pattern[i - 1] : '';
|
|
87
|
+
if (prevChar === '\\')
|
|
88
|
+
continue;
|
|
89
|
+
if (char === '[' && !inBracket) {
|
|
90
|
+
inBracket = true;
|
|
91
|
+
}
|
|
92
|
+
else if (char === ']' && inBracket) {
|
|
93
|
+
inBracket = false;
|
|
94
|
+
}
|
|
95
|
+
else if (char === '{' && !inBracket) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Expand braces one level (non-recursive, nested braces are an error).
|
|
103
|
+
*/
|
|
104
|
+
function expandBracesOnce(pattern, state) {
|
|
105
|
+
// Find the first top-level brace
|
|
106
|
+
let inBracket = false;
|
|
107
|
+
let braceStart = -1;
|
|
108
|
+
let braceEnd = -1;
|
|
109
|
+
let braceDepth = 0;
|
|
110
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
111
|
+
const char = pattern[i];
|
|
112
|
+
const prevChar = i > 0 ? pattern[i - 1] : '';
|
|
113
|
+
if (prevChar === '\\')
|
|
114
|
+
continue;
|
|
115
|
+
if (char === '[' && !inBracket) {
|
|
116
|
+
inBracket = true;
|
|
117
|
+
}
|
|
118
|
+
else if (char === ']' && inBracket) {
|
|
119
|
+
inBracket = false;
|
|
120
|
+
}
|
|
121
|
+
else if (char === '{' && !inBracket) {
|
|
122
|
+
if (braceDepth === 0) {
|
|
123
|
+
braceStart = i;
|
|
124
|
+
}
|
|
125
|
+
braceDepth++;
|
|
126
|
+
// Check for nested braces
|
|
127
|
+
if (braceDepth > 1) {
|
|
128
|
+
state.errors.push({
|
|
129
|
+
code: 'NESTED_BRACES',
|
|
130
|
+
message: 'Nested braces are not allowed',
|
|
131
|
+
position: i,
|
|
132
|
+
length: 1,
|
|
133
|
+
});
|
|
134
|
+
// Continue parsing as if it weren't nested
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (char === '}' && !inBracket) {
|
|
138
|
+
if (braceDepth > 0) {
|
|
139
|
+
braceDepth--;
|
|
140
|
+
if (braceDepth === 0) {
|
|
141
|
+
braceEnd = i;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (braceStart === -1) {
|
|
148
|
+
return [pattern];
|
|
149
|
+
}
|
|
150
|
+
if (braceEnd === -1) {
|
|
151
|
+
state.errors.push({
|
|
152
|
+
code: 'UNCLOSED_BRACE',
|
|
153
|
+
message: 'Unclosed brace in pattern',
|
|
154
|
+
position: braceStart,
|
|
155
|
+
length: 1,
|
|
156
|
+
});
|
|
157
|
+
return [pattern];
|
|
158
|
+
}
|
|
159
|
+
const prefix = pattern.slice(0, braceStart);
|
|
160
|
+
const braceContent = pattern.slice(braceStart + 1, braceEnd);
|
|
161
|
+
const suffix = pattern.slice(braceEnd + 1);
|
|
162
|
+
// Split brace content by commas (not inside nested structures)
|
|
163
|
+
const alternatives = splitByComma(braceContent);
|
|
164
|
+
// Check for numeric range like {1..10}
|
|
165
|
+
if (alternatives.length === 1 && alternatives[0].includes('..')) {
|
|
166
|
+
const rangeMatch = /^(-?\d+)\.\.(-?\d+)$/.exec(alternatives[0]);
|
|
167
|
+
if (rangeMatch) {
|
|
168
|
+
const start = parseInt(rangeMatch[1], 10);
|
|
169
|
+
const end = parseInt(rangeMatch[2], 10);
|
|
170
|
+
const step = start <= end ? 1 : -1;
|
|
171
|
+
const count = Math.abs(end - start) + 1;
|
|
172
|
+
if (count > 50) {
|
|
173
|
+
state.errors.push({
|
|
174
|
+
code: 'EXPANSION_LIMIT',
|
|
175
|
+
message: `Numeric range {${start}..${end}} exceeds limit of 50 elements`,
|
|
176
|
+
position: braceStart,
|
|
177
|
+
length: braceEnd - braceStart + 1,
|
|
178
|
+
});
|
|
179
|
+
// Truncate to 50
|
|
180
|
+
const truncated = [];
|
|
181
|
+
for (let i = 0; i < 50; i++) {
|
|
182
|
+
truncated.push(String(start + i * step));
|
|
183
|
+
}
|
|
184
|
+
return expandBracesOnce(truncated.map((n) => prefix + n + suffix).join('|SPLIT|'), state).flatMap((p) => p.split('|SPLIT|'));
|
|
185
|
+
}
|
|
186
|
+
const expanded = [];
|
|
187
|
+
for (let n = start; step > 0 ? n <= end : n >= end; n += step) {
|
|
188
|
+
expanded.push(prefix + n + suffix);
|
|
189
|
+
}
|
|
190
|
+
// Recursively expand if more braces in suffix
|
|
191
|
+
if (containsTopLevelBraces(suffix)) {
|
|
192
|
+
return expanded.flatMap((p) => expandBracesOnce(p, state));
|
|
193
|
+
}
|
|
194
|
+
return expanded;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Standard brace expansion
|
|
198
|
+
const expanded = alternatives.map((alt) => prefix + alt + suffix);
|
|
199
|
+
// Recursively expand if more braces
|
|
200
|
+
return expanded.flatMap((p) => {
|
|
201
|
+
if (containsTopLevelBraces(p)) {
|
|
202
|
+
return expandBracesOnce(p, state);
|
|
203
|
+
}
|
|
204
|
+
return [p];
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Split a string by top-level commas.
|
|
209
|
+
*/
|
|
210
|
+
function splitByComma(content) {
|
|
211
|
+
const parts = [];
|
|
212
|
+
let current = '';
|
|
213
|
+
let depth = 0;
|
|
214
|
+
let inBracket = false;
|
|
215
|
+
for (let i = 0; i < content.length; i++) {
|
|
216
|
+
const char = content[i];
|
|
217
|
+
const prevChar = i > 0 ? content[i - 1] : '';
|
|
218
|
+
if (prevChar === '\\') {
|
|
219
|
+
current += char;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (char === '[' && !inBracket) {
|
|
223
|
+
inBracket = true;
|
|
224
|
+
current += char;
|
|
225
|
+
}
|
|
226
|
+
else if (char === ']' && inBracket) {
|
|
227
|
+
inBracket = false;
|
|
228
|
+
current += char;
|
|
229
|
+
}
|
|
230
|
+
else if (char === '{' && !inBracket) {
|
|
231
|
+
depth++;
|
|
232
|
+
current += char;
|
|
233
|
+
}
|
|
234
|
+
else if (char === '}' && !inBracket && depth > 0) {
|
|
235
|
+
depth--;
|
|
236
|
+
current += char;
|
|
237
|
+
}
|
|
238
|
+
else if (char === ',' && depth === 0 && !inBracket) {
|
|
239
|
+
parts.push(current);
|
|
240
|
+
current = '';
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
current += char;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
parts.push(current);
|
|
247
|
+
return parts;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Parse a segment sequence (path split by /).
|
|
251
|
+
*/
|
|
252
|
+
function parseSegmentSequence(pattern, state) {
|
|
253
|
+
// Remove leading / for absolute paths (we track absoluteness separately)
|
|
254
|
+
let toParse = pattern;
|
|
255
|
+
if (toParse.startsWith('/')) {
|
|
256
|
+
toParse = toParse.slice(1);
|
|
257
|
+
}
|
|
258
|
+
else if (toParse.startsWith('~/')) {
|
|
259
|
+
toParse = toParse.slice(2);
|
|
260
|
+
}
|
|
261
|
+
else if (toParse === '~') {
|
|
262
|
+
toParse = '';
|
|
263
|
+
}
|
|
264
|
+
if (toParse === '') {
|
|
265
|
+
return { type: 'sequence', segments: [] };
|
|
266
|
+
}
|
|
267
|
+
// Split by / but not inside brackets
|
|
268
|
+
const segmentStrings = splitBySlash(toParse);
|
|
269
|
+
const segments = segmentStrings.map((seg) => parseSegment(seg, state));
|
|
270
|
+
return { type: 'sequence', segments };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Split pattern by forward slashes (not inside brackets).
|
|
274
|
+
*/
|
|
275
|
+
function splitBySlash(pattern) {
|
|
276
|
+
const parts = [];
|
|
277
|
+
let current = '';
|
|
278
|
+
let inBracket = false;
|
|
279
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
280
|
+
const char = pattern[i];
|
|
281
|
+
const prevChar = i > 0 ? pattern[i - 1] : '';
|
|
282
|
+
if (prevChar === '\\') {
|
|
283
|
+
current += char;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (char === '[' && !inBracket) {
|
|
287
|
+
inBracket = true;
|
|
288
|
+
current += char;
|
|
289
|
+
}
|
|
290
|
+
else if (char === ']' && inBracket) {
|
|
291
|
+
inBracket = false;
|
|
292
|
+
current += char;
|
|
293
|
+
}
|
|
294
|
+
else if (char === '/' && !inBracket) {
|
|
295
|
+
if (current !== '') {
|
|
296
|
+
parts.push(current);
|
|
297
|
+
}
|
|
298
|
+
current = '';
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
current += char;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (current !== '') {
|
|
305
|
+
parts.push(current);
|
|
306
|
+
}
|
|
307
|
+
return parts;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Check if a segment contains unescaped special characters.
|
|
311
|
+
*/
|
|
312
|
+
function hasUnescapedSpecial(segment, chars) {
|
|
313
|
+
for (let i = 0; i < segment.length; i++) {
|
|
314
|
+
const char = segment[i];
|
|
315
|
+
// Skip escaped characters
|
|
316
|
+
if (char === '\\' && i + 1 < segment.length) {
|
|
317
|
+
i++; // Skip next character
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (chars.includes(char)) {
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Parse a single segment string into a Segment node.
|
|
328
|
+
*/
|
|
329
|
+
function parseSegment(segment, state) {
|
|
330
|
+
// Check for globstar
|
|
331
|
+
if (segment === '**') {
|
|
332
|
+
return { type: 'globstar' };
|
|
333
|
+
}
|
|
334
|
+
// Check for invalid globstar usage (unescaped **)
|
|
335
|
+
if (hasUnescapedSpecial(segment, '*') && segment !== '**') {
|
|
336
|
+
// Check if there's an actual ** sequence (not escaped)
|
|
337
|
+
for (let i = 0; i < segment.length - 1; i++) {
|
|
338
|
+
if (segment[i] === '\\') {
|
|
339
|
+
i++; // Skip escaped char
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (segment[i] === '*' && segment[i + 1] === '*') {
|
|
343
|
+
state.errors.push({
|
|
344
|
+
code: 'INVALID_GLOBSTAR',
|
|
345
|
+
message: '** must be a complete path segment, not part of a larger pattern',
|
|
346
|
+
position: state.source.indexOf(segment),
|
|
347
|
+
length: segment.length,
|
|
348
|
+
});
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Analyze segment content - check for UNESCAPED special chars
|
|
354
|
+
const hasWildcard = hasUnescapedSpecial(segment, '*?');
|
|
355
|
+
const hasBracket = hasUnescapedSpecial(segment, '[');
|
|
356
|
+
// Pure literal (no unescaped wildcards or brackets)
|
|
357
|
+
if (!hasWildcard && !hasBracket) {
|
|
358
|
+
return {
|
|
359
|
+
type: 'literal',
|
|
360
|
+
value: unescapeSegment(segment),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// Parse into parts
|
|
364
|
+
const parts = parseSegmentParts(segment, state);
|
|
365
|
+
// If only wildcards (no charclass), use WildcardSegment
|
|
366
|
+
if (!hasBracket) {
|
|
367
|
+
const wildcardParts = parts.map((p) => {
|
|
368
|
+
if (p.type === 'literal')
|
|
369
|
+
return { type: 'literal', value: p.value };
|
|
370
|
+
if (p.type === 'star')
|
|
371
|
+
return { type: 'star' };
|
|
372
|
+
if (p.type === 'question')
|
|
373
|
+
return { type: 'question' };
|
|
374
|
+
// Should not happen for non-bracket segments
|
|
375
|
+
throw new Error(`Unexpected part type: ${p.type}`);
|
|
376
|
+
});
|
|
377
|
+
return {
|
|
378
|
+
type: 'wildcard',
|
|
379
|
+
pattern: segment,
|
|
380
|
+
parts: wildcardParts,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
// Has character classes - use CompositeSegment
|
|
384
|
+
return {
|
|
385
|
+
type: 'composite',
|
|
386
|
+
parts,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Parse segment into component parts (literals, wildcards, charclasses).
|
|
391
|
+
*/
|
|
392
|
+
function parseSegmentParts(segment, state) {
|
|
393
|
+
const parts = [];
|
|
394
|
+
let i = 0;
|
|
395
|
+
let literalBuffer = '';
|
|
396
|
+
const flushLiteral = () => {
|
|
397
|
+
if (literalBuffer !== '') {
|
|
398
|
+
parts.push({ type: 'literal', value: literalBuffer });
|
|
399
|
+
literalBuffer = '';
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
while (i < segment.length) {
|
|
403
|
+
const char = segment[i];
|
|
404
|
+
// Handle escapes
|
|
405
|
+
if (char === '\\' && i + 1 < segment.length) {
|
|
406
|
+
literalBuffer += segment[i + 1];
|
|
407
|
+
i += 2;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (char === '*') {
|
|
411
|
+
flushLiteral();
|
|
412
|
+
parts.push({ type: 'star' });
|
|
413
|
+
i++;
|
|
414
|
+
}
|
|
415
|
+
else if (char === '?') {
|
|
416
|
+
flushLiteral();
|
|
417
|
+
parts.push({ type: 'question' });
|
|
418
|
+
i++;
|
|
419
|
+
}
|
|
420
|
+
else if (char === '[') {
|
|
421
|
+
flushLiteral();
|
|
422
|
+
const { charclass, endIndex } = parseCharClass(segment, i, state);
|
|
423
|
+
parts.push({ type: 'charclass', spec: charclass });
|
|
424
|
+
i = endIndex;
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
literalBuffer += char;
|
|
428
|
+
i++;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
flushLiteral();
|
|
432
|
+
return parts;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Parse a character class [abc] or [a-z] or [!...].
|
|
436
|
+
*/
|
|
437
|
+
function parseCharClass(segment, startIndex, state) {
|
|
438
|
+
let i = startIndex + 1; // Skip opening [
|
|
439
|
+
let negated = false;
|
|
440
|
+
const ranges = [];
|
|
441
|
+
let chars = '';
|
|
442
|
+
// Check for negation
|
|
443
|
+
if (i < segment.length && (segment[i] === '!' || segment[i] === '^')) {
|
|
444
|
+
negated = true;
|
|
445
|
+
i++;
|
|
446
|
+
}
|
|
447
|
+
// Handle ] as first char (literal)
|
|
448
|
+
if (i < segment.length && segment[i] === ']') {
|
|
449
|
+
chars += ']';
|
|
450
|
+
i++;
|
|
451
|
+
}
|
|
452
|
+
while (i < segment.length) {
|
|
453
|
+
const char = segment[i];
|
|
454
|
+
if (char === ']') {
|
|
455
|
+
// End of character class
|
|
456
|
+
if (chars === '' && ranges.length === 0) {
|
|
457
|
+
state.errors.push({
|
|
458
|
+
code: 'EMPTY_CHARCLASS',
|
|
459
|
+
message: 'Empty character class',
|
|
460
|
+
position: startIndex,
|
|
461
|
+
length: i - startIndex + 1,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
charclass: { type: 'charclass', negated, ranges, chars },
|
|
466
|
+
endIndex: i + 1,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
if (char === '\\' && i + 1 < segment.length) {
|
|
470
|
+
// Escaped character
|
|
471
|
+
chars += segment[i + 1];
|
|
472
|
+
i += 2;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
// Check for range
|
|
476
|
+
if (i + 2 < segment.length && segment[i + 1] === '-' && segment[i + 2] !== ']') {
|
|
477
|
+
const start = char;
|
|
478
|
+
const end = segment[i + 2];
|
|
479
|
+
// Validate range
|
|
480
|
+
if (start.charCodeAt(0) > end.charCodeAt(0)) {
|
|
481
|
+
state.errors.push({
|
|
482
|
+
code: 'INVALID_RANGE',
|
|
483
|
+
message: `Invalid range [${start}-${end}]: start > end`,
|
|
484
|
+
position: startIndex + i,
|
|
485
|
+
length: 3,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
ranges.push({ start, end });
|
|
489
|
+
i += 3;
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
chars += char;
|
|
493
|
+
i++;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Unclosed bracket
|
|
497
|
+
state.errors.push({
|
|
498
|
+
code: 'UNCLOSED_BRACKET',
|
|
499
|
+
message: 'Unclosed character class',
|
|
500
|
+
position: startIndex,
|
|
501
|
+
length: segment.length - startIndex,
|
|
502
|
+
});
|
|
503
|
+
return {
|
|
504
|
+
charclass: { type: 'charclass', negated, ranges, chars },
|
|
505
|
+
endIndex: segment.length,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Unescape a literal segment (remove backslashes).
|
|
510
|
+
*/
|
|
511
|
+
function unescapeSegment(segment) {
|
|
512
|
+
let result = '';
|
|
513
|
+
let i = 0;
|
|
514
|
+
while (i < segment.length) {
|
|
515
|
+
if (segment[i] === '\\' && i + 1 < segment.length) {
|
|
516
|
+
result += segment[i + 1];
|
|
517
|
+
i += 2;
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
result += segment[i];
|
|
521
|
+
i++;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
//# sourceMappingURL=parser.js.map
|