Swift用Swift编写的AST。 ∞的第2部分

在上一部分中,我确实为Swift语言构建了基本的顶级AST。 还没什么壮观的。 只是一些枚举和严格的数据建模。

今天,我将更深入地研究并复制Swift语法的Declaration部分。 让我们看一下:

它看起来与语句完全相同,因此让我们为其编写类似的枚举样式类型并集。

 枚举声明{ 
案例`import`(ImportDeclaration)
大小写常量(ConstantDeclaration)
大小写变量(VariableDeclaration)
case`typealias`(TypealiasDeclaration)
案例函数(FunctionDeclaration)
案例enum(EnumDeclaration)
case`struct`(StructDeclaration)
案例`class`(ClassDeclaration)
案例`protocol`(ProtocolDeclaration)
大小写初始值设定项(InitializerDeclaration)
案例deinitializer(DeinitializerDeclaration)
case`extension`(ExtensionDeclaration)
case`subscript`(SubscriptDeclaration)
case`operator`(OperatorDeclaration)
大小写PriorityGroup(PrecedenceGroupDeclaration)
}

有意地错过了一种语法定义。 哪一个? declarations -> ...实际上从未在语法中使用。 因此,我们不需要它。

进口

导入语法是我们的​​第一个非平凡语法。 它看起来并不那么简单。 主要是因为隐藏的功能不是导入整个模块而是从中导入单个定义。 这是语法:

第一行告诉我们,导入语句由可选属性列表,关键字,可选种类说明符和路径组成。 这次我们不能import-declaration建模为union类型。 我们需要每种类型的单个值来形成正确的类型实例。 这种情况通常称为类型产品。

在Swift中,我们可以通过几种方式对产品进行建模:

  1. 元组。 这确实是临时类型的产品。 易于编写,难以支持。 我发现Xcode IDE中缺少元组支持,因此我们将寻找其他选择。
  2. 类。 用多种OOP语言对产品进行建模的方式。 类的问题(继承性,行为和可变性)使我们几乎无法使用此选项。
  3. 结构。 好的旧结构/记录。 简单而强大的建模产品的方法。 非常适合我们的情况。

构造该语法的哪个模型将如下所示:

  struct ImportDeclaration { 
let属性:[Attribute]
让importKind:ImportKind吗?
让importPath:ImportPath
}

ImportKind对我们ImportKind是一个新案例。 不再参考其他语法部分! 这种类型的声明通常称为“终端声明”。 在Swift中,我们可以将其建模为没有额外类型的枚举:

 枚举ImportKind { 
case`typealias`
案例`struct`
案例类
大小写枚举
案例`protocol`
案例`var`
case`func`
}

这个枚举的所有情况都是Swift本身的保留关键字。 因此,我们需要将它们包装在`刻度中。

关于ImportKind有趣事实-您不能从模块导入运算符。

声明的最后一部分是ImportPath 。 Path只是通过链接的标识符的递归列表. 符号。 我们如何在Swift中表示递归列表? 我们不能与数组一起使用。 数组可以为空,我们严格要求至少有一个元素。 写自己的名单? 创建递归数据结构并不有趣。 有时我们无法摆脱它,但通常我们可以逃脱它。 在这种情况下,我们可以将其建模为第一个元素和其余元素的元组!

  struct ImportPath { 
让头:ImportPathIdentifier
让尾巴:[ImportPathIdentifier]
}

它能解决问题吗? 是。 我们可以做得更好吗? 是!

问题是,我们强迫所有客户考虑此限制,并分别对待头和尾。 让我们为他们简化生活:

 扩展名ImportPath { 
var标识符:[ImportPathIdentifier] {
返回[头] +尾
}
}

现在,每个人都可以使用标识符,而不必考虑我们的实现细节。 但是是否需要将这些信息完全保留在我们的结构中? 如果我们希望API客户端使用标识符,那么让我们在其中存储数据。 幸运的是,迅速允许我们重写默认的成员明智的初始化器:

  struct ImportPath { 
让标识符:[ImportPathIdentifier]
  init(head:ImportPathIdentifier,tail:[ImportPathIdentifier]){ 
标识符= [第一] +其余
}
}

每个标识符可以是常规标识符或运算符。 等待。 操作员? 看起来Swift语法允许运算符模块名称。 从来没有见过这种疯狂。 但是,我今天不想深入探讨并实现标识符。 对于简单的导入声明,我们已经足够了。

Typealias

今天的第二种语法:打字。 导入后非常简单:

我们可以看到语法作者决定对typealias-nametypealias-assignement字段进行某种间接化。 我将冒险,并将其全部建模为单个结构:

  struct TypealiasDeclaration { 
let属性:[Attribute]
让accessLevel:AccessLevelModifier?
命名:标识符
让genericArguments:GenericArgumentClause吗?
让类型:类型
}

这里有很多新类型! 如您所见,兔子洞真的很深。 为了跟踪事物,我将逐层介绍语法,而不是一次全部分支。