使用正则表达式解析VBA Const声明...

时间:2014-11-07 06:43:50

标签: c# regex parsing

我正在尝试编写VBA解析器;为了创建ConstantNode,我需要能够匹配Const声明的所有可能变体。

这些工作很精彩:

  • Const foo = 123
  • Const foo$ = "123"
  • Const foo As String = "123"
  • Private Const foo = 123
  • Public Const foo As Integer = 123
  • Global Const foo% = 123

但我有两个问题:

  1. 如果声明末尾有评论,我会把它作为价值的一部分:

       
    Const foo = 123 'this comment is included as part of the value
    
  2. 如果在同一条指令中声明了两个或多个常量,我将无法匹配整个指令:

       
    Const foo = 123, bar = 456 
    
  3. 这是我正在使用的正则表达式:

        /// <summary>
        /// Gets a regular expression pattern for matching a constant declaration.
        /// </summary>
        /// <remarks>
        /// Constants declared in class modules may only be <c>Private</c>.
        /// Constants declared at procedure scope cannot have an access modifier.
        /// </remarks>
        public static string GetConstantDeclarationSyntax()
        {
            return @"^((Private|Public|Global)\s)?Const\s(?<identifier>[a-zA-Z][a-zA-Z0-9_]*)(?<specifier>[%&@!#$])?(?<as>\sAs\s(?<reference>(((?<library>[a-zA-Z][a-zA-Z0-9_]*))\.)?(?<identifier>[a-zA-Z][a-zA-Z0-9_]*)))?\s\=\s(?<value>.*)$";
        }
    

    显然,这两个问题都是由(?<value>.*)$部分引起的,该部分与任何匹配,直到行尾。我通过将整个模式封装在捕获组中并添加可选的逗号来获得VariableNode来支持一个指令中的多个声明,但因为常量具有此value组,所以这样做导致第一个常量具有所有以下声明作为其价值的一部分被捕获......这使我回到问题#1。

    我想知道是否可以用正则表达式解决问题#1,因为该值可能是包含撇号的字符串,可能还有一些转义(双倍)双引号。

    我想我可以在ConstantNode类本身,Value的getter中解决它:

    /// <summary>
    /// Gets the constant's value. Strings include delimiting quotes.
    /// </summary>
    public string Value
    {
        get
        {
            return RegexMatch.Groups["value"].Value;
        }
    }
    

    我的意思是,我可以在这里实现一些额外的逻辑,做一些我不能用正则表达式做的事情。


    如果问题#1可以通过正则表达式来解决,那么我相信问题#2也可以......或者我是否在正确的轨道上?我应该放弃[相当复杂]的正则表达式模式并想出另一种方式吗?我不太熟悉贪婪的子表达式反向引用和其他更高级的正则表达式功能 - 这是限制我的原因,还是仅仅因为我使用了错误的锤子这个钉子?

    注意:模式可能与非法语法匹配并不重要 - 此代码仅针对可编译的VBA代码运行。

1 个答案:

答案 0 :(得分:3)

让我继续,在这个上加上免责声明。这绝对不是一个好主意(但这是一个有趣的挑战)。我即将呈现的正则表达式将解析问题中的测试用例,但它们显然不是防弹。使用解析器可以为您节省很多麻烦。我确实试图为VBA找到一个解析器,但空手而归(而且我假设其他人也都有)。

<强>正则表达式

为了使其工作得很好,你需要控制进来的VBA代码。如果你不能这样做,那么你真的需要考虑编写解析器而不是使用正则表达式。但是,根据你已经说过的话,你可能会有一些控制权。所以也许这会有所帮助。

所以为此,我不得不将正则表达式分成两个不同的正则表达式。原因是.Net Regex库无法处理重复组中的捕获组。

捕获行并开始解析,这会将变量(带有值)放入一个组中,但第二个Regex将解析它们。正如fyi,正则表达式使用负面的外观。

