Tokenizer FSM in Rust - modo migliore per fare le dichiarazioni di corrispondenza?

Aug 24 2020

Sto imparando Rust e ho deciso di codificare un parser YANG giocattolo come esercizio. Nel caso sia rilevante, lavoro principalmente in C e Python.

Mentre lavoravo sul tokenizer/lexer, ho provato a renderlo Rust-y, ma sono finito con alcune dichiarazioni di corrispondenza dall'aspetto piuttosto goffo. Sono della forma:

match fn_which_returns_result_my_type() {
    Err(err_str) => Err(err_str),
    Ok(MyType0(MyArg0)) => Ok(MyType0(MyArg0)), // This seems especially clunky :(
    Ok(MyType1) => Ok(MyType1),
    Ok(MyType2) => Ok(MyType2),
    Ok(_) => unreachable!()                     // Catch all other instances of the MyType Enum
}

Ho evidenziato la sezione per la quale sono interessato al feedback. Tuttavia, qualsiasi suggerimento generale in cui mi sembra di seguire la strada sbagliata (considerando che si tratta di un work-in-progress) è il benvenuto.

// @@@REMOVE
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_imports)]

use std::convert::TryFrom;
use std::default::Default;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;

/// This module takes a YANG file and tokenizes it (i.e. splits it into a series of tokens suitable 
/// for the yang_parser module)

static YANG_KEYWORDS: [&str; 86] = [
    ...truncated for clarity...
];

#[derive()]
struct Tokenizer {
    file: BufReader<File>,
}

impl Tokenizer {
    fn new(input: BufReader<File>) -> Self {
        Self { file: input }
    }
}

enum ExpectedToken {
    Keyword,
    ArgumentOrSemicolonOrOpenBrace,
    SemicolonOrOpenBrace,
    KeywordOrCloseBrace,
}

enum ActualToken {
    Keyword(String),
    Argument(String),
    Semicolon,
    OpenBrace,
    CloseBrace,
    EOF,
}

#[derive()]
pub struct YangTokens {
    tokens: Vec<ActualToken>,
}

impl YangTokens {
    fn get_keyword(&self, cursor: &mut Tokenizer) -> Result<ActualToken, &'static str> {
        todo!()
    }

    fn get_argument_or_semicolon_or_open_brace(
        &self,
        cursor: &mut Tokenizer,
    ) -> Result<ActualToken, &'static str> {
        todo!()
    }

    fn get_semicolon_or_close_brace(
        &self,
        cursor: &mut Tokenizer,
    ) -> Result<ActualToken, &'static str> {
        todo!()
    }

    fn get_keyword_or_close_brace(
        &self,
        cursor: &mut Tokenizer,
    ) -> Result<ActualToken, &'static str> {
        todo!()
    }

    // ==============================================================================
    // THIS IS WHERE I WANT SPECIFIC FEEDBACK
    fn get_next_token(
        &self,
        cursor: &mut Tokenizer,
        next_exp_token: &mut ExpectedToken,
    ) -> Result<ActualToken, &'static str> {
        match next_exp_token {
            ExpectedToken::Keyword => {
                *next_exp_token = ExpectedToken::ArgumentOrSemicolonOrOpenBrace;
                match self.get_keyword(cursor) {
                    Err(err_str) => Err(err_str),
                    Ok(ActualToken::Keyword(kw)) => Ok(ActualToken::Keyword(kw)),
                    Ok(ActualToken::EOF) => Ok(ActualToken::EOF),
                    Ok(_) => unreachable!(),
                }
            }
            ExpectedToken::ArgumentOrSemicolonOrOpenBrace => {
                match self.get_argument_or_semicolon_or_open_brace(cursor) {
                    Err(err_str) => Err(err_str),
                    Ok(ActualToken::Argument(arg)) => {
                        *next_exp_token = ExpectedToken::SemicolonOrOpenBrace;
                        Ok(ActualToken::Argument(arg))
                    },
                    Ok(ActualToken::Semicolon) => {
                        *next_exp_token = ExpectedToken::KeywordOrCloseBrace;
                        Ok(ActualToken::Semicolon)
                    },
                    Ok(ActualToken::OpenBrace) => {
                        *next_exp_token = ExpectedToken::KeywordOrCloseBrace;
                        Ok(ActualToken::OpenBrace)
                    },
                    Ok(_) => unreachable!(),
                }
            }
            ExpectedToken::SemicolonOrOpenBrace => {
                *next_exp_token = ExpectedToken::KeywordOrCloseBrace;
                match self.get_semicolon_or_close_brace(cursor) {
                    Err(err_str) => Err(err_str),
                    Ok(ActualToken::Semicolon) => Ok(ActualToken::Semicolon),
                    Ok(ActualToken::CloseBrace) => Ok(ActualToken::CloseBrace),
                    Ok(_) => unreachable!(),

                }
            }
            ExpectedToken::KeywordOrCloseBrace => {
                *next_exp_token = ExpectedToken::ArgumentOrSemicolonOrOpenBrace;
                match self.get_keyword_or_close_brace(cursor) {
                    Err(err_str) => Err(err_str),
                    Ok(ActualToken::Keyword(kw)) => Ok(ActualToken::Keyword(kw)),
                    Ok(ActualToken::EOF) => Ok(ActualToken::EOF),
                    Ok(ActualToken::CloseBrace) => Ok(ActualToken::CloseBrace),
                    Ok(_) => unreachable!(),
                }
            }
        }
    }
    // END OF WHERE I WANT SPECIFIC FEEDBACK
    // ==============================================================================
}

