mairo
mairo

Reputation: 155

How to use Result type to store success/failure states in Swift

Within chapter on error handling / asynchronous programming there is an example on expression evaluator and code on storing Failable Results for Later - code below, lines after implementation of function evaluate. This part does throw more errors, would like to ask if you can help to clarify these. As I was adviced I changed lex() method return type to Result<[Token], Lexer.Error>. And return success or failure. .success(tokens) or .failure(.invalidCharacter(nextCharacter)), however later did not work. Also tried to comment out all lines related to Parser. If you can advice.

Within last lines I include code revised as per input from all of you, thanks for help. Changes commented.

   enum Token: CustomStringConvertible {
    case number (Int)
    case plus
    
    var description: String {
        switch self {
        case .number (let n):
            return "Number: \(n)"
        case .plus:
            return "Symbol: +"
        }
    }
}

class Lexer {
    enum Error: Swift.Error {
        case invalidCharacter (Character)
    }
    
    let input: String
    var position: String.Index
    
    init(input: String) {
        self.input = input
        self.position = 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 getNumber() -> Int {
        var value = 0
        
        while let nextCharacter = peek() {
            switch nextCharacter {
            case "0" ... "9":
                let digitValue = Int(String(nextCharacter))!
                value = 10*value + digitValue
                advance()
            default:
                return value
            }
        }
        return value
    }
    
    func lex() throws -> [Token] { // changed to [Token]
        var tokens = [Token]()
        
        while let nextCharacter = peek() {
            switch nextCharacter {
            case "0" ... "9":
                let value = getNumber()
                tokens.append(.number(value))
            case "+":
                tokens.append(.plus)
                advance()
            case " ":
                advance()
            default:
                throw Lexer.Error.invalidCharacter(nextCharacter)
                // .failure(.invalidCharacter(nextCharacter)) did not work here
            }
        }
        return tokens // changed to tokens
    }
}

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) {
        print("Input contained an invalid character: \(character)")
    // } catch Parser.Error.unexpectedEndOfInput {
        // print("Unexpected end of input during parsing")
    // } catch Parser.Error.invalidToken(let token) {
       //  print("Invalid token during parsing: \(token)")
    } catch {
        print("An error occured: \(error)")
    }
}
/*
class Parser {
    enum Error: Swift.Error {
        case unexpectedEndOfInput
        case invalidToken(Token)
    }
   
    let tokens: [Token] // [Token]
    var position = 0
    
    init(tokens: [Token]) {
        self.tokens = tokens // tokens
    }
    
    func getNextToken() -> Token? {
        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 {
        case .number(let value):
            return value
        case .plus:
            throw Parser.Error.invalidToken(token)
        }
    }
    
    func parse() throws -> Int {
        var value = try getNumber()
        
        while let token = getNextToken() {
            switch token {
            case .plus:
                let nextNumber = try getNumber()
                value += nextNumber
            case .number:
                throw Parser.Error.invalidToken(token)
            }
        }
        return value
    }
}
*/

// evaluate("10 + 1")

/*
let lexer = Lexer(input: "1 + 2")
let tokensResult = Result { try lexer.lex() }
print(tokensResult)
*/

/*
let lexer = Lexer(input: "1 + 2 + 3")
let tokensResult = Result { try lexer.lex() } // Result<Success, Failure>' cannot be constructed because it has no accessible initializers

enum Result<Success, Failure> where Failure : Error {
    case .success(Success) // Extraneous '.' in enum 'case' declaration
    case .failure(Failure) // Extraneous '.' in enum 'case' declaration
}

switch tokensResult {
case let .success(tokens):
    print("Found \(tokens.count) tokens \(tokens)")
case let .failure(error):
    print("Could not lex \(lexer.input): \(error)")
}

let numbersResult: Result<[Int],Error> = tokensResult.map {
      tokens.compactMap { token in // Cannot find 'tokens' in scope
          switch token {
          case let .number(digit): return digit
          default: return nil
          }
      }
}

func extractNumbers(from result: Result<[Int],Error>) throws -> [Int] {
        return try result.get() // Value of type 'Result<[Int], Error>' has no member 'get'
    }
*/

let lexer = Lexer(input: "1 + 7")
let tokensResult = Result { try lexer.lex() }
var tokens = [Token]() // added

// deleted redeclaration of Result

switch tokensResult {
case let .success(tokens):
    print("Found \(tokens.count) tokens \(tokens)")
case let .failure(error):
    print("Could not lex \(lexer.input): \(error)")
}

// before error: Contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored, fixed by: insert _ in

let numbersResult: Result<[Int],Error> = tokensResult.map { _ in
      tokens.compactMap { token in
          switch token {
          case let .number(digit): return digit
          default: return nil
          }
      }
}

func extractNumbers(from result: Result<[Int],Error>) throws -> [Int] {
        return try result.get()
    }

Upvotes: 0

Views: 1926

Answers (1)

Joakim Danielson
Joakim Danielson

Reputation: 51973

I think you need to do a lot more reading up on the Result type and how to use it because you have some very strange code above.

Here is my cleaned up version of your code, without a redefinition of Result and a proper way to handle .success

let lexer = Lexer(input: "1 + 2 + 3")
let tokensResult = try lexer.lex()

var tokens =  [Token]()
switch tokensResult {
case let .success(result):
    print("Found \(tokens.count) tokens \(tokens)")
    tokens = result
case let .failure(error):
    print("Could not lex \(lexer.input): \(error)")
}

let numbersResult = tokens.compactMap { token in
    switch token {
    case let .number(digit): return digit
    default: return nil
    }
}

Upvotes: 1

Related Questions