Rust研究：シンプルな数式パーサ

Rust 初心者です。もう一か月半も Rust を使ってるのですがいまだに自信がつきません。

今回は Rust で数式パーサを作ってみました。

数式を与えると、数式の抽象構文木 (AST; Abstract Syntax Tree) を作成します。
また、スタックマシンのコマンドに変換するところも実装してみました。

以下、エッセンスを紹介します。

# 数式の構文定義

数式の構文の BNF (Backus–Naur form) は以下のように与えました。

```
expr  ::= expr2 [ [ '+' | '-' ] expr2 ]*
expr2 ::= expr3 [ [ '*' | '/' ] expr3 ]*
expr3 ::= number | '(' expr ')'
number ::= digit+
digit ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
```

シンプルにするため、正の整数の四則演算に限定しました。
数式における演算子の優先順位、+,- ＜＜ *,/ ＜＜ (,)  に対応させる形で、expr1, expr2, expr3 の構文ルールを与えています。
また、演算子 - と / は「交換法則」が成り立たないことから、同じ優先順位の演算子同士が並ぶ場合は左の項を優先します。

例： X + Y - Z では + と - の二つの演算子が出現するが同じ優先順位なので左を優先して (X + Y) - Z とする。

この程度の構文仕様なら ANTLR などのパーサジェネレータを使わなくても自分でパーサを実装できそうです。

# 数式の AST

先ほどの数式の構文に対して、パースした後の内部表現の型を以下に示します。
演算子の優先順位はすでにパーサが解決した後なので先ほどの expr1,expr2,expr3 のような構文上の分類は考慮する必要はありません。

演算子の優先順位を考える必要がないので、数式に出現する数字 Expr::Number と二項演算子の式 Expr::BinaryExpr の二種類だけを考えればよいことになります。
Expr::BinaryExpr の二つのオペランド部分が Expr になっていて、再帰的な型になっています。
この部分、Box でラップしないとコンパイラにしかられました。

```rust
// 数式の AST (Abstract Syntax Tree)
#[derive(Debug, PartialEq)]
pub enum Expr {
    Number (i32),
    BinaryExpr (Op, Box<Expr>, Box<Expr>),
}

// 二項演算子
#[derive(Debug, PartialEq)]
pub enum Op {
    Add,
    Sub,
    Mul,
    Div,
}
```

# 数式パーサ

最初に数式パーサの呼び出し方を示します。与えられた数式の文字列をパースし、対応する AST を返します。

```rust
let p = Parser::new();
let ast = p.parse("111 + 222 + 333")?;
```

この例ではパースの結果、変数 ast に次のような AST が代入される想定です。

```rust
Expr::BinaryExpr(Op::Add, 
    Expr::BinaryExpr(Op::Add, Expr::Number(111), Expr::Number(222)), 
    Expr::Number(333)
) 
```

数式パーサの実装例です。

メソッドの呼び出しは、parse → parse_expr → parse_expr2 → parse_expr3 → parse_expr … というように再帰的に行われます。

```rust
// 数式パーサ

pub struct Parser {
    // 処理中のトークンの位置
    pos: usize,
    // トークン列
    tokens: Vec<Token>,
}

impl Parser {

    pub fn new () -> Self {
        Parser {
            pos: 0,
            tokens: vec![],
        }
    }

    pub fn parse (&mut self, expr_str:&str) -> Result<Expr> {
        let bytes:&[u8] = expr_str.as_bytes();

        // [MEMO] Read だけなら
        // let mut r = BufReader::new (bytes);
        // でもよいが、今回は Seek も使うので Cursor でラップする
        let mut r = BufReader::new (Cursor::new(bytes));

        // トークン列の読み出し
        if let Some(tokens) = read_tokens_from_reader(&mut r)? {
            self.tokens = tokens;
            self.pos = 0;
        } else {
            return Err(Error::ParseError);
        }


        self.parse_expr ()
    }

    // 次のトークンをトークン列から取得する
    fn next_token (&mut self) -> Option<Token> {
        if self.pos >= self.tokens.len () {
            return None;
        }
        let r = self.tokens[self.pos];
        self.pos += 1;
        Some(r)
    }

    // 処理中のトークンの位置を一つ前の位置に戻す
    fn back (&mut self) {
        if self.pos > 0 {
            self.pos -= 1;
        }
    }

    // 数式のパース
    fn parse_expr (&mut self) -> Result<Expr> {
        let mut left = self.parse_expr2 ()?;

        loop {
            if let Some(op) = self.next_token () {

                match op {
                    Token::Add|Token::Sub => {},
                    _ => {
                        self.back ();
                        return Ok(left);
                    },
                }
                let right = self.parse_expr2 ()?;

                match op {
                    Token::Add => {
                        left = Expr::BinaryExpr (Op::Add, Box::new(left), Box::new(right));
                    },
                    Token::Sub => {
                        left = Expr::BinaryExpr (Op::Sub, Box::new(left), Box::new(right));
                    },
                    _ => {
                        self.back();
                        break;
                    },
                }
            } else {
                // None がきたらそれ以上読めないのでループを抜ける
                break
            }
        }

        Ok(left)
    }

    fn parse_expr2 (&mut self) -> Result<Expr> {
        let mut left = self.parse_expr3 ()?;

        loop {
            if let Some(op) = self.next_token () {

                match op {
                    Token::Mul|Token::Div => {},
                    _ => {
                        self.back ();
                        return Ok(left);
                    },
                }

                let right = self.parse_expr3 ()?;

                match op {
                    Token::Mul => {
                        left = Expr::BinaryExpr (Op::Mul, Box::new (left), Box::new (right));
                    },
                    Token::Div => {
                        left = Expr::BinaryExpr (Op::Div, Box::new (left), Box::new (right));
                    },
                    _ => {
                        self.back ();
                        break;
                    },
                }
            } else {
                break
            }
        }

        Ok(left)
    }

    fn parse_expr3 (&mut self) -> Result<Expr> {
        if let Some(token) = self.next_token () {
            match token {
                Token::Number (value) => {
                    return Ok(Expr::Number (value));
                },
                Token::Lp => {
                    let expr = self.parse_expr ()?;
                    if let Some(token) = self.next_token () {
                        if token == Token::Rp {
                            return Ok (expr);
                        }
                    }
                },
                _ => {
                    self.back ();
                },
            }
        }

        Err(Error::ParseError)
    }
}
```