^(?:(?<Accessibility>Private|Public|Global)\s)?Const\s(?<variable>[a-zA-Z][a-zA-Z0-9_]*(?:[%&@!#$])?(?:\sAs)?\s(?:(?:[a-zA-Z][a-zA-Z0-9_]*)\s)?=\s[^',]+(?:(?:(?!"").)+"")?(?:,\s)?){1,}(?:'(?<comment>.+))?$

Regex Demo

这是解析变量的正则表达式

(?<identifier>[a-zA-Z][a-zA-Z0-9_]*)(?<specifier>[%&@!#$])?(?:\sAs)?\s(?:(?<reference>[a-zA-Z][a-zA-Z0-9_]*)\s)?=\s(?<value>[^',]+(?:(?:(?!").)+")?),?

Regex Demo

这里有一些c#代码你可以投入并测试一切。这样可以轻松测试您拥有的任何边缘情况。

static void Main(string[] args)
{
    List<String> test = new List<string> {
        "Const foo = 123",
        "Const foo$ = \"123\"",
        "Const foo As String = \"1'2'3\"",
        "Const foo As String = \"123\"",
        "Private Const foo = 123",
        "Public Const foo As Integer = 123",
        "Global Const foo% = 123",
        "Const foo = 123 'this comment is included as part of the value",
        "Const foo = 123, bar = 456",
        "'Const foo As String = \"123\"",
    };


    foreach (var str in test)
        Parse(str);

    Console.Read();
}

private static Regex parse = new Regex(@"^(?:(?<Accessibility>Private|Public|Global)\s)?Const\s(?<variable>[a-zA-Z][a-zA-Z0-9_]*(?:[%&@!#$])?(?:\sAs)?\s(?:(?:[a-zA-Z][a-zA-Z0-9_]*)\s)?=\s[^',]+(?:(?:(?!"").)+"")?(?:,\s)?){1,}(?:'(?<comment>.+))?$", RegexOptions.Compiled | RegexOptions.Singleline, new TimeSpan(0, 0, 20));
private static Regex variableRegex = new Regex(@"(?<identifier>[a-zA-Z][a-zA-Z0-9_]*)(?<specifier>[%&@!#$])?(?:\sAs)?\s(?:(?<reference>[a-zA-Z][a-zA-Z0-9_]*)\s)?=\s(?<value>[^',]+(?:(?:(?!"").)+"")?),?", RegexOptions.Compiled | RegexOptions.Singleline, new TimeSpan(0, 0, 20));

public static void Parse(String str)
{
    Console.WriteLine(String.Format("Parsing: {0}", str));

    var match = parse.Match(str);

    if (match.Success)
    {
        //Private/Public/Global
        var accessibility = match.Groups["Accessibility"].Value;
        //Since we defined this with atleast one capture, there should always be something here.
        foreach (Capture variable in match.Groups["variable"].Captures)
        {
            //Console.WriteLine(variable);
            var variableMatch = variableRegex.Match(variable.Value);
            if (variableMatch.Success) 
            {
                Console.WriteLine(String.Format("Identifier: {0}", variableMatch.Groups["identifier"].Value));

                if (variableMatch.Groups["specifier"].Success)
                    Console.WriteLine(String.Format("specifier: {0}", variableMatch.Groups["specifier"].Value));

                if (variableMatch.Groups["reference"].Success)
                    Console.WriteLine(String.Format("reference: {0}", variableMatch.Groups["reference"].Value));

                Console.WriteLine(String.Format("value: {0}", variableMatch.Groups["value"].Value));

                Console.WriteLine("");
            }
            else
            {
                Console.WriteLine(String.Format("FAILED VARIABLE: {0}", variable.Value));
            }

        }

        if (match.Groups["comment"].Success)
        {
            Console.WriteLine(String.Format("Comment: {0}", match.Groups["comment"].Value));
        }
    }
    else
    {
        Console.WriteLine(String.Format("FAILED: {0}", str));
    }

    Console.WriteLine("+++++++++++++++++++++++++++++++++++++++++++++");
    Console.WriteLine("");
}

c#代码就是我用来测试我的理论的代码,所以我为其中的疯狂道歉。

为了完整性,这里有一小部分输出。如果您运行代码,您将获得更多输出,但这直接表明它可以处理您询问的情况。

Parsing: Const foo = 123 'this comment is included as part of the value
Identifier: foo
value: 123
Comment: this comment is included as part of the value


Parsing: Const foo = 123, bar = 456
Identifier: foo
value: 123

Identifier: bar
value: 456

处理的内容

以下是我可以想到的主要案例,你可能会对它感兴趣。它应该仍然可以处理你之前的所有内容,因为我刚刚添加到你提供的正则表达式中。

  • 评论
  • 单行多个变量声明
  • 字符串值中的撇号(注释字符)。即foo =“她很棒”
  • 如果该行以注释开头,则应忽略该行

它无法处理

我没有真正处理的一件事是间距,但如果你需要的话,不应该在你自己中添加它。因此,例如,如果声明多个变量,则必须在逗号后面有空格。即(有效:foo = 123,foobar = 124)(无效:foo = 123,foobar = 124)

你不会对它的格式有多大的宽容,但是在使用正则表达式时,你可以做很多事情。


希望这会帮助你,如果你需要任何更多的解释,请告诉我。 只知道这是个坏主意。你会遇到正则表达式无法处理的情况。如果我在你的位置,我会考虑编写一个简单的解析器,从长远来看会给你更大的灵活性。祝你好运。