⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 expressionfinder.cs

📁 SharpDevelop2.0.0 c#开发免费工具
💻 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.Collections.Generic;
using System.Text;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Dom;

namespace Grunwald.BooBinding.CodeCompletion
{
	// TODO: We could need some unit tests for this.
	public class ExpressionFinder : IExpressionFinder
	{
		string fileName;
		
		public ExpressionFinder(string fileName)
		{
			this.fileName = fileName;
		}
		
		#region RemoveLastPart
		/// <summary>
		/// Removes the last part of the expression.
		/// </summary>
		/// <example>
		/// "arr[i]" => "arr"
		/// "obj.Field" => "obj"
		/// "obj.Method(args,...)" => "obj.Method"
		/// </example>
		public string RemoveLastPart(string expression)
		{
			int state = 0;
			int pos = 0;
			int lastFinishPos = 0;
			int brackets = 0;
			while (state >= 0) {
				state = FindNextCodeCharacter(state, expression, ref pos);
				if (pos >= expression.Length)
					break;
				char c = expression[pos];
				if (c == '[' || c == '(' || c == '{') {
					if (brackets == 0)
						lastFinishPos = pos;
					brackets += 1;
				}
				if (brackets == 0 && c == '.') {
					lastFinishPos = pos;
				}
				if (brackets > 0 && (c == ']' || c == ')' || c == '}')) {
					brackets -= 1;
				}
			}
			return expression.Substring(0, lastFinishPos);
		}
		#endregion
		
		#region Find Expression
		// The expression finder can find an expression in a text
		// inText is the full source code, offset the cursor position
		
		// example: "_var = 'bla'\n_var^\nprint _var"
		// where ^ is the cursor position
		// in that simple case the expression finder should return 'n_var'.
		
		// but also complex expressions like
		// 'filename.Substring(filename.IndexOf("var="))'
		// should be returned if the cursor is after the last ).
		
		// implementation note: the text after offset is irrelevant, so
		// every operation on the string aborts after reaching offset
		
		const string _closingBrackets = "}])";
		const string _openingBrackets = "{[(";
		
		public ExpressionResult FindExpression(string inText, int offset)
		{
			if (inText == null || offset >= inText.Length)
				return new ExpressionResult(null);
			// OK, first try a kind of "quick find"
			int i = offset + 1;
			const string forbidden = "\"\'/#)]}";
			const string finish = "([{=+*<,:";
			int start = -1;
			while (i > 0) {
				i -= 1;
				char c = inText[i];
				if (finish.IndexOf(c) >= 0) {
					start = i + 1;
					break;
				}
				if (forbidden.IndexOf(c) >= 0) {
					//LoggingService.Debug("Quickfind failed: got " + c);
					break;
				}
				if (char.IsWhiteSpace(c)) {
					break;
				}
				if (start >= 0) {
					if (CheckString(inText, start, "/#\"\'", "\r\n")) {
						return GetExpression(inText, start, offset + 1);
					}
				}
			}
			
			inText = SimplifyCode(inText, offset);
			if (inText == null) {
				return new ExpressionResult(null);
			}
			// inText now has no comments or string literals, but the same meaning in
			// terms of the type system
			// Now go back until a finish-character or a whitespace character
			Stack<int> bracketStack = new Stack<int>();
			i = inText.Length;
			while (i > 0) {
				i -= 1;
				char c = inText[i];
				if (bracketStack.Count == 0 && (finish.IndexOf(c) >= 0 || Char.IsWhiteSpace(c))) {
					// SUCCESS!
					return GetExpression(inText, i + 1, inText.Length);
				}
				int bracket = _closingBrackets.IndexOf(c);
				if (bracket >= 0) {
					bracketStack.Push(bracket);
				}
				bracket = _openingBrackets.IndexOf(c);
				if (bracket >= 0) {
					while (bracketStack.Count > 0 && bracketStack.Pop() > bracket);
				}
			}
			return new ExpressionResult(null);
		}
		
