Files

88 lines
2.8 KiB
Swift

//
// Lexer.swift
// Parsing
//
// Created by Nick Lockwood on 03/09/2018.
// Copyright © 2018 Nick Lockwood. All rights reserved.
//
import Foundation
// MARK: interface
public enum Token: Equatable {
case assign // = operator
case plus // + operator
case identifier(String) // letter followed by one or more alphanumeric chars
case number(Double) // any valid floating point number
case string(String) // a string literal surrounded by ""
case `let` // let keyword
case print // print keyword
}
public enum LexerError: Error, Equatable {
case unrecognizedInput(String)
}
public func tokenize(_ input: String) throws -> [Token] {
let whitespace = try NSRegularExpression(pattern: "(\\s|\\n)+")
let assign = try NSRegularExpression(pattern: "=")
let plus = try NSRegularExpression(pattern: "\\+")
let identifier = try NSRegularExpression(pattern: "[a-z][a-z0-9]*", options: .caseInsensitive)
let number = try NSRegularExpression(pattern: "[0-9.]+")
let string = try NSRegularExpression(pattern: "\"(\\\\\"|\\\\\\\\|[^\"\\\\])*\"") // !!!
// this part is nasty because NSRange indices don't map directly to String indices
var range = NSRange(location: 0, length: input.utf16.count)
func readToken(_ regex: NSRegularExpression) -> String? {
guard let match = regex.firstMatch(in: input, options: .anchored, range: range) else {
return nil
}
range.location += match.range.length
range.length -= match.range.length
return (input as NSString).substring(with: match.range)
}
func readToken() -> Token? {
_ = readToken(whitespace) // skip whitespace
if readToken(assign) != nil {
return .assign
}
if readToken(plus) != nil {
return .plus
}
if let name = readToken(identifier) {
switch name {
case "let":
return .let
case "print":
return .print
default:
return .identifier(name)
}
}
let start = range
if let digits = readToken(number), let double = Double(digits) {
return .number(double)
} else {
range = start
}
if let string = readToken(string) {
let unescapedString = String(string.dropFirst().dropLast())
.replacingOccurrences(of: "\\\"", with: "\"")
.replacingOccurrences(of: "\\\\", with: "\\")
return .string(unescapedString)
}
return nil
}
var tokens: [Token] = []
while let token = readToken() {
tokens.append(token)
}
if range.length != 0 {
throw LexerError.unrecognizedInput((input as NSString).substring(with: range))
}
return tokens
}