[InlineData(@"{p1:regex(([{{(])\w+)}", @"regex(([{(])\w+)")] // Not balanced { public void Parse_RegularExpressions(string template, string constraint) { // Arrange var builder = RoutePatternBuilder.Create(template); builder.AddPathSegmentFromText( template, RoutePatternPart.CreateParameterFromText( template, "p1", null, RoutePatternParameterKind.Standard, ConstraintReference.CreateFromText(constraint, constraint))); var expected = builder.Build(); // Act var actual = RoutePatternParser.Parse(template); // Assert Assert.Equal <RoutePattern>(expected, actual, new RoutePatternEqualityComparer()); }
private static ConstraintParseResults ParseConstraints( string parameter, int currentIndex, int endIndex) { var constraints = new List <ConstraintReference>(); var state = ParseState.Start; var startIndex = currentIndex; do { var currentChar = currentIndex > endIndex ? null : (char?)parameter[currentIndex]; switch (state) { case ParseState.Start: switch (currentChar) { case null: state = ParseState.End; break; case ':': state = ParseState.ParsingName; startIndex = currentIndex + 1; break; case '(': state = ParseState.InsideParenthesis; break; case '=': state = ParseState.End; currentIndex--; break; } break; case ParseState.InsideParenthesis: switch (currentChar) { case null: state = ParseState.End; var constraintText = parameter.Substring(startIndex, currentIndex - startIndex); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); break; case ')': // Only consume a ')' token if // (a) it is the last token // (b) the next character is the start of the new constraint ':' // (c) the next character is the start of the default value. var nextChar = currentIndex + 1 > endIndex ? null : (char?)parameter[currentIndex + 1]; switch (nextChar) { case null: state = ParseState.End; constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); break; case ':': state = ParseState.Start; constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); startIndex = currentIndex + 1; break; case '=': state = ParseState.End; constraintText = parameter.Substring(startIndex, currentIndex - startIndex + 1); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); break; } break; case ':': case '=': // In the original implementation, the Regex would've backtracked if it encountered an // unbalanced opening bracket followed by (not necessarily immediatiely) a delimiter. // Simply verifying that the parantheses will eventually be closed should suffice to // determine if the terminator needs to be consumed as part of the current constraint // specification. var indexOfClosingParantheses = parameter.IndexOf(')', currentIndex + 1); if (indexOfClosingParantheses == -1) { constraintText = parameter.Substring(startIndex, currentIndex - startIndex); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); if (currentChar == ':') { state = ParseState.ParsingName; startIndex = currentIndex + 1; } else { state = ParseState.End; currentIndex--; } } else { currentIndex = indexOfClosingParantheses; } break; } break; case ParseState.ParsingName: switch (currentChar) { case null: state = ParseState.End; var constraintText = parameter.Substring(startIndex, currentIndex - startIndex); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); break; case ':': constraintText = parameter.Substring(startIndex, currentIndex - startIndex); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); startIndex = currentIndex + 1; break; case '(': state = ParseState.InsideParenthesis; break; case '=': state = ParseState.End; constraintText = parameter.Substring(startIndex, currentIndex - startIndex); constraints.Add(ConstraintReference.CreateFromText(constraintText, constraintText)); currentIndex--; break; } break; } currentIndex++; } while (state != ParseState.End); return(new ConstraintParseResults { CurrentIndex = currentIndex, Constraints = constraints }); }