情况:
我为一些使用过的编程语言制作了扫描仪,解析器和各种AST类,这是我的一个爱好项目。解析器在扫描器的帮助下构建了一个异构的AST,我会对其进行一些操作。在过去,我为一些IDE创建了一个插件/加载项,用于语法高亮和其他一些元素。
问题在于错误:解析器生成一些并且可以访问构成语句的标记。然而,稍后仅出现一些错误,例如无法解析标识符。我想在这些标识符或其他有缺陷的令牌下显示波形。不仅如此,我喜欢操纵我的AST节点而不会丢失原始文档中的所有注释,间距等。
在我的AST中创建一个新的Statement时,我可以轻松地添加组成此语句的标记作为子项。但是......
问题:
如果合理可行,我希望包括显示曲线的支持。这需要一个语句来了解构成语句的标记的位置。不幸的是,声明中的变体有时会包含更多令牌,有时候会包含更少的令牌。如果我正在制作只读AST,这不会有问题。但是,我希望我的AST可以读写为重构目的!这意味着在AST中更改语句本质上意味着添加令牌(Statement的子节点),因此Statement类应该能够自我解析。
这会通过解析代码来判断AST并且不再保持关注点的分离!
技术细节:
另一种方法是将AssignmentStatement转换为工厂,获取一组标记,生成语句的实例并不断让它知道自己的标记。
在我的情况下,用于分配的AST节点目前基本上是这样的:
分层样本AST可以是:
AssemblyDeclaration
.. Statement ..
.. Statement ..
ClassDeclaration
.. Token .. // one or more that make up the entire class statement
.. Token .. // one or more that make up the entire class statement
Statement(s)
.. Token .. // Which have their own tokens that make up the statement .. and possibly have sub-nodes of their own such as Expressions which have -their- own Tokens that comprise it.
基本ast节点的概念性概念,即语法树中所有内容的父节点,无论它是类声明,语句还是令牌。
public abstract class BaseAstNode : IList<BaseAstNode>
{
... implementation of IList<BaseAstNode>
... implementation of Visitor Pattern
... implementation of Clonable
}
public sealed class AssignmentStatement : BaseAstNode
{
public Expression Expression { get; set; }; // Setting this will alter the Tokens (children!) of this node, possibly even ADD Tokens!
public TypeReference Target { get; set; }
}
public sealed class PrimitiveNumberExpression : Expression // is a BaseAstNode
{
public int Value { get; set; } // Setting this will alter the Tokens (children!) of this node!
}
public abstract class Token : BaseAstNode
{
public Layers Layer { get; set; }
public TokenType TokenType { get; set; }
public int Column { get; set; }
public int Line { get; set; }
public int Position { get; set; }
public abstract int Length { get; set; }
public virtual string Value { get; set; }
public override string ToString(){}
}
其他人如何解决这个问题?这是正确的方法吗?
答案 0 :(得分:0)
如果您可以获取发生错误的语句的代码,您应该能够重新解析单个语句,这次跟踪令牌的位置并注意那些无效的(因为您没有)令牌的位置第一次保存)。至少你不需要解析整个文档,只需要一行...也许重新使用你已经拥有的一些解析代码。
或者,执行存储每个令牌的位置(文档中的开头和长度),以便在需要引用其源时轻松访问该令牌的位置。为什么不这样做?
如果你可以从令牌对象重新生成令牌文本,那么一些可能的优化就是不存储令牌长度,或者只是在行内搜索该文本,假设你想对每个人做同样的事情在该行(或所有行)上出现该令牌。
编辑:现在您已将令牌链接到解析它们的原始源代码,您需要将语句链接到解析它们的令牌。有两种方法可以做到这一点:
现在您从语句到源代码都有一个链,这样您就可以识别与每个语句的每个部分相关的源代码。
编辑2:如果你想弄清楚实际的解析如何将令牌处理成BaseAstNode派生的对象,我还没有考虑过这个问题(这不是很清楚这是部分问题)。由于我之前没有这样做,我的建议可能不是第一次尝试的最好。但希望它可以根据需要进行改进。我的第一个想法是引导我到System.Xml命名空间,它也实现了一个解析器。也许您的一些设计可以在XML解析器和相关类之后建模。 XmlNode可能与您的BaesAstNode类相似。 XmlElement可能类似于您的Token类。 XmlDocument是XmlNode的一个特化,它处理一个LoadXml函数中的所有解析,该函数用子节点填充对象。
因此,遵循Xml命名空间的示例,您可以使ParseCode成为ClassDeclaration的成员,该类使用所有适当的子对象填充对象。它可以接受字符串或有序的标记集合作为输入,并将BaseAstNode的派生实例添加到存储在类中的有序BaseAstNode对象集合中。但并非所有解析都需要在一个函数中。在解析文本或标记时,我假设您将从ParseCode函数开始,将解析后的标记推送到堆栈或队列,直到您解析了足够的标记来唯一标识要解析的语句类型,然后传递那些排队的标记和解析器的当前状态到其中一个语句类的适当派生解析器。当派生类完成解析定义语句的标记时,标记将被添加到语句中,然后调用代码将解析的语句添加到父语句集合中。