Result를 이용한 구조화된 오류처리

다음은 표현식 언어의 매우 간단한 파서를 구현합니다. 그러나 패닉을 통해 오류를 처리합니다. 대신 관용적인 오류 처리를 사용하고 오류를 main의 반환으로 전파하도록 다시 작성합니다. thiserroranyhow를 얼마든지 사용하세요.

힌트: 먼저 parse 함수에서 오류 처리를 수정하세요. 제대로 작동하면 Iterator<Item=Result<Token, TokenizerError>>를 구현하도록 Tokenizer를 업데이트하고 파서에서 처리합니다.

use std::iter::Peekable;
use std::str::Chars;

/// 산술 연산자입니다.
#[derive(Debug, PartialEq, Clone, Copy)]
enum Op {
    Add,
    Sub,
}

/// 표현식 언어의 토큰입니다.
#[derive(Debug, PartialEq)]
enum Token {
    Number(String),
    Identifier(String),
    Operator(Op),
}

/// 표현식 언어의 표현식입니다.
#[derive(Debug, PartialEq)]
enum Expression {
    /// 변수 참조입니다.
    Var(String),
    /// 리터럴 숫자입니다.
    Number(u32),
    /// 이진 연산입니다.
    Operation(Box<Expression>, Op, Box<Expression>),
}

fn tokenize(input: &str) -> Tokenizer {
    return Tokenizer(input.chars().peekable());
}

struct Tokenizer<'a>(Peekable<Chars<'a>>);

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Token;

    fn next(&mut self) -> Option<Token> {
        let c = self.0.next()?;
        match c {
            '0'..='9' => {
                let mut num = String::from(c);
                while let Some(c @ '0'..='9') = self.0.peek() {
                    num.push(*c);
                    self.0.next();
                }
                Some(Token::Number(num))
            }
            'a'..='z' => {
                let mut ident = String::from(c);
                while let Some(c @ ('a'..='z' | '_' | '0'..='9')) = self.0.peek() {
                    ident.push(*c);
                    self.0.next();
                }
                Some(Token::Identifier(ident))
            }
            '+' => Some(Token::Operator(Op::Add)),
            '-' => Some(Token::Operator(Op::Sub)),
            _ => panic!("예기치 않은 문자 {c}"),
        }
    }
}

fn parse(input: &str) -> Expression {
    let mut tokens = tokenize(input);

    fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Expression {
        let Some(tok) = tokens.next() else {
            panic!("예기치 않은 입력 종료");
        };
        let expr = match tok {
            Token::Number(num) => {
                let v = num.parse().expect("잘못된 32비트 정수입니다.'");
                Expression::Number(v)
            }
            Token::Identifier(ident) => Expression::Var(ident),
            Token::Operator(_) => panic!("예기치 않은 토큰 {tok:?}"),
        };
        // 이진 연산이 있는 경우 이를 파싱합니다.
        match tokens.next() {
            None => expr,
            Some(Token::Operator(op)) => Expression::Operation(
                Box::new(expr),
                op,
                Box::new(parse_expr(tokens)),
            ),
            Some(tok) => panic!("예기치 않은 토큰 {tok:?}"),
        }
    }

    parse_expr(&mut tokens)
}

fn main() {
    let expr = parse("10+foo+20-30");
    println!("{expr:?}");
}