impl TryFrom<&mut Tokenizer> for YangTokens {
    type Error = &'static str;

    fn try_from(cursor: &mut Tokenizer) -> Result<Self, Self::Error> {
        let mut next_exp_token = ExpectedToken::Keyword;

        todo!()
    }
}

pub fn tokenize_yang_file(filename: &Path) -> Result<YangTokens, &str> {
    let file = File::open(&filename).unwrap(); //@@@ THIS SHOULDN'T PANIC
    let mut file = BufReader::new(file);
    let mut cursor = Tokenizer::new(file);

    YangTokens::try_from(&mut cursor)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn new() {
        todo!()
    }
}

Risposte

Kyle_S-C Aug 26 2020 at 16:35

Per la funzione in questione, la cosa fondamentale era ricordare i binding @ dalla sezione 18.3 del libro di Rust.

Consentono di associare il valore corrispondente a una variabile da utilizzare nel braccio di corrispondenza.

    fn get_next_token(
        &self,
        cursor: &mut Tokenizer,
        next_exp_token: &mut ExpectedToken,
    ) -> Result<ActualToken, &'static str> {
        match next_exp_token {
            ExpectedToken::Keyword => {
                *next_exp_token = ExpectedToken::ArgumentOrSemicolonOrOpenBrace;
                match self.get_keyword(cursor) {
                    val @ Err(_) => val,
                    val @ Ok(ActualToken::Keyword(_)) => val,
                    val @ Ok(ActualToken::EOF) => val,
                    Ok(_) => unreachable!(),
                }
            }
            ExpectedToken::ArgumentOrSemicolonOrOpenBrace => {
                match self.get_argument_or_semicolon_or_open_brace(cursor) {
                    val @ Err(_) => val,
                    val @ Ok(ActualToken::Argument(_)) => {
                        *next_exp_token = ExpectedToken::SemicolonOrOpenBrace;
                        val
                    },
                    val @ Ok(ActualToken::Semicolon) => {
                        *next_exp_token = ExpectedToken::KeywordOrCloseBrace;
                        val
                    },
                    val @ Ok(ActualToken::OpenBrace) => {
                        *next_exp_token = ExpectedToken::KeywordOrCloseBrace;
                        val
                    },
                    Ok(_) => unreachable!(),
                }
            }
            ExpectedToken::SemicolonOrOpenBrace => {
                *next_exp_token = ExpectedToken::KeywordOrCloseBrace;
                match self.get_semicolon_or_close_brace(cursor) {
                    val @ Err(_) => val,
                    val @ Ok(ActualToken::Semicolon) => val,
                    val @ Ok(ActualToken::CloseBrace) => val,
                    Ok(_) => unreachable!(),

                }
            }
            ExpectedToken::KeywordOrCloseBrace => {
                *next_exp_token = ExpectedToken::ArgumentOrSemicolonOrOpenBrace;
                match self.get_keyword_or_close_brace(cursor) {
                    val @ Err(_) => val,
                    val @ Ok(ActualToken::Keyword(_)) => val,
                    val @ Ok(ActualToken::EOF) => val,
                    val @ Ok(ActualToken::CloseBrace) => val,
                    Ok(_) => unreachable!(),
                }
            }
        }
    }