我从来没有写过解析器,所以这个幼稚的实现在所有意义上都是幼稚的:我想尽可能地将其推销,直到我停止重构为止-所以在这里我用的是
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
来存储它:方法名称,类名称,变量,常量等。指定了显式类型的标识符下面将带有
ReferenceNode
。 ReferenceNode
表示一种类型的用法,既可以是内置类型(String
,Integer
等),也可以指向IdentifierNode
的Name
。对于函数和属性获取器(即具有返回值的成员),成员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
}
}
创建
KeywordMatched
,KeywordNotMatched
,由于它,您将代码墙拆分为两个较小的方法。它更容易阅读。然后转到KeywordMatched
if (!new[] { ReservedKeywords.Sub, ReservedKeywords.Function, ReservedKeywords.Property }.Contains(modifier))
{
// Wall of code
}
else
{
// Wall of code
}
并重复该过程。创建
ParseSubOrFunctionOrProperty
和ParseOther
或whateva有意义的名称。过了一会儿,您将获得代码,其中有许多带有易于读取名称的小功能。 编辑:还有一件事
var pattern = @"((?<keyword>Public|Private|Friend)...";
var regex = new Regex(pattern);
请使用更好的名称。什么是
pattern
? correctKeyword
还是什么? #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 line
与regex
匹配,但是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)
我很容易理解!
Match
是anyKeyword
。但是,让我们走得更远,将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
类的ParseFileHeader
,ParseDeclarations
和ParseMembers
方法中的CodeFileParser
。在
ParseDeclarations
方法中,将if (isDeclarationSection) { ... }
反转为if (!isDeclarationSection) { continue; }
,因为我是“快速失败”的支持者。ParseMembers
是一种巨大的方法。我不能完全说出它所做的一切。或者,更重要的是,它似乎可以做所有事情。我看到很多if..else
看起来像是进入其他方法的良好分解区域-可能是ParseProperty
,ParseLoop
,ParseConditional
,ParseMethod
等。我将在代码的其他部分进行介绍并更新这个答案有点。
重新整理的代码:
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);
}
}
评论
\ $ \ begingroup \ $
更好的是,使索引数组成为静态变量,而不是每次都更新它。
\ $ \ endgroup \ $
– jpmc26
2014年7月2日在4:03