public MatchContext(GlobMatcherOptions options, string str, PatternCase patternCase) { _myOptions = options; _myStr = str; _myPatternCase = patternCase; _myStartOffset = 0; _myEndOffset = _myStr.Length; _myStartItem = 0; _myEndItem = _myPatternCase.Count - 1; _myLastAsteriskItem = -1; _myNextPositionForAsterisk = -1; }
// parse a component of the expanded set. // ReSharper disable once FunctionComplexityOverflow private static PatternCase Parse(GlobMatcherOptions options, string?pattern) { if (pattern?.Length == 0) { return(new PatternCase()); } var result = new PatternCase(); var sb = new StringBuilder(); bool escaping = false, inClass = false, negate = false, range = false; int classStart = -1; void FinishLiteral() { Debug.Assert(!escaping && !inClass, "!escaping && !inClass"); if (sb.Length <= 0) { return; } result.Add(new Literal(sb.ToString())); sb.Clear(); } void AppendChar(char c1) { if (inClass && range) { char firstChar = sb[sb.Length - 1]; firstChar++; for (char c2 = firstChar; c2 <= c1; c2++) { sb.Append(c2); } range = false; } else { sb.Append(c1); } } if (pattern == null) { throw new InvalidOperationException(); } for (var i = 0; i < pattern.Length; i++) { var c = pattern[i]; // skip over any that are escaped. if (escaping && c != '/') { AppendChar(c); escaping = false; } else { switch (c) { case '/': if (inClass) { // Class is left open HandleOpenClass(); continue; } else { if (escaping) { sb.Append('\\'); escaping = false; } FinishLiteral(); if (!(result.LastOrDefault() is PathSeparator)) { result.Add(PathSeparator.Instance); } } break; case '\\': escaping = true; break; case '!': case '^': if (inClass && i == classStart + 1) { // the glob [!a] means negation negate = true; } else { AppendChar(c); } break; case '?': if (inClass) { AppendChar(c); } else { FinishLiteral(); result.Add(OneChar.EmptyInstance); } break; case '*': if (inClass) { AppendChar(c); } else { FinishLiteral(); if (result.LastOrDefault() is Asterisk && !options.NoGlobStar) { result.RemoveAt(result.Count - 1); result.Add(new DoubleAsterisk()); } else if (!(result.LastOrDefault() is SimpleAsterisk)) { result.Add(new SimpleAsterisk()); } } break; // these are mostly the same in regexp and glob case '[': if (inClass) { AppendChar(c); } else { FinishLiteral(); inClass = true; negate = false; range = false; classStart = i; } break; case ']': // a right bracket shall lose its special // meaning and represent itself in // a bracket expression if it occurs // first in the list. -- POSIX.2 2.8.3.2 if (i == classStart + 1 || (negate && i == classStart + 2) || !inClass) { AppendChar(c); } else { if (range) { sb.Append('-'); } // finish up the class. inClass = false; result.Add(new OneChar(sb.ToString(), negate)); sb.Clear(); } break; case '-': if (i == classStart + 1 || (negate && i == classStart + 2) || !inClass || range) { AppendChar(c); } else { range = true; } break; default: AppendChar(c); break; } // switch } // if if (i == pattern.Length - 1) { if (inClass) { HandleOpenClass(); // Do not continue, because next check could be relevant } } if (i == pattern.Length - 1) { if (escaping) { sb.Append('\\'); escaping = false; FinishLiteral(); } else { FinishLiteral(); } } void HandleOpenClass() { // handle the case where we left a class open. // "[abc" is valid, equivalent to "\[abc" // split where the last [ was, and escape it // this is a huge pita. We now have to re-walk // the contents of the would-be class to re-translate // any characters that were passed through as-is sb.Clear(); if (result.LastOrDefault() is Literal literal) { sb.Append(literal.Source); result.RemoveAt(result.Count - 1); } sb.Append('['); escaping = false; i = classStart; inClass = false; } // Handle open class } // for result.Build(); return(result); }