3
// This file was derived from a file from #Develop.
5
// Copyright (C) 2001-2007 Daniel Grunwald <daniel@danielgrunwald.de>
7
// This program is free software; you can redistribute it and/or modify
8
// it under the terms of the GNU General Public License as published by
9
// the Free Software Foundation; either version 2 of the License, or
10
// (at your option) any later version.
12
// This program is distributed in the hope that it will be useful,
13
// but WITHOUT ANY WARRANTY; without even the implied warranty of
14
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
// GNU General Public License for more details.
17
// You should have received a copy of the GNU General Public License
18
// along with this program; if not, write to the Free Software
19
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
using MonoDevelop.Projects.Parser;
25
namespace CSharpBinding.Parser
28
/// Description of ExpressionFinder.
30
public class ExpressionFinder : IExpressionFinder
34
public ExpressionFinder(string fileName)
36
// this.fileName = fileName;
39
#region Capture Context
40
ExpressionResult CreateResult(string expression, string inText, int offset)
42
if (expression == null)
43
return new ExpressionResult(null);
44
if (expression.StartsWith("using "))
45
return new ExpressionResult(expression.Substring(6).TrimStart(), ExpressionContext.Namespace, null);
46
if (!hadParenthesis && expression.StartsWith("new ")) {
47
return new ExpressionResult(expression.Substring(4).TrimStart(), GetCreationContext(), null);
49
if (IsInAttribute(inText, offset))
50
return new ExpressionResult(expression, ExpressionContext.Attribute);
51
return new ExpressionResult(expression);
54
ExpressionContext GetCreationContext()
58
if (GetNextNonWhiteSpace() == '=') { // was: "= new"
60
if (curTokenType == Ident) { // was: "ident = new"
64
while (curTokenType == Ident) {
65
typeStart = offset + 1;
67
if (curTokenType == Dot) {
74
string className = text.Substring(typeStart, typeEnd - typeStart);
75
int pos = className.IndexOf('<');
76
string nonGenericClassName, genericPart;
77
int typeParameterCount = 0;
79
nonGenericClassName = className.Substring(0, pos);
80
genericPart = className.Substring(pos);
83
typeParameterCount += 1;
84
pos = genericPart.IndexOf(',', pos + 1);
87
nonGenericClassName = className;
90
ClassFinder finder = new ClassFinder(fileName, text, typeStart);
91
IReturnType t = finder.SearchType(nonGenericClassName, typeParameterCount);
92
IClass c = (t != null) ? t.GetUnderlyingClass() : null;
94
ExpressionContext context = ExpressionContext.TypeDerivingFrom(c, true);
95
if (context.ShowEntry(c)) {
96
if (genericPart != null) {
97
DefaultClass genericClass = new DefaultClass(c.CompilationUnit, c.ClassType, c.Modifiers, c.Region, c.DeclaringType);
98
genericClass.FullyQualifiedName = c.FullyQualifiedName + genericPart;
99
genericClass.Documentation = c.Documentation;
100
context.SuggestedItem = genericClass;
102
context.SuggestedItem = c;
112
if (curTokenType == Ident && lastIdentifier == "throw") {
113
return ExpressionContext.TypeDerivingFrom(ProjectContentRegistry.Mscorlib.GetClass("System.Exception"), true);
116
return ExpressionContext.ObjectCreation;
120
bool IsInAttribute(string txt, int offset)
123
int lineStart = offset;
124
while (--lineStart > 0 && txt[lineStart] != '\n')
127
bool inAttribute = false;
129
for (int i = lineStart + 1; i < offset; i++) {
131
if (char.IsWhiteSpace(ch))
139
} else if (parens == 0) {
140
// inside attribute, outside parameter list
145
else if (!char.IsLetterOrDigit(ch) && ch != ',')
148
// inside attribute, inside parameter list
155
return inAttribute && parens == 0;
159
#region RemoveLastPart
161
/// Removed the last part of the expression.
164
/// "arr[i]" => "arr"
165
/// "obj.Field" => "obj"
166
/// "obj.Method(args,...)" => "obj.Method"
168
public string RemoveLastPart(string expression)
171
offset = text.Length - 1;
173
if (curTokenType == Ident && Peek() == '.')
175
return text.Substring(0, offset + 1);
179
#region Find Expression
180
public ExpressionResult FindExpression(string inText, int offset)
182
inText = FilterComments(inText, ref offset);
183
return CreateResult(FindExpressionInternal(inText, offset), inText, offset);
186
public string FindExpressionInternal(string inText, int offset)
188
// warning: Do not confuse this.offset and offset
190
this.offset = this.lastAccept = offset;
192
hadParenthesis = false;
193
if (this.text == null) {
197
while (state != ERROR) {
199
state = stateTable[state, curTokenType];
201
if (state == ACCEPT || state == ACCEPT2) {
202
lastAccept = this.offset;
204
if (state == ACCEPTNOMORE) {
205
lastExpressionStartPosition = this.offset + 1;
206
return this.text.Substring(this.offset + 1, offset - this.offset);
213
lastExpressionStartPosition = this.lastAccept + 1;
215
return this.text.Substring(this.lastAccept + 1, offset - this.lastAccept);
218
int lastExpressionStartPosition;
220
internal int LastExpressionStartPosition {
222
return lastExpressionStartPosition;
227
#region FindFullExpression
228
public ExpressionResult FindFullExpression(string inText, int offset)
231
return new ExpressionResult (null);
232
int offsetWithoutComments = offset;
233
string textWithoutComments = FilterComments(inText, ref offsetWithoutComments);
234
string expressionBeforeOffset = FindExpressionInternal(textWithoutComments, offsetWithoutComments);
235
if (expressionBeforeOffset == null || expressionBeforeOffset.Length == 0)
236
return CreateResult(null, textWithoutComments, offsetWithoutComments);
237
StringBuilder b = new StringBuilder(expressionBeforeOffset);
238
// append characters after expression
239
bool wordFollowing = false;
241
for (i = offset + 1; i < inText.Length; ++i) {
243
if (Char.IsLetterOrDigit(c) || c == '_') {
244
if (Char.IsWhiteSpace(inText, i - 1)) {
245
wordFollowing = true;
249
} else if (Char.IsWhiteSpace(c)) {
251
} else if (c == '(' || c == '[') {
252
int otherBracket = SearchBracketForward(inText, i + 1, c, (c == '(') ? ')' : ']');
253
if (otherBracket < 0)
256
// do not include [] when it is an array declaration (versus indexer call)
258
for (int j = i + 1; j < otherBracket; j++) {
259
if (inText[j] != ',' && !char.IsWhiteSpace(inText, j)) {
268
b.Append(inText, i, otherBracket - i + 1);
270
} else if (c == '<') {
271
// accept only if this is a generic type reference
272
int typeParameterEnd = FindEndOfTypeParameters(inText, i);
273
if (typeParameterEnd < 0)
275
b.Append(inText, i, typeParameterEnd - i + 1);
276
i = typeParameterEnd;
281
ExpressionResult res = CreateResult(b.ToString(), textWithoutComments, offsetWithoutComments);
282
if (res.Context == ExpressionContext.Default && wordFollowing) {
283
b = new StringBuilder();
284
for (; i < inText.Length; ++i) {
286
if (char.IsLetterOrDigit(c) || c == '_')
292
if (ICSharpCode.NRefactory.Parser.CSharp.Keywords.GetToken(b.ToString()) < 0) {
293
res.Context = ExpressionContext.Type;
300
int FindEndOfTypeParameters(string inText, int offset)
303
for (int i = offset; i < inText.Length; ++i) {
305
if (Char.IsLetterOrDigit(c) || Char.IsWhiteSpace(c)) {
306
// ignore identifiers and whitespace
307
} else if (c == ',' || c == '?' || c == '[' || c == ']') {
308
// , : seperating generic type parameters
309
// ? : nullable types
311
} else if (c == '<') {
313
} else if (c == '>') {
325
#region SearchBracketForward
326
// like CSharpFormattingStrategy.SearchBracketForward, but operates on a string.
327
private int SearchBracketForward(string text, int offset, char openBracket, char closingBracket)
329
bool inString = false;
331
bool verbatim = false;
333
bool lineComment = false;
334
bool blockComment = false;
336
if (offset < 0) return -1;
340
for (; offset < text.Length; ++offset) {
341
char ch = text[offset];
347
if (!verbatim) inString = false;
351
if (offset > 0 && text[offset - 1] == '*') {
352
blockComment = false;
355
if (!inString && !inChar && offset + 1 < text.Length) {
356
if (!blockComment && text[offset + 1] == '/') {
359
if (!lineComment && text[offset + 1] == '*') {
365
if (!(inChar || lineComment || blockComment)) {
366
if (inString && verbatim) {
367
if (offset + 1 < text.Length && text[offset + 1] == '"') {
368
++offset; // skip escaped quote
369
inString = false; // let the string go on
373
} else if (!inString && offset > 0 && text[offset - 1] == '@') {
376
inString = !inString;
380
if (!(inString || lineComment || blockComment)) {
385
if ((inString && !verbatim) || inChar)
386
++offset; // skip next character
389
if (ch == openBracket) {
390
if (!(inString || inChar || lineComment || blockComment)) {
393
} else if (ch == closingBracket) {
394
if (!(inString || inChar || lineComment || blockComment)) {
408
#region Comment Filter and 'inside string watcher'
410
public string FilterComments(string text, ref int offset)
412
if (text == null || text.Length <= offset)
414
this.initialOffset = offset;
415
StringBuilder result = new StringBuilder();
418
while (curOffset <= initialOffset) {
419
char ch = text[curOffset];
423
if (curOffset + 1 < text.Length && text[curOffset + 1] == '"') {
424
result.Append(text[curOffset++]); // @
425
result.Append(text[curOffset++]); // "
426
if (!ReadVerbatimString(result, text, ref curOffset)) {
437
if(! ReadChar(result, text, ref curOffset)) {
444
if (!ReadString(result, text, ref curOffset)) {
449
if (curOffset + 1 < text.Length && text[curOffset + 1] == '/') {
452
if (!ReadToEOL(text, ref curOffset, ref offset)) {
455
} else if (curOffset + 1 < text.Length && text[curOffset + 1] == '*') {
458
if (!ReadMultiLineComment(text, ref curOffset, ref offset)) {
466
if (!ReadToEOL(text, ref curOffset, ref offset)) {
477
return result.ToString();
480
bool ReadToEOL(string text, ref int curOffset, ref int offset)
482
while (curOffset <= initialOffset) {
483
char ch = text[curOffset++];
492
bool ReadChar(StringBuilder outText, string text, ref int curOffset)
494
if (curOffset > initialOffset)
496
char first = text[curOffset++];
497
outText.Append(first);
498
if (curOffset > initialOffset)
500
char second = text[curOffset++];
501
outText.Append(second);
503
// character is escape sequence, so read one char more
506
if (curOffset > initialOffset)
508
next = text[curOffset++];
509
outText.Append(next);
510
// unicode or hexadecimal character literals can have more content characters
511
} while((second == 'u' || second == 'x') && char.IsLetterOrDigit(next));
513
return text[curOffset - 1] == '\'';
516
bool ReadString(StringBuilder outText, string text, ref int curOffset)
518
while (curOffset <= initialOffset) {
519
char ch = text[curOffset++];
523
} else if (ch == '\\') {
524
if (curOffset <= initialOffset)
525
outText.Append(text[curOffset++]);
531
bool ReadVerbatimString(StringBuilder outText, string text, ref int curOffset)
533
while (curOffset <= initialOffset) {
534
char ch = text[curOffset++];
537
if (curOffset < text.Length && text[curOffset] == '"') {
538
outText.Append(text[curOffset++]);
547
bool ReadMultiLineComment(string text, ref int curOffset, ref int offset)
549
while (curOffset <= initialOffset) {
550
char ch = text[curOffset++];
553
if (curOffset < text.Length && text[curOffset] == '/') {
564
#region mini backward lexer
571
return text[offset--];
576
char GetNextNonWhiteSpace()
581
} while (char.IsWhiteSpace(ch));
587
if (offset - n >= 0) {
588
return text[offset - n];
610
} while (char.IsLetterOrDigit(Peek()));
613
// tokens for our lexer
616
static int StrLit = 2;
617
static int Ident = 3;
619
static int Bracket = 5;
620
static int Parent = 6;
621
static int Curly = 7;
622
static int Using = 8;
623
static int Digit = 9;
626
readonly static string[] tokenStateName = new string[] {
627
"Err", "Dot", "StrLit", "Ident", "New", "Bracket", "Paren", "Curly", "Using", "Digit"
631
/// used to control whether an expression is in a ObjectCreation context (new *expr*),
632
/// or is in the default context (e.g. "new MainForm().Show()", 'new ' is there part of the expression
636
string lastIdentifier;
641
char ch = GetNextNonWhiteSpace();
648
if (ReadBracket('{', '}')) {
649
curTokenType = Curly;
653
if (ReadBracket('(', ')')) {
654
hadParenthesis = true;
655
curTokenType = Parent;
659
if (ReadBracket('[', ']')) {
660
curTokenType = Bracket;
664
if (ReadTypeParameters()) {
665
// hack: ignore type parameters and continue reading without changing state
673
if (GetNext() == ':') {
680
if (ReadStringLiteral(ch)) {
681
curTokenType = StrLit;
687
curTokenType = Digit;
688
} else if (IsIdentifierPart(ch)) {
689
string ident = ReadIdentifier(ch);
696
curTokenType = Using;
702
// treat as error / end of expression
705
curTokenType = Ident;
706
lastIdentifier = ident;
715
bool IsNumber(char ch)
717
if (!Char.IsDigit(ch))
722
if (Char.IsDigit(ch)) {
726
return n > 0 && !Char.IsLetter(ch);
729
bool ReadStringLiteral(char litStart)
736
if (ch == litStart) {
737
if (Peek() == '@' && litStart == '"') {
745
bool ReadTypeParameters()
763
if (!char.IsWhiteSpace(ch) && !char.IsLetterOrDigit(ch))
771
bool ReadBracket(char openBracket, char closingBracket)
773
int curlyBraceLevel = 0;
774
int squareBracketLevel = 0;
775
int parenthesisLevel = 0;
776
switch (openBracket) {
781
squareBracketLevel++;
788
while (parenthesisLevel != 0 || squareBracketLevel != 0 || curlyBraceLevel != 0) {
797
squareBracketLevel--;
806
squareBracketLevel++;
816
string ReadIdentifier(char ch)
818
string identifier = ch.ToString();
819
while (IsIdentifierPart(Peek())) {
820
identifier = GetNext() + identifier;
825
void ReadDigit(char ch)
827
//string digit = ch.ToString();
828
while (Char.IsDigit(Peek()) || Peek() == '.') {
830
//digit = GetNext() + digit;
835
bool IsIdentifierPart(char ch)
837
return Char.IsLetterOrDigit(ch) || ch == '_' || ch == '@';
841
#region finite state machine
842
readonly static int ERROR = 0;
843
readonly static int START = 1;
844
readonly static int DOT = 2;
845
readonly static int MORE = 3;
846
readonly static int CURLY = 4;
847
readonly static int CURLY2 = 5;
848
readonly static int CURLY3 = 6;
850
readonly static int ACCEPT = 7;
851
readonly static int ACCEPTNOMORE = 8;
852
readonly static int ACCEPT2 = 9;
854
readonly static string[] stateName = new string[] {
869
static int[,] stateTable = new int[,] {
870
// Err, Dot, Str, ID, New, Brk, Par, Cur, Using, digit
871
/*ERROR*/ { ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR},
872
/*START*/ { ERROR, DOT, ACCEPT, ACCEPT, ERROR, MORE, ACCEPT2, CURLY, ACCEPTNOMORE, ERROR},
873
/*DOT*/ { ERROR, ERROR, ACCEPT, ACCEPT, ERROR, MORE, ACCEPT, CURLY, ERROR, ACCEPT},
874
/*MORE*/ { ERROR, ERROR, ACCEPT, ACCEPT, ERROR, MORE, ACCEPT2, CURLY, ERROR, ACCEPT},
875
/*CURLY*/ { ERROR, ERROR, ERROR, ERROR, ERROR, CURLY2, ERROR, ERROR, ERROR, ERROR},
876
/*CURLY2*/ { ERROR, ERROR, ERROR, CURLY3, ERROR, ERROR, ERROR, ERROR, ERROR, CURLY3},
877
/*CURLY3*/ { ERROR, ERROR, ERROR, ERROR, ACCEPTNOMORE, ERROR, ERROR, ERROR, ERROR, ERROR},
878
/*ACCEPT*/ { ERROR, MORE, ERROR, ERROR, ACCEPT, ERROR, ERROR, ERROR, ACCEPTNOMORE, ERROR},
879
/*ACCEPTNOMORE*/ { ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR},
880
/*ACCEPT2*/ { ERROR, MORE, ERROR, ACCEPT, ACCEPT, ERROR, ERROR, ERROR, ERROR, ACCEPT},