From a17fed9eda03c75acc395458ae82bfbbf74ee926 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 5 May 2005 20:36:11 +0000 Subject: [PATCH] Optimized StringParser. Included PieceTableTextBufferStrategy from David McCloskey, but it's currently commented out. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@121 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/ICSharpCode.TextEditor.csproj | 1 + .../PieceTableTextBufferStrategy.cs | 321 ++++++++++++++++++ .../StringTextBufferStrategy.cs | 3 + .../Project/Src/Gui/Dialogs/NewFileDialog.cs | 2 +- .../Src/Services/StringParser/StringParser.cs | 59 +++- .../Core/Test/ICSharpCode.Core.Tests.csproj | 1 + src/Main/Core/Test/StringParserTests.cs | 82 +++++ 7 files changed, 464 insertions(+), 5 deletions(-) create mode 100644 src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/PieceTableTextBufferStrategy.cs create mode 100644 src/Main/Core/Test/StringParserTests.cs diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj b/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj index 6f6ae64fd3..01732e74be 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj +++ b/src/Libraries/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditor.csproj @@ -177,6 +177,7 @@ + \ No newline at end of file diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/PieceTableTextBufferStrategy.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/PieceTableTextBufferStrategy.cs new file mode 100644 index 0000000000..33c90f8981 --- /dev/null +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/PieceTableTextBufferStrategy.cs @@ -0,0 +1,321 @@ +// +// +// +// +// +// + +using System; +using System.Collections; + +namespace ICSharpCode.TextEditor.Document +{ + // Currently not in use and doesn't compile because SetContent is not implemented. + // If anyone wants to use this, it should be easy to fix it. + /* + public class PieceTableTextBufferStrategy : ITextBufferStrategy + { + protected struct PieceTableDescriptor + { + public bool Buffer; // False for original, True for modified + + public int Offset; + public int Length; + } + + protected string OriginalBuffer = ""; + protected string ModifiedBuffer = ""; // Use StringBuilder + + // Use List + protected ArrayList Descriptors = new ArrayList(); + + public int Length + { + get + { + int length = 0; + + for(int i = 0; i < Descriptors.Count; i++) + { + length += ((PieceTableDescriptor)Descriptors[i]).Length; + } + + return length; + } + } + + public void Insert(int offset, string text) + { + int Len = 0; + int CurrentDesc = 0; + + while(true) + { + PieceTableDescriptor desc = (PieceTableDescriptor)Descriptors[CurrentDesc]; + + // Is the offset in this descriptor + if((Len + desc.Length) >= offset) + { + int gap = offset - Len; + int newoffset = ModifiedBuffer.Length; + + // Add the text to the end of the buffer + ModifiedBuffer += text; + + // Set up descriptor for the new text + PieceTableDescriptor newtext = new PieceTableDescriptor(); + newtext.Offset = newoffset; + newtext.Length = text.Length; + newtext.Buffer = true; + + // Is the offset in the middle of the descriptor + if(gap != 0 && gap != desc.Length) + { + int end = desc.Offset + desc.Length; + + desc.Length = gap; + + // Set up descriptor for the end of the current descriptor + PieceTableDescriptor newdesc = new PieceTableDescriptor(); + newdesc.Offset = desc.Offset + desc.Length; + newdesc.Length = end - newdesc.Offset; + + Descriptors[CurrentDesc] = desc; + Descriptors.Insert(CurrentDesc + 1, newtext); + Descriptors.Insert(CurrentDesc + 2, newdesc); + } + else if(gap == desc.Length) // Is it at the end + { + Descriptors.Insert(CurrentDesc + 1, newtext); + } + else // Is it at the beginning + { + Descriptors.Insert(CurrentDesc, newtext); + } + + break; + } + else + { + CurrentDesc++; + + Len += desc.Length; + + if(CurrentDesc == Descriptors.Count) + break; + } + } + } + + public void Remove(int offset, int length) + { + int Len = 0; + int CurrentDesc = 0; + + while(true) + { + // Does the descriptor contain the offset + if((Len + ((PieceTableDescriptor)Descriptors[CurrentDesc]).Length) >= offset) + { + // Remove the text from the descriptor + RemoveInternal(CurrentDesc, Len, offset, length); + + break; + } + else + { + CurrentDesc++; + + Len += ((PieceTableDescriptor)Descriptors[CurrentDesc]).Length; + + if(CurrentDesc == Descriptors.Count) + break; + } + } + } + + protected void RemoveInternal(int descriptor, int lentodesc, int offset, int length) + { + PieceTableDescriptor desc = (PieceTableDescriptor)Descriptors[descriptor]; + + int gap = offset - lentodesc; + + // Is all the text we want to remove span over multiple descriptors + if((offset + length) > (lentodesc + desc.Length)) + { + lentodesc += desc.Length; + length -= lentodesc - offset; + offset = lentodesc; + + desc.Length = gap; + + // Does the text we want to remove encompass all of this descriptor + if(gap != 0) + { + Descriptors[descriptor] = desc; + + RemoveInternal(descriptor + 1, lentodesc, offset, length); + } + else // It does encompass all of this descriptor so remove it + { + Descriptors.RemoveAt(descriptor); + + RemoveInternal(descriptor, lentodesc, offset, length); + } + } + else + { + // Set up new descriptor to reflect removed text + PieceTableDescriptor newdesc = new PieceTableDescriptor(); + newdesc.Buffer = desc.Buffer; + newdesc.Offset = desc.Offset + gap + length; + newdesc.Length = (desc.Offset + desc.Length) - newdesc.Offset; + + desc.Length = gap; + + // Does the text we want to remove encompass all of this descriptor + if(gap != 0) + { + Descriptors.Insert(descriptor + 1, newdesc); + Descriptors[descriptor] = desc; + } + else + { + // Instead of removing the old and inserting the new, just set the old to the new inside the array + Descriptors[descriptor] = newdesc; + } + } + } + + public void Replace(int offset, int length, string text) + { + Remove(offset, length); + Insert(offset, text); + } + + public void SetText(string text) + { + Descriptors.Clear(); + + ModifiedBuffer = ""; + OriginalBuffer = text; + + PieceTableDescriptor desc = new PieceTableDescriptor(); + desc.Buffer = false; + desc.Offset = 0; + desc.Length = text.Length; + + Descriptors.Add(desc); + } + + public char GetCharAt(int offset) + { + int off = 0; + int currdesc = 0; + + while(true) + { + PieceTableDescriptor desc = (PieceTableDescriptor)Descriptors[currdesc]; + + // Is the offset in the current descriptor + if((off + desc.Length) > offset) + { + // Find the difference between the beginning of the descriptor and the offset + int gap = offset - off; + + if(desc.Buffer == false) // Original Buffer + { + return OriginalBuffer[desc.Offset + gap]; + } + else // Modified Buffer + { + return ModifiedBuffer[desc.Offset + gap]; + } + } + else + { + off += desc.Length; + } + + currdesc++; + + if(currdesc == Descriptors.Count) return '\0'; + } + } + + public string GetText(int offset, int length) + { + string text = ""; + + int off = 0; + int currdesc = 0; + + while(true) + { + // Does the descriptor contain the offset + if((off + ((PieceTableDescriptor)Descriptors[currdesc]).Length) > offset) + { + // Get the text + text += GetTextInternal(currdesc, off, offset, length); + + break; + } + else + { + currdesc++; + + off += ((PieceTableDescriptor)Descriptors[currdesc]).Length; + + if(currdesc == Descriptors.Count) + break; + } + } + + return text; + } + + protected string GetTextInternal(int descriptor, int lentodesc, int offset, int length) + { + PieceTableDescriptor desc = (PieceTableDescriptor)Descriptors[descriptor]; + + int gap = offset - lentodesc; + + string text = ""; + + // Is the text we want greater than this descriptor + if((offset + length) > (lentodesc + desc.Length)) + { + if(desc.Buffer) + { + text += ModifiedBuffer.Substring(desc.Offset + gap, desc.Length); + } + else + { + text += OriginalBuffer.Substring(desc.Offset + gap, desc.Length); + } + + lentodesc += desc.Length; + length -= lentodesc - offset; + offset = lentodesc; + + // Get the text from the next descriptor + text += GetTextInternal(descriptor + 1, lentodesc, offset, length); + } + else + { + // The text we want is in this descriptor so get it + if(desc.Buffer) + { + text += ModifiedBuffer.Substring(desc.Offset + gap, length); + } + else + { + text += OriginalBuffer.Substring(desc.Offset + gap, length); + } + } + + return text; + } + } + */ +} diff --git a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs index 4779152889..aa984eafa6 100644 --- a/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs +++ b/src/Libraries/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs @@ -50,6 +50,9 @@ namespace ICSharpCode.TextEditor.Document if (length == 0) { return ""; } + if (offset == 0 && length >= storedText.Length) { + return storedText; + } return storedText.Substring(offset, Math.Min(length, storedText.Length - offset)); } diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs b/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs index 1491370517..c5b168dcba 100644 --- a/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs +++ b/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs @@ -395,7 +395,7 @@ namespace ICSharpCode.SharpDevelop.Gui string[] subdirs = relPath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); StringBuilder standardNameSpace = new StringBuilder(project.RootNamespace); foreach(string subdir in subdirs) { - if (subdir == "." || subdir == ".." || subdir == "") + if (subdir == "." || subdir == ".." || subdir == "" || subdir.Equals("src", StringComparison.OrdinalIgnoreCase)) continue; standardNameSpace.Append('.'); standardNameSpace.Append(GenerateValidClassName(subdir)); diff --git a/src/Main/Core/Project/Src/Services/StringParser/StringParser.cs b/src/Main/Core/Project/Src/Services/StringParser/StringParser.cs index 4248757be1..e9a23d5ef6 100644 --- a/src/Main/Core/Project/Src/Services/StringParser/StringParser.cs +++ b/src/Main/Core/Project/Src/Services/StringParser/StringParser.cs @@ -76,13 +76,16 @@ namespace ICSharpCode.Core } } - readonly static Regex pattern = new Regex(@"\$\{([^\}]*)\}", RegexOptions.Compiled | RegexOptions.CultureInvariant); + //readonly static Regex pattern = new Regex(@"\$\{([^\}]*)\}", RegexOptions.Compiled | RegexOptions.CultureInvariant); /// /// Expands ${xyz} style property values. /// public static string Parse(string input, string[,] customTags) { + // Parse is a important method and should have good performance, + // so we don't use an expensive Regex here. + /* string output = input; if (input != null) { foreach (Match m in pattern.Matches(input)) { @@ -103,6 +106,54 @@ namespace ICSharpCode.Core } } return output; + */ + if (input == null) + return null; + int pos = 0; + StringBuilder output = null; + do { + int oldPos = pos; + pos = input.IndexOf("${", pos); + if (pos < 0) { + if (output == null) { + return input; + } else { + if (oldPos < input.Length) { + // normal text after last property + output.Append(input, oldPos, input.Length - oldPos); + } + return output.ToString(); + } + } + if (output == null) { + if (pos == 0) + output = new StringBuilder(); + else + output = new StringBuilder(input, 0, pos, pos + 16); + } else { + if (pos > oldPos) { + // normal text between two properties + output.Append(input, oldPos, pos - oldPos); + } + } + int end = input.IndexOf('}', pos + 1); + if (end < 0) { + output.Append("${"); + pos += 2; + } else { + string property = input.Substring(pos + 2, end - pos - 2); + string val = GetValue(property, customTags); + if (val == null) { + output.Append("${"); + output.Append(property); + output.Append('}'); + } else { + output.Append(val); + } + pos = end + 1; + } + } while (pos < input.Length); + return output.ToString(); } static string GetValue(string propertyName, string[,] customTags) @@ -116,14 +167,14 @@ namespace ICSharpCode.Core return null; } } - if (propertyName.Equals("DATE", StringComparison.InvariantCultureIgnoreCase)) + if (propertyName.Equals("DATE", StringComparison.OrdinalIgnoreCase)) return DateTime.Today.ToShortDateString(); - if (propertyName.Equals("TIME", StringComparison.InvariantCultureIgnoreCase)) + if (propertyName.Equals("TIME", StringComparison.OrdinalIgnoreCase)) return DateTime.Now.ToShortTimeString(); if (customTags != null) { for (int j = 0; j < customTags.GetLength(0); ++j) { - if (propertyName.Equals(customTags[j, 0], StringComparison.InvariantCultureIgnoreCase)) { + if (propertyName.Equals(customTags[j, 0], StringComparison.OrdinalIgnoreCase)) { return customTags[j, 1]; } } diff --git a/src/Main/Core/Test/ICSharpCode.Core.Tests.csproj b/src/Main/Core/Test/ICSharpCode.Core.Tests.csproj index 551af6d091..1cd8f0e387 100644 --- a/src/Main/Core/Test/ICSharpCode.Core.Tests.csproj +++ b/src/Main/Core/Test/ICSharpCode.Core.Tests.csproj @@ -43,6 +43,7 @@ + diff --git a/src/Main/Core/Test/StringParserTests.cs b/src/Main/Core/Test/StringParserTests.cs new file mode 100644 index 0000000000..8de2108f40 --- /dev/null +++ b/src/Main/Core/Test/StringParserTests.cs @@ -0,0 +1,82 @@ +/* + * Created by SharpDevelop. + * User: Daniel Grunwald + * Date: 05.05.2005 + * Time: 22:16 + */ + +using System; +using System.Collections.Generic; +using System.IO; +using NUnit.Framework; + +namespace ICSharpCode.Core.Tests +{ + [TestFixture] + public class StringParserTest + { + public StringParserTest() + { + StringParser.Properties["test"] = "Value"; + StringParser.PropertyObjects["obj"] = this; + } + + public string TestProperty { + get { + return "Hello!"; + } + } + + [Test] + public void SimpleProperty() + { + Assert.AreEqual("Value", StringParser.Parse("${test}")); + } + + [Test] + public void CustomInput() + { + Assert.AreEqual("12", StringParser.Parse("${a}${b}", new string[,] {{"a", "1"}, {"b", "2"}})); + } + + [Test] + public void CaseInsensitiveProperty() + { + Assert.AreEqual("Value", StringParser.Parse("${tEsT}")); + } + + [Test] + public void TextOnly() + { + const string txt = "Text"; + Assert.AreEqual(txt, StringParser.Parse(txt)); + // reference should be same: StringParser should not do unnecessary allocations + Assert.AreSame(txt, StringParser.Parse(txt)); + } + + [Test] + public void Mixed() + { + Assert.AreEqual("aValueb", StringParser.Parse("a${test}b")); + } + + [Test] + public void MultipleReplacements() + { + Assert.AreEqual("aValuebValuec", StringParser.Parse("a${test}b${test}c")); + } + + [Test] + public void PropertyObject() + { + Assert.AreEqual("Hello!", StringParser.Parse("${obj:TestProperty}")); + } + + [Test] + public void InvalidPropertyObject() + { + Assert.AreEqual("${invalidObj:TestProperty}", StringParser.Parse("${invalidObj:TestProperty}")); + Assert.AreEqual("${obj:InvalidProperty}", StringParser.Parse("${obj:InvalidProperty}")); + } + } +}