2009-12-01 18 views
20

Poszukuję szybkiego sposobu (w języku C#), aby ustalić, czy ciąg znaków jest poprawną nazwą zmiennej. Moją pierwszą intuicją jest pobudzić jakiś regex, żeby to zrobić, ale zastanawiam się, czy jest lepszy sposób na zrobienie tego. Może jakaś tajna metoda ukryta głęboko pod nazwą IsThisAValidVariableName (nazwa napisu) lub inna łatwa do zrobienia metoda, która nie jest podatna na błędy, które mogą powstać z powodu braku sprawności wyrażenia regularnego.Jak ustalić, czy ciąg znaków jest poprawną nazwą zmiennej?

+0

masz na myśli C# nazwę zmiennej? I myślę, że regex to twój najlepszy zakład, chyba że rzucisz swoją małą parserem (co jest przesadą dla tak małej rzeczy do sprawdzenia). – Earlz

+0

Jedna rzecz, na którą należy zwrócić uwagę, jeśli używasz wyrażenia regularnego, jest to, że istnieje kilka klas znaków uinicode być może trzeba będzie wziąć pod uwagę: http://msdn.microsoft.com/en-us/library/aa664670%28VS.71%29.aspx –

+0

+1 brak zaufania do premii regex – bobince

Odpowiedz

42

Spróbuj tego:

// using System.CodeDom.Compiler; 
CodeDomProvider provider = CodeDomProvider.CreateProvider("C#"); 
if (provider.IsValidIdentifier (YOUR_VARIABLE_NAME)) { 
     // Valid 
} else { 
     // Not valid 
} 
+1

Musisz odwołać się do Przestrzeń nazw 'System.CodeDom.Compiler' dla tego :-) – CesarGon

+15

Tak. Musisz również umieścić ten kod w metodzie, a metodę w klasie i zmiennej o nazwie YOUR_VARIABLE_NAME i ...;-) – Gonzalo

+1

Jak drogi jest "CodeDomProvider"? –

1

dłuższej drogi, plus jest znacznie wolniejsze, jest użycie refleksji iteracyjne nad członków klasy/nazw i porównać przez sprawdzenie czy odbitego członka ** toString (.) ** jest takie samo jak wejście łańcuchowe, wymaga to wcześniejszego wczytania zespołu.

Innym sposobem wykonania tej czynności (o wiele dłuższą drogą, która pokonuje użycie wyrażenia regularnego, przy użyciu już dostępnego skanera/parsera Antlr), jest graniczenie z parsowaniem/kopiowaniem kodu C#, a następnie skanowanie pod kątem nazw elementów (tj. Zmiennych) i porównując do ciągu znaków użytego jako dane wejściowe, na przykład wprowadź ciąg o nazwie "fooBar", a następnie podaj źródło (na przykład kod zespołu lub kod C#) i zeskanuj go, analizując szukając konkretnie deklaracji członków, takich jak na przykład

 
private int fooBar; 

Tak, jest to skomplikowane, ale pojawi się potężne zrozumienie, gdy zdasz sobie sprawę z tego, co robią autorzy kompilatorów, i zwiększysz swoją znajomość języka C# do poziomu, w którym będziesz bardzo intymny z składnia i jej cechy szczególne.

+0

Źle zrozumiałem pytanie. –

1

Istnieje kilka szczególnych przypadkach wokół postaci @, które są łatwe do zapomnij sprawdzić - mianowicie '@' sama w sobie nie jest poprawnym identyfikatorem, a nie jest "@1foo". Aby je złapać, możesz najpierw sprawdzić, czy ciąg znaków jest słowem kluczowym, a następnie usunąć @ od początku łańcucha, a następnie sprawdzić, czy to, co pozostało, jest prawidłowym identyfikatorem (nie dopuszczając znaków @).

Połączyłem to z metodą analizowania sekwencji unikodowych Unicode w identyfikatorach i miejmy nadzieję, że zakończono sprawdzanie znaków w kodzie Unicode w C# (5.0). Aby z niego skorzystać, najpierw zadzwoń pod numer TryParseRawIdentifier(), aby obsłużyć słowa kluczowe, sekwencje specjalne, znaki formatujące (które zostały usunięte) oraz dokładne identyfikatory. Następnie przekaż wynik do IsValidParsedIdentifier(), aby sprawdzić, czy pierwszy i kolejne znaki są poprawne. Zwróć uwagę, że ciągi znaków zwrócone od TryParseRawIdentifier() są równe wtedy i tylko wtedy, gdy identyfikatory są uważane za identyczne przez C#.

