RustのトークナイザーFSM-一致ステートメントを実行するためのより良い方法?

Aug 24 2020

私はRustを学んでいて、おもちゃのYANGパーサーを演習としてコーディングすることにしました。関連する場合、私は主にCとPythonで作業します。

トークナイザー/レクサーで作業しているときに、Rust-yにしようとしましたが、かなり不格好な一致ステートメントになってしまいました。それらは次の形式です。

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
}

フィードバックに関心のあるセクションを強調表示しました。ただし、(これが進行中の作業であると考えて)間違ったルートをたどっているように見える一般的なヒントは歓迎します。

// @@@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!()
    }
}

回答

Kyle_S-C Aug 26 2020 at 16:35

問題の関数の場合、重要なことは、Rustブックセクション18.3の@バインディングを覚えておくことでした。

これらを使用すると、一致した値を変数にバインドして、一致アームで使用できます。

    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!(),
                }
            }
        }
    }