Rustのお勉強がてらbrainfuckを書いた。
#[macro_use]
extern crate nom;
use nom::IResult;
#[derive(PartialEq, Eq, Debug)]
enum Inst {
PInc,
PDec,
Inc,
Dec,
Put,
Get,
Bgn,
End,
Nop,
}
impl std::fmt::Display for Inst {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let repr = match self {
&Inst::PInc => "pinc",
&Inst::PDec => "pdec",
&Inst::Inc => "inc",
&Inst::Dec => "dec",
&Inst::Put => "put",
&Inst::Get => "get",
&Inst::Bgn => "bgn",
&Inst::End => "end",
&Inst::Nop => "nop",
};
write!(f, "{}", repr)
}
}
fn from_str(symbol: &str) -> Inst {
match symbol {
">" => Inst::PInc,
"<" => Inst::PDec,
"+" => Inst::Inc,
"-" => Inst::Dec,
"." => Inst::Put,
"," => Inst::Get,
"[" => Inst::Bgn,
"]" => Inst::End,
_ => Inst::Nop,
}
}
fn from_bytes(symbol: &[u8]) -> Inst {
from_str(std::str::from_utf8(symbol).unwrap())
}
named!(
parser,
alt!(tag!(">") | tag!("<") | tag!("+") | tag!("-") | tag!(".") | tag!(",") | tag!("[") | tag!("]"))
);
fn parse_symbol(input: &[u8]) -> Option<(Inst, &[u8])> {
match parser(input) {
IResult::Done(rest, token) => Some((from_bytes(token), rest)),
IResult::Incomplete(_) => None,
IResult::Error(_) => panic!("Parse Error!"),
}
}
fn parse(input: &str) -> Vec<Inst> {
let mut v = Vec::with_capacity(input.len());
let mut rest = input.as_bytes();
while let Some((inst, next)) = parse_symbol(rest) {
rest = next;
v.push(inst);
}
v
}
struct Processor {
sp: usize,
ip: usize,
memory: [u8; 256],
}
impl Processor {
fn new() -> Processor {
Processor {
sp: 0,
ip: 0,
memory: [0; 256],
}
}
fn set_mem(&mut self, val: u8) {
self.memory[self.sp] = val;
}
fn get_mem(&self) -> u8 {
self.memory[self.sp]
}
fn pinc(&mut self) {
self.sp += 1;
self.ip += 1;
}
fn pdec(&mut self) {
self.sp -= 1;
self.ip += 1;
}
fn inc(&mut self) {
let val = self.get_mem() + 1;
self.set_mem(val);
self.ip += 1;
}
fn dec(&mut self) {
let val = self.get_mem() - 1;
self.set_mem(val);
self.ip += 1;
}
fn put(&mut self) {
print!("{}", self.get_mem() as char);
self.ip += 1;
}
fn get(&mut self) {
use std::io::*;
self.set_mem(stdin().bytes().nth(0).unwrap().unwrap());
self.ip += 1;
}
fn bgn(&mut self, exec: &Vec<Inst>) {
if self.get_mem() == 0 {
let len = exec.len();
self.ip += 1;
while self.ip < len {
if exec[self.ip] == Inst::End {
self.ip += 1;
break;
} else {
self.ip += 1;
}
}
} else {
self.ip += 1;
}
}
fn end(&mut self, exec: &Vec<Inst>) {
if self.get_mem() != 0 {
let mut nest_level = 0;
self.ip -= 1;
loop {
let inst = &exec[self.ip];
if inst == &Inst::Bgn {
if nest_level == 0 {
self.ip += 1;
break;
} else {
nest_level -= 1;
}
} else if inst == &Inst::End {
nest_level += 1;
} else {
self.ip -= 1;
}
}
} else {
self.ip += 1;
}
}
fn nop(&mut self) {
// this means inst that do nothing
self.ip += 1;
}
fn exec(&mut self, exec: &Vec<Inst>) {
use Inst::*;
let len = exec.len();
while self.ip < len {
match &exec[self.ip] {
&PInc => self.pinc(),
&PDec => self.pdec(),
&Inc => self.inc(),
&Dec => self.dec(),
&Put => self.put(),
&Get => self.get(),
&Bgn => self.bgn(exec),
&End => self.end(exec),
&Nop => self.nop(),
}
}
}
}
fn main() {
let mut machine = Processor::new();
let hello_world = "+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.";
let v = parse(hello_world);
machine.exec(&v);
}
ショボい実装なので関係ない文字が混じるとクラッシュする。
Rustの感想
やっぱり借用の概念が独特でなかなか慣れなかったため、頻繁にコンパイルエラーになった。
でもコンパイラのエラーメッセージがかなり親切で詳しいので、エラーの原因はたいていすぐに特定することができた。
場合によってはコンパイラが提示してくれたヒントがそのまま解決策だったりすることもあった。
借用とかの概念があまり理解できていなかったのでうまく書けるか心配だったけど、意外なことにあまりストレスなく書き進められた。
コンパイラのメッセージに含まれる情報が豊富なので調べるときに詰まることもなかったし、公式のリファレンスも整っているので特に困らなかった。
まったく問題がなかったというわけでもなかった。発展途中の言語なので仕方がないが、ちょっと前の情報だと動かないということがたびたびあった。とはいえこれから安定していくだろうからそこまで大きな問題ではないと思うが、注意しないと落し穴にはまりそう。