Just An Application

September 10, 2015

Swift Miscellany: Too Many Switches — The Case Of The Nested Enums

At the lexical level Swift code comprises five token types

  • identifiers

  • keywords

  • literals

  • operators

  • punctuation

seperated by whitespace, so a Swift token can be represented quite nicely by an enum.

    enum Token
    {
        case IDENTIFIER
        case KEYWORD
        case LITERAL
        case OPERATOR
        case PUNCTUATION
    }

The token type on its own is not use much use.

In each case we also need the associated value which, because its Swift, we can store in the enum as well.

    enum Token
    {
        case IDENTIFIER(Identifier)
        case KEYWORD(Keyword)
        case LITERAL(Literal)
        case OPERATOR(Operator)
        case PUNCTUATION(Punctuation)
    }

The types of the associated values are also best represented as enums, for example,

    enum Keyword: String
    {
        case AS             = "as"
        case ASSOCIATIVITY  = "associativity"
    
        case BREAK          = "break"
    
        case CASE           = "case"
        case CATCH          = "catch"
        case CLASS          = "class"
        case CONTINUE       = "continue"
        case CONVENIENCE    = "convenience"
    
        ...
    
        case WEAK           = "weak"
        case WHERE          = "where"
        case WHILE          = "while"
        case WILL_SET       = "willSet"
    }
    enum Punctuation: String
    {
        case AMPERSAND              = "&"
        case ARROW                  = "->"
        case AT                     = "@"
    
        case COLON                  = ":"
        case COMMA                  = ","
    
        case DOT                    = "."
    
        ...
        
        case LEFT_PAREN             = "("
        case RIGHT_PAREN            = ")"
    }

Now we have a 'nested enum' which is all very interesting but how do you use it ?

Parsing A Swift Pattern

This is the Swift grammar for a pattern

    patternwildcard-pattern type-annotationopt 
    patternidentifier-pattern type-annotationopt 
    patternvalue-binding-pattern 
    patterntuple-pattern type-annotationopt 
    patternenum-case-pattern 
    patternoptional-pattern 
    patterntype-casting-pattern 
    patternexpression-pattern 

and these are the grammars for the various types of pattern

    wildcard-pattern_ 
    identifier-patternidentifier 
    value-binding-patternvar pattern | let pattern 
    tuple-pattern( tuple-pattern-element-listopt ) 
    enum-case-patterntype-identifieropt . enum-case-name tuple-patternopt 
    optional-patternidentifier-pattern ?
    type-casting-patternis-pattern | as-pattern
    is-patternis type 
    as-patternpattern  as type 
    expression-patternexpression

A pattern can start with an indentifier, a keyword, punctuation, or anything an expression can start with which adds operators so we now have a full-house.

To parse a pattern given a token we need to identify the token type and then for each type identify whether it can start a pattern.

To identify the token type given the enum token representation above we can use a switch.

    func parse() throws -> Pattern
    {
        let pattern : Pattern
    
        switch peek()
        {
            case .IDENTIFIER:
    
                pattern = ????
    
            case .KEYWORD:
    
                pattern = ????
    
            case .PUNCTUATION:
    
                pattern = ????
    
            default:
    
                pattern = try expression()
        }
        return pattern
    }

That won't work as is because in the keyword or punctuation case we need to know what the actual keyword or punctuation is.

We can fix this by binding the associated values in those cases.

    func parse() throws -> Pattern
    {
        let pattern : Pattern
    
        switch peek()
        {
            case let .IDENTIFIER:
    
                pattern = ????
    
            case let .KEYWORD(keyword):
    
                pattern = ????
    
            case let .PUNCTUATION(punctuation):
    
                pattern = ????
    
            default:
    
                pattern = try expression()
        }
        return pattern
    }

Now we can see whether we have the 'right kind' of keyword or punctuation.

