Rustでプログラミング言語を作る

元ネタは『WEB+DB PRESS Vol.125』の https://github.com/kmizu/toys

ディレクトリ構成は src/toys の下に各種 rs ファイルと pest ファイルを置いている。

四則演算の AST とインタプリタを作る。

operator.rs

#[derive(Debug, PartialEq, Eq)]
pub enum Operator {
    ADD,
    SUBTRACT,
    MULTIPLY,
    DIVIDE,
}

#[derive(Debug, PartialEq, Eq)]
pub enum UnaryOperator {
    PLUS,
    MINUS,
}

ast.rs

use super::operator::{Operator, UnaryOperator};

#[derive(Debug, PartialEq, Eq)]
pub enum Expression {
    Nil,
    BinaryExpression {
        operator: Operator,
        lhs: Box<Expression>,
        rhs: Box<Expression>,
    },
    IntegerLiteral {
        value: isize,
    },
    UnaryExpression {
        operator: UnaryOperator,
        operand: Box<Expression>,
    },
}

#[derive(Debug)]
pub struct Ast {}

impl Ast {
    pub fn add(&self, lhs: Expression, rhs: Expression) -> Expression {
        // BinaryExpression
        Expression::BinaryExpression {
            operator: Operator::ADD,
            lhs: Box::new(lhs),
            rhs: Box::new(rhs),
        }
    }
    pub fn subtract(&self, lhs: Expression, rhs: Expression) -> Expression {
        Expression::BinaryExpression {
            operator: Operator::SUBTRACT,
            lhs: Box::new(lhs),
            rhs: Box::new(rhs),
        }
    }
    pub fn multiply(&self, lhs: Expression, rhs: Expression) -> Expression {
        Expression::BinaryExpression {
            operator: Operator::MULTIPLY,
            lhs: Box::new(lhs),
            rhs: Box::new(rhs),
        }
    }
    pub fn divide(&self, lhs: Expression, rhs: Expression) -> Expression {
        Expression::BinaryExpression {
            operator: Operator::DIVIDE,
            lhs: Box::new(lhs),
            rhs: Box::new(rhs),
        }
    }
    pub fn integer(&self, value: isize) -> Expression {
        // IntegerLiteral
        Expression::IntegerLiteral { value }
    }

    pub fn unary_plus(&self, operand: Expression) -> Expression {
        Expression::UnaryExpression {
            operator: UnaryOperator::PLUS,
            operand: Box::new(operand),
        }
    }
    pub fn unary_minus(&self, operand: Expression) -> Expression {
        Expression::UnaryExpression {
            operator: UnaryOperator::MINUS,
            operand: Box::new(operand),
        }
    }
}

interpreter.rs

use super::ast::Expression;
use super::operator::{Operator, UnaryOperator};

pub struct Interpreter {}

impl Interpreter {
    pub fn interpret(&self, expression: Expression) -> isize {
        match expression {
            Expression::BinaryExpression { operator, lhs, rhs } => {
                let lhs = Self::interpret(&self, *lhs);
                let rhs = Self::interpret(&self, *rhs);
                return match operator {
                    Operator::ADD => lhs + rhs,
                    Operator::SUBTRACT => lhs - rhs,
                    Operator::MULTIPLY => lhs * rhs,
                    Operator::DIVIDE => lhs / rhs,
                };
            }
            Expression::IntegerLiteral { value } => {
                return value;
            }
            Expression::UnaryExpression { operator, operand } => {
                return match operator {
                    UnaryOperator::PLUS => Self::interpret(&self, *operand),
                    UnaryOperator::MINUS => -Self::interpret(&self, *operand),
                };
            }
            _ => {
                panic!("not reach here")
            }
        }
    }
}

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

    #[test]
    fn test_10_plus_20_should_work() {
        let interpreter: Interpreter = Interpreter {};
        let e: Expression = Ast::add(
            &Ast {},
            Ast::integer(&Ast {}, 10),
            Ast::integer(&Ast {}, 20),
        );
        assert_eq!(interpreter.interpret(e), 30);
    }

    #[test]
    fn test_10_minus_20_should_work() {
        let interpreter: Interpreter = Interpreter {};
        let e: Expression = Ast::subtract(
            &Ast {},
            Ast::integer(&Ast {}, 10),
            Ast::integer(&Ast {}, 20),
        );
        assert_eq!(interpreter.interpret(e), -10);
    }

    #[test]
    fn test_10_multiply_20_should_work() {
        let interpreter: Interpreter = Interpreter {};
        let e: Expression = Ast::multiply(
            &Ast {},
            Ast::integer(&Ast {}, 10),
            Ast::integer(&Ast {}, 20),
        );
        assert_eq!(interpreter.interpret(e), 200);
    }

    #[test]
    fn test_10_divide_20_should_work() {
        let interpreter: Interpreter = Interpreter {};
        let e: Expression = Ast::divide(
            &Ast {},
            Ast::integer(&Ast {}, 10),
            Ast::integer(&Ast {}, 20),
        );
        assert_eq!(interpreter.interpret(e), 0);
    }

    #[test]
    #[should_panic(expected = "attempt to divide by zero")]
    fn test_10_divide_0_should_not_work() {
        let interpreter: Interpreter = Interpreter {};
        let e: Expression =
            Ast::divide(&Ast {}, Ast::integer(&Ast {}, 10), Ast::integer(&Ast {}, 0));
        interpreter.interpret(e);
    }

    #[test]
    fn test_unary_plus_should_work() {
        let interpreter: Interpreter = Interpreter {};
        let e: Expression = Ast::unary_plus(&Ast {}, Ast::integer(&Ast {}, 20));
        assert_eq!(interpreter.interpret(e), 20);
    }

    #[test]
    fn test_unary_minus_should_work() {
        let interpreter: Interpreter = Interpreter {};
        let e: Expression = Ast::unary_minus(&Ast {}, Ast::integer(&Ast {}, 20));
        assert_eq!(interpreter.interpret(e), -20);
    }
}

それっぽいのができた気がする。

パーサも pest を使って単項演算子以外(四則演算、かっこ付けてOK、スペース入れてOK、行末に // で始まるコメント入れてOK)は実装できたけれど、ちょっとコードがあまりにも汚すぎるので、載せられない。。

コメント

タイトルとURLをコピーしました