我整理了一个VB6 / VBA解析器的幼稚实现,我想看看CR社区是否认为可以使用此代码改进与我看到的相同的事情,然后再开始重构。

我从来没有写过解析器,所以这个幼稚的实现在所有意义上都是幼稚的:我想尽可能地将其推销,直到我停止重构为止-所以在这里我用的是CodeFileParser,除了递归解析代码块;目前尚未处理实际的语言语法,但代码仍会生成一个对整个代码文件建模的ISyntaxTree

public interface ISyntaxTree
{
    string Name { get; }
    IEnumerable<IAttribute> Attributes { get; }
    IList<ISyntaxTree> Nodes { get; }
}


例如,我可以给它一个List类(在这里进行了修改,以实现一些IEnumerable接口),并且在沙箱WPF应用程序中有一个HierarchicalDataTemplate(可能还可以使用同行评审!),我得到了..这已经很棒了:
br />

因此,当我们解析Public Property Get Item(index As Long) As Variant时,它像这样分解:



PropertyNode ("Item (Get)")



IdentifierNode ("Item")


ReferenceNode ("Variant")



ParameterNode ("index")



IdentifierNode ("index")


ReferenceNode ("Long")





CodeBlockNode



(更多CodeBlockNodes





所以IdentifierNode基本上代表一个标识符;我正在自我辩论是否IdentifierNode应该具有Scope属性,或者是否应该从其在树中的位置推断出标识符的范围。任何有名称的东西,都应该有一个IdentifierNode来存储它:方法名称,类名称,变量,常量等。

指定了显式类型的标识符下面将带有ReferenceNodeReferenceNode表示一种类型的用法,既可以是内置类型(StringInteger等),也可以指向IdentifierNodeName。对于函数和属性获取器(即具有返回值的成员),成员ReferenceNode下的IdentifierNode指向返回类型。

想法是在树结构中具有足够的元数据,以便能够最终,例如,编写可以遍历一棵或多棵此类树并找到给定标识符的所有引用的代码检查器-然后可能应用重命名重构。或查找所有未使用的变量。随你。在开始分析树结构之前,我需要首先重构这块废话:

解析

public class CodeFileParser
{
    public ISyntaxTree Parse (string fileName)
    {
        var content = File.ReadAllLines(fileName);
        var currentLine = 0;

        var header = ParseFileHeader(fileName, content, ref currentLine);
        var declarations = ParseDeclarations(content, ref currentLine);
        var members = ParseMembers(content, ref currentLine);

        var module = new ModuleNode(header, declarations, members);
        return module;
    }


ParseFileHeader

我不喜欢在这里进行硬编码,该函数不应“知道”文件头中每一行的内容...而且AttributeParser是以前方法的遗物,可能不会完全发疯(但肯定可以清除): br />
这是事情变得非常丑陋的地方。知道我将在这里提取一种方法来消除4x代码重复,并且其中一些逻辑将必须包含在递归函数中。就像我说的那样,我尽可能地采用了天真的方法,所以这是尽可能地使天真的方法使我无需递归(不是不骂人!): />在重构时,我觉得在某一点上可能会从设计中出现类似构建器模式之类的东西。


这里是AttributeParser类,它实际上只是一个抽象工厂: />
    private ISyntaxTree ParseFileHeader(string fileName, string[] content, ref int currentLine)
    {
        var attributeParser = new AttributeParser();
        IList<IAttribute> attributes = new List<IAttribute>();
        ISyntaxTree result;

        var firstLine = content[0].Trim();
        if (firstLine == "VERSION 1.0 CLASS")
        {
            var multiUse = content[2].Trim();
            var persistable = content[3].Trim();
            var dataBindingBehavior = content[4].Trim();
            var dataSourceBehavior = content[5].Trim();
            var mtsTransactionMode = content[6].Trim();

            attributes.Add(attributeParser.Parse(content[8].Trim()));
            attributes.Add(attributeParser.Parse(content[9].Trim()));
            attributes.Add(attributeParser.Parse(content[10].Trim()));
            attributes.Add(attributeParser.Parse(content[11].Trim()));
            attributes.Add(attributeParser.Parse(content[12].Trim()));

            var regex = new Regex(@"\=\s\-?(?<IntValue>\d+)\s");
            result = new ClassModule(fileName, attributes)
                            {
                                DataBindingBehavior = int.Parse(regex.Match(dataBindingBehavior).Groups["IntValue"].Value),
                                DataSourceBehavior = int.Parse(regex.Match(dataSourceBehavior).Groups["IntValue"].Value),
                                MTSTransactionMode = int.Parse(regex.Match(mtsTransactionMode).Groups["IntValue"].Value),
                                MultiUse = int.Parse(regex.Match(multiUse).Groups["IntValue"].Value),
                                Persistable = int.Parse(regex.Match(persistable).Groups["IntValue"].Value)
                            };

            currentLine = 13;
        }
        else
        {
            attributes.Add(attributeParser.Parse(content[0].Trim()));
            result = new CodeModule(fileName, attributes);

            currentLine = 1;
        }

        return result;
    }


我想我会需要更多的这些。 Parser是它的好名字吗?

#1 楼

ParseFileHeader中,您会重复很多次。每次Trim之后content[...]。您可以使用循环Trim

    for (int i = 0; i < content.Length; i++)
        content[i] = content[i].Trim();


此代码...

        attributes.Add(attributeParser.Parse(content[8].Trim()));
        attributes.Add(attributeParser.Parse(content[9].Trim()));
        attributes.Add(attributeParser.Parse(content[10].Trim()));
        attributes.Add(attributeParser.Parse(content[11].Trim()));
        attributes.Add(attributeParser.Parse(content[12].Trim()));


...可以写...如

        foreach (var index in new[] { 8,9,10,11,12)
            attributes.Add(attributeParser.Parse(content[index])


ParseMembers太大。将其分为几种方法。您具有

private IEnumerable<ISyntaxTree> ParseMembers(string[] content, ref int currentLine)
{
    ....

    if (match.Success)
    {
        // Wall of code
    }
    else
    {
        // Wall of code
    }
}


创建KeywordMatchedKeywordNotMatched,由于它,您将代码墙拆分为两个较小的方法。它更容易阅读。然后转到KeywordMatched

if (!new[] { ReservedKeywords.Sub, ReservedKeywords.Function, ReservedKeywords.Property }.Contains(modifier))
{
    // Wall of code
}
else
{
    // Wall of code
}


并重复该过程。创建ParseSubOrFunctionOrPropertyParseOther或whateva有意义的名称。过了一会儿,您将获得代码,其中有许多带有易于读取名称的小功能。

编辑:还有一件事

var pattern = @"((?<keyword>Public|Private|Friend)...";
var regex = new Regex(pattern);


请使用更好的名称。什么是patterncorrectKeyword还是什么?

评论


\ $ \ begingroup \ $
更好的是,使索引数组成为静态变量,而不是每次都更新它。
\ $ \ endgroup \ $
– jpmc26
2014年7月2日在4:03

#2 楼

大多数时候,我不想阅读代码。例如,存在一个与数据源行为有关的错误

var multiUse            = // I DON'T CARE!
var persistable         = // I DON'T CARE!
var dataBindingBehavior = // I DON'T CARE!
var dataSourceBehavior  = // I DON'T CARE!
var mtsTransactionMode  = content[6].Trim();


我负担不起看过multiUse的定义的方式。这不是我要找的东西。我知道这是因为您使用了有意义的名称。

var match = regex.Match(content[currentLine]);
if (match.Success)


在此代码中,我知道您尝试将current lineregex匹配,但是regex是什么?我跟踪regex的定义,现在我必须阅读和理解非常冗长而复杂的正则表达式@"((?<keyword>Public|Private|Friend)\s)?(?<keyword>Property|Function|Sub)\s+((?<keyword>Get|Let|Set)\s+)?(?<name>[a-zA-Z][a-zA-Z0-9_]*)(\((?<parameters>.*)\))?(\s+As\s+(?<type>.*))?$";

不,谢谢!您可以将pattern更改为patternAnyKeyword,将regex更改为regexAnyKeyword。然后我有

var match = regexAnyKeyword.Match(content[currentLine]);
if (match.Success)


我很容易理解! MatchanyKeyword。但是,让我们走得更远,将match更改为matchAnyKeyword,然后我就会有了

var matchAnyKeyword = regexAnyKeyword.Match(content[currentLine]);
if (matchAnyKeyword.Success)


当我在阅读时,是

var matchAnyKeyword = // I DON'T CARE!
if (matchAnyKeyword.Success)


例如,您简短地评论了

// comment node
if (content[currentLine].Trim().StartsWith("'"))
{
    currentLine++;
    continue;
}


,但几行后我看到if (!new[]{ ReservedKeywords.Sub, ReservedKeywords.Function, ReservedKeywords.Property }.Contains(modifier)),您应该评论它的作用,例如

bool isSubOrFunctionOrProperty = new[]{ ReservedKeywords.Sub, ReservedKeywords.Function, ReservedKeywords.Property }.Contains(modifier));
if (! isSubOrFunctionOrProperty )


在拥有类似

的代码之后,30行代码很多,是时候创建新方法了。非常简单!提取它并命名为ParseFor。现在,您可以使用一种功能。易于测试,易于理解。

我注意到您在3个地方重复了
bool isSingleLineComment = // I DON'T CARE
if (isSingleLineComment)
{
    // 3 lines of code
}
else if (isFunction)
{
    // 5 lines of code
}
else if (isFor)
{
    // 30 lines of code
}


。您的方法有250行代码。提取方法和ParseMembers将减少到100行+您将获得包含50行代码的新方法。

#3 楼

我将对AttributeParser抽象工厂做几件事:


Regex的创建从Parse方法中分离出来,因为每个调用都相同。
每个if..else里面有一个return有点罗word。我只喜欢这些实例的三元运算符。
(1)中的相同建议也适用于Regex类的ParseFileHeaderParseDeclarationsParseMembers方法中的CodeFileParser
ParseDeclarations方法中,将if (isDeclarationSection) { ... }反转为if (!isDeclarationSection) { continue; },因为我是“快速失败”的支持者。
ParseMembers是一种巨大的方法。我不能完全说出它所做的一切。或者,更重要的是,它似乎可以做所有事情。我看到很多if..else看起来像是进入其他方法的良好分解区域-可能是ParsePropertyParseLoopParseConditionalParseMethod等。
我将在代码的其他部分进行介绍并更新这个答案有点。

重新整理的代码:

public class AttributeParser : IAttributeParser
{
    private const string Syntax = @"^Attribute\s(?<Member>[a-zA-Z]+\.)?(?<Name>VB_\w+)\s=\s(?<Value>.*)$";

    private static readonly Regex regex = new Regex(Syntax, RegexOptions.Compiled);

    public IAttribute Parse(string instruction)
    {
        if (!regex.IsMatch(instruction))
        {
            return null;
        }

        var match = regex.Match(instruction);
        var member = match.Groups["Member"].Value;
        var name = match.Groups["Name"].Value;
        var value = match.Groups["Value"].Value;

        return string.IsNullOrEmpty(member)
            ? new Attribute(name, value)
            : new MemberAttribute(name, value, member);
    }
}