My solution consists to keep track of the position of each token in the input string. To do so I added a struct containing the Token type and an Int. Then the Lexer and Parser classes work with an Array made of this new TokenStruct struct. The error thrown by the Parser contains both the faulty token and its position in the input string.
import Cocoa
enum Token {
case number(Int)
case plus
case minus
}
struct TokenStruct {
var token: Token
var posit: Int
}
class Lexer {
enum Error: Swift.Error {
case invalidCharacter(Character, pos:Int)
}
let input: String.CharacterView
var position: String.CharacterView.Index
var indexes = [Int]()
init(input: String) {
self.input = input.characters
self.position = self.input.startIndex
}
func peek() -> Character? {
guard position < input.endIndex else {
return nil
}
return input[position]
}
func advance() {
assert(position < input.endIndex, "Cannot advance past endIndex!")
position = input.index(after: position)
}
func lex() throws -> [TokenStruct] {
var tokens = [TokenStruct]()
while let nextCharacter = peek() {
switch nextCharacter {
case "0"..."9":
// Start of a number - need to grab the rest
let posChar = input.distance(from: input.startIndex, to: position)
let value = getNumber()
tokens.append(TokenStruct(token: .number(value), posit: posChar))
case "+":
let posChar = input.distance(from: input.startIndex, to: position)
tokens.append(TokenStruct(token: .plus, posit: posChar))
advance()
case "-":
let posChar = input.distance(from: input.startIndex, to: position)
tokens.append(TokenStruct(token: .minus, posit: posChar))
advance()
case " ":
// Just advance to ignore spaces advance()
advance()
default:
// Something unexpected - need to send back an error
let distanceToPosition = input.distance(from: input.startIndex, to: position)
throw Lexer.Error.invalidCharacter(nextCharacter, pos: distanceToPosition)
}
}
return tokens
}
func getNumber() -> Int {
var value = 0
while let nextCharacter = peek() {
switch nextCharacter {
case "0" ... "9": // Another digit - add it into value
let digitValue = Int(String(nextCharacter))!
value = 10*value + digitValue
advance()
default: // A nondigit - go back to regular lexing
return value
}
}
return value
}
}
class Parser {
enum Error: Swift.Error {
case unexpectedEndOfInput
case invalidToken(TokenStruct)
}
let tokens: [TokenStruct]
var position = 0
init(tokens: [TokenStruct]) {
self.tokens = tokens
}
func getNextToken() -> TokenStruct? {
guard position < tokens.count else {
return nil
}
let token = tokens[position]
position += 1
return token
}
func getNumber() throws -> Int {
guard let token = getNextToken() else {
throw Parser.Error.unexpectedEndOfInput
}
switch token.token {
case .number(let value):
return value
case .plus, .minus:
throw Parser.Error.invalidToken(token)
}
}
func parse() throws -> Int { // Require a number first
var value = try getNumber()
while let token = getNextToken() {
switch token.token { // Getting a plus after a number is legal
case .plus: // After a plus, we must get another number
let nextNumber = try getNumber()
value += nextNumber // Getting a number after a number is not legal
case .minus:
let nextNumber = try getNumber()
value -= nextNumber
case .number:
throw Parser.Error.invalidToken(token)
}
}
return value
}
}
func evaluate(_ input: String) {
print(" Evaluating: \(input)")
let lexer = Lexer(input: input)
do {
let tokens = try lexer.lex()
// print(" Lexer output: \(tokens)")
let parser = Parser(tokens: tokens)
let result = try parser.parse()
print(" Parser output: \( result)")
} catch Lexer.Error.invalidCharacter(let character, let posErr) {
print(" Input contained an invalid character at index \(posErr) : \(character)")
} catch Parser.Error.unexpectedEndOfInput {
print(" Unexpected end of input during parsing")
} catch Parser.Error.invalidToken(let token) {
print(" Invalid token \(token.token) during parsing at index: \(token.posit)")
} catch {
print(" An error occurred: \(error)")
}
}
evaluate("10 + 3 3 + 7")