//
//
//
//
// $Revision: 1751 $
//
using System;
using System.Text;
namespace ICSharpCode.SharpDevelop.Dom.SQL
{
///
/// Supports getting the expression including context from the cursor position.
///
public class SQLExpressionFinder : IExpressionFinder
{
string fileName;
public SQLExpressionFinder(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.GetAttribute(HostCallback.GetCurrentProjectContent()));
return new ExpressionResult(expression);
}
ExpressionContext GetCreationContext()
{
UnGetToken();
if (GetNextNonWhiteSpace() == '=') { // was: "= new"
ReadNextToken();
if (curTokenType == Ident) { // was: "ident = new"
int typeEnd = offset;
ReadNextToken();
int typeStart = -1;
while (curTokenType == Ident) {
typeStart = offset + 1;
ReadNextToken();
if (curTokenType == Dot) {
ReadNextToken();
} else {
break;
}
}
if (typeStart >= 0) {
string className = text.Substring(typeStart, typeEnd - typeStart);
int pos = className.IndexOf('<');
string nonGenericClassName, genericPart;
int typeParameterCount = 0;
if (pos > 0) {
nonGenericClassName = className.Substring(0, pos);
genericPart = className.Substring(pos);
pos = 0;
do {
typeParameterCount += 1;
pos = genericPart.IndexOf(',', pos + 1);
} while (pos > 0);
} else {
nonGenericClassName = className;
genericPart = null;
}
ClassFinder finder = new ClassFinder(fileName, text, typeStart);
IReturnType t = finder.SearchType(nonGenericClassName, typeParameterCount);
IClass c = (t != null) ? t.GetUnderlyingClass() : null;
if (c != null) {
ExpressionContext context = ExpressionContext.TypeDerivingFrom(c, true);
if (context.ShowEntry(c)) {
if (genericPart != null) {
DefaultClass genericClass = new DefaultClass(c.CompilationUnit, c.ClassType, c.Modifiers, c.Region, c.DeclaringType);
genericClass.FullyQualifiedName = c.FullyQualifiedName + genericPart;
genericClass.Documentation = c.Documentation;
context.SuggestedItem = genericClass;
} else {
context.SuggestedItem = c;
}
}
return context;
}
}
}
} else {
UnGet();
if (ReadIdentifier(GetNextNonWhiteSpace()) == "throw") {
return ExpressionContext.TypeDerivingFrom(HostCallback.GetCurrentProjectContent().GetClass("System.Exception"), true);
}
}
return ExpressionContext.ObjectCreation;
}
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
///
/// Removed the last part of the expression.
///
///
/// "arr[i]" => "arr"
/// "obj.Field" => "obj"
/// "obj.Method(args,...)" => "obj.Method"
///
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;
///
/// Gets the position in the source string (after filtering out comments)
/// where the beginning of last expression was found.
///
public 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.NRefactory.Parser.CSharp.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];
}
///
/// 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
///
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
}
}