In case case we have another enum so we can use a switch.

    func parse() throws -> Pattern
    {
        let pattern : Pattern
    
        switch peek()
        {
            case .IDENTIFIER:
    
                pattern = ????
    
            case let .KEYWORD(keyword):
    
                switch keyword
                {
                    case .IS:
    
                        pattern = try isPattern()
    
                    case .LET:
    
                        pattern = try valueBindingPattern()
                        
                    case .UNDERSCORE:
                    
                        pattern = try wilcardPattern()
    
                    case .VAR:
    
                        pattern = try valueBindingPattern()
    
                    default:
    
                        pattern = try expression()
                }
    
            case let .PUNCTUATION(punctuation):
    
                switch punctuation
                {
                    case .DOT:
    
                        pattern = try enumCasePattern()
    
                    case .LEFT_PAREN:
    
                        pattern = try tuplePattern()
    
                    default:
    
                        pattern = try expression()
                }
    
            default:
    
                pattern = try expression()
        }
        return pattern
    }

Now we've got a bit of a mess really. Lets just hope they don't add any more pattern types. Maybe nested enums are not such a great idea after all.

In fact nested enums are a perfectly fine idea. The reason its a bit of a mess is the nested switches, but nested enums do not require nested switches. We, in this case I, have simply been doing it wrong.

A Closer Look At Patterns

Of the eight patterns in the grammar shown above, the switch in the last version of the method uses two

The case

    case .IDENTIFIER:

uses the simplest form of an enum-case-pattern

    . enum-case-name

The other two cases

    case let .KEYWORD(keyword):

and

    case let .PUNCTUATION(punctuation):

use the let form of a value-binding-pattern.

A value-binding-pattern is simply a pattern prefixed by either of the keywords let or var.

The prefixed pattern in each case is another example of an enum-case-pattern.

This time the optional tuple-pattern suffix element is present.

A tuple-pattern is a tuple-pattern-element-list delimited by parentheses.

This is the grammar for a tuple-pattern-element-list

    tuple-pattern-element-listtuple-pattern-element | tuple-pattern-element , tuple-pattern-element-list
    tuple-pattern-elementpattern

A tuple-pattern is a parentheses delimited list of comma separated patterns.

In the two cases above there is only one pattern in the list and that is an identifier-pattern.

According to the grammar there can be any number of patterns and the patterns are not limited to identifier-patterns.

At this point it is worth mentioning that the grammar as given in the Swift language reference is not always entirely accurate.

In the case of a value-binding-pattern it would imply that you could write this

    case let let .KEYWORD(keyword):

which you cannot.

Nor can you write this

    case let .KEYWORD(let keyword):

although you can write

    case .KEYWORD(let keyword):

In short, value-binding-patterns are not recursive whatever the grammar might say.

On the other hand, enum-case-patterns are recursive which means that you can write this

    case .KEYWORD(.IS):

which means that the method above can be rewritten more succinctly like so

    func parse() throws -> Pattern
    {
        let pattern : Pattern
    
        switch peek()
        {
            case .IDENTIFIER:
    
                pattern = try identifierOrEnum()
    
            case .KEYWORD(.IS):
    
                pattern = try isPattern()
    
            case .KEYWORD(.LET):
    
                pattern = try valueBindingPattern(.LET)
    
            case .KEYWORD(.UNDERSCORE):
    
                pattern = try wildcardPattern()
    
            case .KEYWORD(.VAR):
    
                pattern = try valueBindingPattern(.VAR)
    
            case .PUNCTUATION(.DOT):
    
                pattern = try enumCasePattern()
    
            case .PUNCTUATION(.LEFT_PAREN):
    
                pattern = try tuplePattern()
    
            default: // expression
    
                pattern = try expression()
        }
        if peek(.AS)
        {
            return try asPattern(pattern)
        }
        else
        {
            return pattern
        }
    }

There is no need for nested switches because we can use nested patterns instead.


Copyright (c) 2015 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog's author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Advertisements

September 9, 2015

Swift Miscellany: Keywords, Reserved And Escaped

There are a fair few keywords in Swift 2.0, I think we’re up to around about seventy-five at the moment, but it wasn’t until I started rummaging around in the grammar that I discovered that some of them are context senstive.

The context sensitive keywords are those which are used in only one specific context and most of the declaration modifiers

Infix Operator Declaration

  • associativity

  • left

  • none

  • precedence

  • right

are reserved keywords in the context of an infix-operator-declaration.

    infix-operator-declaration infix operator operator { infix-operator-attributesopt }
    infix-operator-attributes precedence-clauseopt associativity-clauseopt
    precedence-clause precedence precedence-level
    associativity-clause associativity associativity
    associativity left | right | none

