Logo Search packages:      
Sourcecode: monodevelop version File versions  Download package

ExpressionFinder.cs

// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
//     <version>$Revision: 915 $</version>
// </file>

using System;
using System.Text;
using MonoDevelop.Projects.Parser;

namespace CSharpBinding.Parser
{
      /// <summary>
      /// Description of ExpressionFinder.
      /// </summary>
00017       public class ExpressionFinder : IExpressionFinder
      {
            string fileName;
            
            public ExpressionFinder(string fileName)
            {
                  this.fileName = fileName;
            }
            
            #region Capture Context
            ExpressionResult CreateResult(string expression, string inText, int offset)
            {
                  if (expression == null)
                        return new ExpressionResult(null);
                  if (expression.StartsWith("using "))
                        return new ExpressionResult(expression.Substring(6).TrimStart(), ExpressionContext.Namespace, null);
                  if (!hadParenthesis && expression.StartsWith("new ")) {
                        return new ExpressionResult(expression.Substring(4).TrimStart(), GetCreationContext(), null);
                  }
                  if (IsInAttribute(inText, offset))
                        return new ExpressionResult(expression, ExpressionContext.Attribute);
                  return new ExpressionResult(expression);
            }
            
            ExpressionContext GetCreationContext()
            {
                  return null;
            }
            
            bool IsInAttribute(string txt, int offset)
            {
                  // Get line start:
                  int lineStart = offset;
                  while (--lineStart > 0 && txt[lineStart] != '\n');
                  
                  bool inAttribute = false;
                  int parens = 0;
                  for (int i = lineStart + 1; i < offset; i++) {
                        char ch = txt[i];
                        if (char.IsWhiteSpace(ch))
                              continue;
                        if (!inAttribute) {
                              // outside attribute
                              if (ch == '[')
                                    inAttribute = true;
                              else
                                    return false;
                        } else if (parens == 0) {
                              // inside attribute, outside parameter list
                              if (ch == ']')
                                    inAttribute = false;
                              else if (ch == '(')
                                    parens = 1;
                              else if (!char.IsLetterOrDigit(ch) && ch != ',')
                                    return false;
                        } else {
                              // inside attribute, inside parameter list
                              if (ch == '(')
                                    parens++;
                              else if (ch == ')')
                                    parens--;
                        }
                  }
                  return inAttribute && parens == 0;
            }
            #endregion
            
            #region RemoveLastPart
            /// <summary>
            /// Removed the last part of the expression.
            /// </summary>
            /// <example>
            /// "arr[i]" => "arr"
            /// "obj.Field" => "obj"
            /// "obj.Method(args,...)" => "obj.Method"
            /// </example>
00093             public string RemoveLastPart(string expression)
            {
                  text = expression;
                  offset = text.Length - 1;
                  ReadNextToken();
                  if (curTokenType == Ident && Peek() == '.')
                        GetNext();
                  return text.Substring(0, offset + 1);
            }
            #endregion
            
            #region Find Expression
            public ExpressionResult FindExpression(string inText, int offset)
            {
                  inText = FilterComments(inText, ref offset);
                  return CreateResult(FindExpressionInternal(inText, offset), inText, offset);
            }
            
            public string FindExpressionInternal(string inText, int offset)
            {
                  // warning: Do not confuse this.offset and offset
                  this.text = inText;
                  this.offset = this.lastAccept = offset;
                  this.state  = START;
                  hadParenthesis = false;
                  if (this.text == null) {
                        return null;
                  }
                  
                  while (state != ERROR) {
                        ReadNextToken();
                        state = stateTable[state, curTokenType];
                        
                        if (state == ACCEPT || state == ACCEPT2) {
                              lastAccept = this.offset;
                        }
                        if (state == ACCEPTNOMORE) {
                              lastExpressionStartPosition = this.offset + 1;
                              return this.text.Substring(this.offset + 1, offset - this.offset);
                        }
                  }
                  
                  if (lastAccept < 0)
                        return null;
                  
                  lastExpressionStartPosition = this.lastAccept + 1;
                  
                  return this.text.Substring(this.lastAccept + 1, offset - this.lastAccept);
            }
            
            int lastExpressionStartPosition;
            
            internal int LastExpressionStartPosition {
                  get {
                        return lastExpressionStartPosition;
                  }
            }
            #endregion
            
            #region FindFullExpression
            public ExpressionResult FindFullExpression(string inText, int offset)
            {
                  int offsetWithoutComments = offset;
                  string textWithoutComments = FilterComments(inText, ref offsetWithoutComments);
                  string expressionBeforeOffset = FindExpressionInternal(textWithoutComments, offsetWithoutComments);
                  if (expressionBeforeOffset == null || expressionBeforeOffset.Length == 0)
                        return CreateResult(null, textWithoutComments, offsetWithoutComments);
                  StringBuilder b = new StringBuilder(expressionBeforeOffset);
                  // append characters after expression
                  bool wordFollowing = false;
                  int i;
                  for (i = offset + 1; i < inText.Length; ++i) {
                        char c = inText[i];
                        if (Char.IsLetterOrDigit(c) || c == '_') {
                              if (Char.IsWhiteSpace(inText, i - 1)) {
                                    wordFollowing = true;
                                    break;
                              }
                              b.Append(c);
                        } else if (Char.IsWhiteSpace(c)) {
                              // ignore whitespace
                        } else if (c == '(' || c == '[') {
                              int otherBracket = SearchBracketForward(inText, i + 1, c, (c == '(') ? ')' : ']');
                              if (otherBracket < 0)
                                    break;
                              if (c == '[') {
                                    // do not include [] when it is an array declaration (versus indexer call)
                                    bool ok = false;
                                    for (int j = i + 1; j < otherBracket; j++) {
                                          if (inText[j] != ',' && !char.IsWhiteSpace(inText, j)) {
                                                ok = true;
                                                break;
                                          }
                                    }
                                    if (!ok) {
                                          break;
                                    }
                              }
                              b.Append(inText, i, otherBracket - i + 1);
                              break;
                        } else if (c == '<') {
                              // accept only if this is a generic type reference
                              int typeParameterEnd = FindEndOfTypeParameters(inText, i);
                              if (typeParameterEnd < 0)
                                    break;
                              b.Append(inText, i, typeParameterEnd - i + 1);
                              i = typeParameterEnd;
                        } else {
                              break;
                        }
                  }
                  ExpressionResult res = CreateResult(b.ToString(), textWithoutComments, offsetWithoutComments);
                  if (res.Context == ExpressionContext.Default && wordFollowing) {
                        b = new StringBuilder();
                        for (; i < inText.Length; ++i) {
                              char c = inText[i];
                              if (char.IsLetterOrDigit(c) || c == '_')
                                    b.Append(c);
                              else
                                    break;
                        }
                        if (b.Length > 0) {
                              if (ICSharpCode.SharpRefactory.Parser.Keywords.GetToken(b.ToString()) < 0) {
                                    res.Context = ExpressionContext.Type;
                              }
                        }
                  }
                  return res;
            }
            
            int FindEndOfTypeParameters(string inText, int offset)
            {
                  int level = 0;
                  for (int i = offset; i < inText.Length; ++i) {
                        char c = inText[i];
                        if (Char.IsLetterOrDigit(c) || Char.IsWhiteSpace(c)) {
                              // ignore identifiers and whitespace
                        } else if (c == ',' || c == '?' || c == '[' || c == ']') {
                              // ,  : seperating generic type parameters
                              // ?  : nullable types
                              // [] : arrays
                        } else if (c == '<') {
                              ++level;
                        } else if (c == '>') {
                              --level;
                        } else {
                              return -1;
                        }
                        if (level == 0)
                              return i;
                  }
                  return -1;
            }
            #endregion
            
            #region SearchBracketForward
            // like CSharpFormattingStrategy.SearchBracketForward, but operates on a string.
            private int SearchBracketForward(string text, int offset, char openBracket, char closingBracket)
            {
                  bool inString = false;
                  bool inChar   = false;
                  bool verbatim = false;
                  
                  bool lineComment  = false;
                  bool blockComment = false;
                  
                  if (offset < 0) return -1;
                  
                  int brackets = 1;
                  
                  for (; offset < text.Length; ++offset) {
                        char ch = text[offset];
                        switch (ch) {
                              case '\r':
                              case '\n':
                                    lineComment = false;
                                    inChar = false;
                                    if (!verbatim) inString = false;
                                    break;
                              case '/':
                                    if (blockComment) {
                                          if (offset > 0 && text[offset - 1] == '*') {
                                                blockComment = false;
                                          }
                                    }
                                    if (!inString && !inChar && offset + 1 < text.Length) {
                                          if (!blockComment && text[offset + 1] == '/') {
                                                lineComment = true;
                                          }
                                          if (!lineComment && text[offset + 1] == '*') {
                                                blockComment = true;
                                          }
                                    }
                                    break;
                              case '"':
                                    if (!(inChar || lineComment || blockComment)) {
                                          if (inString && verbatim) {
                                                if (offset + 1 < text.Length && text[offset + 1] == '"') {
                                                      ++offset; // skip escaped quote
                                                      inString = false; // let the string go on
                                                } else {
                                                      verbatim = false;
                                                }
                                          } else if (!inString && offset > 0 && text[offset - 1] == '@') {
                                                verbatim = true;
                                          }
                                          inString = !inString;
                                    }
                                    break;
                              case '\'':
                                    if (!(inString || lineComment || blockComment)) {
                                          inChar = !inChar;
                                    }
                                    break;
                              case '\\':
                                    if ((inString && !verbatim) || inChar)
                                          ++offset; // skip next character
                                    break;
                              default:
                                    if (ch == openBracket) {
                                          if (!(inString || inChar || lineComment || blockComment)) {
                                                ++brackets;
                                          }
                                    } else if (ch == closingBracket) {
                                          if (!(inString || inChar || lineComment || blockComment)) {
                                                --brackets;
                                                if (brackets == 0) {
                                                      return offset;
                                                }
                                          }
                                    }
                                    break;
                        }
                  }
                  return -1;
            }
            #endregion
            
            #region Comment Filter and 'inside string watcher'
            int initialOffset;
            public string FilterComments(string text, ref int offset)
            {
                  if (text.Length <= offset)
                        return null;
                  this.initialOffset = offset;
                  StringBuilder outText = new StringBuilder();
                  int curOffset = 0;
                  
                  while (curOffset <= initialOffset) {
                        char ch = text[curOffset];
                        
                        switch (ch) {
                              case '@':
                                    if (curOffset + 1 < text.Length && text[curOffset + 1] == '"') {
                                          outText.Append(text[curOffset++]); // @
                                          outText.Append(text[curOffset++]); // "
                                          if (!ReadVerbatimString(outText, text, ref curOffset)) {
                                                return null;
                                          }
                                    }else{
                                          outText.Append(ch);
                                          ++curOffset;
                                    }
                                    break;
                              case '\'':
                                    outText.Append(ch);
                                    curOffset++;
                                    if(! ReadChar(outText, text, ref curOffset)) {
                                          return null;
                                    }
                                    break;
                              case '"':
                                    outText.Append(ch);
                                    curOffset++;
                                    if (!ReadString(outText, text, ref curOffset)) {
                                          return null;
                                    }
                                    break;
                              case '/':
                                    if (curOffset + 1 < text.Length && text[curOffset + 1] == '/') {
                                          offset    -= 2;
                                          curOffset += 2;
                                          if (!ReadToEOL(text, ref curOffset, ref offset)) {
                                                return null;
                                          }
                                    } else if (curOffset + 1 < text.Length && text[curOffset + 1] == '*') {
                                          offset    -= 2;
                                          curOffset += 2;
                                          if (!ReadMultiLineComment(text, ref curOffset, ref offset)) {
                                                return null;
                                          }
                                    } else {
                                          goto default;
                                    }
                                    break;
                              case '#':
                                    if (!ReadToEOL(text, ref curOffset, ref offset)) {
                                          return null;
                                    }
                                    break;
                              default:
                                    outText.Append(ch);
                                    ++curOffset;
                                    break;
                        }
                  }
                  
                  return outText.ToString();
            }
            
            bool ReadToEOL(string text, ref int curOffset, ref int offset)
            {
                  while (curOffset <= initialOffset) {
                        char ch = text[curOffset++];
                        --offset;
                        if (ch == '\n') {
                              return true;
                        }
                  }
                  return false;
            }
            
            bool ReadChar(StringBuilder outText, string text, ref int curOffset)
            {
                  if (curOffset > initialOffset)
                        return false;
                  char first = text[curOffset++];
                  outText.Append(first);
                  if (curOffset > initialOffset)
                        return false;
                  char second = text[curOffset++];
                  outText.Append(second);
                  if (first == '\\') {
                        // character is escape sequence, so read one char more
                        char next;
                        do {
                              if (curOffset > initialOffset)
                                    return false;
                              next = text[curOffset++];
                              outText.Append(next);
                              // unicode or hexadecimal character literals can have more content characters
                        } while((second == 'u' || second == 'x') && char.IsLetterOrDigit(next));
                  }
                  return text[curOffset - 1] == '\'';
            }
            
            bool ReadString(StringBuilder outText, string text, ref int curOffset)
            {
                  while (curOffset <= initialOffset) {
                        char ch = text[curOffset++];
                        outText.Append(ch);
                        if (ch == '"') {
                              return true;
                        } else if (ch == '\\') {
                              if (curOffset <= initialOffset)
                                    outText.Append(text[curOffset++]);
                        }
                  }
                  return false;
            }
            
            bool ReadVerbatimString(StringBuilder outText, string text, ref int curOffset)
            {
                  while (curOffset <= initialOffset) {
                        char ch = text[curOffset++];
                        outText.Append(ch);
                        if (ch == '"') {
                              if (curOffset < text.Length && text[curOffset] == '"') {
                                    outText.Append(text[curOffset++]);
                              } else {
                                    return true;
                              }
                        }
                  }
                  return false;
            }
            
            bool ReadMultiLineComment(string text, ref int curOffset, ref int offset)
            {
                  while (curOffset <= initialOffset) {
                        char ch = text[curOffset++];
                        --offset;
                        if (ch == '*') {
                              if (curOffset < text.Length && text[curOffset] == '/') {
                                    ++curOffset;
                                    --offset;
                                    return true;
                              }
                        }
                  }
                  return false;
            }
            #endregion
            
            #region mini backward lexer
            string text;
            int    offset;
            
            char GetNext()
            {
                  if (offset >= 0) {
                        return text[offset--];
                  }
                  return '\0';
            }
            
            char GetNextNonWhiteSpace()
            {
                  char ch;
                  do {
                        ch = GetNext();
                  } while (char.IsWhiteSpace(ch));
                  return ch;
            }
            
            char Peek(int n)
            {
                  if (offset - n >= 0) {
                        return text[offset - n];
                  }
                  return '\0';
            }
            
            char Peek()
            {
                  if (offset >= 0) {
                        return text[offset];
                  }
                  return '\0';
            }
            
            void UnGet()
            {
                  ++offset;
            }
            
            void UnGetToken()
            {
                  do {
                        UnGet();
                  } while (char.IsLetterOrDigit(Peek()));
            }
            
            // tokens for our lexer
            static int Err     = 0;
            static int Dot     = 1;
            static int StrLit  = 2;
            static int Ident   = 3;
            static int New     = 4;
            static int Bracket = 5;
            static int Parent  = 6;
            static int Curly   = 7;
            static int Using   = 8;
            static int Digit   = 9;
            int curTokenType;
            
            readonly static string[] tokenStateName = new string[] {
                  "Err", "Dot", "StrLit", "Ident", "New", "Bracket", "Paren", "Curly", "Using", "Digit"
            };
            
            string GetTokenName(int state)
            {
                  return tokenStateName[state];
            }
            
            /// <summary>
            /// used to control whether an expression is in a ObjectCreation context (new *expr*),
            /// or is in the default context (e.g. "new MainForm().Show()", 'new ' is there part of the expression
            /// </summary>
00562             bool hadParenthesis;
            
            string lastIdentifier;
            
            void ReadNextToken()
            {
                  curTokenType = Err;
                  char ch = GetNextNonWhiteSpace();
                  if (ch == '\0') {
                        return;
                  }
                  
                  switch (ch) {
                        case '}':
                              if (ReadBracket('{', '}')) {
                                    curTokenType = Curly;
                              }
                              break;
                        case ')':
                              if (ReadBracket('(', ')')) {
                                    hadParenthesis = true;
                                    curTokenType = Parent;
                              }
                              break;
                        case ']':
                              if (ReadBracket('[', ']')) {
                                    curTokenType = Bracket;
                              }
                              break;
                        case '>':
                              if (ReadTypeParameters()) {
                                    // hack: ignore type parameters and continue reading without changing state
                                    ReadNextToken();
                              }
                              break;
                        case '.':
                              curTokenType = Dot;
                              break;
                        case ':':
                              if (GetNext() == ':') {
                                    // treat :: like dot
                                    curTokenType = Dot;
                              }
                              break;
                        case '\'':
                        case '"':
                              if (ReadStringLiteral(ch)) {
                                    curTokenType = StrLit;
                              }
                              break;
                        default:
                              if (IsNumber(ch)) {
                                    ReadDigit(ch);
                                    curTokenType = Digit;
                              } else if (IsIdentifierPart(ch)) {
                                    string ident = ReadIdentifier(ch);
                                    if (ident != null) {
                                          switch (ident) {
                                                case "new":
                                                      curTokenType = New;
                                                      break;
                                                case "using":
                                                      curTokenType = Using;
                                                      break;
                                                case "return":
                                                case "throw":
                                                case "in":
                                                case "else":
                                                      // treat as error / end of expression
                                                      break;
                                                default:
                                                      curTokenType = Ident;
                                                      lastIdentifier = ident;
                                                      break;
                                          }
                                    }
                              }
                              
                              break;
                  }
            }
            bool IsNumber(char ch)
            {
                  if (!Char.IsDigit(ch))
                        return false;
                  int n = 0;
                  while (true) {
                        ch = Peek(n);
                        if (Char.IsDigit(ch)) {
                              n++;
                              continue;
                        }
                        return n > 0 && !Char.IsLetter(ch);
                  }
            }
            bool ReadStringLiteral(char litStart)
            {
                  while (true) {
                        char ch = GetNext();
                        if (ch == '\0') {
                              return false;
                        }
                        if (ch == litStart) {
                              if (Peek() == '@' && litStart == '"') {
                                    GetNext();
                              }
                              return true;
                        }
                  }
            }
            
            bool ReadTypeParameters()
            {
                  int level = 1;
                  while (level > 0) {
                        char ch = GetNext();
                        switch (ch) {
                              case '?':
                              case '[':
                              case ',':
                              case ']':
                                    break;
                              case '<':
                                    --level;
                                    break;
                              case '>':
                                    ++level;
                                    break;
                              default:
                                    if (!char.IsWhiteSpace(ch) && !char.IsLetterOrDigit(ch))
                                          return false;
                                    break;
                        }
                  }
                  return true;
            }
            
            bool ReadBracket(char openBracket, char closingBracket)
            {
                  int curlyBraceLevel    = 0;
                  int squareBracketLevel = 0;
                  int parenthesisLevel   = 0;
                  switch (openBracket) {
                        case '(':
                              parenthesisLevel++;
                              break;
                        case '[':
                              squareBracketLevel++;
                              break;
                        case '{':
                              curlyBraceLevel++;
                              break;
                  }
                  
                  while (parenthesisLevel != 0 || squareBracketLevel != 0 || curlyBraceLevel != 0) {
                        char ch = GetNext();
                        switch (ch) {
                              case '\0':
                                    return false;
                              case '(':
                                    parenthesisLevel--;
                                    break;
                              case '[':
                                    squareBracketLevel--;
                                    break;
                              case '{':
                                    curlyBraceLevel--;
                                    break;
                              case ')':
                                    parenthesisLevel++;
                                    break;
                              case ']':
                                    squareBracketLevel++;
                                    break;
                              case '}':
                                    curlyBraceLevel++;
                                    break;
                        }
                  }
                  return true;
            }
            
            string ReadIdentifier(char ch)
            {
                  string identifier = ch.ToString();
                  while (IsIdentifierPart(Peek())) {
                        identifier = GetNext() + identifier;
                  }
                  return identifier;
            }
            
            void ReadDigit(char ch)
            {
                  //string digit = ch.ToString();
                  while (Char.IsDigit(Peek()) || Peek() == '.') {
                        GetNext();
                        //digit = GetNext() + digit;
                  }
                  //return digit;
            }
            
            bool IsIdentifierPart(char ch)
            {
                  return Char.IsLetterOrDigit(ch) || ch == '_' || ch == '@';
            }
            #endregion
            
            #region finite state machine
            readonly static int ERROR  = 0;
            readonly static int START  = 1;
            readonly static int DOT    = 2;
            readonly static int MORE   = 3;
            readonly static int CURLY  = 4;
            readonly static int CURLY2 = 5;
            readonly static int CURLY3 = 6;
            
            readonly static int ACCEPT = 7;
            readonly static int ACCEPTNOMORE = 8;
            readonly static int ACCEPT2 = 9;
            
            readonly static string[] stateName = new string[] {
                  "ERROR",
                  "START",
                  "DOT",
                  "MORE",
                  "CURLY",
                  "CURLY2",
                  "CURLY3",
                  "ACCEPT",
                  "ACCEPTNOMORE",
                  "ACCEPT2"
            };
            
            string GetStateName(int state)
            {
                  return stateName[state];
            }
            
            int state = 0;
            int lastAccept = 0;
            static int[,] stateTable = new int[,] {
                  //                   Err,     Dot,     Str,      ID,         New,     Brk,     Par,     Cur,   Using,       digit
                  /*ERROR*/        { ERROR,   ERROR,   ERROR,   ERROR,        ERROR,  ERROR,   ERROR,   ERROR,   ERROR,        ERROR},
                  /*START*/        { ERROR,     DOT,  ACCEPT,  ACCEPT,        ERROR,   MORE, ACCEPT2,   CURLY,   ACCEPTNOMORE, ERROR},
                  /*DOT*/          { ERROR,   ERROR,  ACCEPT,  ACCEPT,        ERROR,   MORE,  ACCEPT,   CURLY,   ERROR,        ACCEPT},
                  /*MORE*/         { ERROR,   ERROR,  ACCEPT,  ACCEPT,        ERROR,   MORE, ACCEPT2,   CURLY,   ERROR,        ACCEPT},
                  /*CURLY*/        { ERROR,   ERROR,   ERROR,   ERROR,        ERROR, CURLY2,   ERROR,   ERROR,   ERROR,        ERROR},
                  /*CURLY2*/       { ERROR,   ERROR,   ERROR,  CURLY3,        ERROR,  ERROR,   ERROR,   ERROR,   ERROR,        CURLY3},
                  /*CURLY3*/       { ERROR,   ERROR,   ERROR,   ERROR, ACCEPTNOMORE,  ERROR,   ERROR,   ERROR,   ERROR,        ERROR},
                  /*ACCEPT*/       { ERROR,    MORE,   ERROR,   ERROR,       ACCEPT,  ERROR,   ERROR,   ERROR,   ACCEPTNOMORE, ERROR},
                  /*ACCEPTNOMORE*/ { ERROR,   ERROR,   ERROR,   ERROR,        ERROR,  ERROR,   ERROR,   ERROR,   ERROR,        ERROR},
                  /*ACCEPT2*/      { ERROR,    MORE,   ERROR,  ACCEPT,       ACCEPT,  ERROR,   ERROR,   ERROR,   ERROR,        ACCEPT},
            };
            #endregion
      }
}

Generated by  Doxygen 1.6.0   Back to index