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

我正在尝试编写一个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
  • 但我有两个问题:

  • 如果在声明结尾处有评论,我会将其作为价值的一部分来提取:

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

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

        /// <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)?Consts(?<identifier>[a-zA-Z][a-zA-Z0-9_]*)(?<specifier>[%&@!#$])?(?<as>sAss(?<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,因为该值可能是一个包含撇号的字符串,可能还有一些转义的双引号(doubled-up)。

    我想我可以在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代码运行。


    让我继续,并在这一个添加免责声明。 这绝对不是一个好主意(但这是一个有趣的挑战)。 我将要提出的正则表达式将解析这个问题中的测试用例,但它们显然不是防弹的。 使用解析器将在以后为您节省很多头痛的问题。 我曾尝试为VBA找到一个解析器,但却空手而来(我假设其他人也都是这样)。

    正则表达式

    为了更好地工作,你需要对VBA代码进行一些控制。如果你不能这样做,那么你真的需要考虑编写一个解析器而不是使用Regexes。 然而,从你已经说过的话来看,你可能有一点控制权。 所以也许这会有所帮助。

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

    捕获该行并开始分析,这将把变量(包含值)放入一个组中,但第二个正则表达式将解析它们。 正义,正则表达式利用负向后视。

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

    正则表达式演示

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

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

    正则表达式演示

    这里有一些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)?Consts(?<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
    

    它处理的是什么

    以下是我可以想到的您可能感兴趣的主要情况。它应该仍然可以处理您之前刚刚添加到您提供的正则表达式中的所有内容。

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

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

    你不会从格式上获得太多宽大的处理,但是在使用正则表达式的时候,你可以用它做很多事情。


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

    链接地址: http://www.djcxy.com/p/23485.html

    上一篇: Parsing VBA Const declarations... with regex

    下一篇: Load sound from memory in Android