Computed Properties/Variables

  • get

and

  • set

are reserved keywords in the context of a computed property/variable declaration.

    variable-declarationvariable-declaration-head variable-name type-annotation getter-setter-block
    getter-setter-block{ getter-clause setter-clauseopt }
    getter-setter-block{ setter-clause getter-clause }
    getter-clauseattributesopt get code-block
    setter-clauseattributesopt set setter-nameopt code-block

Property/Variable Observers

  • didSet

and

  • willSet

are keywords in the context of a stored variable observer declaration or a property observer declaration

    variable-declarationvariable-declaration-head variable-name initializer willSet-didSet-block
    variable-declarationvariable-declaration-head variable-name type-annotation initializeropt willSet-didSet-block
    willSet-didSet-block{ willSet-clause didSet-clauseopt }
    willSet-didSet-block{ didSet-clause willSet-clauseopt }
    willSet-clauseattributesopt willSet setter-nameopt code-block
    didSet-clauseattributesopt didSet setter-nameopt code-block

Metatype Type

  • Protocol

and

  • Type

are keywords in the context of a metatype-type

    metatype-typetype  . Type | type  . Protocol 

Declaration Modifiers

  • convenience

  • dynamic

  • final

  • infix

  • indirect

  • lazy

  • mutating

  • nonmutating

  • optional

  • override

  • postfix

  • prefix

  • required

  • unowned

  • weak

are reserved keywords when they appear before a declaration.

When not being used in their specific contexts reserved keywords can moonlight as identifiers which means that you can, if you want to, do this

    ...
    
    private var final    = true
    private var left     = true
    private var optional = true
    private var override = false
    private var required = true
    private var right    = false
    private var set      = true
    private var unowned  = true
    private var weak     = false
    
    ...

or this

    enum associativity
    {
        case left
        case right
        case none
    }

or even this

    func infix()
    {
    
    }

Escaped Keywords

If your favourite identifier is a non-reserved Swift keyword there is still hope.

Any Swift keyword can be used as an identifier if it is escaped, so if you have always wanted a class called class

    class `class`
    {
    
    }

or a global variable called self

    var `self` = `class`()

you can have one.