# トークン

トークンの型は次のように与えました。

数字、四則演算子、左右の括弧だけです。

```rust
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Token {
    Number (i32),
    Add,
    Sub,
    Mul,
    Div,
    Lp,
    Rp,
}
```

トークンを読みだす部分の実装例です。
今回も BufReader にトレイトを適用してメソッドを追加しました。
また、今回は数字の読み出しで 1 バイトだけ先読みが発生するため、Read に加えて Seek も使いました。

```rust
// 文字列からトークンを読みだす
pub fn read_tokens_from_str(expr_str:&str) -> ResultOption<Vec<Token>> {
    let bytes:&[u8] = expr_str.as_bytes();

    // [MEMO] Read だけ必要なら
    // let mut r = BufReader::new (bytes);
    // でもよいが、今回は Seek も必要なので Cursor でラップする
    let mut r = BufReader::new (Cursor::new(bytes));

    read_tokens_from_reader(&mut r)
}

// バッファリーダーからトークンを読みだす
pub fn read_tokens_from_reader <R:Read+Seek> (r:&mut BufReader<R>) -> ResultOption<Vec<Token>> {
    let mut tokens:Vec<Token> = vec![];

    loop {
        match r.read_token ()? {
            Some(token) => tokens.push (token),
            None => break,
        }
    }

    if tokens.len() < 1 {
        return Ok(None)
    }

    Ok(Some(tokens))
}

impl<R:Read+Seek> MyRead for std::io::BufReader<R>{}

// 使用する文字コード
const CR:u8 = 0x0D; // Carriage Return
const LF:u8 = 0x0A; // Line Feed
const HT:u8 = 0x09; // Horizontal Tabulation
const SP:u8 = 0x20; // SPace
const LP:u8 = 0x28; // (
const RP:u8 = 0x29; // )
const MUL:u8 = 0x2A; // *
const ADD:u8 = 0x2B; // +
const SUB:u8 = 0x2D; // -
const DIV:u8 = 0x2F; // /
const NUM0:u8 = 0x30; // 0
const NUM9:u8 = 0x39; // 9

// トークン読み出し対応トレイト
pub trait MyRead: Read+Seek {

    // トークンの読み出し
    fn read_token(&mut self) -> ResultOption<Token> {

        loop {
            if let Some(c) = self.read_1byte ()? {
                if is_space (c) {
                    continue
                } else if c == LP {
                    return Ok(Some(Token::Lp))
                } else if c == RP {
                    return Ok(Some(Token::Rp))
                } else if c == MUL {
                    return Ok(Some(Token::Mul))
                } else if c == ADD {
                    return Ok(Some(Token::Add))
                } else if c == SUB {
                    return Ok(Some(Token::Sub))
                } else if c == DIV {
                    return Ok(Some(Token::Div))
                } else if c >= NUM0 && c <= NUM9 {
                    return Ok(Some(self.read_number (i32::from (c - NUM0))?));
                } else {
                    return Err(Error::UnknownCharacter)
                }
            }
            break
        }

        return Ok(None)
    }

    // 数字の読みだし
    fn read_number (&mut self, number:i32) -> Result<Token> {
        let mut number:i32 = number; 
        loop {
            // 1バイト読みだして
            if let Some(c) = self.read_1byte()? {
                // 数字の場合はすでに読みだしている値を10倍して桁あげして加算
                if c >= NUM0 && c <= NUM9 {
                    number = number * 10 + i32::from(c - NUM0);
                    continue
                } else {
                    // 1バイト戻してループを抜ける
                    self.seek(SeekFrom::Current(-1))?;
                }
            }
            break
        }

        return Ok(Token::Number(number))
    }

    // 1バイトを読みだすデフォルトメソッド
    fn read_1byte (&mut self) -> ResultOption<u8> {
        // (以前紹介したので省略)
}

// 空白かどうかを判定する
fn is_space (c:u8) -> bool {
    c == CR || c == LF || c == HT || c == SP
}
```

