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
pattern → wildcard-pattern type-annotationopt
pattern → identifier-pattern type-annotationopt
pattern → value-binding-pattern
pattern → tuple-pattern type-annotationopt
pattern → enum-case-pattern
pattern → optional-pattern
pattern → type-casting-pattern
pattern → expression-pattern
and these are the grammars for the various types of pattern
wildcard-pattern → _
identifier-pattern → identifier
value-binding-pattern → var pattern | let pattern
tuple-pattern → ( tuple-pattern-element-listopt )
enum-case-pattern → type-identifieropt . enum-case-name tuple-patternopt
optional-pattern → identifier-pattern ?
type-casting-pattern → is-pattern | as-pattern
is-pattern → is
type
as-pattern → pattern as
type
expression-pattern → expression
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-list → tuple-pattern-element | tuple-pattern-element , tuple-pattern-element-list
tuple-pattern-element → pattern
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.