/// <summary> /// Parses and validates a given CSharp namespace string. /// </summary> /// <param name="namespaceName">A string representing the namespace.</param> /// <returns>A parse/validation results object. <see cref="CSharpNamespaceValidatorResult" /></returns> /// <exception cref="ArgumentNullException"><paramref name="namespaceName"/> is <c>null</c>.</exception> public static CSharpNamespaceValidatorResult Validate(string namespaceName) { if (namespaceName == null) { throw new ArgumentNullException("namespaceName"); } if (DoubleDot.IsMatch(namespaceName)) { return(new CSharpNamespaceValidatorResult(false, "'..' is not valid in a namespace name.")); } var inputs = namespaceName.Split('.'); foreach (var item in inputs) { if (string.IsNullOrWhiteSpace(item)) { return(new CSharpNamespaceValidatorResult(false, "The namespace name is incomplete.")); } if (!CSharpIDValidator.IsValidIdentifier(item)) { return(new CSharpNamespaceValidatorResult(false, string.Format("'{0}' is invalid namespace content.", item))); } } return(new CSharpNamespaceValidatorResult(true)); }
/// <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); }
/// <summary> /// Validates the specified constructor signature string. /// It expects a constructor signature string which starts at the first parenthesis after the constructor's name /// identifier. /// </summary> /// <param name="input">The constructor signature string.</param> /// <param name="validateTypeCallback"> /// The validate type callback, used for additional custom validation of parameter types /// in the constructor signature. /// </param> /// <returns>A parse/validation results object. <see cref="CSharpConstructorSignatureValidationResult" /></returns> /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="input" /> is <c>null</c>.</exception> public static CSharpConstructorSignatureValidationResult Validate(string input, CSharpParsedTypeValidateTypeCallback validateTypeCallback = null) { if (input == null) { throw new ArgumentNullException("input", "Constructor signature string cannot be null!"); } var result = new CSharpConstructorSignatureValidationResult { Success = true, ParameterForwarding = CSharpConstructorParameterForwarding.None }; if (string.IsNullOrWhiteSpace(input)) { result.Success = false; result.ErrorDescription = "Constructor signature cannot be whitespace."; return(result); } States state = States.Start; string accum = ""; var parameterTypes = new GenericArray <CSharpClassNameValidationResult>(); var parameterNames = new HashSet <string>(); var forwardedParameters = new GenericArray <string>(); int unclosedGenericsBrackets = 0; //weeeeeeeeeeeeeeee! for (int index = 0; index < input.Length; index++) { var c = input[index]; accum += c; if (c == '(') { switch (state) { case States.Start: state = States.WaitingForParamType; accum = ""; continue; case States.AccumulatingForwardingKeyword: switch (accum) { case "base(": result.ParameterForwarding = CSharpConstructorParameterForwarding.Base; break; case "this(": result.ParameterForwarding = CSharpConstructorParameterForwarding.This; break; default: result.Success = false; result.ErrorDescription = "Constructor forwarding keyword must be 'base' or 'this'."; result.ErrorIndex = index - 5; return(result); } accum = ""; state = States.WaitingForForwardedParam; continue; case States.AfterForwardingKeyword: state = States.WaitingForForwardedParam; accum = ""; continue; default: result.Success = false; result.ErrorDescription = "Unexpected opening parenthesis."; result.ErrorIndex = index; return(result); } } if (c == ':') { if (state == States.EndOfBasicSignature) { accum = ""; state = States.WaitingForForwardingKeyword; continue; } result.Success = false; result.ErrorDescription = "CSharpIDValidator.IsValidIdentifier:' character."; result.ErrorIndex = index; return(result); } if (c == '<') { if (state == States.AccumulatingParamType) { unclosedGenericsBrackets++; state = States.AccumulatingGenericType; continue; } if (state == States.AccumulatingGenericType) { unclosedGenericsBrackets++; continue; } result.Success = false; result.ErrorDescription = "CSharpIDValidator.IsValidIdentifier<' character."; result.ErrorIndex = index; return(result); } if (c == '>') { if (state == States.AccumulatingGenericType) { unclosedGenericsBrackets--; if (unclosedGenericsBrackets == 0) { state = States.AccumulatingParamType; } continue; } result.Success = false; result.ErrorDescription = "CSharpIDValidator.IsValidIdentifier>' character."; result.ErrorIndex = index; return(result); } if (c == ',' || c == ')') { switch (state) { case States.WaitingForParamType: if (parameterTypes.Count > 0 || (parameterTypes.Count == 0 && c == ',')) { result.Success = false; result.ErrorDescription = "Missing parameter declaration."; result.ErrorIndex = index - 1; return(result); } accum = ""; state = States.EndOfBasicSignature; continue; case States.WaitingForForwardedParam: if (forwardedParameters.Count > 0 || (forwardedParameters.Count == 0 && c == ',')) { result.Success = false; result.ErrorDescription = "Missing forwarded argument declaration."; result.ErrorIndex = index; return(result); } accum = ""; state = States.EndOfForwardingSignature; continue; case States.AccumulatingParamType: result.Success = false; result.ErrorDescription = "Missing parameter name."; result.ErrorIndex = index; return(result); case States.AccumulatingParamName: { var pname = accum.TrimEnd(',', ')').Trim(); if (!CSharpIDValidator.IsValidIdentifier(pname)) { result.Success = false; result.ErrorDescription = string.Format("Invalid parameter name '{0}'.", pname); result.ErrorIndex = index - accum.Length; return(result); } if (!parameterNames.Contains(pname)) { parameterNames.Add(pname); } else { result.Success = false; result.ErrorDescription = string.Format("Parameter name '{0}' used more than once.", pname); result.ErrorIndex = index - accum.Length; return(result); } accum = ""; state = c == ',' ? States.WaitingForParamType : States.EndOfBasicSignature; continue; } case States.AccumulatingForwardedParam: { var pname = accum.TrimEnd(',', ')').Trim(); if (!CSharpIDValidator.IsValidIdentifier(pname)) { result.Success = false; result.ErrorDescription = string.Format( "Invalid forwarded parameter '{0}', invalid identifier syntax.", pname); result.ErrorIndex = index - accum.Length; return(result); } if (!parameterNames.Contains(pname)) { result.Success = false; result.ErrorDescription = string.Format( "Invalid forwarded parameter '{0}', name was not previously declared.", pname); result.ErrorIndex = index - accum.Length; return(result); } forwardedParameters.Add(pname); accum = ""; state = c == ',' ? States.WaitingForForwardedParam : States.EndOfForwardingSignature; continue; } default: if (c == ',' && state == States.AccumulatingGenericType) { continue; } result.Success = false; result.ErrorDescription = string.Format( "CSharpIDValidator.IsValidIdentifier{0}' character.", c); result.ErrorIndex = index; return(result); } } if (!char.IsWhiteSpace(c)) { switch (state) { case States.EndOfBasicSignature: case States.EndOfForwardingSignature: result.Success = false; result.ErrorDescription = string.Format("Unexpected character '{0}' after signature.", c); result.ErrorIndex = index; return(result); case States.Start: result.Success = false; result.ErrorDescription = string.Format("Unexpected character '{0}', was expecting '('.", c); result.ErrorIndex = index; return(result); case States.WaitingForParamType: state = States.AccumulatingParamType; accum = "" + c; continue; case States.WaitingForParameterName: state = States.AccumulatingParamName; accum = "" + c; continue; case States.WaitingForForwardingKeyword: state = States.AccumulatingForwardingKeyword; accum = "" + c; continue; case States.WaitingForForwardedParam: state = States.AccumulatingForwardedParam; accum = "" + c; continue; } } if (char.IsWhiteSpace(c)) { switch (state) { case States.WaitingForParamType: case States.WaitingForParameterName: case States.EndOfBasicSignature: case States.WaitingForForwardingKeyword: case States.AfterForwardingKeyword: case States.WaitingForForwardedParam: accum = ""; continue; case States.AccumulatingForwardingKeyword: switch (accum) { case "base ": result.ParameterForwarding = CSharpConstructorParameterForwarding.Base; break; case "this ": result.ParameterForwarding = CSharpConstructorParameterForwarding.This; break; default: result.Success = false; result.ErrorDescription = "Constructor forwarding keyword must be 'base' or 'this'."; result.ErrorIndex = index - 5; return(result); } accum = ""; state = States.AfterForwardingKeyword; continue; case States.AccumulatingParamType: var ptype = accum.TrimEnd(); var validateParameter = CSharpClassNameValidator.ValidateInitialization(accum.TrimEnd(), true, validateTypeCallback); if (!validateParameter.Success) { result.Success = false; result.ErrorDescription = string.Format("Invalid parameter type '{0}': {1}", ptype, validateParameter.ErrorDescription); result.ErrorIndex = (index - accum.Length) + validateParameter.ErrorIndex + 1; return(result); } parameterTypes.Add(validateParameter); state = States.WaitingForParameterName; accum = ""; continue; } } } if (state != States.EndOfBasicSignature && state != States.EndOfForwardingSignature) { result.Success = false; result.ErrorDescription = "Incomplete constructor signature."; result.ErrorIndex = input.Length - 1; return(result); } result.ParameterTypes = parameterTypes; result.ParameterNames = parameterNames.ToGenericArray(); result.ForwardedParameters = forwardedParameters; return(result); }
/// <summary> /// Parses and validates a string as a CSharp method call. /// </summary> /// <param name="signature">The method call signature, without a semi-colon at the end.</param> /// <returns>A parse/validation results object. <see cref="CSharpFunctionCallValidationResult" /></returns> /// <exception cref="ArgumentNullException"><paramref name="signature"/> is <c>null</c>.</exception> public static CSharpFunctionCallValidationResult Validate(string signature) { if (signature == null) { throw new ArgumentNullException("signature"); } var result = new CSharpFunctionCallValidationResult { Success = true }; var explicitGenericParameters = new GenericArray <CSharpClassNameValidationResult>(); var parameters = new GenericArray <CSharpParameterSignature>(); var lastModifier = CSharpParameterModifier.None; var state = States.WaitingForFirstCharacter; var stateBeforeBrackets = new Stack <States>(); var stateBeforeGenericTypePart = new Stack <States>(); var stateBeforeParenthesis = new Stack <States>(); var stateBeforeCurlyBraces = new Stack <States>(); string accum = ""; int unmatchedGenericBrackets = 0; int unmatchedParenthesis = 0; int unmatchedCurlyBraces = 0; int unmatchedBrackets = 0; string methodName = ""; for (int index = 0; index < signature.Length; index++) { var c = signature[index]; accum += c; if (c == 'o' || c == 'r' || c == 'n') { int wordBoundry = index + 2; int ahead = wordBoundry + 1; if (wordBoundry >= signature.Length) { goto afterRefOutChecks; } var lookAhead = signature.Substring(index, 3); var lookAheadAssertion = lookAhead == "out" || lookAhead == "ref" || lookAhead == "new"; if (ahead < signature.Length && !char.IsWhiteSpace(signature[ahead])) { goto afterRefOutChecks; } if (lookAheadAssertion && state == States.WaitingForParameterName && lastModifier == CSharpParameterModifier.None) { lastModifier = lookAhead == "out" ? CSharpParameterModifier.Out : (lookAhead == "ref" ? CSharpParameterModifier.Ref : CSharpParameterModifier.New); accum = ""; index = wordBoundry; state = States.WaitingForParameterName; continue; } } afterRefOutChecks: if (c == '[') { if (state == States.AccumulatingParameter || state == States.WaitingForParameterName) { stateBeforeBrackets.Push(States.AccumulatingParameter); state = States.InBracketExpression; unmatchedBrackets++; continue; } if (state == States.InBracketExpression || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression) { unmatchedBrackets++; continue; } } if (c == ']') { if (state == States.InBracketExpression || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression) { unmatchedBrackets--; if (unmatchedBrackets < 0) { result.Success = false; result.ErrorDescription = "Unexpected closing bracket."; result.ErrorIndex = index; return(result); } if (unmatchedBrackets == 0) { if (stateBeforeBrackets.Count > 0) { state = stateBeforeBrackets.Pop(); } else if (state == States.InBracketExpression) { goto unexpectedBracket; } } continue; unexpectedBracket: result.Success = false; result.ErrorDescription = "Unexpected closing bracket."; result.ErrorIndex = index; return(result); } } if (c == '{') { if (state == States.AccumulatingParameter || state == States.WaitingForParameterName) { stateBeforeCurlyBraces.Push(States.AccumulatingParameter); state = States.InCurlyBraceExpression; unmatchedCurlyBraces++; continue; } if (state == States.InBracketExpression || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression) { unmatchedCurlyBraces++; continue; } } if (c == '}') { if (state == States.InBracketExpression || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression) { unmatchedCurlyBraces--; if (unmatchedCurlyBraces < 0) { result.Success = false; result.ErrorDescription = "Unexpected closing brace."; result.ErrorIndex = index; return(result); } if (unmatchedCurlyBraces == 0) { if (stateBeforeCurlyBraces.Count > 0) { state = stateBeforeCurlyBraces.Pop(); } else if (state == States.InCurlyBraceExpression) { goto unexpectedCurlyBrace; } } continue; } unexpectedCurlyBrace: result.Success = false; result.ErrorDescription = "Unexpected closing brace."; result.ErrorIndex = index; return(result); } if (c == ')') { if (state == States.WaitingForParameterName && parameters.Count == 0) { if (lastModifier == CSharpParameterModifier.None) { state = States.AfterSignature; accum = ""; } //otherwise the signature is incomplete continue; } if (state == States.InBracketExpression || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression) { unmatchedParenthesis--; if (unmatchedParenthesis < 0) { result.Success = false; result.ErrorDescription = "Unexpected closing parenthesis."; result.ErrorIndex = index; return(result); } if (unmatchedParenthesis == 0) { if (stateBeforeParenthesis.Count > 0) { state = stateBeforeParenthesis.Pop(); } else if (state == States.InParenthesizedExpression) { goto unexpectedParenth; } } continue; } if (state == States.AccumulatingParameter) { var param = accum.Substring(0, accum.Length - 1).Trim(); if (!CSharpIDValidator.IsValidIdentifier(param) && (lastModifier == CSharpParameterModifier.Ref || lastModifier == CSharpParameterModifier.Out)) { result.Success = false; result.ErrorDescription = "'ref' and 'out' can only be used with a direct variable reference."; result.ErrorIndex = index - (accum.Length - 1); return(result); } string err; int errIndex; if (lastModifier == CSharpParameterModifier.New && !IsValidNewParameter(param, index - (accum.Length - 1), out err, out errIndex)) { result.Success = false; result.ErrorDescription = err; result.ErrorIndex = errIndex; return(result); } if (lastModifier == CSharpParameterModifier.None && !IsValidPlainParameter(param, index - (accum.Length - 1), out err, out errIndex)) { result.Success = false; result.ErrorDescription = err; result.ErrorIndex = errIndex; return(result); } parameters.Add(new CSharpParameterSignature(param, lastModifier)); accum = ""; lastModifier = CSharpParameterModifier.None; state = States.AfterSignature; continue; } unexpectedParenth: result.Success = false; result.ErrorDescription = "Unexpected closing parenthesis."; result.ErrorIndex = index; return(result); } if (c == '(') { if (state == States.AccumulatingParameter || state == States.WaitingForParameterName) { stateBeforeParenthesis.Push(States.AccumulatingParameter); state = States.InParenthesizedExpression; unmatchedParenthesis++; continue; } if (state == States.AfterExplicitGenericMethodParameters) { accum = ""; state = States.WaitingForParameterName; continue; } if (state == States.AccumulatingMethodNamePart) { methodName = accum.TrimEnd('(').Trim(); accum = ""; state = States.WaitingForParameterName; continue; } if (state == States.InBracketExpression || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression) { unmatchedParenthesis++; continue; } result.Success = false; result.ErrorDescription = "Unexpected opening parenthesis."; result.ErrorIndex = index; return(result); } if (c == ',') { if (state == States.InBracketExpression || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression) { continue; } if (state == States.AccumulatingExplicitGenericMethodParameters) { var param = accum.TrimEnd(',').Trim(); var validate = CSharpClassNameValidator.ValidateInitialization(param, true); if (!validate.Success) { result.Success = false; result.ErrorDescription = validate.ErrorDescription; result.ErrorIndex = (index - (accum.Length - 1)) + validate.ErrorIndex; return(result); } explicitGenericParameters.Add(validate); accum = ""; continue; } if (state == States.AccumulatingParameter) { var param = accum.TrimEnd(',').Trim(); if (!CSharpIDValidator.IsValidIdentifier(param) && (lastModifier == CSharpParameterModifier.Ref || lastModifier == CSharpParameterModifier.Out)) { result.Success = false; result.ErrorDescription = "'ref' and 'out' can only be used with a direct variable reference."; result.ErrorIndex = index - (accum.Length - 1); return(result); } string err; int errIndex; if (lastModifier == CSharpParameterModifier.New && !IsValidNewParameter(param, index - (accum.Length - 1), out err, out errIndex)) { result.Success = false; result.ErrorDescription = err; result.ErrorIndex = errIndex; return(result); } if (lastModifier == CSharpParameterModifier.None && !IsValidPlainParameter(param, index - (accum.Length - 1), out err, out errIndex)) { result.Success = false; result.ErrorDescription = err; result.ErrorIndex = errIndex; return(result); } state = States.WaitingForParameterName; parameters.Add(new CSharpParameterSignature(param, lastModifier)); lastModifier = CSharpParameterModifier.None; accum = ""; continue; } result.Success = false; result.ErrorDescription = "Unexpected ',' character."; result.ErrorIndex = index; return(result); } if (c == '<') { if (state == States.AccumulatingParameter || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression || state == States.InBracketExpression) { continue; } if (state == States.AccumulatingMethodNamePart) { methodName = accum.TrimEnd('<').Trim(); accum = ""; state = States.AccumulatingExplicitGenericMethodParameters; continue; } if (state == States.AccumulatingExplicitGenericMethodParameters) { unmatchedGenericBrackets++; stateBeforeGenericTypePart.Push(state); state = States.AccumulatingGenericTypePart; continue; } if (state == States.AccumulatingGenericTypePart) { unmatchedGenericBrackets++; continue; } result.Success = false; result.ErrorDescription = "Unexpected '<' character."; result.ErrorIndex = index; return(result); } if (c == '>') { if (state == States.AccumulatingParameter || state == States.InCurlyBraceExpression || state == States.InParenthesizedExpression || state == States.InBracketExpression) { continue; } if (state == States.AccumulatingGenericTypePart) { unmatchedGenericBrackets--; if (unmatchedGenericBrackets == 0) { state = stateBeforeGenericTypePart.Pop(); } continue; } if (state == States.AccumulatingExplicitGenericMethodParameters) { var param = accum.Substring(0, accum.Length > 0 ? accum.Length - 1 : 0).Trim(); var validate = CSharpClassNameValidator.ValidateInitialization(param, true); if (!validate.Success) { result.Success = false; result.ErrorDescription = validate.ErrorDescription; result.ErrorIndex = (index - (accum.Length - 1)) + validate.ErrorIndex; return(result); } explicitGenericParameters.Add(validate); accum = ""; state = States.AfterExplicitGenericMethodParameters; continue; } result.Success = false; result.ErrorDescription = "Unexpected '>' character."; result.ErrorIndex = index; return(result); } if (!char.IsWhiteSpace(c)) { if (state == States.AccumulatingParameter || state == States.AccumulatingExplicitGenericMethodParameters || state == States.AccumulatingMethodNamePart || state == States.AccumulatingGenericTypePart || state == States.InCurlyBraceExpression || state == States.InBracketExpression || state == States.InParenthesizedExpression) { continue; } if (state == States.WaitingForParameterName) { accum = "" + c; state = States.AccumulatingParameter; continue; } if (state == States.WaitingForFirstCharacter) { accum = "" + c; state = States.AccumulatingMethodNamePart; continue; } result.Success = false; result.ErrorDescription = string.Format("Unexpected '{0}' character.", c); result.ErrorIndex = index; return(result); } } if (state != States.AfterSignature) { result.Success = false; result.ErrorDescription = "Call signature incomplete."; result.ErrorIndex = signature.Length - 1; return(result); } result.MethodName = methodName; result.Parameters = parameters; result.ExplicitGenericParameters = explicitGenericParameters; return(result); }
private static bool IsValidNewParameter(string paramText, int paramStartIndex, out string err, out int errIndex) { errIndex = paramStartIndex; err = null; char startChar = paramText[0]; if (CSharpIDValidator.IsValidIdentifier(paramText)) { errIndex = paramStartIndex; err = "'new' keyword cannot be used with a variable reference."; return(false); } if (char.IsDigit(startChar)) { errIndex = paramStartIndex; err = "'new' keyword cannot be used on a numeric literal."; return(false); } if (startChar == '"' || paramText.StartsWith("@\"")) { errIndex = paramStartIndex; err = "'new' keyword cannot be used on a string literal."; return(false); } if (startChar == '\'') { errIndex = paramStartIndex; err = "'new' keyword cannot be used on a character literal."; return(false); } string testp = string.Format("class P{{void F(object a){{}}P(){{F(new {0});}}}}", paramText); const int pstartIndex = 36; using (var compiler = CodeDomProvider.CreateProvider("CSharp")) { var cParams = new CompilerParameters(new string[] {}) { GenerateInMemory = true, GenerateExecutable = false }; var r = compiler.CompileAssemblyFromSource(cParams, testp); //all errors except references to undefined variable/method/type references //CS0103: is undefined variable references //CS0246: is undefined type references var errs = r.Errors.Cast <CompilerError>().ToList(); var relevantErrors = errs.Where( x => x.ErrorNumber != "CS0103" && x.ErrorNumber != "CS0246").ToList(); if (relevantErrors.Count > 0) { err = relevantErrors[0].ErrorText; errIndex = (relevantErrors[0].Column - pstartIndex) + paramStartIndex; return(false); } } return(true); }
private static CSharpClassNameValidationResult _Validate(string input, ClassSigType signatureType, bool allowBuiltinAliases, CSharpParsedTypeValidateTypeCallback validateTypeCallback, int index) { if (input == null) { throw new ArgumentNullException("input", "Class name/signature string cannot be null!"); } if (string.IsNullOrWhiteSpace(input)) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "Class name/signature cannot be whitespace.", ErrorIndex = index, }); } string fullyQualifiedName = ""; var genericArgs = new GenericArray <CSharpClassNameValidationResult>(); var qualifications = new GenericArray <Qualification>() { new Qualification(new StringBuilder(), index) }; string genericPart = ""; bool isGeneric = false; int genericBrackets = 0; foreach (var c in input) { //enter generic arguments if (c == '<') { isGeneric = true; genericBrackets++; } //check if we have gotten to the generic part of the type signature yet (if any) if (!isGeneric) { if (c == '.') { //qualifier operator is not allowed anywhere in declaration signatures because //they only consist of a raw type name and generic argument specifications if (signatureType == ClassSigType.Declaration) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format( "'.' name qualifier operator is not valid in a class declaration/generic " + "type placeholder name."), ErrorIndex = index, }); } //detect a missing qualifier section that's terminated with a dot. if (string.IsNullOrWhiteSpace(qualifications.Last().ToString())) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "\'..\' is an invalid use of the qualification operator.", ErrorIndex = index, }); } qualifications.Add(new Qualification(new StringBuilder(), index + 1)); } else { qualifications.Last().Builder.Append(c); } } if (genericBrackets == 0 && !isGeneric) { //accumulate to our fully qualified name fullyQualifiedName += c; } else if (genericBrackets == 0 && isGeneric) { //we have exited even pairs of brackets and are on the //other side of the generic arguments at the end of the signature //there should not be anything here return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "extra content found after generic argument list.", ErrorIndex = index }); } else if (!(genericBrackets == 1 && c == '<')) { //passed the start of the first < in the signature by 1 if ((c == ',' || c == '>') && genericBrackets == 1) { //we have accumulated a type argument suitable for recursive decent //validate it recursively var validateGenericArgument = _Validate(genericPart.Trim(), signatureType, allowBuiltinAliases, validateTypeCallback, index - genericPart.Length); //return immediately on failure if (!validateGenericArgument.Success) { return(validateGenericArgument); } //add the validated 'tree' genericArgs.Add(validateGenericArgument); //reset the accumulator genericPart = ""; } else { //accumulate until we hit a comma or the > character genericPart += c; } } if (c == '>') { //exit a generic type scope genericBrackets--; } index++; } if (genericBrackets > 0) { //something is amiss with bracket matching return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "mismatched generic type brackets.", ErrorIndex = index }); } //there may be only one qualification, in which case //the base name is also the fully qualified name. var baseName = qualifications.Last(); if (qualifications.Count > 1) { //uses qualified names, this is definitely an initialization signature //because the qualifier '.' operator returns an error in declaration signatures //above the recursive call to validate foreach (var name in qualifications) { if (string.IsNullOrWhiteSpace(name.Builder.ToString())) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("qualified type name '{0}' is incomplete.", fullyQualifiedName), ErrorIndex = name.StartIndex }); } } foreach (var name in qualifications) { //check for syntax errors in the qualified name pieces, they need to be valid ID's and not keywords //IsValidIdentifier takes care of both these criteria if (!CSharpIDValidator.IsValidIdentifier(name.Builder.ToString())) { //sound something funky return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format( "'{0}' is not valid in the given qualified type name '{1}'.", name.Builder, fullyQualifiedName), ErrorIndex = name.StartIndex }); } } } else { var shortName = baseName.Builder.ToString(); //single type argument to a generic type if (string.IsNullOrWhiteSpace(shortName)) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("missing generic {0} name.", signatureType == ClassSigType.Initialization ? "type argument" : "placeholder type"), ErrorIndex = baseName.StartIndex, }); } bool aliasInitialization = allowBuiltinAliases && CSharpKeywords.BuiltInTypeMap.ContainsKey(shortName) && signatureType == ClassSigType.Initialization; if (!aliasInitialization && !CSharpIDValidator.IsValidIdentifier(baseName.Builder.ToString())) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("'{0}' is not an allowed CSharp identifier.", baseName.Builder), ErrorIndex = baseName.StartIndex }); } } var baseNameString = baseName.Builder.ToString(); if (isGeneric && CSharpKeywords.IsTypeAliasKeyword(fullyQualifiedName)) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("Built in type alias '{0}' is not a generic type.", baseName.Builder), ErrorIndex = baseName.StartIndex }); } //success var classDescription = new CSharpClassNameValidationResult() { QualifiedName = fullyQualifiedName, BaseName = baseNameString, GenericArguments = genericArgs, IsGeneric = isGeneric, Success = true }; if (isGeneric) { classDescription.FullSignature = fullyQualifiedName + "<" + string.Join(", ", classDescription.GenericArguments.Select(x => x.FullSignature)) + ">"; } else { classDescription.FullSignature = fullyQualifiedName; } if (validateTypeCallback == null || signatureType != ClassSigType.Initialization) { return(classDescription); } var typeCheckResult = validateTypeCallback(classDescription); if (typeCheckResult.IsValid) { return(classDescription); } classDescription.FullSignature = null; classDescription.ErrorIndex = qualifications.First().StartIndex; classDescription.ErrorDescription = typeCheckResult.ErrorMessage; classDescription.Success = false; return(classDescription); }