public static class CSharpIdentifiers 
{ 
    private static HashSet<string> _keywords = new HashSet<string> { 
     "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", 
     "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", 
     "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", 
     "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", 
     "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", 
     "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", 
     "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", 
     "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", 
     "virtual", "void", "volatile", "while" 
    }; 

    public static IReadOnlyCollection<string> Keywords { get { return _keywords; } } 


    public static bool TryParseRawIdentifier(string str, out string parsed) 
    { 
     if (string.IsNullOrEmpty(str) || _keywords.Contains(str)) { parsed = null; return false; } 

     StringBuilder sb = new StringBuilder(str.Length); 

     int verbatimCharWidth = str[0] == '@' ? 1 : 0; 

     for (int i = verbatimCharWidth; i < str.Length;) //Manual increment 
     { 
      char c = str[i]; 

      if (c == '\\') 
      { 
       char next = str[i + 1]; 

       int charCodeLength; 
       if (next == 'u') charCodeLength = 4; 
       else if (next == 'U') charCodeLength = 8; 
       else { parsed = null; return false; } 
       //No need to check for escaped backslashes or special sequences like \n, 
       //as they not valid identifier characters 

       int charCode; 
       if (!TryParseHex(str.Substring(i + 2, charCodeLength), out charCode)) { parsed = null; return false; } 

       sb.Append(char.ConvertFromUtf32(charCodeLength)); //Handle characters above 2^16 by converting them to a surrogate pair 
       i += 2 + charCodeLength; 
      } 
      else if (char.GetUnicodeCategory(str, i) == UnicodeCategory.Format) 
      { 
       //Use (string, index) in order to handle surrogate pairs 
       //Skip this character 
       if (char.IsSurrogatePair(str, i)) i += 2; 
       else i += 1; 
      } 
      else 
      { 
       sb.Append(c); 
       i++; 
      } 
     } 

     parsed = sb.ToString(); 
     return true; 
    } 

    private static bool TryParseHex(string str, out int result) 
    { 
     return int.TryParse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out result); 
     //NumberStyles.AllowHexSpecifier forces all characters to be hex digits 
    } 

    public static bool IsValidParsedIdentifier(string str) 
    { 
     if (string.IsNullOrEmpty(str)) return false; 

     if (!IsValidParsedIdentifierStart(str, 0)) return false; 

     int firstCharWidth = char.IsSurrogatePair(str, 0) ? 2 : 1; 

     for (int i = firstCharWidth; i < str.Length;) //Manual increment 
     { 
      if (!IsValidParsedIdentifierPart(str, i)) return false; 
      if (char.IsSurrogatePair(str, i)) i += 2; 
      else i += 1; 
     } 

     return true; 
    } 

    //(String, index) pairs are used instead of chars in order to support surrogate pairs 
    //(Unicode code-points above 2^16 represented using two 16-bit characters) 

    public static bool IsValidParsedIdentifierStart(string s, int index) 
    { 
     return s[index] == '_' || char.IsLetter(s, index) || char.GetUnicodeCategory(s, index) == UnicodeCategory.LetterNumber; 
    } 

    public static bool IsValidParsedIdentifierPart(string s, int index) 
    { 
     if (s[index] == '_' || (s[index] >= '0' && s[index] <= '9') || char.IsLetter(s, index)) return true; 

     switch (char.GetUnicodeCategory(s, index)) 
     { 
      case UnicodeCategory.LetterNumber: //Eg. Special Roman numeral characters (not covered by IsLetter()) 
      case UnicodeCategory.DecimalDigitNumber: //Includes decimal digits in other cultures 
      case UnicodeCategory.ConnectorPunctuation: 
      case UnicodeCategory.NonSpacingMark: 
      case UnicodeCategory.SpacingCombiningMark: 
      //UnicodeCategory.Format handled in TryParseRawIdentifier() 
       return true; 
      default: 
       return false; 
     } 
    } 
} 
0
public static bool IsIdentifier(string text) 
    { 
    if (string.IsNullOrEmpty(text)) 
     return false; 
    if (!char.IsLetter(text[0]) && text[0] != '_') 
     return false; 
    for (int ix = 1; ix < text.Length; ++ix) 
     if (!char.IsLetterOrDigit(text[ix]) && text[ix] != '_') 
      return false; 
    return true; 
    } 
Powiązane problemy