		bool CheckString(string text, int offset, string forbidden, string finish)
		{
			int i = offset;
			while (i > 0) {
				i -= 1;
				char c = text[i];
				if (forbidden.IndexOf(c) >= 0) return false;
				if (finish.IndexOf(c) >= 0) return true;
			}
			return true;
		}
		
		ExpressionResult GetExpression(string inText, int start, int end)
		{
			if (start == end) return new ExpressionResult(null);
			StringBuilder b = new StringBuilder();
			bool wasSpace = true;
			int i = start;
			while (i < end) {
				char c = inText[i];
				if (Char.IsWhiteSpace(c)) {
					if (!wasSpace) b.Append(' ');
					wasSpace = true;
				} else {
					wasSpace = false;
					b.Append(c);
				}
				i += 1;
			}
			ExpressionResult result = new ExpressionResult(b.ToString());
			// Now try to find the context of the expression
			while (--start > 0 && char.IsWhiteSpace(inText, start));
			if (start > 2 && char.IsWhiteSpace(inText, start - 2)
			    && inText[start - 1] == 'a' && inText[start] == 's')
			{
				result.Context = ExpressionContext.Type;
			} else if (start > 6 && char.IsWhiteSpace(inText, start - 6)
			           && inText[start - 5] == 'i' && inText[start - 4] == 'm'
			           && inText[start - 3] == 'p' && inText[start - 2] == 'o'
			           && inText[start - 1] == 'r' && inText[start] == 't')
			{
				result.Context = ExpressionContext.Importable;
			} else {
				bool wasSquareBracket = false;
				int brackets = 0;
				while (start > 0) {
					char c = inText[start];
					if (c == '\n') break;
					if (brackets == 0) {
						if (c == '(' || c == ',')
							break;
						if (!char.IsWhiteSpace(inText, start))
							wasSquareBracket = inText[start] == '[';
					} else {
						if (c == '[' || c == '(')
							brackets -= 1;
					}
					if (c == ')' || c == ']')
						brackets += 1;
					start -= 1;
				}
				if (wasSquareBracket) {
					result.Context = BooAttributeContext.Instance;
				}
			}
			
			return result;
		}
		
		internal class BooAttributeContext : ExpressionContext
		{
			public static BooAttributeContext Instance = new BooAttributeContext();
			
			public override bool ShowEntry(object o)
			{
				IClass c = o as IClass;
				if (c != null && c.IsAbstract)
					return false;
				if (ExpressionContext.Attribute.ShowEntry(o))
					return true;
				if (c == null)
					return false;
				if (BooProject.BooCompilerPC != null) {
					return c.IsTypeInInheritanceTree(BooProject.BooCompilerPC.GetClass("Boo.Lang.Compiler.AbstractAstAttribute"));
				} else {
					foreach (IReturnType baseType in c.BaseTypes) {
						if (baseType.FullyQualifiedName == "Boo.Lang.Compiler.AbstractAstAttribute")
							return true;
					}
					return false;
				}
			}
		}
		#endregion
		
		#region Find Full Expression
		public ExpressionResult FindFullExpression(string inText, int offset)
		{
			ExpressionResult result = FindExpression(inText, offset);
			if (result.Expression == null)
				return result;
			StringBuilder b = new StringBuilder(result.Expression);
			//  accepting current identifier
			int i;
			for (i = offset + 1; i < inText.Length; i++) {
				char c = inText[i];
				if (!char.IsLetterOrDigit(c) && c != '_') {
					break;
				}
			}
			i -= 1;
			// accepting brackets/parenthesis
			int state = 0;
			ResetStateMachine();
			Stack<int> bracketStack = new Stack<int>();
			while (state >= 0) {
				state = FindNextCodeCharacter(state, inText, ref i);
				if (state < 0) break;
				char c = (i < inText.Length) ? inText[i] : '\0';
				int bracket = _openingBrackets.IndexOf(c);
				if (bracket >= 0) {
					bracketStack.Push(bracket);
				} else {
					if (bracketStack.Count == 0) {
						b.Append(inText, offset + 1, i - offset - 1);
						result.Expression = b.ToString();
						return result;
					} else if (c == '\0') {
						// end of document
						break;
					}
				}
				bracket = _closingBrackets.IndexOf(c);
				if (bracket >= 0) {
					while (bracketStack.Count > 0 && bracketStack.Pop() > bracket);
				}
			}
			return new ExpressionResult(null);
		}
		#endregion
		
