mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-06-16 10:34:34 +00:00
88 lines
2.8 KiB
Swift
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
|
|
}
|