2019/03/20

Rustの逆ポーランド記法電卓を改造中

逆ポーランド記法

一般的な数式は中置記法infix、Common Lispは前置記法prefix、後置記法である逆ポーランド記法はpostfix。
postfixは、スタックをうまく使い、pushとpopで処理していきます。仕組みを理解してから私は、これを改良して簡単なプログラミング言語的なものを作れないかと考えて、日々試行錯誤しております。
現在まだ開発中で、四則演算・比較演算までできるようになりました。方向性は良さそうなのですが、まだまだ改良しなければなりません。とりあえず現状のコードは以下のようなものになっています。

use std::io::Write;
use std::io;


#[derive(Debug)]
enum Token {
    Num(f64),
    Bool(bool),
    Str(String),
}

impl Token {
    fn get_num(self) -> Option<f64> {
        match self {
            Token::Num(n) => Some(n),
            _ => None,
        }
    }
}

macro_rules! pop2 {
    ( $v:expr ) => ({
        let x = $v.pop().unwrap();
        let y = $v.pop().unwrap();
        (x.get_num().unwrap(), y.get_num().unwrap())
    });
}

fn rpn(text: &String) -> Vec<Token> {
    let text2: Vec<&str> = text.split_whitespace().collect();
    let mut stack: Vec<Token> = vec![];

    for t in text2 {
        match t {
            "+" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Num(x + y));
                continue;
            },
            "-" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Num(y - x));
                continue;

            }, 
            "*" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Num(x * y));
                continue;

            }, 
            "/" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Num(y / x));
                continue;

            },
            "eq"|"==" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Bool(x == y));
                continue;
            },
            "not"|"!=" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Bool(x != y));
                continue;
            },
            "lt"|"<" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Bool(y < x));
                continue;
            },
            "gt"|">" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Bool(y > x));
                continue;
            }
            "le"|"<=" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Bool(y <= x));
                continue;
            },
            "ge"|">=" => {
                let (x, y) = pop2!(stack);
                stack.push(Token::Bool(y >= x));
                continue;
            },
            _ => {
                if t.parse::<f64>().is_ok() {
                    stack.push(Token::Num(t.parse::<f64>().unwrap()));
                } else {
                    stack.push(Token::Str(t.to_string()));
                }
                continue;
            }
        }
    }
    stack
}

fn main() {
    println!(r#"Calculator of Reverse Polish Notation
* Whitespace is important!!
* e.g) 2 1 +  => [Num(3.0)]
       2 1+  => panic!
       1 2 3 + + => Ok! return [Num(6.0)]
       2 3 + 2 * => [Num(10.0)]
       1 2 + 3 == => [Bool(true)]
* input "quit" => close this app.
"#);
    loop {
        let mut s = String::new();
        print!(">> ");
        io::stdout().flush().expect("Could't flush stdout.");
        io::stdin().read_line(&mut s).expect("Failed");
        if s.starts_with("quit") {
            break;
        } else {
            println!("{:?}", rpn(&s));
        }
    }

}

これを私のGitHubレポジトリに置いているumeboshiとうまく組み合わせて、変数なども扱えるようにすればいい感じに仕上がるのでは?と考えています。
が、どこかデータ構造自体が間違っているような気がして、現在ベストな形を探すべく試行錯誤しております。別バージョンではオペレータ群をenumにして試していますが、お見せできる代物ではないので。変数の扱いもlazy_staticを用いずに何とかできないか?などと課題は山積みでございます。
いい感じに仕上がったらまたご報告いたします。