		#region State Machine / SimplifyCode
		static readonly int[] inputTable;
		
		static ExpressionFinder()
		{
			inputTable = new int[128];
			for (int i = 0; i < inputTable.Length; i++) {
				inputTable[i] = _elseIndex;
			}
			inputTable[ 34] = 0; // "
			inputTable[ 39] = 1; // '
			inputTable[ 92] = 2; // \
			inputTable[ 10] = 3; // \n
			inputTable[ 13] = 3; // \r
			inputTable[ 36] = 4; // $
			inputTable[123] = 5; // {
			inputTable[125] = 6; // }
			inputTable[ 35] = 7; // #
			inputTable[ 47] = 8; // /
			inputTable[ 42] = 9; // *
		}
		
		const int _elseIndex = 10;
		public const int PossibleRegexStart = 12;
		public const int LineCommentState = 13;
		
		static readonly
			int[][] _stateTable =         { // "    '    \    \n   $    {    }    #    /    *   else
			/* 0: in Code       */ new int[] { 1  , 7  , 0  , 0  , 0  , 0  , 0  , 13 , 12 , 0  , 0  },
			/* 1: after "       */ new int[] { 2  , 6  , 10 , 0  , 8  , 6  , 6  , 6  , 6  , 6  , 6  },
			/* 2: after ""      */ new int[] { 3  , 7  , 0  , 0  , 0  , 0  , 0  , 13 , 12 , 0  , 0  },
			/* 3: in """        */ new int[] { 4  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  },
			/* 4: in """, "     */ new int[] { 5  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  },
			/* 5: in """, ""    */ new int[] { 0  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  , 3  },
			/* 6: in "-string   */ new int[] { 0  , 6  , 10 , 0  , 8  , 6  , 6  , 6  , 6  , 6  , 6  },
			/* 7: in '-string   */ new int[] { 7  , 0  , 11 , 0  , 7  , 7  , 7  , 7  , 7  , 7  , 7  },
			/* 8: after $ in "  */ new int[] { 0  , 6  , 10 , 0  , 8  , 9  , 6  , 6  , 6  , 6  , 6  },
			/* 9: in "{         */ new int[] { 9  , 9  , 9  , 9  , 9  , 9  , 6  , 9  , 9  , 9  , 9  },
			/* 10: after \ in " */ new int[] { 6  , 6  , 6  , 0  , 6  , 6  , 6  , 6  , 6  , 6  , 6  },
			/* 11: after \ in ' */ new int[] { 7  , 7  , 7  , 0  , 7  , 7  , 7  , 7  , 7  , 7  , 7  },
			/* 12: after /      */ new int[] { 1  , 7  , 0  , 0  , 0  , 0  , 0  , 0  , 13 ,-14 , 0  },
			/* 13: line comment */ new int[] { 13 , 13 , 13 , 0  , 13 , 13 , 13 , 13 , 13 , 13 , 13 },
			/* 14: block comment*/ new int[] { 14 , 14 , 14 , 14 , 14 , 14 , 14 , 14 , 14 , 15 , 14 },
			/* 15: after * in bc*/ new int[] { 14 , 14 , 14 , 14 , 14 , 14 , 14 , 14 ,-15 , 15 , 14 }
		};
		
		static bool IsInNormalCode(int state)
		{
			return state == 0 || state == 2 || state == 12;
		}
		
		int commentblocks;
		
		public void ResetStateMachine()
		{
			commentblocks = 0;
		}
		