The escape character is the grave accent/backtick/backquote character (`) ASCII 96/0x60.


Copyright (c) 2015 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Swift Miscellany: More Parsing With Generics And Protocols

Having stumbled upon my new, at least to me, idiom I was curious whether it could be used as the basis for a complete parser.

The answer seems to be yes.

With a handful of protocols and generic methods it is possible to write a basic parser for Swift which is at least capable of parsing itself.

Protocols

Parsable

    protocol Parsable
    {
        typealias ParserType: ElementParser

        static var parserType: ParserType.Type { get }
    }
    protocol ElementParser
    {
        typealias ElementType
    
        init(context:ParserContext, tokens:TokenStream)
    
        func parse() throws -> ElementType
    }

A type is parsable if it has an associated ElementParser type

Element

    protocol Element: Parsable
    {
    }

An Element is a non-terminal which appears exactly once in a grammar production.

A condition-clause is an example of an Element.

It appears exactly once, for example, in a guard-statement

    guard-statement -> guard condition-clause else code-block

or a while-statement

    while-statement -> while condition-clause code-block

OptionalElement

    protocol OptionalElement
    {
        typealias ParserType: OptionalElementParser
    
        static var parserType: ParserType.Type { get }
    }
    protocol OptionalElementParser: ElementParser
    {
        func elementIsPresent() -> Bool
    }

An OptionalElement is a non-terminal which may appear exactly once or not at all in a grammar production.

A generic-parameter-clause

    generic-parameter-clause< generic-parameter-list requirement-clauseopt >

and a type-inheritance-clause

    type-inheritance-clause: class-requirement , type-inheritance-list
    
    type-inheritance-clause: class-requirement
    
    type-inheritance-clause: type-inheritance-list

are both examples of an OptionalElement.

They may both appear, or not appear, in a class-declaration

    class-declarationattributesopt access-level-modifieropt class class-name generic-parameter-clauseopt type-inheritance-clauseopt class-body

an enum-declaration

    enum-declarationattributesopt access-level-modifieropt union-style-enum
    enum-declarationattributesopt access-level-modifieropt raw-value-style-enum
    union-style-enumindirectopt enum enum-name generic-parameter-clauseopt type-inheritance-clauseopt { union-style-enum-membersopt }
    raw-value-style-enumenum enum-name generic-parameter-clauseopt type-inheritance-clauseopt { raw-value-style-enum-members }

or a struct-declaration

    struct-declarationattributesopt access-level-modifieropt struct class-name generic-parameter-clauseopt type-inheritance-clauseopt struct-body

A generic-parameter-clause may also appear, or not appear, in a function-declaration

    function-declarationfunction-head function-name generic-parameter-clauseopt function-signature function-bodyopt 

or an initializer declaration

    initializer-declarationinitializer-head generic-parameter-clauseopt parameter-clause throwsopt initializer-body 
    initializer-declarationinitializer-head generic-parameter-clauseopt parameter-clause rethrows initializer-body 

A type-inheritance-clause may also appear in an extension-declaration

    extension-declarationaccess-level-modifieropt extension type-identifier type-inheritance-clauseopt extension-body 

A type-annotation is a third example of an OptionalElement

    type-annotation: attributesopt type 

A type-annotation is optional in a constant declaration or a variable declaration, but it is required in a parameter, so the type TypeAnnotation is declared as both an Element and an OptionalElement.

    extension TypeAnnotation: Element, OptionalElement
    {
        static let parserType = TypeAnnotationParser.self
    }

RepeatableElement

    protocol RepeatableElement: Parsable
    {
    }

A RepeatableElement is a non-terminal which can appear one or more times in a grammar production. If it appears more than once then there is a separator between each occurence.

A condition is an example of a RepeatableElement.

    conditionavailability-condition | case-condition | optional-binding-condition 

It can appear one or more times in a condition-list.

    condition-listcondition | condition , condition-list

If there are multiple occurences then they are separated by commas

BlockElement

    protocol BlockElement: Parsable
    {
    }

A BlockElement is a non-terminal that can appear zero or more times in a grammer production, delimited by left and right braces and with no separators.

A statement is a BlockElement.

Zero or more statements can appear in a code-block

    statementsstatement statementsopt
    code-block{ statementsopt }

A declaration is also a BlockElement.

Zero or more declarations can appear in the body of a class or struct declaration

    declarationsdeclaration declarationsopt
    class-body{ declarationsopt }
    struct-body{ declarationsopt }

Both declarations and statements are also Elements since they can appear outside blocks.

Statements, for example, can appear in a switch-case

Declarations can appear anywhere in an enum-declaration.

The Declaration and Statement types are therefore declared to implement both the BlockElement and the Element protocols.

    extension Declaration: BlockElement, Element
    {
        static let parserType = DeclarationParser.self
    }
    extension Statement: BlockElement, Element
    {
        static let parserType = StatementParser.self
    }

Generic Methods

The following methods are defined on the class Parser which is the base class for the various element parsers.

element<E:Element … >

    final func element<E:Element where E.ParserType.ElementType == E>(eType:E.Type) throws -> E
    {
        let parser = eType.parserType.init(context:context, tokens: tokens)
    
        return try parser.parse()
    }

The element method takes a type which implements the Element protocol and returns an instance of that type.

optional<O:OptionalElement … >

    final func optional<O:OptionalElement where O.ParserType.ElementType == O>(oType:O.Type) throws -> O?
    {
        let parser = oType.parserType.init(context:context, tokens: tokens)
    
        if parser.elementIsPresent()
        {
            return try parser.parse()
        }
        else
        {
            return nil
        }
    }

The optional method takes a type which implements the OptionalElement protocol and returns an instance of that type or nil if the element is not present.

elements<R:RepeatableElement …>

     final func elements
        <
            R:RepeatableElement where R.ParserType.ElementType == R
        >
        (rType:R.Type, _ separator:Punctuation = .COMMA) throws -> [R]
        {
            let parser   = rType.parserType.init(context:context, tokens: tokens)
            var elements = [try parser.parse()]
        
            while peek(separator)
            {
                assertNext(separator)
                elements.append(try parser.parse())
            }
            return elements
        }

The elements method takes a type which implements the RepeatableElement protocol and returns an array of one or more instances of that type.

The method takes a second argument which specifies the separator.

By default the separator is defined to be a comma, but specifying a dot instead means it is also possible to use the method to parse, for example, an import-path

    import-pathimport-path-identifier | import-path-identifier . import-path 

or a type-identifier

    type-identifiertype-name generic-argument-clauseopt| type-name generic-argument-clauseopt. type-identifier 

elements<O:OptionalElement …>

    final func elements<O:OptionalElement where O.ParserType.ElementType == O>(oType:O.Type) throws -> [O]?
    {
        let parser = oType.parserType.init(context:context, tokens: tokens)
    
        if parser.elementIsPresent()
        {
            var elements = [O]()
    
            while parser.elementIsPresent()
            {
                elements.append(try parser.parse())
            }
            return elements
        }
        else
        {
            return nil
        }
    }

The elements method takes a type which implements the OptionalElement protocol and returns an array of one or more instances of that type or nil.

block<B:BlockElement …>

    final func block<B:BlockElement where B.ParserType.ElementType == B>(bType:B.Type) throws -> [B]
    {
        try next(.LEFT_BRACE)
    
        let parser   = bType.parserType.init(context:context, tokens: tokens)
        var elements = [B]()
    
        while !peek(.RIGHT_BRACE)
        {
            elements.append(try parser.parse())
        }
        assertNext(.RIGHT_BRACE)
        return elements
    }

The block method takes a type which implements the BlockElement protocol and returns an array of zero or more instances of that type or nil.

Examples

Class Declaration

    final class ClassDeclarationParser: Parser
    {
        func parse() throws -> Class
        {
            assertNext(.CLASS)
    
            let name  = try identifier()
            let gpc   = try optional(GenericParameterClause.self)
            let tic   = try optional(TypeInheritanceClause.self)
    
            pushContext(DeclarationContext())
    
            let decls = try block(Declaration.self)
    
            popContext()
    
            if gpc == nil
            {
                return .CLASS(name, tic, decls)
            }
            else
            {
                return .GENERIC_CLASS(name, gpc!, tic, decls)
            }
        }
    }

Extension Declaration

    final class ExtensionDeclarationParser: Parser
    {
        func parse() throws -> Extension
        {
            assertNext(.EXTENSION)
    
            let typeIdentifier  = try element(TypeIdentifier.self)
            let typeInheritance = try optional(TypeInheritanceClause.self)
            let body            = try block(Declaration.self)
    
            return
                Extension(
                    typeId:
                        typeIdentifier,
                    typeInheritance:
                        typeInheritance,
                    declarations:
                        body)
        }
    }

This method would be even simpler if Swift guarantees that arguments are evaluated left-to-right but I’ve not been able to find anything to that effect as yet.

Guard Statement

    final class GuardStatementParser: Parser
    {
        func parse() throws -> Guard
        {
            assertNext(.GUARD)
    
            let condition = try element(ConditionClause.self)
    
            try next(.ELSE)
    
            let codeBlock = try block(Statement.self)
    
            return Guard(condition: condition, codeBlock:codeBlock)
        }
    }

While Statement

    final class WhileStatementParser: Parser
    {
        func parse() throws -> While
        {
            assertNext(.WHILE)
    
            let condition = try element(ConditionClause.self)
            let codeBlock = try block(Statement.self)
    
            return While(condition:condition, codeBlock:codeBlock)
        }
    }

Type Annotation

    final class TypeAnnotationParser: Parser, OptionalElementParser
    {
        func elementIsPresent() -> Bool
        {
            return peek(.COLON)
        }
    
        func parse() throws -> TypeAnnotation
        {
            assertNext(.COLON)
    
            let attributes = try elements(Attribute.self)
            let type       = try element(Type.self)
    
            if attributes == nil
            {
                return .TYPE(type)
            }
            else
            {
                return .ATTRIBUTED_TYPE(attributes!, type)
            }
        }
    }

Copyright (c) 2015 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Create a free website or blog at WordPress.com.

%d bloggers like this: