/// <summary> /// Initializes a new instance of the <see cref="CSharpInheritanceList" /> class by parsing a class inheritance list /// from <paramref name="fullSignature" /> /// </summary> /// <param name="fullSignature">The full signature.</param> /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="fullSignature" /> is <c>null</c>.</exception> /// <exception cref="System.ArgumentException"> /// Thrown if <see cref="CSharpInheritanceListValidator.Validate" /> fails to /// successfully parse <paramref name="fullSignature" />. /// </exception> public CSharpInheritanceList(string fullSignature) { if (fullSignature == null) { throw new ArgumentNullException("fullSignature", "Inheritance list signature string cannot be null."); } _validatedSignature = CSharpInheritanceListValidator.Validate(fullSignature); if (!_validatedSignature.Success) { throw new ArgumentException(_validatedSignature.ErrorDescription, "fullSignature"); } _fullSignature = fullSignature; }
/// <summary> /// Validates/parses the specified CSharp inheritance list given in <paramref name="input" />. /// The parser supports 'where' type constraints on generic parameters. /// The signature given should be the source content immediately after a classes declared name, without the separating /// colon if there is one. /// </summary> /// <param name="input">The inheritance list signature to parse.</param> /// <returns>A parse/validation results object. <see cref="CSharpInheritanceListValidationResult" /></returns> /// <exception cref="ArgumentNullException"><paramref name="input"/> is <c>null</c>.</exception> public static CSharpInheritanceListValidationResult Validate(string input) { if (input == null) { throw new ArgumentNullException("input"); } var result = new CSharpInheritanceListValidationResult(); var compareValidatedType = new EquateValidatedTypes(); var inheritedTypes = new HashSet <CSharpClassNameValidationResult>(compareValidatedType); var constrainedTypeParameters = new HashSet <string>(); var typeConstraints = new GenericArray <HashSet <CSharpTypeConstraintValidationResult> >(); result.Success = true; States state = States.WaitingForFirstWord; States stateBeforeGenericPart = 0; string accum = ""; int unmatchedGenericBraces = 0; for (int index = 0; index < input.Length; index++) { var c = input[index]; bool end = index == input.Length - 1; accum += c; if (end) { if (state == States.AccumulatingInheritedType || state == States.AfterFirstInheritedType || state == States.AccumulatingFirstWord || state == States.WaitingForFirstWord || (state == States.AccumulatingGenericPart && (stateBeforeGenericPart == States.AccumulatingInheritedType || stateBeforeGenericPart == States.AccumulatingFirstWord))) { var word = accum.Trim(); CSharpClassNameValidationResult init; string err; if (!IsValidInheritedType(word, out err, out init)) { result.ErrorDescription = err; result.ErrorIndex = (index - (accum.Length - 1)) + (init == null ? 0 : init.ErrorIndex); result.Success = false; return(result); } if (!inheritedTypes.Add(init)) { result.ErrorDescription = string.Format("Type '{0}' cannot be inherited more than once.", init.FullSignature); result.ErrorIndex = (index - (accum.Length - 1)) + (init.ErrorIndex); result.Success = false; return(result); } state = States.EndOfListWithoutWhereClauses; continue; } if (state == States.AccumulatingTypeConstraint || state == States.AfterConstraintColon || (state == States.AccumulatingGenericPart && (stateBeforeGenericPart == States.AccumulatingTypeConstraint))) { var word = accum.Trim(); string err; CSharpTypeConstraintValidationResult init; if (!IsValidTypeConstraint(word, out err, out init)) { result.ErrorDescription = err; result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } if (!typeConstraints.Last().Add(init)) { result.ErrorDescription = string.Format( "Type constraint '{0}' cannot be used more than once for generic parameter '{1}'.", init.ConstraintString, constrainedTypeParameters.Last()); result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } state = States.EndOfListWithWhereClauses; continue; } } if (c == '<') { if (state == States.AccumulatingFirstWord || state == States.AccumulatingTypeConstraint || state == States.AccumulatingInheritedType) { stateBeforeGenericPart = state; state = States.AccumulatingGenericPart; unmatchedGenericBraces++; continue; } if (state == States.AccumulatingGenericPart) { unmatchedGenericBraces++; continue; } result.ErrorDescription = string.Format("Unexpected character '{0}'.", c); result.ErrorIndex = index; result.Success = false; return(result); } if (c == '>') { if (state == States.AccumulatingGenericPart) { unmatchedGenericBraces--; if (unmatchedGenericBraces == 0) { state = stateBeforeGenericPart; } continue; } result.ErrorDescription = string.Format("Unexpected character '{0}'.", c); result.ErrorIndex = index; result.Success = false; return(result); } if (c == ',') { if ((state == States.AfterWhereKeyword && inheritedTypes.Count > 0) || state == States.AccumulatingConstraintParam) { result.ErrorDescription = string.Format("Unexpected character '{0}'.", c); result.ErrorIndex = index; result.Success = false; return(result); } if (state == States.AccumulatingTypeConstraint) { var word = accum.TrimEnd(',').Trim(); string err; CSharpTypeConstraintValidationResult init; if (!IsValidTypeConstraint(word, out err, out init)) { result.ErrorDescription = err; result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } if (!typeConstraints.Last().Add(init)) { result.ErrorDescription = string.Format( "Type constraint '{0}' cannot be used more than once for generic parameter '{1}'.", init.ConstraintString, constrainedTypeParameters.Last()); result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } accum = ""; continue; } if (state == States.AccumulatingInheritedType || state == States.AccumulatingFirstWord || (state == States.AfterWhereKeyword && inheritedTypes.Count == 0)) { var type = accum.TrimEnd(',').Trim(); CSharpClassNameValidationResult init; string err; if (!IsValidInheritedType(type, out err, out init)) { result.ErrorDescription = err; result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } if (!inheritedTypes.Add(init)) { result.ErrorDescription = string.Format( "Type '{0}' cannot be inherited more than once.", init.FullSignature); result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } accum = ""; state = States.AfterFirstInheritedType; continue; } } if (c == 'w') { var ahead = index + 5; if (ahead > input.Length) { goto pastWhereCheck; } var lookAheadAsertion = input.Substring(index, 5) == "where"; var lookBehindsertion = index == 0 || char.IsWhiteSpace(input[index - 1]); if (lookAheadAsertion && lookBehindsertion) { if (ahead < input.Length && !char.IsWhiteSpace(input[ahead]) && input[ahead] != ',') { goto pastWhereCheck; } if (state == States.WaitingForFirstWord) { accum = ""; index += 4; state = States.AfterWhereKeyword; //there is an ambiguous case here because you can inherit a class named where, before a where clause occurs if (index + 1 == input.Length) { inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where", false)); state = States.EndOfListWithoutWhereClauses; continue; } bool haveWhitespace = false; for (int i = index + 1; i < input.Length; i++) { var cr = input[i]; if (char.IsWhiteSpace(cr)) { haveWhitespace = true; if (i == input.Length - 1) { inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where", false)); state = States.EndOfListWithoutWhereClauses; break; } continue; } if (cr == 'w' && haveWhitespace) { ahead = i + 5; if (ahead > input.Length) { continue; } lookAheadAsertion = input.Substring(i, 5) == "where"; if (lookAheadAsertion) { if (ahead < input.Length && !char.IsWhiteSpace(input[ahead]) && input[ahead] != ',') { continue; } inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where", false)); index = i + 4; state = States.AfterWhereKeyword; break; } } if (cr == ',') { inheritedTypes.Add(CSharpClassNameValidator.ValidateInitialization("where", false)); index = i; state = States.AccumulatingInheritedType; } break; } continue; } if (state == States.AccumulatingTypeConstraint) { var word = accum.TrimEnd('w').Trim(); string err; CSharpTypeConstraintValidationResult init; if (!IsValidTypeConstraint(word, out err, out init)) { result.ErrorDescription = err; result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } if (!typeConstraints.Last().Add(init)) { result.ErrorDescription = string.Format( "Type constraint '{0}' cannot be used more than once for generic parameter '{1}'.", init.ConstraintString, constrainedTypeParameters.Last()); result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } accum = ""; index += 4; state = States.AfterWhereKeyword; continue; } if (state == States.AccumulatingInheritedType || state == States.AccumulatingFirstWord) { var word = accum.TrimEnd('w').Trim(); CSharpClassNameValidationResult init; string err; if (!IsValidInheritedType(word, out err, out init)) { result.ErrorDescription = err; result.ErrorIndex = (index - (accum.Length - 1)) + (init == null ? 0 : init.ErrorIndex); result.Success = false; return(result); } if (!inheritedTypes.Add(init)) { result.ErrorDescription = string.Format("Type '{0}' cannot be inherited more than once.", init.FullSignature); result.ErrorIndex = (index - (accum.Length - 1)) + (init.ErrorIndex); result.Success = false; return(result); } state = States.AfterWhereKeyword; accum = ""; index += 4; continue; } } } pastWhereCheck: if (c == ':') { if (state == States.AfterWhereKeyword) { result.ErrorDescription = string.Format("Unexpected character '{0}'.", c); result.ErrorIndex = index; result.Success = false; return(result); } if (state == States.AccumulatingConstraintParam) { var constrainedType = accum.TrimEnd(':').Trim(); if (!CSharpIDValidator.IsValidIdentifier(constrainedType)) { result.ErrorDescription = string.Format("Invalid generic type constraint name '{0}'.", constrainedType); result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } if (!constrainedTypeParameters.Add(constrainedType)) { result.ErrorDescription = string.Format( "Generic parameter '{0}' cannot have more than one type constraint list.", constrainedType); result.ErrorIndex = (index - (accum.Length - 1)); result.Success = false; return(result); } typeConstraints.Add(new HashSet <CSharpTypeConstraintValidationResult>()); accum = ""; state = States.AfterConstraintColon; continue; } } if (!char.IsWhiteSpace(c)) { switch (state) { case States.AfterWhereKeyword: accum = "" + c; state = States.AccumulatingConstraintParam; continue; case States.WaitingForFirstWord: accum = "" + c; state = States.AccumulatingFirstWord; continue; case States.AfterFirstInheritedType: accum = "" + c; state = States.AccumulatingInheritedType; continue; case States.AfterConstraintColon: accum = "" + c; state = States.AccumulatingTypeConstraint; continue; } } if (!char.IsWhiteSpace(c)) { continue; } switch (state) { case States.AfterConstraintColon: accum = ""; continue; case States.WaitingForFirstWord: accum = ""; continue; case States.AfterFirstInheritedType: accum = ""; continue; case States.AfterWhereKeyword: accum = ""; break; } } if (state != States.EndOfListWithoutWhereClauses && state != States.EndOfListWithWhereClauses && state != States.WaitingForFirstWord) { result.Success = false; result.ErrorDescription = "Class inheritance list is incomplete."; result.ErrorIndex = input.Length; return(result); } result.ConstrainedTypeParameters = constrainedTypeParameters.ToGenericArray(); result.InheritedTypes = inheritedTypes.ToGenericArray(); result.ParameterConstraints = typeConstraints.Select(x => x.ToGenericArray()).ToGenericArray(); result.Fullsignature = ""; if (result.InheritedTypes.Count > 0) { result.Fullsignature += string.Join(", ", result.InheritedTypes.Select(x => x.FullSignature)); if (result.ConstrainedTypeParameters.Count > 0) { result.Fullsignature += " "; } } result.Fullsignature += string.Join(" ", result.ConstrainedTypeParameters.Select( (x, i) => "where " + result.ConstrainedTypeParameters[i] + " : " + string.Join(", ", result.ParameterConstraints[i].Select(c => c.ConstraintString)))); return(result); }