Silver Challenge


#1

I have not been able to figure out the second part of the silver challenge completely. The first part I could match up by simply changing the error thrown in the Lexer (although it says I should expect the error at index 9 and that is what I get… the character “a” is actually the 10th character?) . The second part I can get the value easily enough to return BUT… my efforts to pass the location here return one more than the challenge says I should get (returns index 8 instead of 7). Funny thing is… the character throwing things off actually is the 8th character. Anyway, I’m a bit stuck on this one and not really sure how or where to go from here. Any suggestions or ideas where I’m going wrong on this?

[code]import Cocoa

enum Token {
case Number(Int, String.CharacterView.Index)
case Plus(String.CharacterView.Index)
case Minus(String.CharacterView.Index)
}

class Lexer {
enum Error: ErrorType {
case InvalidCharacter(Character, loc: String.CharacterView.Index)
}

let input: String.CharacterView
var position: String.CharacterView.Index

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 the end!")
    ++position
}

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 non-digit - go back to regular lexing
            return value
        }
    }
    return value
}

func lex() throws -> [Token] {
    var tokens = [Token]()
    
    while let nextCharacter = peek() {
        switch nextCharacter {
        case "0" ... "9":
            let value = getNumber()
            tokens.append(.Number(value, position))
        case "+":
            tokens.append(.Plus(position))
            advance()
        case "-":
            tokens.append(.Minus(position))
            advance()
        case " ":
            // Just advance to ignore spaces
            advance()
        default:
            throw Error.InvalidCharacter(nextCharacter, loc: position)
        }
    }
    return tokens
}

}

class Parser {
enum Error: ErrorType {
case UnexpectedEndOfInput
case InvalidToken(String, loc: String.CharacterView.Index)
}

let tokens: [Token]
var position = 0

init(tokens: [Token]) {
    self.tokens = tokens
}

func getNextToken() -> Token? {
    guard position < tokens.count else {
        return nil
    }
    return tokens[position++]
}

func getNumber() throws -> Int {
    guard let token = getNextToken() else {
        throw Error.UnexpectedEndOfInput
    }
    
    switch token {
    case .Number(let value, _):
        return value
    case .Plus(let location):
        throw Error.InvalidToken("+", loc: location)
    case .Minus(let location):
        throw Error.InvalidToken("-", loc: location)
    }
}

func parse() throws -> Int {
    //Require a number first
    var value = try getNumber()
    
    while let token = getNextToken() {
        switch 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
            
        case .Minus:
            let nextNumber = try getNumber()
            value -= nextNumber
            
        // Getting a Number after a Number is not legal
        case .Number(let value, let location):
            throw Error.InvalidToken(String(value), loc: location)
        }
    }
    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 loc) {
    print("Input contained an invalid character at index \(loc): \(character)")
} catch Parser.Error.UnexpectedEndOfInput {
    print("Unexpected end of input during parsing")
} catch Parser.Error.InvalidToken(let character, let loc) {
    print("Invalid token during parsing at index \(loc): \(character)")
} catch {
    print("An error occurred: \(error)")
}

}

evaluate(“10 + 3 + 5”)
evaluate(“10 + 5 - 3 - 1”)
evaluate(“1 + 3 + 7a + 8”) // This one matches the what the challenge says I should get
evaluate(“10 + 3 3 + 7”) // This one doesn’t match the challenge, off by 1.
[/code]


#2

OK, that was just me being stupid forgetting that indexes are zero based and counting the characters like a normal person from 1. So that part I answered myself, but still not sure what to do about the position. I mean, I suppose I could convert it to an Int and then subtract 1 to get the correct value returned, but that seems a bit odd (like correcting a mistake after it happens rather than preventing the mistake to begin with). Any ideas?


#3

Or not. Looks like the complier doesn’t like changing a String.CharacterView.Index to an Int.


#4

Well… I could do this and I get the correct result:

} catch Parser.Error.InvalidToken(let character, var loc) { print("Invalid token during parsing at index \(--loc): \(character)")

Still doesn’t seem like this is the best way though.


#5

Still improving on the correction after the fact:

} catch Parser.Error.InvalidToken(let character, var loc) { let length = character.characters.count for _ in 1 ... length { --loc } print("Invalid token during parsing at index \(loc): \(character)")

At least this returns the starting index number if the number in question is more than a single digit so if I change the error value from “3” to “33” it still returns index 7 instead of 8 (as it would have in my previous fix after the fact).

Still seems like there must be some way to get it to return the correct position to begin with though.


#6

Hi MikeYenko, please have a look at my solution: