From 5c06585fdfe647d22578167e770a6005814786b5 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sat, 18 Jun 2016 23:41:58 +0200 Subject: [PATCH 01/31] Implemented Quantifiers and CharacterClasses. Tests for both, as well as basic i18n --- Rubberduck.RegexAssistant/Atom.cs | 160 +++++++++++++++ .../IRegularExpression.cs | 18 ++ Rubberduck.RegexAssistant/Pattern.cs | 30 +++ .../Properties/AssemblyInfo.cs | 36 ++++ Rubberduck.RegexAssistant/Quantifier.cs | 90 +++++++++ .../QuantifierExtensions.cs | 45 +++++ .../Rubberduck.RegexAssistant.csproj | 75 +++++++ .../Tests/CharacterClassTests.cs | 188 ++++++++++++++++++ .../Tests/QuantifierTests.cs | 64 ++++++ .../i18n/AssistantResources.Designer.cs | 144 ++++++++++++++ .../i18n/AssistantResources.resx | 148 ++++++++++++++ Rubberduck.sln | 66 +++++- 12 files changed, 1063 insertions(+), 1 deletion(-) create mode 100644 Rubberduck.RegexAssistant/Atom.cs create mode 100644 Rubberduck.RegexAssistant/IRegularExpression.cs create mode 100644 Rubberduck.RegexAssistant/Pattern.cs create mode 100644 Rubberduck.RegexAssistant/Properties/AssemblyInfo.cs create mode 100644 Rubberduck.RegexAssistant/Quantifier.cs create mode 100644 Rubberduck.RegexAssistant/QuantifierExtensions.cs create mode 100644 Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj create mode 100644 Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs create mode 100644 Rubberduck.RegexAssistant/Tests/QuantifierTests.cs create mode 100644 Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs create mode 100644 Rubberduck.RegexAssistant/i18n/AssistantResources.resx diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs new file mode 100644 index 0000000000..f4eb56b80f --- /dev/null +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -0,0 +1,160 @@ +using Rubberduck.RegexAssistant.Extensions; +using Rubberduck.RegexAssistant.i18n; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rubberduck.RegexAssistant +{ + public interface Atom : IRegularExpression + { + + } + + public class CharacterClass : Atom + { + public static readonly string Pattern = @"(?.*?)(? CharacterSpecifiers { get; } + + public CharacterClass(string specifier) + { + Match m = Matcher.Match(specifier); + if (!m.Success) + { + throw new ArgumentException("The give specifier does not denote a character class"); + } + string actualSpecifier = m.Groups["expression"].Value; + InverseMatching = actualSpecifier.StartsWith("^"); + CharacterSpecifiers = new List(); + + ExtractCharacterSpecifiers(InverseMatching ? actualSpecifier.Substring(1) : actualSpecifier); + } + + private static readonly Regex CharacterRanges = new Regex(@"(\\[dDwWsS]|(\\[ntfvr]|\\([0-7]{3}|x[\dA-F]{2}|u[\dA-F]{4}|[\\\.\[\]])|.)(-(\\[ntfvr]|\\([0-7]{3}|x[A-F]{2}|u[\dA-F]{4}|[\.\\\[\]])|.))?)"); + private void ExtractCharacterSpecifiers(string characterClass) + { + MatchCollection specifiers = CharacterRanges.Matches(characterClass); + + foreach (Match specifier in specifiers) + { + if (specifier.Value.Contains("\\")) + { + if (specifier.Value.EndsWith("-\\")) + { + // BOOM! + throw new ArgumentException("Character Ranges that have incorrectly escaped characters as target are not allowed"); + } + else if (specifier.Value.Length == 1) + { + // fun... we somehow got to grab a single backslash. Pattern is probably broken + // alas for simplicity we just skip the incorrect backslash + // TODO: make a warning from this.. how?? no idea + continue; + } + } + CharacterSpecifiers.Add(specifier.Value); + } + } + + public string Description + { + get + { + return string.Format(InverseMatching ? AssistantResources.AtomDescription_CharacterClass_Inverted : AssistantResources.AtomDescription_CharacterClass, HumanReadableClass(), Quantifier.HumanReadable()); + } + } + + private string HumanReadableClass() + { + return string.Join(", ", CharacterSpecifiers); // join last with and? + } + + public Quantifier Quantifier + { + get + { + throw new NotImplementedException(); + } + } + + public bool TryMatch(ref string text) + { + throw new NotImplementedException(); + } + } + + class Group : Atom + { + public string Description + { + get + { + throw new NotImplementedException(); + } + } + + public Quantifier Quantifier + { + get + { + throw new NotImplementedException(); + } + } + + public bool TryMatch(ref string text) + { + throw new NotImplementedException(); + } + } + + class EscapedCharacter : Atom + { + public string Description + { + get + { + throw new NotImplementedException(); + } + } + + public Quantifier Quantifier + { + get + { + throw new NotImplementedException(); + } + } + + public bool TryMatch(ref string text) + { + throw new NotImplementedException(); + } + } + + class Literal : Atom + { + public string Description + { + get + { + throw new NotImplementedException(); + } + } + + public Quantifier Quantifier + { + get + { + throw new NotImplementedException(); + } + } + + public bool TryMatch(ref string text) + { + throw new NotImplementedException(); + } + } +} diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs new file mode 100644 index 0000000000..f507dac207 --- /dev/null +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rubberduck.RegexAssistant +{ + public interface IRegularExpression + { + Quantifier Quantifier { get; } + + + String Description { get; } + Boolean TryMatch(ref String text); + + } +} diff --git a/Rubberduck.RegexAssistant/Pattern.cs b/Rubberduck.RegexAssistant/Pattern.cs new file mode 100644 index 0000000000..7b40ac9539 --- /dev/null +++ b/Rubberduck.RegexAssistant/Pattern.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rubberduck.RegexAssistant +{ + class Pattern + { + IRegularExpression RootExpression; + MatcherFlags Flags; + + public Pattern(string expression, bool ignoreCase, bool global) + { + Flags = ignoreCase ? MatcherFlags.IgnoreCase : 0; + Flags = global ? Flags | MatcherFlags.Global : Flags; + + // FIXME build expression from string + } + + } + + [Flags] + enum MatcherFlags + { + IgnoreCase = 1 << 0, + Global = 1 << 1, + } +} diff --git a/Rubberduck.RegexAssistant/Properties/AssemblyInfo.cs b/Rubberduck.RegexAssistant/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..32022c89d5 --- /dev/null +++ b/Rubberduck.RegexAssistant/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Rubberduck.RegexAssistant")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Rubberduck.RegexAssistant")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("40cc03e3-756c-4674-af07-384115deaee2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Rubberduck.RegexAssistant/Quantifier.cs b/Rubberduck.RegexAssistant/Quantifier.cs new file mode 100644 index 0000000000..0ddf5d39c1 --- /dev/null +++ b/Rubberduck.RegexAssistant/Quantifier.cs @@ -0,0 +1,90 @@ + +using System; +using System.Text.RegularExpressions; + +namespace Rubberduck.RegexAssistant +{ + public class Quantifier + { + public static readonly Regex QuantifierMatch = new Regex(@"(?\d+)(?,\d*)?\}$"); + + public readonly QuantifierKind Kind; + public readonly int MinimumMatches; + public readonly int MaximumMatches; + + public Quantifier(string expression) + { + if (expression.Length == 0) + { + Kind = QuantifierKind.None; + MaximumMatches = 1; + MinimumMatches = 1; + } + else if (expression.Length > 1) + { + Kind = QuantifierKind.Expression; + if (!_expressionPattern.IsMatch(expression)) + { + throw new ArgumentException(String.Format("Cannot extract a Quantifier from the expression {1}", expression)); + } + Match m = _expressionPattern.Match(expression); + int minimum; + // shouldn't ever happen + if (!int.TryParse(m.Groups["min"].Value, out minimum)) + { + throw new ArgumentException("Cannot Parse Quantifier Expression into Range"); + } + MinimumMatches = minimum; + + string maximumString = m.Groups["max"].Value; // drop the comma + if (maximumString.Length > 1) + { + int maximum; + // shouldn't ever happen + if (!int.TryParse(maximumString.Substring(1), out maximum)) + { + throw new ArgumentException("Cannot Parse Quantifier Expression into Range"); + } + MaximumMatches = maximum; + } + else if (maximumString.Length == 1) // got a comma, so we're unbounded + { + MaximumMatches = int.MaxValue; + } + else // exact match, because no comma + { + MaximumMatches = minimum; + } + } + else + { + switch (expression.ToCharArray()[0]) + { + case '*': + MinimumMatches = 0; + MaximumMatches = int.MaxValue; + Kind = QuantifierKind.Wildcard; + break; + case '+': + MinimumMatches = 1; + MaximumMatches = int.MaxValue; + Kind = QuantifierKind.Wildcard; + break; + case '?': + MinimumMatches = 0; + MaximumMatches = 1; + Kind = QuantifierKind.Wildcard; + break; + default: + throw new ArgumentException("Passed Quantifier String was not an allowed Quantifier"); + } + } + } + } + + public enum QuantifierKind + { + None, Expression, Wildcard + } +} diff --git a/Rubberduck.RegexAssistant/QuantifierExtensions.cs b/Rubberduck.RegexAssistant/QuantifierExtensions.cs new file mode 100644 index 0000000000..c77fd00c8c --- /dev/null +++ b/Rubberduck.RegexAssistant/QuantifierExtensions.cs @@ -0,0 +1,45 @@ +using Rubberduck.RegexAssistant.i18n; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rubberduck.RegexAssistant.Extensions +{ + static class QuantifierExtensions + { + public static string HumanReadable(this Quantifier quant) + { + switch (quant.Kind) + { + case QuantifierKind.None: + return AssistantResources.Quantifier_None; + case QuantifierKind.Wildcard: + if (quant.MaximumMatches == 1) + { + return AssistantResources.Quantifier_Optional; + } + if (quant.MinimumMatches == 0) + { + return AssistantResources.Quantifier_Asterisk; + } + return AssistantResources.Quantifer_Plus; + case QuantifierKind.Expression: + if (quant.MaximumMatches == quant.MinimumMatches) + { + return string.Format(AssistantResources.Quantifier_Exact, quant.MinimumMatches); + } + if (quant.MaximumMatches == int.MaxValue) + { + return string.Format(AssistantResources.Quantifier_OpenRange, quant.MinimumMatches); + } + return string.Format(AssistantResources.Quantifier_ClosedRange, quant.MinimumMatches, quant.MaximumMatches); + + } + return ""; + } + } +} + + \ No newline at end of file diff --git a/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj new file mode 100644 index 0000000000..76e99d728f --- /dev/null +++ b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {40CC03E3-756C-4674-AF07-384115DEAEE2} + Library + Properties + Rubberduck.RegexAssistant + Rubberduck.RegexAssistant + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + True + True + AssistantResources.resx + + + + + + + + + + + + + + + ResXFileCodeGenerator + AssistantResources.Designer.cs + + + + + \ No newline at end of file diff --git a/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs b/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs new file mode 100644 index 0000000000..07230ead38 --- /dev/null +++ b/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs @@ -0,0 +1,188 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Rubberduck.RegexAssistant; +using System.Collections.Generic; + +namespace RegexAssistantTests +{ + [TestClass] + public class CharacterClassTests + { + [TestMethod] + public void InvertedCharacterClass() + { + CharacterClass cut = new CharacterClass("[^ ]"); + Assert.IsTrue(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(" "); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + + [TestMethod] + public void SimpleCharacterRange() + { + CharacterClass cut = new CharacterClass("[a-z]"); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add("a-z"); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + + [TestMethod] + public void UnicodeCharacterRange() + { + CharacterClass cut = new CharacterClass(@"[\u00A2-\uFFFF]"); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(@"\u00A2-\uFFFF"); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + + [TestMethod] + public void OctalCharacterRange() + { + CharacterClass cut = new CharacterClass(@"[\011-\777]"); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(@"\011-\777"); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + + [TestMethod] + public void HexadecimalCharacterRange() + { + CharacterClass cut = new CharacterClass(@"[\x00-\xFF]"); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(@"\x00-\xFF"); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + + [TestMethod] + public void MixedCharacterRanges() + { + CharacterClass cut = new CharacterClass(@"[\x00-\777\u001A-Z]"); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(@"\x00-\777"); + expectedSpecifiers.Add(@"\u001A-Z"); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + + [TestMethod] + public void RangeFailureWithCharacterClass() + { + foreach (string charClass in new string[]{ @"\D", @"\d", @"\s", @"\S", @"\w", @"\W" }){ + CharacterClass cut = new CharacterClass(string.Format("[{0}-F]", charClass)); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(charClass); + expectedSpecifiers.Add(@"-"); + expectedSpecifiers.Add(@"F"); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + } + + [TestMethod] + public void EscapedLiteralRanges() + { + foreach (string escapedLiteral in new string[] { @"\.", @"\[", @"\]" }) + { + CharacterClass cut = new CharacterClass(string.Format("[{0}-F]", escapedLiteral)); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(string.Format("{0}-F",escapedLiteral)); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + // invert this + cut = new CharacterClass(string.Format("[F-{0}]", escapedLiteral)); + Assert.IsFalse(cut.InverseMatching); + expectedSpecifiers.Clear(); + expectedSpecifiers.Add(string.Format("F-{0}", escapedLiteral)); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } + } + + [TestMethod] + public void SkipsIncorrectlyEscapedLiterals() + { + foreach (string escapedLiteral in new string[] { @"\(", @"\)", @"\{", @"\}", @"\|", @"\?", @"\*" }) + { + CharacterClass cut = new CharacterClass(string.Format("[{0}-F]", escapedLiteral)); + Assert.IsFalse(cut.InverseMatching); + List expectedSpecifiers = new List(); + expectedSpecifiers.Add(string.Format("{0}-F", escapedLiteral.Substring(1))); + + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + // inverted doesn't need to behave the same, because VBA blows up for ranges like R-\( + + } + } + + [TestMethod] + public void IncorrectlyEscapedRangeTargetLiteralsBlowUp() + { + foreach (string escapedLiteral in new string[] { @"\(", @"\)", @"\{", @"\}", @"\|", @"\?", @"\*" }) + { + try + { + CharacterClass cut = new CharacterClass(string.Format("[F-{0}]", escapedLiteral)); + } + catch (ArgumentException ex) + { + continue; + } + Assert.Fail("Incorrectly allowed character range with {0} as target", escapedLiteral); + } + + } + } +} diff --git a/Rubberduck.RegexAssistant/Tests/QuantifierTests.cs b/Rubberduck.RegexAssistant/Tests/QuantifierTests.cs new file mode 100644 index 0000000000..d7cab4a0a0 --- /dev/null +++ b/Rubberduck.RegexAssistant/Tests/QuantifierTests.cs @@ -0,0 +1,64 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Rubberduck.RegexAssistant; + +namespace RegexAssistantTests +{ + [TestClass] + public class QuantifierTests + { + [TestMethod] + public void AsteriskQuantifier() + { + Quantifier cut = new Quantifier("*"); + Assert.AreEqual(int.MaxValue, cut.MaximumMatches); + Assert.AreEqual(0, cut.MinimumMatches); + Assert.AreEqual(QuantifierKind.Wildcard, cut.Kind); + } + + [TestMethod] + public void QuestionMarkQuantifier() + { + Quantifier cut = new Quantifier("?"); + Assert.AreEqual(1, cut.MaximumMatches); + Assert.AreEqual(0, cut.MinimumMatches); + Assert.AreEqual(QuantifierKind.Wildcard, cut.Kind); + } + + [TestMethod] + public void PlusQuantifier() + { + Quantifier cut = new Quantifier("+"); + Assert.AreEqual(int.MaxValue, cut.MaximumMatches); + Assert.AreEqual(1, cut.MinimumMatches); + Assert.AreEqual(QuantifierKind.Wildcard, cut.Kind); + } + + [TestMethod] + public void ExactQuantifier() + { + Quantifier cut = new Quantifier("{5}"); + Assert.AreEqual(5, cut.MaximumMatches); + Assert.AreEqual(5, cut.MinimumMatches); + Assert.AreEqual(QuantifierKind.Expression, cut.Kind); + } + + [TestMethod] + public void FullRangeQuantifier() + { + Quantifier cut = new Quantifier("{2,5}"); + Assert.AreEqual(2, cut.MinimumMatches); + Assert.AreEqual(5, cut.MaximumMatches); + Assert.AreEqual(QuantifierKind.Expression, cut.Kind); + } + + [TestMethod] + public void OpenRangeQuantifier() + { + Quantifier cut = new Quantifier("{3,}"); + Assert.AreEqual(3, cut.MinimumMatches); + Assert.AreEqual(int.MaxValue, cut.MaximumMatches); + Assert.AreEqual(QuantifierKind.Expression, cut.Kind); + + } + } +} diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs new file mode 100644 index 0000000000..c46be40c35 --- /dev/null +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Rubberduck.RegexAssistant.i18n { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class AssistantResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal AssistantResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Rubberduck.RegexAssistant.i18n.AssistantResources", typeof(AssistantResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Matches any of the following characters {0} {1}.. + /// + internal static string AtomDescription_CharacterClass { + get { + return ResourceManager.GetString("AtomDescription_CharacterClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches any character that is not one of {0} {1}.. + /// + internal static string AtomDescription_CharacterClass_Inverted { + get { + return ResourceManager.GetString("AtomDescription_CharacterClass_Inverted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to at least once. + /// + internal static string Quantifer_Plus { + get { + return ResourceManager.GetString("Quantifer_Plus", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to any number of times, including never. + /// + internal static string Quantifier_Asterisk { + get { + return ResourceManager.GetString("Quantifier_Asterisk", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to between {0} and {1} times. + /// + internal static string Quantifier_ClosedRange { + get { + return ResourceManager.GetString("Quantifier_ClosedRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to exactly {0} times. + /// + internal static string Quantifier_Exact { + get { + return ResourceManager.GetString("Quantifier_Exact", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to exactly once. + /// + internal static string Quantifier_None { + get { + return ResourceManager.GetString("Quantifier_None", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to at least {0} times. + /// + internal static string Quantifier_OpenRange { + get { + return ResourceManager.GetString("Quantifier_OpenRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to either once or never. + /// + internal static string Quantifier_Optional { + get { + return ResourceManager.GetString("Quantifier_Optional", resourceCulture); + } + } + } +} diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.resx b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx new file mode 100644 index 0000000000..8d69104be3 --- /dev/null +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Matches any of the following characters {0} {1}. + first param for the class, second for the quantifier + + + Matches any character that is not one of {0} {1}. + + + at least once + + + any number of times, including never + + + between {0} and {1} times + + + exactly {0} times + + + exactly once + + + at least {0} times + + + either once or never + + \ No newline at end of file diff --git a/Rubberduck.sln b/Rubberduck.sln index 76231b3dcd..1b4df07996 100644 --- a/Rubberduck.sln +++ b/Rubberduck.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rubberduck", "RetailCoder.VBE\Rubberduck.csproj", "{20589DE8-432E-4359-9232-69EB070B7185}" ProjectSection(ProjectDependencies) = postProject @@ -41,6 +41,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RubberduckTests", "Rubberdu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rubberduck.SettingsProvider", "Rubberduck.SettingsProvider\Rubberduck.SettingsProvider.csproj", "{E85E1253-86D6-45EE-968B-F37348D44132}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rubberduck.RegexAssistant", "Rubberduck.RegexAssistant\Rubberduck.RegexAssistant.csproj", "{40CC03E3-756C-4674-AF07-384115DEAEE2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegexAssistantTests", "RegexAssistantTests\RegexAssistantTests.csproj", "{ADBFEE9A-3E20-4D24-939A-12C143A065F2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -209,6 +213,66 @@ Global {E85E1253-86D6-45EE-968B-F37348D44132}.Release64|Any CPU.Build.0 = Release|Any CPU {E85E1253-86D6-45EE-968B-F37348D44132}.Release64|x64.ActiveCfg = Release|Any CPU {E85E1253-86D6-45EE-968B-F37348D44132}.Release64|x86.ActiveCfg = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug|x64.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug|x64.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug|x86.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug|x86.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug64|Any CPU.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug64|Any CPU.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug64|x64.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug64|x64.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug64|x86.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Debug64|x86.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.DebugAccess|Any CPU.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.DebugAccess|Any CPU.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.DebugAccess|x64.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.DebugAccess|x64.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.DebugAccess|x86.ActiveCfg = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.DebugAccess|x86.Build.0 = Debug|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release|Any CPU.Build.0 = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release|x64.ActiveCfg = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release|x64.Build.0 = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release|x86.ActiveCfg = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release|x86.Build.0 = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|Any CPU.ActiveCfg = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|Any CPU.Build.0 = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|x64.ActiveCfg = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|x64.Build.0 = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|x86.ActiveCfg = Release|Any CPU + {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|x86.Build.0 = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x64.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x86.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|Any CPU.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|Any CPU.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x64.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x64.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x86.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x86.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|Any CPU.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|Any CPU.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x64.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x64.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x86.ActiveCfg = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x86.Build.0 = Debug|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|Any CPU.Build.0 = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x64.ActiveCfg = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x64.Build.0 = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x86.ActiveCfg = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x86.Build.0 = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|Any CPU.ActiveCfg = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|Any CPU.Build.0 = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x64.ActiveCfg = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x64.Build.0 = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x86.ActiveCfg = Release|Any CPU + {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e2fb2bfa811eb97b19ee7211e24a7cb5e7fabbe7 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 19 Jun 2016 02:00:38 +0200 Subject: [PATCH 02/31] Minor Rework of interactions between Atoms and IRegularExpression. Added all necessary i18n strings for basic stuff. Stuffed minor leak in CharacterClassTests --- Rubberduck.RegexAssistant/Atom.cs | 20 +- Rubberduck.RegexAssistant/IDescribable.cs | 7 + .../Rubberduck.RegexAssistant.csproj | 1 + .../Tests/CharacterClassTests.cs | 15 ++ .../i18n/AssistantResources.Designer.cs | 193 +++++++++++++++++- .../i18n/AssistantResources.resx | 68 +++++- Rubberduck.sln | 32 --- 7 files changed, 286 insertions(+), 50 deletions(-) create mode 100644 Rubberduck.RegexAssistant/IDescribable.cs diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index f4eb56b80f..38a4b2be8e 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -1,12 +1,11 @@ -using Rubberduck.RegexAssistant.Extensions; -using Rubberduck.RegexAssistant.i18n; +using Rubberduck.RegexAssistant.i18n; using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Rubberduck.RegexAssistant { - public interface Atom : IRegularExpression + public interface Atom : IDescribable { } @@ -24,7 +23,7 @@ public CharacterClass(string specifier) Match m = Matcher.Match(specifier); if (!m.Success) { - throw new ArgumentException("The give specifier does not denote a character class"); + throw new ArgumentException("The given specifier does not denote a character class"); } string actualSpecifier = m.Groups["expression"].Value; InverseMatching = actualSpecifier.StartsWith("^"); @@ -63,7 +62,10 @@ public string Description { get { - return string.Format(InverseMatching ? AssistantResources.AtomDescription_CharacterClass_Inverted : AssistantResources.AtomDescription_CharacterClass, HumanReadableClass(), Quantifier.HumanReadable()); + return string.Format(InverseMatching + ? AssistantResources.AtomDescription_CharacterClass_Inverted + : AssistantResources.AtomDescription_CharacterClass + , HumanReadableClass()); } } @@ -72,14 +74,6 @@ private string HumanReadableClass() return string.Join(", ", CharacterSpecifiers); // join last with and? } - public Quantifier Quantifier - { - get - { - throw new NotImplementedException(); - } - } - public bool TryMatch(ref string text) { throw new NotImplementedException(); diff --git a/Rubberduck.RegexAssistant/IDescribable.cs b/Rubberduck.RegexAssistant/IDescribable.cs new file mode 100644 index 0000000000..c6dc828cf1 --- /dev/null +++ b/Rubberduck.RegexAssistant/IDescribable.cs @@ -0,0 +1,7 @@ +namespace Rubberduck.RegexAssistant +{ + public interface IDescribable + { + string Description { get; } + } +} \ No newline at end of file diff --git a/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj index 76e99d728f..966f5990db 100644 --- a/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj +++ b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj @@ -47,6 +47,7 @@ True AssistantResources.resx + diff --git a/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs b/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs index 07230ead38..f94ac924a8 100644 --- a/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs +++ b/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs @@ -184,5 +184,20 @@ public void IncorrectlyEscapedRangeTargetLiteralsBlowUp() } } + + [TestMethod] + public void IgnoresBackreferenceSpecifiers() + { + CharacterClass cut = new CharacterClass(@"[\1]"); + Assert.IsFalse(cut.InverseMatching); + + List expectedSpecifiers = new List(); + expectedSpecifiers.Add("1"); + Assert.AreEqual(expectedSpecifiers.Count, cut.CharacterSpecifiers.Count); + for (int i = 0; i < expectedSpecifiers.Count; i++) + { + Assert.AreEqual(expectedSpecifiers[i], cut.CharacterSpecifiers[i]); + } + } } } diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs index c46be40c35..6e388ff833 100644 --- a/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs @@ -61,7 +61,16 @@ internal AssistantResources() { } /// - /// Looks up a localized string similar to Matches any of the following characters {0} {1}.. + /// Looks up a localized string similar to Matches the ASCII character CR. + /// + internal static string AtomDescription_CarriageReturn { + get { + return ResourceManager.GetString("AtomDescription_CarriageReturn", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches any of the following characters {0}. /// internal static string AtomDescription_CharacterClass { get { @@ -70,7 +79,7 @@ internal static string AtomDescription_CharacterClass { } /// - /// Looks up a localized string similar to Matches any character that is not one of {0} {1}.. + /// Looks up a localized string similar to Matches any character that is not one of {0}. /// internal static string AtomDescription_CharacterClass_Inverted { get { @@ -78,6 +87,186 @@ internal static string AtomDescription_CharacterClass_Inverted { } } + /// + /// Looks up a localized string similar to Matches any digit. Equivalent to "[0-9]". + /// + internal static string AtomDescription_Digit { + get { + return ResourceManager.GetString("AtomDescription_Digit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches anything. + /// + internal static string AtomDescription_Dot { + get { + return ResourceManager.GetString("AtomDescription_Dot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the "Form-Feed"-character. + /// + internal static string AtomDescription_FormFeed { + get { + return ResourceManager.GetString("AtomDescription_FormFeed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the group {0} as follows. + /// + internal static string AtomDescription_Group { + get { + return ResourceManager.GetString("AtomDescription_Group", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the horizontal "Tab"-character. + /// + internal static string AtomDescription_HTab { + get { + return ResourceManager.GetString("AtomDescription_HTab", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Literally matches {0}. + /// + internal static string AtomDescription_Literal_ActualLiteral { + get { + return ResourceManager.GetString("AtomDescription_Literal_ActualLiteral", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exactly matches what the capture group at position {0} matched again. + /// + internal static string AtomDescription_Literal_Backreference { + get { + return ResourceManager.GetString("AtomDescription_Literal_Backreference", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the escaped literal {0}. + /// + internal static string AtomDescription_Literal_EscapedLiteral { + get { + return ResourceManager.GetString("AtomDescription_Literal_EscapedLiteral", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the Hexadecimal ASCII value {0}. + /// + internal static string AtomDescription_Literal_HexCodepoint { + get { + return ResourceManager.GetString("AtomDescription_Literal_HexCodepoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the Octal ASCII value {0}. + /// + internal static string AtomDescription_Literal_OctalCodepoint { + get { + return ResourceManager.GetString("AtomDescription_Literal_OctalCodepoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the Unicode Codepoint {0}. + /// + internal static string AtomDescription_Literal_UnicodePoint { + get { + return ResourceManager.GetString("AtomDescription_Literal_UnicodePoint", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the ASCII character LF, also known as newline. + /// + internal static string AtomDescription_Newline { + get { + return ResourceManager.GetString("AtomDescription_Newline", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches non-digits. Equivalent to "[^\d]". + /// + internal static string AtomDescription_NonDigit { + get { + return ResourceManager.GetString("AtomDescription_NonDigit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches non-whitespace characters. Equivalent to "[^\s]". + /// + internal static string AtomDescription_NonWhitespace { + get { + return ResourceManager.GetString("AtomDescription_NonWhitespace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ensures that the current position is at a "non-word-boundary". + /// + internal static string AtomDescription_NonWordBoundary { + get { + return ResourceManager.GetString("AtomDescription_NonWordBoundary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches "non-word characters". Equivalent to "[^\w]". + /// + internal static string AtomDescription_NonWordCharacter { + get { + return ResourceManager.GetString("AtomDescription_NonWordCharacter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches the vertical "Tab"-character. + /// + internal static string AtomDescription_VTab { + get { + return ResourceManager.GetString("AtomDescription_VTab", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches any whitespace character. Equivalent to "[ \t\r\n\v\f]".. + /// + internal static string AtomDescription_Whitespace { + get { + return ResourceManager.GetString("AtomDescription_Whitespace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ensures that the current position is at a "word boundary". + /// + internal static string AtomDescription_WordBoundary { + get { + return ResourceManager.GetString("AtomDescription_WordBoundary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matches any "word character". Equivalent to "[a-zA-Z_0-9]". + /// + internal static string AtomDescription_WordCharacter { + get { + return ResourceManager.GetString("AtomDescription_WordCharacter", resourceCulture); + } + } + /// /// Looks up a localized string similar to at least once. /// diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.resx b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx index 8d69104be3..8fce5982f4 100644 --- a/Rubberduck.RegexAssistant/i18n/AssistantResources.resx +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx @@ -117,12 +117,74 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Matches the ASCII character CR + - Matches any of the following characters {0} {1}. - first param for the class, second for the quantifier + Matches any of the following characters {0} - Matches any character that is not one of {0} {1}. + Matches any character that is not one of {0} + + + Matches any digit. Equivalent to "[0-9]" + + + Matches anything + + + Matches the "Form-Feed"-character + + + Matches the group {0} as follows + + + Matches the horizontal "Tab"-character + + + Literally matches {0} + + + Exactly matches what the capture group at position {0} matched again + + + Matches the escaped literal {0} + + + Matches the Hexadecimal ASCII value {0} + + + Matches the Octal ASCII value {0} + + + Matches the Unicode Codepoint {0} + + + Matches the ASCII character LF, also known as newline + + + Matches non-digits. Equivalent to "[^\d]" + + + Matches non-whitespace characters. Equivalent to "[^\s]" + + + Ensures that the current position is at a "non-word-boundary" + + + Matches "non-word characters". Equivalent to "[^\w]" + + + Matches the vertical "Tab"-character + + + Matches any whitespace character. Equivalent to "[ \t\r\n\v\f]". + + + Ensures that the current position is at a "word boundary" + + + Matches any "word character". Equivalent to "[a-zA-Z_0-9]" at least once diff --git a/Rubberduck.sln b/Rubberduck.sln index 1b4df07996..dff7323885 100644 --- a/Rubberduck.sln +++ b/Rubberduck.sln @@ -43,8 +43,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rubberduck.SettingsProvider EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rubberduck.RegexAssistant", "Rubberduck.RegexAssistant\Rubberduck.RegexAssistant.csproj", "{40CC03E3-756C-4674-AF07-384115DEAEE2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegexAssistantTests", "RegexAssistantTests\RegexAssistantTests.csproj", "{ADBFEE9A-3E20-4D24-939A-12C143A065F2}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -243,36 +241,6 @@ Global {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|x64.Build.0 = Release|Any CPU {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|x86.ActiveCfg = Release|Any CPU {40CC03E3-756C-4674-AF07-384115DEAEE2}.Release64|x86.Build.0 = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x64.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x64.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x86.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug|x86.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|Any CPU.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|Any CPU.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x64.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x64.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x86.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Debug64|x86.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|Any CPU.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|Any CPU.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x64.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x64.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x86.ActiveCfg = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.DebugAccess|x86.Build.0 = Debug|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|Any CPU.Build.0 = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x64.ActiveCfg = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x64.Build.0 = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x86.ActiveCfg = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release|x86.Build.0 = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|Any CPU.ActiveCfg = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|Any CPU.Build.0 = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x64.ActiveCfg = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x64.Build.0 = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x86.ActiveCfg = Release|Any CPU - {ADBFEE9A-3E20-4D24-939A-12C143A065F2}.Release64|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6d93a676808bb363ebb6f23ac277eaa02b23b063 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 19 Jun 2016 02:01:02 +0200 Subject: [PATCH 03/31] Churned out the Group Atom --- Rubberduck.RegexAssistant/Atom.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index 38a4b2be8e..6cb2b4f049 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -82,19 +82,27 @@ public bool TryMatch(ref string text) class Group : Atom { - public string Description - { - get + public static readonly string Pattern = @"(?.*)(? Date: Sun, 19 Jun 2016 02:01:19 +0200 Subject: [PATCH 04/31] Churned out the Literal atom --- Rubberduck.RegexAssistant/Atom.cs | 116 ++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 23 deletions(-) diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index 6cb2b4f049..9a2d2f9cd7 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -112,44 +112,114 @@ public bool TryMatch(ref string text) } } - class EscapedCharacter : Atom + class Literal : Atom { - public string Description - { - get - { - throw new NotImplementedException(); - } - } + public static readonly string Pattern = @"\(?:[bB(){}\\\[\]\.+*?\dnftvrdDwWsS]|u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3})|."; + private static readonly Regex Matcher = new Regex("^" + Pattern + "$"); + private static readonly ISet EscapeLiterals = new HashSet(); + private readonly string specifier; - public Quantifier Quantifier - { - get + static Literal() { + foreach (char escape in new char[]{ '.', '+', '*', '?', '(', ')', '{', '}', '[', ']', '|', '\\' }) { - throw new NotImplementedException(); + EscapeLiterals.Add(escape); } } - public bool TryMatch(ref string text) - { - throw new NotImplementedException(); - } - } - class Literal : Atom - { - public string Description + public Literal(string specifier) { - get + Match m = Matcher.Match(specifier); + if (!m.Success) { - throw new NotImplementedException(); + throw new ArgumentException("The given specifier does not denote a Literal"); } + this.specifier = specifier; } - public Quantifier Quantifier + public string Description { get { + // here be dragons! + // keep track of: + // - escaped chars + // - escape sequences (each having a different description) + // - codepoint escapes (belongs into above category but kept separate) + // - and actually boring literal matches + if (specifier.Length > 1) + { + string relevant = specifier.Substring(1); // skip the damn Backslash at the start + if (relevant.Length > 1) // longer sequences + { + if (relevant.StartsWith("u")) + { + return string.Format(AssistantResources.AtomDescription_Literal_UnicodePoint, relevant.Substring(1)); //skip u + } + else if (relevant.StartsWith("x")) + { + return string.Format(AssistantResources.AtomDescription_Literal_HexCodepoint, relevant.Substring(1)); // skip x + } + else + { + return string.Format(AssistantResources.AtomDescription_Literal_OctalCodepoint, relevant); // no format specifier to skip + } + } + else if (EscapeLiterals.Contains(relevant[0])) + { + return string.Format(AssistantResources.AtomDescription_Literal_EscapedLiteral, relevant); + } + else if (char.IsDigit(relevant[0])) + { + return string.Format(AssistantResources.AtomDescription_Literal_Backreference, relevant); + } + else + { + // special escapes here + switch (relevant[0]) + { + case 'd': + return AssistantResources.AtomDescription_Digit; + case 'D': + return AssistantResources.AtomDescription_NonDigit; + case 'b': + return AssistantResources.AtomDescription_WordBoundary; + case 'B': + return AssistantResources.AtomDescription_NonWordBoundary; + case 'w': + return AssistantResources.AtomDescription_WordCharacter; + case 'W': + return AssistantResources.AtomDescription_NonWordCharacter; + case 's': + return AssistantResources.AtomDescription_Whitespace; + case 'S': + return AssistantResources.AtomDescription_NonWhitespace; + case 'n': + return AssistantResources.AtomDescription_Newline; + case 'r': + return AssistantResources.AtomDescription_CarriageReturn; + case 'f': + return AssistantResources.AtomDescription_FormFeed; + case 'v': + return AssistantResources.AtomDescription_VTab; + case 't': + return AssistantResources.AtomDescription_HTab; + default: + // shouldn't ever happen, so we blow it all up + throw new InvalidOperationException("took an escape sequence that shouldn't exist"); + } + } + } + else + { + if (specifier.Equals(".")) + { + return AssistantResources.AtomDescription_Dot; + } + // Behaviour with "." needs fix + return string.Format(AssistantResources.AtomDescription_Literal_ActualLiteral, specifier); + } + throw new NotImplementedException(); } } From 500115b97c85087649dddbe929b3a97b4d94831f Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 19 Jun 2016 02:01:54 +0200 Subject: [PATCH 05/31] Added basics to IRegularExpressions, Added Parser-Method (unimplemented) --- .../IRegularExpression.cs | 128 +++++++++++++++++- Rubberduck.RegexAssistant/Pattern.cs | 2 +- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs index f507dac207..abca0921f0 100644 --- a/Rubberduck.RegexAssistant/IRegularExpression.cs +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -1,18 +1,132 @@ -using System; +using Rubberduck.RegexAssistant.Extensions; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rubberduck.RegexAssistant { - public interface IRegularExpression + public interface IRegularExpression : IDescribable { Quantifier Quantifier { get; } + bool TryMatch(string text, out string remaining); + } + + public class ConcatenatedExpression : IRegularExpression + { + private readonly Quantifier quant; + private readonly IList subexpressions; + + public ConcatenatedExpression(IList subexpressions, Quantifier quant) + { + this.quant = quant; + this.subexpressions = subexpressions; + } + + public string Description + { + get + { + throw new NotImplementedException(); + } + } + + public Quantifier Quantifier + { + get + { + return quant; + } + } + + public bool TryMatch(string text, out string remaining) + { + throw new NotImplementedException(); + } + } + + public class AlternativesExpression : IRegularExpression + { + private readonly Quantifier quant; + private readonly IList subexpressions; + + public AlternativesExpression(IList subexpressions, Quantifier quant) + { + this.subexpressions = subexpressions; + this.quant = quant; + } + + public string Description + { + get + { + throw new NotImplementedException(); + } + } + + public Quantifier Quantifier + { + get + { + return quant; + } + } - String Description { get; } - Boolean TryMatch(ref String text); + public bool TryMatch(string text, out string remaining) + { + throw new NotImplementedException(); + } + } + + public class SingleAtomExpression : IRegularExpression + { + private readonly Atom atom; + private readonly Quantifier quant; + + public SingleAtomExpression(Atom atom, Quantifier quant) + { + this.atom = atom; + this.quant = quant; + } + + public string Description + { + get + { + return string.Format("{0} {1}.", atom.Description, Quantifier.HumanReadable()); + } + + } + + public Quantifier Quantifier + { + get + { + return quant; + } + } + + public bool TryMatch(string text, out string remaining) + { + // try to match the atom a given number of times.. + throw new NotImplementedException(); + } + } + public static class RegularExpression + { + public static IRegularExpression Parse(string specifier) + { + /* + We basically run a Chain of Responsibility here. At the outermost level, we need to check whether this is an AlternativesExpression. + If it isn't, we assume it's a ConcatenatedExpression and proceed to create one of these. + The next step is attempting to parse Atoms. Those are packed into a SingleAtomExpression with their respective Quantifier. + + Note that Atoms can request a Parse of their subexpressions. Prominent example here would be Groups. + Also note that this here is responsible for separating atoms and Quantifiers. When we matched an Atom we need to try to match a Quantifier and pack them together. + If there is no Quantifier following (either because the input is exhausted or there directly is the next atom) then we instead pair with `new Quantifier("")` + */ + + return null; + } } } diff --git a/Rubberduck.RegexAssistant/Pattern.cs b/Rubberduck.RegexAssistant/Pattern.cs index 7b40ac9539..afbeea759a 100644 --- a/Rubberduck.RegexAssistant/Pattern.cs +++ b/Rubberduck.RegexAssistant/Pattern.cs @@ -16,7 +16,7 @@ public Pattern(string expression, bool ignoreCase, bool global) Flags = ignoreCase ? MatcherFlags.IgnoreCase : 0; Flags = global ? Flags | MatcherFlags.Global : Flags; - // FIXME build expression from string + RootExpression = RegularExpression.Parse(expression); } } From f6d059a09b45e2b05ffaf0fc1d51a0f69e68358a Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 19 Jun 2016 18:15:55 +0200 Subject: [PATCH 06/31] Added test for Literals --- Rubberduck.RegexAssistant/Atom.cs | 32 +++++++- .../Rubberduck.RegexAssistant.csproj | 1 + .../Tests/CharacterClassTests.cs | 4 +- .../Tests/LiteralTests.cs | 75 +++++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 Rubberduck.RegexAssistant/Tests/LiteralTests.cs diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index 9a2d2f9cd7..d84cea95c8 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -7,7 +7,7 @@ namespace Rubberduck.RegexAssistant { public interface Atom : IDescribable { - + string Specifier { get; } } public class CharacterClass : Atom @@ -17,6 +17,7 @@ public class CharacterClass : Atom public bool InverseMatching { get; } public IList CharacterSpecifiers { get; } + private readonly string specifier; public CharacterClass(string specifier) { @@ -25,6 +26,7 @@ public CharacterClass(string specifier) { throw new ArgumentException("The given specifier does not denote a character class"); } + this.specifier = specifier; string actualSpecifier = m.Groups["expression"].Value; InverseMatching = actualSpecifier.StartsWith("^"); CharacterSpecifiers = new List(); @@ -32,6 +34,15 @@ public CharacterClass(string specifier) ExtractCharacterSpecifiers(InverseMatching ? actualSpecifier.Substring(1) : actualSpecifier); } + public string Specifier + { + get + { + return specifier; + } + } + + private static readonly Regex CharacterRanges = new Regex(@"(\\[dDwWsS]|(\\[ntfvr]|\\([0-7]{3}|x[\dA-F]{2}|u[\dA-F]{4}|[\\\.\[\]])|.)(-(\\[ntfvr]|\\([0-7]{3}|x[A-F]{2}|u[\dA-F]{4}|[\.\\\[\]])|.))?)"); private void ExtractCharacterSpecifiers(string characterClass) { @@ -98,6 +109,14 @@ public Group(string specifier) { this.specifier = specifier; } + public string Specifier + { + get + { + return specifier; + } + } + public string Description { get @@ -114,7 +133,7 @@ public bool TryMatch(ref string text) class Literal : Atom { - public static readonly string Pattern = @"\(?:[bB(){}\\\[\]\.+*?\dnftvrdDwWsS]|u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3})|."; + public static readonly string Pattern = @"(\\([bB\(\){}\\\[\]\.+*?1-9nftvrdDwWsS]|u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3})|.)"; private static readonly Regex Matcher = new Regex("^" + Pattern + "$"); private static readonly ISet EscapeLiterals = new HashSet(); private readonly string specifier; @@ -126,7 +145,6 @@ static Literal() { } } - public Literal(string specifier) { Match m = Matcher.Match(specifier); @@ -137,6 +155,14 @@ public Literal(string specifier) this.specifier = specifier; } + public string Specifier + { + get + { + return specifier; + } + } + public string Description { get diff --git a/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj index 966f5990db..f34f88fc1d 100644 --- a/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj +++ b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj @@ -54,6 +54,7 @@ + diff --git a/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs b/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs index f94ac924a8..b9fd44244d 100644 --- a/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs +++ b/Rubberduck.RegexAssistant/Tests/CharacterClassTests.cs @@ -1,6 +1,6 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Rubberduck.RegexAssistant; +using System; using System.Collections.Generic; namespace RegexAssistantTests diff --git a/Rubberduck.RegexAssistant/Tests/LiteralTests.cs b/Rubberduck.RegexAssistant/Tests/LiteralTests.cs new file mode 100644 index 0000000000..f184bbb098 --- /dev/null +++ b/Rubberduck.RegexAssistant/Tests/LiteralTests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Rubberduck.RegexAssistant; +using System; +using System.Linq; + +namespace RegexAssistantTests +{ + [TestClass] + public class LiteralTests + { + [TestMethod] + public void EscapedLiteralTests() + { + char[] literals = new char[] { '(', ')', '{', '}', '[', ']', '.', '?', '+', '*' }; + foreach (char literal in literals) + { + Literal cut = new Literal("\\" + literal); + Assert.AreEqual("\\" + literal, cut.Specifier); + } + } + + [TestMethod] + public void EscapeSequences() + { + char[] escapes = "sSwWbBdDrnvtf123456789".ToCharArray(); + foreach (char escape in escapes) + { + Literal cut = new Literal("\\" + escape); + Assert.AreEqual("\\" + escape, cut.Specifier); + } + } + + [TestMethod] + public void CodePoints() + { + string[] codePoints = { @"\uFFFF", @"\u0000", @"\xFF", @"\x00", @"\777", @"\000" }; + foreach (string codePoint in codePoints) + { + Literal cut = new Literal(codePoint); + Assert.AreEqual(codePoint, cut.Specifier); + } + } + + [TestMethod] + public void SimpleLiterals() + { + char[] literals = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§%&/=ß#'°".ToCharArray(); + foreach (char literal in literals) + { + Literal cut = new Literal("" + literal); + Assert.AreEqual("" + literal, cut.Specifier); + } + } + + [TestMethod] + public void EverythingElseBlowsUp() + { + char[] allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§%&/=ß#'°".ToCharArray(); + string[] allowedEscapes = { "(", ")", "{", "}", "[", "]", ".", "?", "+", "*", "uFFFF", "u0000", "xFF", "x00", "777", "000" }; + foreach (string blowup in allowedEscapes.Select(e => "\\"+ e).Concat(allowed.Select(c => ""+c))) + { + try + { + Literal cut = new Literal("a"+blowup); + } + catch (ArgumentException ex) + { + Assert.IsTrue(true); // Assert.Pass(); + continue; + } + Assert.Fail("Did not blow up when trying to parse {0} as literal", blowup); + } + } + } +} From e34eb2cdd5d7c0fa4ce2afc3787a91b1b96660e5 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Mon, 20 Jun 2016 01:36:34 +0200 Subject: [PATCH 07/31] Rewrote Quantifier to use public pattern string, added outline for actual RegularExpresion.Parse implementation --- .../IRegularExpression.cs | 49 ++++++++++++++++--- Rubberduck.RegexAssistant/Quantifier.cs | 8 +-- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs index abca0921f0..d8cef0eac8 100644 --- a/Rubberduck.RegexAssistant/IRegularExpression.cs +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -1,6 +1,7 @@ using Rubberduck.RegexAssistant.Extensions; using System; using System.Collections.Generic; +using System.Linq; namespace Rubberduck.RegexAssistant { @@ -16,17 +17,17 @@ public class ConcatenatedExpression : IRegularExpression private readonly Quantifier quant; private readonly IList subexpressions; - public ConcatenatedExpression(IList subexpressions, Quantifier quant) + public ConcatenatedExpression(IList subexpressions) { - this.quant = quant; this.subexpressions = subexpressions; + this.quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups } public string Description { get { - throw new NotImplementedException(); + return string.Join(", ", subexpressions.Select(exp => exp.Description)) + " " + Quantifier.HumanReadable(); } } @@ -49,10 +50,10 @@ public class AlternativesExpression : IRegularExpression private readonly Quantifier quant; private readonly IList subexpressions; - public AlternativesExpression(IList subexpressions, Quantifier quant) + public AlternativesExpression(IList subexpressions) { this.subexpressions = subexpressions; - this.quant = quant; + this.quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups } public string Description @@ -125,8 +126,44 @@ Note that Atoms can request a Parse of their subexpressions. Prominent example h Also note that this here is responsible for separating atoms and Quantifiers. When we matched an Atom we need to try to match a Quantifier and pack them together. If there is no Quantifier following (either because the input is exhausted or there directly is the next atom) then we instead pair with `new Quantifier("")` */ + // KISS: Alternatives is when you can split at + // grab all indices where we have a pipe + List pipeIndices = GrabPipeIndices(specifier); + // and now weed out those inside character classes or groups + WeedPipeIndices(ref pipeIndices, specifier); + if (pipeIndices.Count == 0) + { // assume ConcatenatedExpression when trying to parse all as a single atom fails + IRegularExpression expression; + if (TryParseAsAtom(specifier, out expression)) + { + return expression; + } + else + { + expression = ParseIntoConcatenatedExpression(specifier); + return expression; + } + } + else + { + return ParseIntoAlternativesExpression(pipeIndices, specifier); + } + } + + private static IRegularExpression ParseIntoAlternativesExpression(List pipeIndices, string specifier) + { + List expressions = new List(); + string currentRemainder = specifier; + for (int i = pipeIndices.Count - 1; i > 0; i--) + { + expressions.Add(Parse(currentRemainder.Substring(pipeIndices[i] + 1))); + currentRemainder = currentRemainder.Substring(0, pipeIndices[i] - 1); + } + expressions.Reverse(); // because we built them from the back + return new AlternativesExpression(expressions); + } return null; } } -} +} \ No newline at end of file diff --git a/Rubberduck.RegexAssistant/Quantifier.cs b/Rubberduck.RegexAssistant/Quantifier.cs index 0ddf5d39c1..a69f8019ab 100644 --- a/Rubberduck.RegexAssistant/Quantifier.cs +++ b/Rubberduck.RegexAssistant/Quantifier.cs @@ -6,8 +6,8 @@ namespace Rubberduck.RegexAssistant { public class Quantifier { - public static readonly Regex QuantifierMatch = new Regex(@"(?\d+)(?,\d*)?\}$"); + public static readonly string Pattern = @"(?(?\d+)(?,\d*)?\}$"); public readonly QuantifierKind Kind; public readonly int MinimumMatches; @@ -24,11 +24,11 @@ public Quantifier(string expression) else if (expression.Length > 1) { Kind = QuantifierKind.Expression; - if (!_expressionPattern.IsMatch(expression)) + Match m = Matcher.Match(expression); + if (!m.Success) { throw new ArgumentException(String.Format("Cannot extract a Quantifier from the expression {1}", expression)); } - Match m = _expressionPattern.Match(expression); int minimum; // shouldn't ever happen if (!int.TryParse(m.Groups["min"].Value, out minimum)) From 6448896d7294cb7e7246b600f9bbf9ae70ce994d Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Tue, 21 Jun 2016 00:40:25 +0200 Subject: [PATCH 08/31] Implemented Parser for ConcatenatedExpressions and wrote a number of tests for Parsing --- Rubberduck.RegexAssistant/Atom.cs | 30 ++- .../IRegularExpression.cs | 136 ++++++++++++-- Rubberduck.RegexAssistant/Quantifier.cs | 21 +++ .../Rubberduck.RegexAssistant.csproj | 1 + .../Tests/RegularExpressionTests.cs | 173 ++++++++++++++++++ 5 files changed, 345 insertions(+), 16 deletions(-) create mode 100644 Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index d84cea95c8..d37de8d8c6 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -42,7 +42,6 @@ public string Specifier } } - private static readonly Regex CharacterRanges = new Regex(@"(\\[dDwWsS]|(\\[ntfvr]|\\([0-7]{3}|x[\dA-F]{2}|u[\dA-F]{4}|[\\\.\[\]])|.)(-(\\[ntfvr]|\\([0-7]{3}|x[A-F]{2}|u[\dA-F]{4}|[\.\\\[\]])|.))?)"); private void ExtractCharacterSpecifiers(string characterClass) { @@ -89,6 +88,15 @@ public bool TryMatch(ref string text) { throw new NotImplementedException(); } + + public override bool Equals(object obj) + { + if (obj is CharacterClass) + { + return (obj as CharacterClass).specifier.Equals(specifier); + } + return false; + } } class Group : Atom @@ -129,11 +137,20 @@ public bool TryMatch(ref string text) { throw new NotImplementedException(); } + + public override bool Equals(object obj) + { + if (obj is Group) + { + return (obj as Group).specifier.Equals(specifier); + } + return false; + } } class Literal : Atom { - public static readonly string Pattern = @"(\\([bB\(\){}\\\[\]\.+*?1-9nftvrdDwWsS]|u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3})|.)"; + public static readonly string Pattern = @"(?\\(u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3}|[bB\(\){}\\\[\]\.+*?1-9nftvrdDwWsS])|[^()\[\]{}\\*+?])"; private static readonly Regex Matcher = new Regex("^" + Pattern + "$"); private static readonly ISet EscapeLiterals = new HashSet(); private readonly string specifier; @@ -254,5 +271,14 @@ public bool TryMatch(ref string text) { throw new NotImplementedException(); } + + public override bool Equals(object obj) + { + if (obj is Literal) + { + return (obj as Literal).specifier.Equals(specifier); + } + return false; + } } } diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs index d8cef0eac8..9af37c3bd0 100644 --- a/Rubberduck.RegexAssistant/IRegularExpression.cs +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace Rubberduck.RegexAssistant { @@ -15,19 +16,19 @@ public interface IRegularExpression : IDescribable public class ConcatenatedExpression : IRegularExpression { private readonly Quantifier quant; - private readonly IList subexpressions; + internal readonly IList Subexpressions; public ConcatenatedExpression(IList subexpressions) { - this.subexpressions = subexpressions; - this.quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups + Subexpressions = subexpressions; + quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups } public string Description { get { - return string.Join(", ", subexpressions.Select(exp => exp.Description)) + " " + Quantifier.HumanReadable(); + return string.Join(", ", Subexpressions.Select(exp => exp.Description)) + " " + Quantifier.HumanReadable(); } } @@ -48,12 +49,12 @@ public bool TryMatch(string text, out string remaining) public class AlternativesExpression : IRegularExpression { private readonly Quantifier quant; - private readonly IList subexpressions; + internal readonly IList Subexpressions; public AlternativesExpression(IList subexpressions) { - this.subexpressions = subexpressions; - this.quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups + Subexpressions = subexpressions; + quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups } public string Description @@ -80,12 +81,12 @@ public bool TryMatch(string text, out string remaining) public class SingleAtomExpression : IRegularExpression { - private readonly Atom atom; + public readonly Atom Atom; private readonly Quantifier quant; public SingleAtomExpression(Atom atom, Quantifier quant) { - this.atom = atom; + Atom = atom; this.quant = quant; } @@ -93,7 +94,7 @@ public string Description { get { - return string.Format("{0} {1}.", atom.Description, Quantifier.HumanReadable()); + return string.Format("{0} {1}.", Atom.Description, Quantifier.HumanReadable()); } } @@ -111,6 +112,16 @@ public bool TryMatch(string text, out string remaining) // try to match the atom a given number of times.. throw new NotImplementedException(); } + + public override bool Equals(object obj) + { + if (obj is SingleAtomExpression) + { + SingleAtomExpression other = obj as SingleAtomExpression; + return other.Atom.Equals(Atom) && other.Quantifier.Equals(Quantifier); + } + return false; + } } public static class RegularExpression @@ -134,14 +145,14 @@ If there is no Quantifier following (either because the input is exhausted or th if (pipeIndices.Count == 0) { // assume ConcatenatedExpression when trying to parse all as a single atom fails IRegularExpression expression; - if (TryParseAsAtom(specifier, out expression)) + string specifierCopy = specifier; + if (TryParseAsAtom(ref specifierCopy, out expression) && specifierCopy.Length == 0) { return expression; } else { - expression = ParseIntoConcatenatedExpression(specifier); - return expression; + return ParseIntoConcatenatedExpression(specifier); } } else @@ -150,6 +161,60 @@ If there is no Quantifier following (either because the input is exhausted or th } } + //private static readonly ISet escapeCharacters + // successively parse everything into atoms + private static IRegularExpression ParseIntoConcatenatedExpression(string specifier) + { + List subexpressions = new List(); + string currentSpecifier = specifier; + while (currentSpecifier.Length > 0) + { + IRegularExpression expression; + if (TryParseAsAtom(ref currentSpecifier, out expression)) + { + subexpressions.Add(expression); + } + } + //subexpressions.Reverse(); + return new ConcatenatedExpression(subexpressions); + } + + private static readonly Regex groupWithQuantifier = new Regex("^" + Group.Pattern + Quantifier.Pattern + "?"); + private static readonly Regex characterClassWithQuantifier = new Regex("^" + CharacterClass.Pattern + Quantifier.Pattern + "?"); + private static readonly Regex literalWithQuantifier = new Regex("^" + Literal.Pattern + Quantifier.Pattern + "?"); + internal static bool TryParseAsAtom(ref string specifier, out IRegularExpression expression) + { + Match m = groupWithQuantifier.Match(specifier); + if (m.Success) + { + string atom = m.Groups["expression"].Value; + string quantifier = m.Groups["quantifier"].Value; + specifier = specifier.Substring(atom.Length + 2 + quantifier.Length); + expression = new SingleAtomExpression(new Group("("+atom+")"), new Quantifier(quantifier)); + return true; + } + m = characterClassWithQuantifier.Match(specifier); + if (m.Success) + { + string atom = m.Groups["expression"].Value; + string quantifier = m.Groups["quantifier"].Value; + specifier = specifier.Substring(atom.Length + 2 + quantifier.Length); + expression = new SingleAtomExpression(new CharacterClass("["+atom+"]"), new Quantifier(quantifier)); + return true; + } + m = literalWithQuantifier.Match(specifier); + if (m.Success) + { + string atom = m.Groups["expression"].Value; + string quantifier = m.Groups["quantifier"].Value; + specifier = specifier.Substring(atom.Length + quantifier.Length); + expression = new SingleAtomExpression(new Literal(atom), new Quantifier(quantifier)); + return true; + } + expression = null; + return false; + } + private static IRegularExpression ParseIntoAlternativesExpression(List pipeIndices, string specifier) { List expressions = new List(); @@ -163,7 +228,50 @@ private static IRegularExpression ParseIntoAlternativesExpression(List pipe return new AlternativesExpression(expressions); } - return null; + + /// + /// Finds all Pipes in the given specifier that are not escaped + /// + /// the regex specifier to search for unescaped pipes + /// A list populated with the indices of all pipes + private static List GrabPipeIndices(string specifier) + { + if (!specifier.Contains("|")) { + return new List(); + } + int currentIndex = 0; + List result = new List(); + while (true) + { + currentIndex = specifier.IndexOf("|", currentIndex); + if(currentIndex == -1) + { + break; + } + if (!specifier.Substring(currentIndex - 1, 2).Equals("\\|")) + { + result.Add(currentIndex); + } + } + return result; + } + + /// + /// Weeds out pipe indices that do not signify alternatives at the current "top level" from the given String. + /// + /// indices of unescaped pipes in the given specifier + /// the regex specifier under scrutiny + private static void WeedPipeIndices(ref List pipeIndices, string specifier) + { + if (pipeIndices.Count == 0) + { + return; + } + foreach (int pipeIndex in pipeIndices) + { + // must not be between () or [] braces, else we just weed it out + + } } } } \ No newline at end of file diff --git a/Rubberduck.RegexAssistant/Quantifier.cs b/Rubberduck.RegexAssistant/Quantifier.cs index a69f8019ab..25a4984e35 100644 --- a/Rubberduck.RegexAssistant/Quantifier.cs +++ b/Rubberduck.RegexAssistant/Quantifier.cs @@ -81,6 +81,27 @@ public Quantifier(string expression) } } } + + public override bool Equals(object obj) + { + if (obj is Quantifier) + { + var other = obj as Quantifier; + return other.Kind == Kind && other.MinimumMatches == MinimumMatches && other.MaximumMatches == MaximumMatches; + } + return false; + } + + public override int GetHashCode() + { + // FIXME get a proper has function + return base.GetHashCode(); + } + + public override string ToString() + { + return string.Format("Quantifier[{0}: {1} to {2}", Kind, MinimumMatches, MaximumMatches); + } } public enum QuantifierKind diff --git a/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj index f34f88fc1d..ba59de37bc 100644 --- a/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj +++ b/Rubberduck.RegexAssistant/Rubberduck.RegexAssistant.csproj @@ -56,6 +56,7 @@ + diff --git a/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs b/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs new file mode 100644 index 0000000000..fb601d8d35 --- /dev/null +++ b/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs @@ -0,0 +1,173 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rubberduck.RegexAssistant.Tests +{ + [TestClass] + public class RegularExpressionTests + { + [TestMethod] + public void ParseSingleLiteralGroupAsAtomWorks() + { + IRegularExpression expression; + string pattern = "(g){2,4}"; + RegularExpression.TryParseAsAtom(ref pattern, out expression); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Quantifier("{2,4}"), expression.Quantifier); + Assert.AreEqual(new Group("(g)"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void ParseCharacterClassAsAtomWorks() + { + IRegularExpression expression; + string pattern = "[abcd]*"; + RegularExpression.TryParseAsAtom(ref pattern, out expression); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Quantifier("*"), expression.Quantifier); + Assert.AreEqual(new CharacterClass("[abcd]"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void ParseLiteralAsAtomWorks() + { + IRegularExpression expression; + string pattern = "a"; + RegularExpression.TryParseAsAtom(ref pattern, out expression); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Quantifier(""), expression.Quantifier); + Assert.AreEqual(new Literal("a"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void ParseUnicodeEscapeAsAtomWorks() + { + IRegularExpression expression; + string pattern = "\\u1234+"; + RegularExpression.TryParseAsAtom(ref pattern, out expression); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Quantifier("+"), expression.Quantifier); + Assert.AreEqual(new Literal("\\u1234"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void ParseHexEscapeSequenceAsAtomWorks() + { + IRegularExpression expression; + string pattern = "\\x12?"; + RegularExpression.TryParseAsAtom(ref pattern, out expression); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Quantifier("?"), expression.Quantifier); + Assert.AreEqual(new Literal("\\x12"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void ParseOctalEscapeSequenceAsAtomWorks() + { + IRegularExpression expression; + string pattern = "\\712{2}"; + RegularExpression.TryParseAsAtom(ref pattern, out expression); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Quantifier("{2}"), expression.Quantifier); + Assert.AreEqual(new Literal("\\712"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void ParseEscapedLiteralAsAtomWorks() + { + IRegularExpression expression; + string pattern = "\\)"; + RegularExpression.TryParseAsAtom(ref pattern, out expression); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Quantifier(""), expression.Quantifier); + Assert.AreEqual(new Literal("\\)"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void ParseUnescapedSpecialCharAsAtomFails() + { + foreach (string paren in "()[]{}*?+".ToCharArray().Select(c => "" + c)) + { + IRegularExpression expression; + string hack = paren; + Assert.IsFalse(RegularExpression.TryParseAsAtom(ref hack, out expression)); + Assert.IsNull(expression); + } + } + + [TestMethod] + public void ParseSimpleLiteralConcatenationAsConcatenatedExpression() + { + List expected = new List(); + expected.Add(new SingleAtomExpression(new Literal("a"), new Quantifier(""))); + expected.Add(new SingleAtomExpression(new Literal("b"), new Quantifier(""))); + + IRegularExpression expression = RegularExpression.Parse("ab"); + Assert.IsInstanceOfType(expression, typeof(ConcatenatedExpression)); + var subexpressions = (expression as ConcatenatedExpression).Subexpressions; + Assert.AreEqual(expected.Count, subexpressions.Count); + for (int i = 0; i < expected.Count; i++) + { + Assert.AreEqual(expected[i], subexpressions[i]); + } + } + + [TestMethod] + public void ParseSimplisticGroupConcatenationAsConcatenatedExpression() + { + List expected = new List(); + expected.Add(new SingleAtomExpression(new Literal("a"), new Quantifier(""))); + expected.Add(new SingleAtomExpression(new Group("(abc)"), new Quantifier("{1,4}"))); + expected.Add(new SingleAtomExpression(new Literal("b"), new Quantifier(""))); + + IRegularExpression expression = RegularExpression.Parse("a(abc){1,4}b"); + Assert.IsInstanceOfType(expression, typeof(ConcatenatedExpression)); + var subexpressions = (expression as ConcatenatedExpression).Subexpressions; + Assert.AreEqual(expected.Count, subexpressions.Count); + for (int i = 0; i < expected.Count; i++) + { + Assert.AreEqual(expected[i], subexpressions[i]); + } + } + + [TestMethod] + public void ParseSimplisticCharacterClassConcatenationAsConcatenatedExpression() + { + List expected = new List(); + expected.Add(new SingleAtomExpression(new Literal("a"), new Quantifier(""))); + expected.Add(new SingleAtomExpression(new CharacterClass("[abc]"), new Quantifier("*"))); + expected.Add(new SingleAtomExpression(new Literal("b"), new Quantifier(""))); + + IRegularExpression expression = RegularExpression.Parse("a[abc]*b"); + Assert.IsInstanceOfType(expression, typeof(ConcatenatedExpression)); + var subexpressions = (expression as ConcatenatedExpression).Subexpressions; + Assert.AreEqual(expected.Count, subexpressions.Count); + for (int i = 0; i < expected.Count; i++) + { + Assert.AreEqual(expected[i], subexpressions[i]); + } + } + + //[TestMethod] + //public void ParseSimplisticCharacterClassConcatenationAsConcatenatedExpression() + //{ + // List expected = new List(); + // expected.Add(new SingleAtomExpression(new Literal("a"), new Quantifier(""))); + // expected.Add(new SingleAtomExpression(new CharacterClass("[abc]"), new Quantifier("*"))); + // expected.Add(new SingleAtomExpression(new Literal("b"), new Quantifier(""))); + + // IRegularExpression expression = RegularExpression.Parse("a[abc]*b"); + // Assert.IsInstanceOfType(expression, typeof(ConcatenatedExpression)); + // var subexpressions = (expression as ConcatenatedExpression).Subexpressions; + // Assert.AreEqual(expected.Count, subexpressions.Count); + // for (int i = 0; i < expected.Count; i++) + // { + // Assert.AreEqual(expected[i], subexpressions[i]); + // } + //} + } +} From df05eb763cb66d8fea74fe97c8b1c0c8989990a3 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Tue, 21 Jun 2016 20:31:43 +0200 Subject: [PATCH 09/31] Added basic XML-Doc to the important methods --- .../IRegularExpression.cs | 62 ++++++++++++++----- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs index 9af37c3bd0..1937a3ff52 100644 --- a/Rubberduck.RegexAssistant/IRegularExpression.cs +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -126,25 +126,27 @@ public override bool Equals(object obj) public static class RegularExpression { + + /// + /// We basically run a Chain of Responsibility here.At the outermost level, we need to check whether this is an AlternativesExpression. + /// If it isn't, we assume it's a ConcatenatedExpression and proceed to create one of these. + /// The next step is attempting to parse Atoms. Those are packed into a SingleAtomExpression with their respective Quantifier. + /// Note that Atoms can request a Parse of their subexpressions. Prominent example here would be Groups. + /// Also note that this here is responsible for separating atoms and Quantifiers. When we matched an Atom we need to try to match a Quantifier and pack them together. + /// If there is no Quantifier following (either because the input is exhausted or there directly is the next atom) then we instead pair with `new Quantifier("")` + /// + /// + /// public static IRegularExpression Parse(string specifier) { - /* - We basically run a Chain of Responsibility here. At the outermost level, we need to check whether this is an AlternativesExpression. - If it isn't, we assume it's a ConcatenatedExpression and proceed to create one of these. - The next step is attempting to parse Atoms. Those are packed into a SingleAtomExpression with their respective Quantifier. - - Note that Atoms can request a Parse of their subexpressions. Prominent example here would be Groups. - Also note that this here is responsible for separating atoms and Quantifiers. When we matched an Atom we need to try to match a Quantifier and pack them together. - If there is no Quantifier following (either because the input is exhausted or there directly is the next atom) then we instead pair with `new Quantifier("")` - */ - // KISS: Alternatives is when you can split at - // grab all indices where we have a pipe - List pipeIndices = GrabPipeIndices(specifier); + // KISS: Alternatives is when you have unescaped |s at the toplevel + List pipeIndices = GrabPipeIndices(specifier); // grabs unescaped pipes // and now weed out those inside character classes or groups WeedPipeIndices(ref pipeIndices, specifier); if (pipeIndices.Count == 0) { // assume ConcatenatedExpression when trying to parse all as a single atom fails IRegularExpression expression; + // ByRef requires us to hack around here, because TryParseAsAtom doesn't fail when it doesn't consume the specifier anymore string specifierCopy = specifier; if (TryParseAsAtom(ref specifierCopy, out expression) && specifierCopy.Length == 0) { @@ -160,9 +162,12 @@ If there is no Quantifier following (either because the input is exhausted or th return ParseIntoAlternativesExpression(pipeIndices, specifier); } } - - //private static readonly ISet escapeCharacters - // successively parse everything into atoms + /// + /// Successively parses the complete specifer into Atoms and returns a ConcatenatedExpression after the specifier has been exhausted. + /// Note: may loop infinitely when the passed specifier is a malformed Regular Expression + /// + /// The specifier to Parse into a concatenated expression + /// The ConcatenatedExpression resulting from parsing the given specifier private static IRegularExpression ParseIntoConcatenatedExpression(string specifier) { List subexpressions = new List(); @@ -175,13 +180,25 @@ private static IRegularExpression ParseIntoConcatenatedExpression(string specifi subexpressions.Add(expression); } } - //subexpressions.Reverse(); return new ConcatenatedExpression(subexpressions); } private static readonly Regex groupWithQuantifier = new Regex("^" + Group.Pattern + Quantifier.Pattern + "?"); private static readonly Regex characterClassWithQuantifier = new Regex("^" + CharacterClass.Pattern + Quantifier.Pattern + "?"); private static readonly Regex literalWithQuantifier = new Regex("^" + Literal.Pattern + Quantifier.Pattern + "?"); + /// + /// Tries to parse the given specifier into an Atom. For that all categories of Atoms are checked in the following order: + /// 1. Group + /// 2. Class + /// 3. Literal + /// When it succeeds, the given expression will be assigned a SingleAtomExpression containing the Atom and it's Quantifier. + /// The parsed atom will be removed from the specifier and the method returns true. To check whether the complete specifier was an Atom, + /// one needs to examine the specifier after calling this method. If it was, the specifier is empty after calling. + /// + /// The specifier to extract the leading Atom out of. Will be shortened if an Atom was successfully extracted + /// The resulting SingleAtomExpression + /// True, if an Atom could be extracted, false otherwise + // Note: could be rewritten to not consume the specifier and instead return an integer specifying the consumed length of specifier. This would remove the by-ref passed string hack internal static bool TryParseAsAtom(ref string specifier, out IRegularExpression expression) { Match m = groupWithQuantifier.Match(specifier); @@ -215,6 +232,12 @@ internal static bool TryParseAsAtom(ref string specifier, out IRegularExpression return false; } + /// + /// Makes the given specifier with the given pipeIndices into an AlternativesExpression + /// + /// The indices of Alternative-indicating pipes on the current expression level + /// The specifier to split into subexpressions + /// An AlternativesExpression consisting of the split alternatives in the specifier, in order of encounter private static IRegularExpression ParseIntoAlternativesExpression(List pipeIndices, string specifier) { List expressions = new List(); @@ -236,6 +259,10 @@ private static IRegularExpression ParseIntoAlternativesExpression(List pipe /// A list populated with the indices of all pipes private static List GrabPipeIndices(string specifier) { + // FIXME: Check assumptions: + // - | is never encountered at index 0 + // - | is never preceded by \\ + if (!specifier.Contains("|")) { return new List(); } @@ -248,6 +275,7 @@ private static List GrabPipeIndices(string specifier) { break; } + // ignore escaped literals if (!specifier.Substring(currentIndex - 1, 2).Equals("\\|")) { result.Add(currentIndex); @@ -261,7 +289,7 @@ private static List GrabPipeIndices(string specifier) /// /// indices of unescaped pipes in the given specifier /// the regex specifier under scrutiny - private static void WeedPipeIndices(ref List pipeIndices, string specifier) + internal static void WeedPipeIndices(ref List pipeIndices, string specifier) { if (pipeIndices.Count == 0) { From 7b3bbd102b87ef0b93553176bf543f8d552c505c Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Tue, 21 Jun 2016 21:18:58 +0200 Subject: [PATCH 10/31] Implemented AlternativesExpression and added basic tests --- .../IRegularExpression.cs | 60 +++++++++++++------ .../Tests/RegularExpressionTests.cs | 49 +++++++++------ 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs index 1937a3ff52..a6ecbd7cf1 100644 --- a/Rubberduck.RegexAssistant/IRegularExpression.cs +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -139,47 +139,67 @@ public static class RegularExpression /// public static IRegularExpression Parse(string specifier) { - // KISS: Alternatives is when you have unescaped |s at the toplevel - List pipeIndices = GrabPipeIndices(specifier); // grabs unescaped pipes - // and now weed out those inside character classes or groups - WeedPipeIndices(ref pipeIndices, specifier); - if (pipeIndices.Count == 0) - { // assume ConcatenatedExpression when trying to parse all as a single atom fails - IRegularExpression expression; - // ByRef requires us to hack around here, because TryParseAsAtom doesn't fail when it doesn't consume the specifier anymore - string specifierCopy = specifier; - if (TryParseAsAtom(ref specifierCopy, out expression) && specifierCopy.Length == 0) + IRegularExpression expression; + // ByRef requires us to hack around here, because TryParseAsAtom doesn't fail when it doesn't consume the specifier anymore + string specifierCopy = specifier; + if (TryParseAsAtom(ref specifierCopy, out expression) && specifierCopy.Length == 0) + { + return expression; + } + else + { + List subexpressions = new List(); + while (specifier.Length != 0) + { + expression = ParseIntoConcatenatedExpression(ref specifier); + // ! actually an AlternativesExpression + if (specifier.Length != 0 || subexpressions.Count != 0) + { + // flatten hierarchy + var parsedSubexpressions = (expression as ConcatenatedExpression).Subexpressions; + if (parsedSubexpressions.Count == 1) + { + expression = parsedSubexpressions[0]; + } + subexpressions.Add(expression); + } + } + if (subexpressions.Count == 0) { return expression; } else { - return ParseIntoConcatenatedExpression(specifier); + return new AlternativesExpression(subexpressions); } } - else - { - return ParseIntoAlternativesExpression(pipeIndices, specifier); - } + } /// - /// Successively parses the complete specifer into Atoms and returns a ConcatenatedExpression after the specifier has been exhausted. + /// Successively parses the complete specifer into Atoms and returns a ConcatenatedExpression after the specifier has been exhausted or a single '|' is encountered at the start of the remaining specifier. /// Note: may loop infinitely when the passed specifier is a malformed Regular Expression /// /// The specifier to Parse into a concatenated expression - /// The ConcatenatedExpression resulting from parsing the given specifier - private static IRegularExpression ParseIntoConcatenatedExpression(string specifier) + /// The ConcatenatedExpression resulting from parsing the given specifier, either completely or up to the first encountered '|' + private static IRegularExpression ParseIntoConcatenatedExpression(ref string specifier) { List subexpressions = new List(); string currentSpecifier = specifier; while (currentSpecifier.Length > 0) { IRegularExpression expression; + // we actually have an AlternativesExpression, return the current status to Parse after updating the specifier + if (currentSpecifier[0].Equals('|')) + { + specifier = currentSpecifier.Substring(1); // skip leading | + return new ConcatenatedExpression(subexpressions); + } if (TryParseAsAtom(ref currentSpecifier, out expression)) { subexpressions.Add(expression); } } + specifier = ""; // we've exhausted the specifier, tell Parse about it return new ConcatenatedExpression(subexpressions); } @@ -276,7 +296,9 @@ private static List GrabPipeIndices(string specifier) break; } // ignore escaped literals - if (!specifier.Substring(currentIndex - 1, 2).Equals("\\|")) + // FIXME this is still a little too naive, since we could actually have something like "\\\|", which means that | is escaped again, but it should suffice for now + if (currentIndex == 0 || !specifier.Substring(currentIndex - 1, 2).Equals("\\|") + || (currentIndex > 1 && specifier.Substring(currentIndex -2, 2).Equals("\\\\"))) { result.Add(currentIndex); } diff --git a/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs b/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs index fb601d8d35..57559568a1 100644 --- a/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs +++ b/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs @@ -152,22 +152,37 @@ public void ParseSimplisticCharacterClassConcatenationAsConcatenatedExpression() } } - //[TestMethod] - //public void ParseSimplisticCharacterClassConcatenationAsConcatenatedExpression() - //{ - // List expected = new List(); - // expected.Add(new SingleAtomExpression(new Literal("a"), new Quantifier(""))); - // expected.Add(new SingleAtomExpression(new CharacterClass("[abc]"), new Quantifier("*"))); - // expected.Add(new SingleAtomExpression(new Literal("b"), new Quantifier(""))); - - // IRegularExpression expression = RegularExpression.Parse("a[abc]*b"); - // Assert.IsInstanceOfType(expression, typeof(ConcatenatedExpression)); - // var subexpressions = (expression as ConcatenatedExpression).Subexpressions; - // Assert.AreEqual(expected.Count, subexpressions.Count); - // for (int i = 0; i < expected.Count; i++) - // { - // Assert.AreEqual(expected[i], subexpressions[i]); - // } - //} + [TestMethod] + public void ParseSimplisticAlternativesExpression() + { + List expected = new List(); + expected.Add(new SingleAtomExpression(new Literal("a"), new Quantifier(""))); + expected.Add(new SingleAtomExpression(new Literal("b"), new Quantifier(""))); + + IRegularExpression expression = RegularExpression.Parse("a|b"); + Assert.IsInstanceOfType(expression, typeof(AlternativesExpression)); + var subexpressions = (expression as AlternativesExpression).Subexpressions; + Assert.AreEqual(expected.Count, subexpressions.Count); + for (int i = 0; i < expected.Count; i++) + { + Assert.AreEqual(expected[i], subexpressions[i]); + } + } + + [TestMethod] + public void CharacterClassIsNotAnAlternativesExpression() + { + IRegularExpression expression = RegularExpression.Parse("[a|b]"); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new CharacterClass("[a|b]"), (expression as SingleAtomExpression).Atom); + } + + [TestMethod] + public void GroupIsNotAnAlternativesExpression() + { + IRegularExpression expression = RegularExpression.Parse("(a|b)"); + Assert.IsInstanceOfType(expression, typeof(SingleAtomExpression)); + Assert.AreEqual(new Group("(a|b)"), (expression as SingleAtomExpression).Atom); + } } } From 975ac5bc0110521b47a8b9af306d02df22ddd397 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Tue, 21 Jun 2016 23:42:45 +0200 Subject: [PATCH 11/31] Fixed simple problems pointed out in review --- Rubberduck.RegexAssistant/Atom.cs | 135 +++++++----------- .../IRegularExpression.cs | 98 +++++-------- .../i18n/AssistantResources.Designer.cs | 9 ++ .../i18n/AssistantResources.resx | 3 + 4 files changed, 98 insertions(+), 147 deletions(-) diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index d37de8d8c6..7b9726fc63 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -5,19 +5,21 @@ namespace Rubberduck.RegexAssistant { - public interface Atom : IDescribable + public interface IAtom : IDescribable { string Specifier { get; } } - public class CharacterClass : Atom + public class CharacterClass : IAtom { public static readonly string Pattern = @"(?.*?)(? CharacterSpecifiers { get; } - private readonly string specifier; + private readonly bool _inverseMatching; + public bool InverseMatching { get { return _inverseMatching; } } + private readonly IList _characterSpecifiers; + public IList CharacterSpecifiers { get { return _characterSpecifiers; } } + private readonly string _specifier; public CharacterClass(string specifier) { @@ -26,26 +28,25 @@ public CharacterClass(string specifier) { throw new ArgumentException("The given specifier does not denote a character class"); } - this.specifier = specifier; + this._specifier = specifier; string actualSpecifier = m.Groups["expression"].Value; - InverseMatching = actualSpecifier.StartsWith("^"); - CharacterSpecifiers = new List(); - - ExtractCharacterSpecifiers(InverseMatching ? actualSpecifier.Substring(1) : actualSpecifier); + _inverseMatching = actualSpecifier.StartsWith("^"); + _characterSpecifiers= ExtractCharacterSpecifiers(InverseMatching ? actualSpecifier.Substring(1) : actualSpecifier); } public string Specifier { get { - return specifier; + return _specifier; } } private static readonly Regex CharacterRanges = new Regex(@"(\\[dDwWsS]|(\\[ntfvr]|\\([0-7]{3}|x[\dA-F]{2}|u[\dA-F]{4}|[\\\.\[\]])|.)(-(\\[ntfvr]|\\([0-7]{3}|x[A-F]{2}|u[\dA-F]{4}|[\.\\\[\]])|.))?)"); - private void ExtractCharacterSpecifiers(string characterClass) + private IList ExtractCharacterSpecifiers(string characterClass) { MatchCollection specifiers = CharacterRanges.Matches(characterClass); + var result = new List(); foreach (Match specifier in specifiers) { @@ -64,8 +65,9 @@ private void ExtractCharacterSpecifiers(string characterClass) continue; } } - CharacterSpecifiers.Add(specifier.Value); + result.Add(specifier.Value); } + return result; } public string Description @@ -84,28 +86,23 @@ private string HumanReadableClass() return string.Join(", ", CharacterSpecifiers); // join last with and? } - public bool TryMatch(ref string text) - { - throw new NotImplementedException(); - } - public override bool Equals(object obj) { if (obj is CharacterClass) { - return (obj as CharacterClass).specifier.Equals(specifier); + return (obj as CharacterClass)._specifier.Equals(_specifier); } return false; } } - class Group : Atom + class Group : IAtom { public static readonly string Pattern = @"(?.*)(?\\(u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3}|[bB\(\){}\\\[\]\.+*?1-9nftvrdDwWsS])|[^()\[\]{}\\*+?])"; private static readonly Regex Matcher = new Regex("^" + Pattern + "$"); private static readonly ISet EscapeLiterals = new HashSet(); - private readonly string specifier; + private readonly string _specifier; static Literal() { foreach (char escape in new char[]{ '.', '+', '*', '?', '(', ')', '{', '}', '[', ']', '|', '\\' }) { EscapeLiterals.Add(escape); } + _escapeDescriptions.Add('d', AssistantResources.AtomDescription_Digit); + _escapeDescriptions.Add('D', AssistantResources.AtomDescription_NonDigit); + _escapeDescriptions.Add('b', AssistantResources.AtomDescription_WordBoundary); + _escapeDescriptions.Add('B', AssistantResources.AtomDescription_NonWordBoundary); + _escapeDescriptions.Add('w', AssistantResources.AtomDescription_WordCharacter); + _escapeDescriptions.Add('W', AssistantResources.AtomDescription_NonWordCharacter); + _escapeDescriptions.Add('s', AssistantResources.AtomDescription_Whitespace); + _escapeDescriptions.Add('S', AssistantResources.AtomDescription_NonWhitespace); + _escapeDescriptions.Add('n', AssistantResources.AtomDescription_Newline); + _escapeDescriptions.Add('r', AssistantResources.AtomDescription_CarriageReturn); + _escapeDescriptions.Add('f', AssistantResources.AtomDescription_FormFeed); + _escapeDescriptions.Add('v', AssistantResources.AtomDescription_VTab); + _escapeDescriptions.Add('t', AssistantResources.AtomDescription_HTab); } public Literal(string specifier) @@ -169,17 +174,19 @@ public Literal(string specifier) { throw new ArgumentException("The given specifier does not denote a Literal"); } - this.specifier = specifier; + _specifier = specifier; } public string Specifier { get { - return specifier; + return _specifier; } } + + private static readonly Dictionary _escapeDescriptions = new Dictionary(); public string Description { get @@ -190,9 +197,9 @@ public string Description // - escape sequences (each having a different description) // - codepoint escapes (belongs into above category but kept separate) // - and actually boring literal matches - if (specifier.Length > 1) + if (_specifier.Length > 1) { - string relevant = specifier.Substring(1); // skip the damn Backslash at the start + string relevant = _specifier.Substring(1); // skip the damn Backslash at the start if (relevant.Length > 1) // longer sequences { if (relevant.StartsWith("u")) @@ -218,65 +225,23 @@ public string Description } else { - // special escapes here - switch (relevant[0]) - { - case 'd': - return AssistantResources.AtomDescription_Digit; - case 'D': - return AssistantResources.AtomDescription_NonDigit; - case 'b': - return AssistantResources.AtomDescription_WordBoundary; - case 'B': - return AssistantResources.AtomDescription_NonWordBoundary; - case 'w': - return AssistantResources.AtomDescription_WordCharacter; - case 'W': - return AssistantResources.AtomDescription_NonWordCharacter; - case 's': - return AssistantResources.AtomDescription_Whitespace; - case 'S': - return AssistantResources.AtomDescription_NonWhitespace; - case 'n': - return AssistantResources.AtomDescription_Newline; - case 'r': - return AssistantResources.AtomDescription_CarriageReturn; - case 'f': - return AssistantResources.AtomDescription_FormFeed; - case 'v': - return AssistantResources.AtomDescription_VTab; - case 't': - return AssistantResources.AtomDescription_HTab; - default: - // shouldn't ever happen, so we blow it all up - throw new InvalidOperationException("took an escape sequence that shouldn't exist"); - } + return _escapeDescriptions[relevant[0]]; } } - else + if (_specifier.Equals(".")) { - if (specifier.Equals(".")) - { - return AssistantResources.AtomDescription_Dot; - } - // Behaviour with "." needs fix - return string.Format(AssistantResources.AtomDescription_Literal_ActualLiteral, specifier); + return AssistantResources.AtomDescription_Dot; } + return string.Format(AssistantResources.AtomDescription_Literal_ActualLiteral, _specifier); - throw new NotImplementedException(); } } - public bool TryMatch(ref string text) - { - throw new NotImplementedException(); - } - public override bool Equals(object obj) { if (obj is Literal) { - return (obj as Literal).specifier.Equals(specifier); + return (obj as Literal)._specifier.Equals(_specifier); } return false; } diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs index a6ecbd7cf1..088d5b2c15 100644 --- a/Rubberduck.RegexAssistant/IRegularExpression.cs +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -1,4 +1,5 @@ using Rubberduck.RegexAssistant.Extensions; +using Rubberduck.RegexAssistant.i18n; using System; using System.Collections.Generic; using System.Linq; @@ -9,19 +10,17 @@ namespace Rubberduck.RegexAssistant public interface IRegularExpression : IDescribable { Quantifier Quantifier { get; } - - bool TryMatch(string text, out string remaining); } public class ConcatenatedExpression : IRegularExpression { - private readonly Quantifier quant; + private readonly Quantifier _quantifier; internal readonly IList Subexpressions; public ConcatenatedExpression(IList subexpressions) { Subexpressions = subexpressions; - quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups + _quantifier = new Quantifier(string.Empty); // these are always exactly once. Quantifying happens through groups } public string Description @@ -36,32 +35,27 @@ public Quantifier Quantifier { get { - return quant; + return _quantifier; } } - - public bool TryMatch(string text, out string remaining) - { - throw new NotImplementedException(); - } } public class AlternativesExpression : IRegularExpression { - private readonly Quantifier quant; + private readonly Quantifier _quantifier; internal readonly IList Subexpressions; public AlternativesExpression(IList subexpressions) { Subexpressions = subexpressions; - quant = new Quantifier(""); // these are always exactly once. Quantifying happens through groups + _quantifier = new Quantifier(string.Empty); // these are always exactly once. Quantifying happens through groups } public string Description { get { - throw new NotImplementedException(); + return string.Format(AssistantResources.ExpressionDescription_AlternativesExpression, Quantifier.HumanReadable()) + string.Join(Environment.NewLine, Subexpressions); } } @@ -69,25 +63,20 @@ public Quantifier Quantifier { get { - return quant; + return _quantifier; } } - - public bool TryMatch(string text, out string remaining) - { - throw new NotImplementedException(); - } } public class SingleAtomExpression : IRegularExpression { - public readonly Atom Atom; - private readonly Quantifier quant; + public readonly IAtom Atom; + private readonly Quantifier _quantifier; - public SingleAtomExpression(Atom atom, Quantifier quant) + public SingleAtomExpression(IAtom atom, Quantifier quantifier) { Atom = atom; - this.quant = quant; + _quantifier = quantifier; } public string Description @@ -96,23 +85,16 @@ public string Description { return string.Format("{0} {1}.", Atom.Description, Quantifier.HumanReadable()); } - } public Quantifier Quantifier { get { - return quant; + return _quantifier; } } - public bool TryMatch(string text, out string remaining) - { - // try to match the atom a given number of times.. - throw new NotImplementedException(); - } - public override bool Equals(object obj) { if (obj is SingleAtomExpression) @@ -128,15 +110,18 @@ public static class RegularExpression { /// - /// We basically run a Chain of Responsibility here.At the outermost level, we need to check whether this is an AlternativesExpression. - /// If it isn't, we assume it's a ConcatenatedExpression and proceed to create one of these. - /// The next step is attempting to parse Atoms. Those are packed into a SingleAtomExpression with their respective Quantifier. - /// Note that Atoms can request a Parse of their subexpressions. Prominent example here would be Groups. - /// Also note that this here is responsible for separating atoms and Quantifiers. When we matched an Atom we need to try to match a Quantifier and pack them together. + /// We basically run a Chain of Responsibility here. At first we try to parse the whole specifier as one Atom. + /// If this fails, we assume it's a ConcatenatedExpression and proceed to create one of these. + /// That works well until we encounter a non-escaped '|' outside of a CharacterClass. Then we know that we actually have an AlternativesExpression. + /// This means we have to check what we got back and add it to a List of subexpressions to the AlternativesExpression. + /// We then proceed to the next alternative (ParseIntoConcatenatedExpression consumes the tokens it uses) and keep adding to our subexpressions. + /// + /// Note that Atoms (or more specifically Groups) can request a Parse of their subexpressions. + /// Also note that TryParseAtom is responsible for grabbing an Atom and it's Quantifier. /// If there is no Quantifier following (either because the input is exhausted or there directly is the next atom) then we instead pair with `new Quantifier("")` /// - /// - /// + /// The full Regular Expression specifier to Parse + /// An IRegularExpression that encompasses the complete given specifier public static IRegularExpression Parse(string specifier) { IRegularExpression expression; @@ -146,38 +131,27 @@ public static IRegularExpression Parse(string specifier) { return expression; } - else + List subexpressions = new List(); + while (specifier.Length != 0) { - List subexpressions = new List(); - while (specifier.Length != 0) + expression = ParseIntoConcatenatedExpression(ref specifier); + // ! actually an AlternativesExpression + if (specifier.Length != 0 || subexpressions.Count != 0) { - expression = ParseIntoConcatenatedExpression(ref specifier); - // ! actually an AlternativesExpression - if (specifier.Length != 0 || subexpressions.Count != 0) + // flatten hierarchy + var parsedSubexpressions = (expression as ConcatenatedExpression).Subexpressions; + if (parsedSubexpressions.Count == 1) { - // flatten hierarchy - var parsedSubexpressions = (expression as ConcatenatedExpression).Subexpressions; - if (parsedSubexpressions.Count == 1) - { - expression = parsedSubexpressions[0]; - } - subexpressions.Add(expression); + expression = parsedSubexpressions[0]; } - } - if (subexpressions.Count == 0) - { - return expression; - } - else - { - return new AlternativesExpression(subexpressions); + subexpressions.Add(expression); } } - + return (subexpressions.Count == 0) ? expression : new AlternativesExpression(subexpressions); } /// /// Successively parses the complete specifer into Atoms and returns a ConcatenatedExpression after the specifier has been exhausted or a single '|' is encountered at the start of the remaining specifier. - /// Note: may loop infinitely when the passed specifier is a malformed Regular Expression + /// Note: this may fail to work if the last encountered token cannot be parsed into an Atom, but the remaining specifier has nonzero lenght /// /// The specifier to Parse into a concatenated expression /// The ConcatenatedExpression resulting from parsing the given specifier, either completely or up to the first encountered '|' @@ -199,7 +173,7 @@ private static IRegularExpression ParseIntoConcatenatedExpression(ref string spe subexpressions.Add(expression); } } - specifier = ""; // we've exhausted the specifier, tell Parse about it + specifier = ""; // we've exhausted the specifier, tell Parse about it to prevent infinite looping return new ConcatenatedExpression(subexpressions); } diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs index 6e388ff833..533cb4a067 100644 --- a/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs @@ -267,6 +267,15 @@ internal static string AtomDescription_WordCharacter { } } + /// + /// Looks up a localized string similar to Matches one of the following alternatives {0}.. + /// + internal static string ExpressionDescription_AlternativesExpression { + get { + return ResourceManager.GetString("ExpressionDescription_AlternativesExpression", resourceCulture); + } + } + /// /// Looks up a localized string similar to at least once. /// diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.resx b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx index 8fce5982f4..986b622611 100644 --- a/Rubberduck.RegexAssistant/i18n/AssistantResources.resx +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx @@ -186,6 +186,9 @@ Matches any "word character". Equivalent to "[a-zA-Z_0-9]" + + Matches one of the following alternatives {0}. + at least once From de001ef0e29a21cdb0b1084d030dc51a061041b7 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sat, 25 Jun 2016 23:39:23 +0200 Subject: [PATCH 12/31] Prevented incorrect parse for to be escaped characters as literals --- Rubberduck.RegexAssistant/Atom.cs | 2 +- .../Tests/LiteralTests.cs | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index 7b9726fc63..b4ae5ad7c5 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -142,7 +142,7 @@ public override bool Equals(object obj) class Literal : IAtom { - public static readonly string Pattern = @"(?\\(u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3}|[bB\(\){}\\\[\]\.+*?1-9nftvrdDwWsS])|[^()\[\]{}\\*+?])"; + public static readonly string Pattern = @"(?\\(u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3}|[bB\(\){}\\\[\]\.+*?1-9nftvrdDwWsS])|[^()\[\]{}\\*+?^$])"; private static readonly Regex Matcher = new Regex("^" + Pattern + "$"); private static readonly ISet EscapeLiterals = new HashSet(); private readonly string _specifier; diff --git a/Rubberduck.RegexAssistant/Tests/LiteralTests.cs b/Rubberduck.RegexAssistant/Tests/LiteralTests.cs index f184bbb098..89186a4d0b 100644 --- a/Rubberduck.RegexAssistant/Tests/LiteralTests.cs +++ b/Rubberduck.RegexAssistant/Tests/LiteralTests.cs @@ -56,7 +56,7 @@ public void SimpleLiterals() public void EverythingElseBlowsUp() { char[] allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§%&/=ß#'°".ToCharArray(); - string[] allowedEscapes = { "(", ")", "{", "}", "[", "]", ".", "?", "+", "*", "uFFFF", "u0000", "xFF", "x00", "777", "000" }; + string[] allowedEscapes = { "(", ")", "{", "}", "[", "]", ".", "?", "+", "*", "$", "^", "uFFFF", "u0000", "xFF", "x00", "777", "000" }; foreach (string blowup in allowedEscapes.Select(e => "\\"+ e).Concat(allowed.Select(c => ""+c))) { try @@ -71,5 +71,25 @@ public void EverythingElseBlowsUp() Assert.Fail("Did not blow up when trying to parse {0} as literal", blowup); } } + + [TestMethod] + public void SingleEscapedCharsAreNotParsedAsLiteral() + { + string[] escapedChars = "(){}[]\\*?+$^".ToCharArray().Select(e => e.ToString()).ToArray(); + foreach (var escape in escapedChars) + { + try + { + Literal cut = new Literal(escape); + } + catch (ArgumentException ex) + { + Assert.IsTrue(true); // Assert.Pass(); + continue; + } + Assert.Fail("Did not blow up when trying to parse {0} as literal", escape); + } + + } } } From 6be77a3c0e3fa3285c06aab2430afe03bb6694ef Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sat, 25 Jun 2016 23:39:56 +0200 Subject: [PATCH 13/31] Added Anchors to Pattern --- Rubberduck.RegexAssistant/Pattern.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Rubberduck.RegexAssistant/Pattern.cs b/Rubberduck.RegexAssistant/Pattern.cs index afbeea759a..e453f4db66 100644 --- a/Rubberduck.RegexAssistant/Pattern.cs +++ b/Rubberduck.RegexAssistant/Pattern.cs @@ -11,12 +11,20 @@ class Pattern IRegularExpression RootExpression; MatcherFlags Flags; + private readonly bool _hasStartAnchor; + private readonly bool _hasEndAnchor; + public Pattern(string expression, bool ignoreCase, bool global) { Flags = ignoreCase ? MatcherFlags.IgnoreCase : 0; Flags = global ? Flags | MatcherFlags.Global : Flags; - RootExpression = RegularExpression.Parse(expression); + _hasEndAnchor = expression[expression.Length - 1].Equals('$'); + _hasStartAnchor = expression[0].Equals('^'); + + int start = _hasStartAnchor ? 1 : 0; + int end = (_hasEndAnchor ? 1 : 0) + start + 1; + RootExpression = RegularExpression.Parse(expression.Substring(start, expression.Length - end)); } } From bc289cc2ced365dfce06a6941eb11c077e1583db Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sat, 25 Jun 2016 23:43:22 +0200 Subject: [PATCH 14/31] Hid away as much of the implementation as possible. Removed unnecessary usings --- Rubberduck.RegexAssistant/Atom.cs | 8 ++++---- Rubberduck.RegexAssistant/IRegularExpression.cs | 12 ++++++------ Rubberduck.RegexAssistant/Quantifier.cs | 7 +++---- Rubberduck.RegexAssistant/QuantifierExtensions.cs | 5 ----- .../Tests/RegularExpressionTests.cs | 3 --- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/Rubberduck.RegexAssistant/Atom.cs b/Rubberduck.RegexAssistant/Atom.cs index b4ae5ad7c5..e632946d89 100644 --- a/Rubberduck.RegexAssistant/Atom.cs +++ b/Rubberduck.RegexAssistant/Atom.cs @@ -5,12 +5,12 @@ namespace Rubberduck.RegexAssistant { - public interface IAtom : IDescribable + internal interface IAtom : IDescribable { string Specifier { get; } } - public class CharacterClass : IAtom + internal class CharacterClass : IAtom { public static readonly string Pattern = @"(?.*?)(?.*)(?\\(u[\dA-F]{4}|x[\dA-F]{2}|[0-7]{3}|[bB\(\){}\\\[\]\.+*?1-9nftvrdDwWsS])|[^()\[\]{}\\*+?^$])"; private static readonly Regex Matcher = new Regex("^" + Pattern + "$"); diff --git a/Rubberduck.RegexAssistant/IRegularExpression.cs b/Rubberduck.RegexAssistant/IRegularExpression.cs index 088d5b2c15..f2097377fb 100644 --- a/Rubberduck.RegexAssistant/IRegularExpression.cs +++ b/Rubberduck.RegexAssistant/IRegularExpression.cs @@ -7,12 +7,12 @@ namespace Rubberduck.RegexAssistant { - public interface IRegularExpression : IDescribable + internal interface IRegularExpression : IDescribable { Quantifier Quantifier { get; } } - public class ConcatenatedExpression : IRegularExpression + internal class ConcatenatedExpression : IRegularExpression { private readonly Quantifier _quantifier; internal readonly IList Subexpressions; @@ -40,7 +40,7 @@ public Quantifier Quantifier } } - public class AlternativesExpression : IRegularExpression + internal class AlternativesExpression : IRegularExpression { private readonly Quantifier _quantifier; internal readonly IList Subexpressions; @@ -68,7 +68,7 @@ public Quantifier Quantifier } } - public class SingleAtomExpression : IRegularExpression + internal class SingleAtomExpression : IRegularExpression { public readonly IAtom Atom; private readonly Quantifier _quantifier; @@ -105,8 +105,8 @@ public override bool Equals(object obj) return false; } } - - public static class RegularExpression + + internal static class RegularExpression { /// diff --git a/Rubberduck.RegexAssistant/Quantifier.cs b/Rubberduck.RegexAssistant/Quantifier.cs index 25a4984e35..ca6b0dcf1c 100644 --- a/Rubberduck.RegexAssistant/Quantifier.cs +++ b/Rubberduck.RegexAssistant/Quantifier.cs @@ -1,10 +1,9 @@ - -using System; +using System; using System.Text.RegularExpressions; namespace Rubberduck.RegexAssistant { - public class Quantifier + internal class Quantifier { public static readonly string Pattern = @"(?(?\d+)(?,\d*)?\}$"); @@ -104,7 +103,7 @@ public override string ToString() } } - public enum QuantifierKind + internal enum QuantifierKind { None, Expression, Wildcard } diff --git a/Rubberduck.RegexAssistant/QuantifierExtensions.cs b/Rubberduck.RegexAssistant/QuantifierExtensions.cs index c77fd00c8c..7903f12b7d 100644 --- a/Rubberduck.RegexAssistant/QuantifierExtensions.cs +++ b/Rubberduck.RegexAssistant/QuantifierExtensions.cs @@ -1,9 +1,4 @@ using Rubberduck.RegexAssistant.i18n; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rubberduck.RegexAssistant.Extensions { diff --git a/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs b/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs index 57559568a1..ba111e2e1e 100644 --- a/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs +++ b/Rubberduck.RegexAssistant/Tests/RegularExpressionTests.cs @@ -1,9 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Rubberduck.RegexAssistant.Tests { From 3c9977a6dfedc5ff8eab799f8e2b3ea4deacd370 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 26 Jun 2016 00:04:58 +0200 Subject: [PATCH 15/31] Added IDescribable to Pattern and implemented it --- Rubberduck.RegexAssistant/Pattern.cs | 33 +++++++++++++---- .../i18n/AssistantResources.Designer.cs | 36 +++++++++++++++++++ .../i18n/AssistantResources.resx | 12 +++++++ 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/Rubberduck.RegexAssistant/Pattern.cs b/Rubberduck.RegexAssistant/Pattern.cs index e453f4db66..b731554f6e 100644 --- a/Rubberduck.RegexAssistant/Pattern.cs +++ b/Rubberduck.RegexAssistant/Pattern.cs @@ -1,18 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Rubberduck.RegexAssistant.i18n; +using System; namespace Rubberduck.RegexAssistant { - class Pattern + public class Pattern : IDescribable { IRegularExpression RootExpression; MatcherFlags Flags; private readonly bool _hasStartAnchor; private readonly bool _hasEndAnchor; + private readonly string _description; + + public string Description + { + get + { + return _description; + } + } public Pattern(string expression, bool ignoreCase, bool global) { @@ -25,8 +31,23 @@ public Pattern(string expression, bool ignoreCase, bool global) int start = _hasStartAnchor ? 1 : 0; int end = (_hasEndAnchor ? 1 : 0) + start + 1; RootExpression = RegularExpression.Parse(expression.Substring(start, expression.Length - end)); + _description = AssembleDescription(); } + private string AssembleDescription() + { + string result = string.Empty; + if (_hasStartAnchor) + { + result += Flags.HasFlag(MatcherFlags.Global) ? AssistantResources.PatternDescription_AnchorStart_GlobalEnabled : AssistantResources.PatternDescription_AnchorStart; + } + result += RootExpression.Description; + if (_hasEndAnchor) + { + result += Flags.HasFlag(MatcherFlags.Global) ? AssistantResources.PatternDescription_AnchorEnd_GlobalEnabled : AssistantResources.PatternDescription_AnchorEnd; + } + return result; + } } [Flags] diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs index 533cb4a067..b671e004ef 100644 --- a/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.Designer.cs @@ -276,6 +276,42 @@ internal static string ExpressionDescription_AlternativesExpression { } } + /// + /// Looks up a localized string similar to $ ensures all characters of the string are consumed. + /// + internal static string PatternDescription_AnchorEnd { + get { + return ResourceManager.GetString("PatternDescription_AnchorEnd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to $ ensures that the line ended or all characters of the input have been consumed. + /// + internal static string PatternDescription_AnchorEnd_GlobalEnabled { + get { + return ResourceManager.GetString("PatternDescription_AnchorEnd_GlobalEnabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ^ ensures we are at the beginning of the string that's to be matched. + /// + internal static string PatternDescription_AnchorStart { + get { + return ResourceManager.GetString("PatternDescription_AnchorStart", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ^ ensures that the matcher starts at the beginning of a line. + /// + internal static string PatternDescription_AnchorStart_GlobalEnabled { + get { + return ResourceManager.GetString("PatternDescription_AnchorStart_GlobalEnabled", resourceCulture); + } + } + /// /// Looks up a localized string similar to at least once. /// diff --git a/Rubberduck.RegexAssistant/i18n/AssistantResources.resx b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx index 986b622611..7a41253e88 100644 --- a/Rubberduck.RegexAssistant/i18n/AssistantResources.resx +++ b/Rubberduck.RegexAssistant/i18n/AssistantResources.resx @@ -189,6 +189,18 @@ Matches one of the following alternatives {0}. + + $ ensures all characters of the string are consumed + + + $ ensures that the line ended or all characters of the input have been consumed + + + ^ ensures we are at the beginning of the string that's to be matched + + + ^ ensures that the matcher starts at the beginning of a line + at least once From 7939b699ecd8c6bee12d3d540d69382a49275eef Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 26 Jun 2016 22:33:59 +0200 Subject: [PATCH 16/31] Added RegexAssistantDialog and WPF Component associated with it --- RetailCoder.VBE/Rubberduck.csproj | 21 +++ .../UI/RegexAssistant/RegexAssistant.xaml | 59 +++++++++ .../UI/RegexAssistant/RegexAssistant.xaml.cs | 10 ++ .../RegexAssistantDialog.Designer.cs | 61 +++++++++ .../UI/RegexAssistant/RegexAssistantDialog.cs | 31 +++++ .../RegexAssistant/RegexAssistantDialog.resx | 120 ++++++++++++++++++ .../RegexAssistant/RegexAssistantViewModel.cs | 71 +++++++++++ RetailCoder.VBE/UI/RubberduckUI.Designer.cs | 45 +++++++ RetailCoder.VBE/UI/RubberduckUI.resx | 15 +++ 9 files changed, 433 insertions(+) create mode 100644 RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml create mode 100644 RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml.cs create mode 100644 RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.Designer.cs create mode 100644 RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.cs create mode 100644 RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.resx create mode 100644 RetailCoder.VBE/UI/RegexAssistant/RegexAssistantViewModel.cs diff --git a/RetailCoder.VBE/Rubberduck.csproj b/RetailCoder.VBE/Rubberduck.csproj index 681c2dddb8..852a72eaaa 100644 --- a/RetailCoder.VBE/Rubberduck.csproj +++ b/RetailCoder.VBE/Rubberduck.csproj @@ -691,6 +691,16 @@ ExtractInterfaceDialog.cs + + RegexAssistant.xaml + + + Form + + + RegexAssistantDialog.cs + + True True @@ -1031,6 +1041,9 @@ ExtractInterfaceDialog.cs + + RegexAssistantDialog.cs + PublicResXFileCodeGenerator RubberduckUI.de.Designer.cs @@ -1352,6 +1365,10 @@ {A4A618E1-CBCA-435F-9C6C-5181E030ADFC} Rubberduck.Parsing + + {40cc03e3-756c-4674-af07-384115deaee2} + Rubberduck.RegexAssistant + {e85e1253-86d6-45ee-968b-f37348d44132} Rubberduck.SettingsProvider @@ -1406,6 +1423,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml new file mode 100644 index 0000000000..e739939de7 --- /dev/null +++ b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + diff --git a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml.cs b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml.cs new file mode 100644 index 0000000000..949a0295ad --- /dev/null +++ b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml.cs @@ -0,0 +1,10 @@ +namespace Rubberduck.UI.RegexAssistant +{ + public partial class RegexAssistant + { + public RegexAssistant() + { + InitializeComponent(); + } + } +} diff --git a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.Designer.cs b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.Designer.cs new file mode 100644 index 0000000000..48d9a509d0 --- /dev/null +++ b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.Designer.cs @@ -0,0 +1,61 @@ +namespace Rubberduck.UI.RegexAssistant +{ + partial class RegexAssistantDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.ElementHost = new System.Windows.Forms.Integration.ElementHost(); + this.RegexAssistant = new Rubberduck.UI.RegexAssistant.RegexAssistant(); + this.SuspendLayout(); + // + // AssistantControl + // + this.ElementHost.Dock = System.Windows.Forms.DockStyle.Fill; + this.ElementHost.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.ElementHost.Location = new System.Drawing.Point(0, 0); + this.ElementHost.Name = "AssistantControl"; + this.ElementHost.Size = new System.Drawing.Size(509, 544); + this.ElementHost.TabIndex = 0; + this.ElementHost.Child = this.RegexAssistant; + // + // RegexAssistantDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(509, 544); + this.Controls.Add(this.ElementHost); + this.Name = "RegexAssistantDialog"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Integration.ElementHost ElementHost; + private RegexAssistant RegexAssistant; + } +} \ No newline at end of file diff --git a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.cs b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.cs new file mode 100644 index 0000000000..c2fd215ce9 --- /dev/null +++ b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Rubberduck.UI.RegexAssistant +{ + public partial class RegexAssistantDialog : Form + { + public RegexAssistantDialog() + { + InitializeComponent(); + ViewModel = new RegexAssistantViewModel(); + } + + private RegexAssistantViewModel _viewModel; + private RegexAssistantViewModel ViewModel { get { return _viewModel; } + set + { + _viewModel = value; + + RegexAssistant.DataContext = _viewModel; + } + } + } +} diff --git a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.resx b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantViewModel.cs b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantViewModel.cs new file mode 100644 index 0000000000..dd20eb8e3f --- /dev/null +++ b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistantViewModel.cs @@ -0,0 +1,71 @@ +using Rubberduck.RegexAssistant; + +namespace Rubberduck.UI.RegexAssistant +{ + class RegexAssistantViewModel : ViewModelBase + { + public RegexAssistantViewModel() + { + RecalculateDescription(); + } + + public bool GlobalFlag { + get + { + return _globalFlag; + } + set + { + _globalFlag = value; + RecalculateDescription(); + } + } + public bool IgnoreCaseFlag + { + get + { + return _ignoreCaseFlag; + } + set + { + _ignoreCaseFlag = value; + RecalculateDescription(); + } + } + public string Pattern + { + get + { + return _pattern; + } + set + { + _pattern = value; + RecalculateDescription(); + } + } + + private string _description; + private bool _globalFlag; + private bool _ignoreCaseFlag; + private string _pattern; + + private void RecalculateDescription() + { + if (_pattern.Equals(string.Empty)) + { + _description = "No Pattern given"; + return; + } + _description = new Pattern(_pattern, _ignoreCaseFlag, _ignoreCaseFlag).Description; + } + + public string DescriptionResults + { + get + { + return _description; + } + } + } +} diff --git a/RetailCoder.VBE/UI/RubberduckUI.Designer.cs b/RetailCoder.VBE/UI/RubberduckUI.Designer.cs index 761f2cf79f..0dcfff4ef5 100644 --- a/RetailCoder.VBE/UI/RubberduckUI.Designer.cs +++ b/RetailCoder.VBE/UI/RubberduckUI.Designer.cs @@ -2430,6 +2430,51 @@ public static string Refresh { } } + /// + /// Looks up a localized string similar to Regex Helper. + /// + public static string RegexAssistant_Caption { + get { + return ResourceManager.GetString("RegexAssistant_Caption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to What the RegExp does:. + /// + public static string RegexAssistant_DescriptionResultsLabel { + get { + return ResourceManager.GetString("RegexAssistant_DescriptionResultsLabel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The flag "Global" is set. + /// + public static string RegexAssistant_GlobalFlag { + get { + return ResourceManager.GetString("RegexAssistant_GlobalFlag", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The flag "IgnoreCase" is set. + /// + public static string RegexAssistant_IgnoreCaseFlag { + get { + return ResourceManager.GetString("RegexAssistant_IgnoreCaseFlag", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The RegExp:. + /// + public static string RegexAssistant_RegexPatternLabel { + get { + return ResourceManager.GetString("RegexAssistant_RegexPatternLabel", resourceCulture); + } + } + /// /// Looks up a localized string similar to Regex Search & Replace. /// diff --git a/RetailCoder.VBE/UI/RubberduckUI.resx b/RetailCoder.VBE/UI/RubberduckUI.resx index dc8898ae0c..8f7e9347ed 100644 --- a/RetailCoder.VBE/UI/RubberduckUI.resx +++ b/RetailCoder.VBE/UI/RubberduckUI.resx @@ -1733,4 +1733,19 @@ All our stargazers, likers & followers, for the warm fuzzies Source Control + + Regex Helper + + + What the RegExp does: + + + The flag "Global" is set + + + The flag "IgnoreCase" is set + + + The RegExp: + \ No newline at end of file From 9231936b2874dae99d63678802a98f2f47956a60 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 26 Jun 2016 23:40:46 +0200 Subject: [PATCH 17/31] Wired the RegexAssistant as a MenuItem --- RetailCoder.VBE/Rubberduck.csproj | 2 ++ .../ParentMenus/RubberduckParentMenu.cs | 3 ++- .../RegexAssistantCommandMenuItem.cs | 21 +++++++++++++++++++ .../UI/Command/RegexAssistantCommand.cs | 20 ++++++++++++++++++ RetailCoder.VBE/UI/RubberduckUI.Designer.cs | 9 ++++++++ RetailCoder.VBE/UI/RubberduckUI.resx | 3 +++ 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 RetailCoder.VBE/UI/Command/MenuItems/RegexAssistantCommandMenuItem.cs create mode 100644 RetailCoder.VBE/UI/Command/RegexAssistantCommand.cs diff --git a/RetailCoder.VBE/Rubberduck.csproj b/RetailCoder.VBE/Rubberduck.csproj index 852a72eaaa..66f189c939 100644 --- a/RetailCoder.VBE/Rubberduck.csproj +++ b/RetailCoder.VBE/Rubberduck.csproj @@ -433,6 +433,8 @@ + + LinkButton.xaml diff --git a/RetailCoder.VBE/UI/Command/MenuItems/ParentMenus/RubberduckParentMenu.cs b/RetailCoder.VBE/UI/Command/MenuItems/ParentMenus/RubberduckParentMenu.cs index 63e2fa8488..e4bb64df77 100644 --- a/RetailCoder.VBE/UI/Command/MenuItems/ParentMenus/RubberduckParentMenu.cs +++ b/RetailCoder.VBE/UI/Command/MenuItems/ParentMenus/RubberduckParentMenu.cs @@ -18,6 +18,7 @@ public enum RubberduckMenuItemDisplayOrder CodeInspections, SourceControl, Settings, - About + About, + RegexAssistant } } diff --git a/RetailCoder.VBE/UI/Command/MenuItems/RegexAssistantCommandMenuItem.cs b/RetailCoder.VBE/UI/Command/MenuItems/RegexAssistantCommandMenuItem.cs new file mode 100644 index 0000000000..8cf8a4774e --- /dev/null +++ b/RetailCoder.VBE/UI/Command/MenuItems/RegexAssistantCommandMenuItem.cs @@ -0,0 +1,21 @@ +using Rubberduck.UI.Command.MenuItems.ParentMenus; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Rubberduck.UI.Command.MenuItems +{ + class RegexAssistantCommandMenuItem : CommandMenuItemBase + { + public RegexAssistantCommandMenuItem(ICommand command) : base(command) + { + } + + public override string Key { get { return "RubberduckMenu_RegexAssistant"; } } + + public override int DisplayOrder { get { return (int)RubberduckMenuItemDisplayOrder.RegexAssistant; } } + } +} diff --git a/RetailCoder.VBE/UI/Command/RegexAssistantCommand.cs b/RetailCoder.VBE/UI/Command/RegexAssistantCommand.cs new file mode 100644 index 0000000000..2f62bd9849 --- /dev/null +++ b/RetailCoder.VBE/UI/Command/RegexAssistantCommand.cs @@ -0,0 +1,20 @@ +using Rubberduck.UI.RegexAssistant; +using System.Runtime.InteropServices; + +namespace Rubberduck.UI.Command +{ + /// + /// A command that displays the RegexAssistantDialog + /// + [ComVisible(false)] + class RegexAssistantCommand : CommandBase + { + public override void Execute(object parameter) + { + using (var window = new RegexAssistantDialog()) + { + window.ShowDialog(); + } + } + } +} diff --git a/RetailCoder.VBE/UI/RubberduckUI.Designer.cs b/RetailCoder.VBE/UI/RubberduckUI.Designer.cs index 0dcfff4ef5..2611d0be14 100644 --- a/RetailCoder.VBE/UI/RubberduckUI.Designer.cs +++ b/RetailCoder.VBE/UI/RubberduckUI.Designer.cs @@ -2917,6 +2917,15 @@ public static string RubberduckMenu_Refactor { } } + /// + /// Looks up a localized string similar to Regex &Assistant. + /// + public static string RubberduckMenu_RegexAssistant { + get { + return ResourceManager.GetString("RubberduckMenu_RegexAssistant", resourceCulture); + } + } + /// /// Looks up a localized string similar to Rege&x Search/Replace. /// diff --git a/RetailCoder.VBE/UI/RubberduckUI.resx b/RetailCoder.VBE/UI/RubberduckUI.resx index 8f7e9347ed..2e64c3e182 100644 --- a/RetailCoder.VBE/UI/RubberduckUI.resx +++ b/RetailCoder.VBE/UI/RubberduckUI.resx @@ -1748,4 +1748,7 @@ All our stargazers, likers & followers, for the warm fuzzies The RegExp: + + Regex &Assistant + \ No newline at end of file From bb2d0a85a7e27d9e6ded856cb64d0174491f31f0 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sun, 26 Jun 2016 23:44:49 +0200 Subject: [PATCH 18/31] Added RegexAssasitantMenuItem to Composition Root --- RetailCoder.VBE/Root/RubberduckModule.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/RetailCoder.VBE/Root/RubberduckModule.cs b/RetailCoder.VBE/Root/RubberduckModule.cs index 1a0c38b542..79b5b8e425 100644 --- a/RetailCoder.VBE/Root/RubberduckModule.cs +++ b/RetailCoder.VBE/Root/RubberduckModule.cs @@ -384,6 +384,7 @@ private IEnumerable GetRubberduckMenuItems() Kernel.Get(), Kernel.Get(), Kernel.Get(), + Kernel.Get(), GetUnitTestingParentMenu(), GetSmartIndenterParentMenu(), GetRefactoringsParentMenu(), From 127833d4091ff44d7130f1b679703edef5eb3605 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Mon, 27 Jun 2016 00:32:55 +0200 Subject: [PATCH 19/31] Made the UI actually work --- RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml | 11 +++++------ .../UI/RegexAssistant/RegexAssistantViewModel.cs | 4 +++- Rubberduck.sln | 6 ++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml index e739939de7..7a5aa8b88d 100644 --- a/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml +++ b/RetailCoder.VBE/UI/RegexAssistant/RegexAssistant.xaml @@ -30,14 +30,14 @@