Swift AST written in Swift. Part 3 of ∞

Alexey Demedeckiy
5 min readMar 18, 2017

--

In last post I have start covering Declaration grammar section of Swift grammar.

In this post I will cover let and var declarartion. This is really tricky part with another portion of Swift grammar weirdinness.

Let

Let’t start from simpler one — let. Here is the grammar:

Nothing conceptually new. Let’s use same approach for list as in ImportPath

struct ConstantDeclaration {
let attributes: [Attribute]
let modifiers: [DeclarationModifier]
let initializers: PatternInitializerList
}

I will not separate pattern-initializer section into another type and will model it as tuple:

struct PatternInitializerList {
let initializers: [(Pattern, Initializer?)]
init(head: (Pattern, Initializer?),
tail: [(Pattern, Initializer?)]
{
initializers = [head] + tail
}
}

Probably we should abstract this lists to something like List<T> in a future.

What about new types? Declaration modifier is something that surprised me. Just look at its grammar:

It contains all possible modifiers. But just several of them can be valid with let declaration. Looks like at this point Swift team experience some crunch and decide to not over-engineer it. We will model it as huge enum:

enum DeclarationModifier {
case `class`
case `convinience`
case `dynamic`
case `final`
case `infix`
case `lazy`
case `optional`
case `override`
case `postfix`
case `prefix`
case `required`
case `static`
case `unowned`
case `unownedSafe`
case `weak`
case access(AccessLevelModifier)
case mutation(MutationModifier)
}
enum AccessLevelModifier {
case `private`, privateSet
case `fileprivate`, fileprivateSet
case `internal`, internalSet
case `public`, publicSet
case `open`, openSet
}
enum MutationModifier {
case mutating
case nonmutating
}

I see this as real fail point. This means that not all grammar correct trees will be correct in terms of Swift compiler. Not good. I can fix it in my version of grammar, or propose change at Swift mailing list. But this action (if any) can be done after covering AST.

Var

Just look at this:

This part do cover a single var declaration. I think that all JavaScript notation grammar is smaller.

This grammar is so massive because it merges all contexts of using var into single definition. It means, again, that not every AST builded using this grammar will be correct.

Disclaimer: My complains about grammar should not convince you that it is bad. Main purpose of grammar — parsing text into lexems. Not into regular code. Syntax correct grammar will just make my life little easier.

Lets cover it section by section.

Variable declaration can be matched to 1 of 6 options.

  1. Simple var with or without assignments.
  2. var with code block aka computed property
  3. var with explicit getter and setter
  4. var with getter and setter declarations (protocols?)
  5. var with initializer (= 5) and willSet / didSet
  6. var with type specification and willSet / didSet

Only first option can be used in any context. Option number 4 is suited for protocols. Other is a different versions of properties from classes and structs.

But let’s convert this into code, and will revisit once more time, after all nodes will be converted.

Every option begins with variable-declaration-head. This declaration contains two optional attributes and modifiers. Just like let.

struct VariableDeclaration {
let attributes: [Attribute]
let modifiers: [DeclarationModifier]
...
}

Let’s wrap all different options here in a separate VariableDeclarationBody enum.

struct VariableDeclaration {
let attributes: [Attribute]
let modifiers: [DeclarationModifier]
let body: VariableDeclarationBody
}
enum VariableDeclarationBody {
case patterInitializer(PatternInitializerList)
case computed(ComputedVariableBody)
case geterSetter(GetterSetterVariableBody)
case willSetDidSet(WillSetDidSetVariableBody)
case `protocol`(ProtocolVariableBody)
}

Hmm.. Let’s drop attributes and modifiers and repead them in each case.

enum VariableDeclaration {
case patterInitializer(PatternInitializerList)
case computed(ComputedVariableDeclaration)
case geterSetter(GetterSetterVariableDeclaration)
case willSetDidSet(WillSetDidSetVariableDeclaration)
case `protocol`(ProtocolVariableDeclaration)
}

Let’s look at each of them in details.

First one is reuse of let PatternInitializer part.

Second is about computed property. Pretty straghtforward.

struct ComputedVariableDeclaration {
let attributes: [Attribute]
let modifiers: [DeclarationModifier]
let name: Identifier
let type: TypeAnnotation
let code: CodeBlock
}

Third is about property with explicit getter and setter. Interesting that according to grammar you can write getter and setter in any order. But getter is mandatory and setter is an optional.

struct GetterSetterVariableDeclaration {
let attributes: [Attribute]
let modifiers: [DeclarationModifier]
let name: Identifier
let type: TypeAnnotation
let getter: GetterBlock
let setter: SetterBlock?
}
struct GetterBlock {
let attributes: [Attribute]
let mutation: MutationModifier?
let code: CodeBlock
}
struct SetterBlock {
let attributes: [Attribute]
let mutation: MutationModifier?
let setterName: Identifier?
let code: CodeBlock
}

Fourth is about property with hooks.

struct WillSetDidSetVariableDeclaration {
let attributes: [Attribute]
let modifiers: [DeclarationModifier]
let name: Identifier
/// At least one of two must be non nil.
let type: TypeAnnotation?
let initializer: Initializer?
let willSet: WillSetClause?
let didSetClause: DidSetClause?
}
struct WillSetClause {
let attributes: [Attribute]
let newValueName: Identifier?
let code: CodeBlock
}
struct DidSetClause {
let attributes: [Attribute]
let oldValueName: Identifier?
let code: CodeBlock
}

Last one is obout variable declaration in protcols.

struct ProtocolVariableDeclaration {
let attributes: [Attribute]
let modifiers: [DeclarationModifier]
let name: Identifier
let type: TypeAnnotation
let getter: GetterKeywordClause
let setter: SetterKeywordClause?
}
struct GetterKeywordClause {
let attributes: [Attribute]
let mutation: MutationModifier?
}
struct SetterKeywordClause {
let attributes: [Attribute]
let mutation: MutationModifier?
}

This struct heavily used TypeAnnotation and CodeBlock declarartions. Here is respective code:

struct TypeAnnotation {
let attributes: [Attribute]
let `inout`: Inout?
let type: Type
}
enum Inout { case `inout` }struct CodeBlock {
let statements: [Statement]
}

From my perspective design of this things has suffered from early language evolution. IMO, it should be separated in several concepts:

  1. Variables with let or var semantics and patter initializer lists.
  2. Properties of 3 kinds: computed, back stored (get / set) and hooked
  3. Protocol declarations.

All these concepts need to be completely separarted.

In next article I will discover a structure of a func declaration.

--

--