		public int FeedStateMachine(int oldState, char c)
		{
			int charNum = (int)c;
			int input;
			if (charNum < inputTable.Length) {
				input = inputTable[charNum];
			} else {
				input = _elseIndex;
			}
			int action = _stateTable[oldState][input];
			if (action == -14) {
				// enter block comment
				commentblocks += 1;
				return 14;
			} else if (action == -15) {
				// leave block comment
				commentblocks -= 1;
				if (commentblocks == 0)
					return 0;
				else
					return 14;
			}
			return action;
		}
		
		/// <summary>
		/// Goes to the next position in "text" that is code (not comment, string etc.).
		/// Returns a state that has be passed in as <paramref name="state"/> on the
		/// next call.
		/// </summary>
		public int FindNextCodeCharacter(int state, string text, ref int pos)
		{
			ResetStateMachine();
			do {
				pos += 1;
				if (pos >= text.Length)
					break;
				char c = text[pos];
				state = FeedStateMachine(state, c);
				if (state == PossibleRegexStart) {
					// after / could be a regular expression, do a special check for that
					int regexEnd = SkipRegularExpression(text, pos, text.Length - 1);
					if (regexEnd > 0) {
						pos = regexEnd;
					} else if (regexEnd == -1) {
						// cursor is in regex
						return -1;
					} // else: regexEnd is 0 if its not a regex
				}
			} while (!IsInNormalCode(state));
			return state;
		}
		
		/// <summary>This method makes boo source code "simpler" by removing all comments
		/// and replacing all string litarals through string.Empty.
		/// Regular expressions literals are replaced with the simple regex /a/</summary>
		public string SimplifyCode(string inText, int offset)
		{
			StringBuilder result = new StringBuilder();
			StringBuilder inStringResult = new StringBuilder(" ");
			int state = 0;
			ResetStateMachine();
			int i = -1;
			while (i < offset) {
				i += 1;
				char c = inText[i];
				int action = FeedStateMachine(state, c);
				if (action == 9) {
					// enter inner string expression (${...})
					if (state == 9)
						inStringResult.Append(c);
					else
						inStringResult.Length = 1;
					state = action;
				} else if (action == 0 || action == PossibleRegexStart) {
					// go to normal code
					if (action == PossibleRegexStart) {
						// after / could be a regular expression, do a special check for that
						int regexEnd = SkipRegularExpression(inText, i, offset);
						if (regexEnd > 0) {
							i = regexEnd;
							result.Append("/a");
						} else if (regexEnd == -1) {
							// cursor is in regex
							return null;
						}
					}
					if (state == 2 || (state >= 6 && state <= 11))
						result.Append("''");
					if (IsInNormalCode(state))
						result.Append(c);
					state = action;
				} else {
					state = action;
				}
			}
			if (IsInNormalCode(state)) {
				// cursor is in normal code
				return result.ToString();
			} else if (state == 9) {
				// cursor is in inner string expression (${...})
				return inStringResult.ToString();
			} else {
				// cursor is in comment/string
				return null;
			}
		}
		
		/// <summary>Skips the regular expression in inText at position pos. Returns end position of the ending / if
		/// successful or 0 is no regular expression was found at the location.
		/// Return -1 if maxOffset is inside the regular expression.</summary>
		public int SkipRegularExpression(string inText, int pos, int maxOffset)
		{
			bool containsWhitespace;
			if (pos > 0) {
				containsWhitespace = (inText[pos - 1] == '@');
			} else {
				containsWhitespace = false;
			}
			if (pos == maxOffset) return -1;             // cursor is after / -> cursor inside regex
			if (inText[pos + 1] == '/') return 0;        // double // is comment, no regex
			int i = pos;
			while (i < maxOffset) {
				i += 1;
				if (!containsWhitespace && Char.IsWhiteSpace(inText, i))
					return 0; // this is no regex
				if (inText[i] == '/')
					return i;
			}
			return -1; // maxOffset inside regex
		}
		#endregion
	}
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -