c# – 使用正则表达式解析VBA Const声明…

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

这些工作很漂亮:

> Const foo = 123
> Const foo $=“123”
> Const foo As String =“123”
>私人Const foo = 123
> Public Const foo As Integer = 123
>全球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)?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>.*)$part引起的,它在行结束前匹配任何内容.我通过将整个模式封装在捕获组中并添加可选的逗号来使VariableNode支持多个声明,但由于常量具有此值组,因此执行该操作会导致第一个常量将所有后续声明作为其值的一部分捕获……让我回到问题#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代码运行.

解决方法

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

正则表达式

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

所以为此,我不得不将正则表达式分成两个不同的正则表达式.原因是.Net 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[^',}(?:'(?<comment>.+))?$",RegexOptions.Compiled | RegexOptions.Singleline,new TimeSpan(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>[^',]+(?:(?:(?!"").)+"")?),?",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)

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

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

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...