# スタックマシンのコマンド

数式の AST をスタックマシンのコマンドにコンパイルする部分も作りました。

```rust
// スタックマシンのコマンド
#[derive(Debug, PartialEq, Clone)]
pub enum Code {
    Add,
    Sub,
    Mul,
    Div,
    Push (i32),
}
    
impl std::fmt::Display for Command {
    fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // 省略
```

想定するスタックマシンではレジスタはなく、スタックだけで演算処理を行います。
例えば、111 + 222 ならば、こんな感じです。

```
push 111
push 222
add
```

スタックから 111 と 222 を pop して加算（add）を行った結果となる 333 が push される想定です。
最新の計算結果は常にスタックの先頭に積まれます。

今回はスタックマシンそのものは実装しません。

以下、コンパイラの実装例です。

```rust
use crate::err::Result;
use crate::ast::{Expr,Op};

// 数式の AST をスタックマシンのコードにコンパイルする
pub fn compile(expr: Expr) -> Result<Vec<Command>> {
    let mut result:Vec<Command> = vec![];

    match expr {

        Expr::Number (value) => {
            result.push(Code::Push(value));
        },

        Expr::BinaryExpr (op, left_expr, right_expr) => {
            let left_commands = compile(*left_expr)?; // left_expr は Box でラップされてる
            for command in left_commands {
                result.push(command);
            }
            let right_commands = compile(*right_expr)?; // right_expr は Box でラップされてる
            for command in right_commands {
                result.push(command);
            }
            result.push(match op {
                Op::Add => Command::Add,
                Op::Sub => Command::Sub,
                Op::Mul => Command::Mul,
                Op::Div => Command::Div,
            });

        },
    };

    Ok(result)    
}
```

数式の AST 構造に基づいて再帰的に実装できるのでシンプルです。
左のオペランドをコンパイルしたら、右のオペランドをコンパイルして、最後に演算子に対応するコマンドを追加する、という流れです。

# 実行例

main.rs の実装例です。
引数で与えた数式をパースし、コンパイルし、スタックマシンへのコマンド列を表示します。

```rust
// main.rs
use std::env;
use calc::err::Result;
use calc::parse::Parser;
use calc::compile::compile;

fn main() {
    let args:Vec<String> = env::args().collect();
    let expr:&str = &args[1];

    if let Err(e) = process(expr) {
        eprintln!("[Error] {:?}", e);
    }
}

fn process(expr: &str) -> Result<()> {

    // 数式をパースする
    let mut p = Parser::new ();
    let ast = p.parse(expr)?;
    eprintln!("[DEBUG] ast = {:?}", ast);

    // 数式の AST をスタックマシンのコマンド列にコンパイルする
    let commands = compile(ast)?;

    // コマンド列の表示
    encode (commands);
    Ok(())
}

// コマンド列の表示
fn encode(codes: Vec<Code>) {
    for code in codes {
        println!("{}", code);
    }
}
```

これを expr.exe という名前の実行ファイルでコンパイルしました。

以下、実行例です。

```
C:\work>expr.exe "10 + 20"
[DEBUG] ast = BinaryExpr(Add, Number(10), Number(20))
push 10
push 20
add

C:\work>expr.exe "10 + 20 + 30"
[DEBUG] ast = BinaryExpr(Add, BinaryExpr(Add, Number(10), Number(20)), Number(30))
push 10
push 20
add
push 30
add

C:\work>expr.exe "10 + 20 * 30"
[DEBUG] ast = BinaryExpr(Add, Number(10), BinaryExpr(Mul, Number(20), Number(30)))
push 10
push 20
push 30
mul
add

C:\work>expr.exe "(10 + 20) * 30"
[DEBUG] ast = BinaryExpr(Mul, BinaryExpr(Add, Number(10), Number(20)), Number(30))
push 10
push 20
add
push 30
mul
```

今回は数式パーサを Rust で実装してみました。

次回は簡単なスタックマシンを Rust 実装し、今回作成した expr.exe と組み合わせて数式を計算してみたいと思います。