元ネタは『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)は実装できたけれど、ちょっとコードがあまりにも汚すぎるので、載せられない。。
コメント