3. 中间表示

为了方便后期处理,我们先将 Brainfuck 代码转换为一种中间表示 (IR).

在 src 目录下添加文件 bfir.rs

此时目录结构

.
├── Cargo.lock
├── Cargo.toml
└── src
   ├── bfir.rs
   └── main.rs

main.rs

mod bfir;

fn main() {
    println!("Hello, world!");
}

本节以下代码都将写入 bfir.rs

IR 定义

AddVal, SubVal 表示将当前单元加减某一数值。

AddPtr, SubPtr 表示将数据指针加减某一数值。

Jz,Jnz 表示跳转指令,带有跳转地址。

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BfIR {
    AddVal(u8),  // +
    SubVal(u8),  // -
    AddPtr(u32), // >
    SubPtr(u32), // <
    GetByte,     // ,
    PutByte,     // .
    Jz,          // [
    Jnz,         // ]
}

错误处理

我们使用 thiserror 库,用来轻松定义错误类型。

https://github.com/dtolnay/thiserror

导入第三方库时:

若 Rust 版本 >= 1.62,可直接 cargo add

若 Rust 版本 < 1.62,推荐用 cargo-edit 插件。安装 cargo-edit 插件后,可以用命令导入第三方库。

https://github.com/killercup/cargo-edit

也可手动编辑 Cargo.toml。

cargo install cargo-edit
cargo add thiserror

错误定义

#[derive(Debug, thiserror::Error)]
pub enum CompileErrorKind {
    #[error("Unclosed left bracket")]
    UnclosedLeftBracket,
    #[error("Unexpected right bracket")]
    UnexpectedRightBracket,
}

#[derive(Debug)]
pub struct CompileError {
    line: u32,
    col: u32,
    kind: CompileErrorKind,
}

为 CompileError 实现 Display 和 Error,Display 用于对人友好的信息,Error 表明它是一个错误类型。

#[derive(Debug)]
pub struct CompileError {
    line: u32,
    col: u32,
    kind: CompileErrorKind,
}

impl fmt::Display for CompileError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} at line {}:{}", self.kind, self.line, self.col)
    }
}

编译为 IR

pub fn compile(src: &str) -> Result<Vec<BfIR>, CompileError> {
    let mut code: Vec<BfIR> = vec![];

    let mut stk: Vec<(u32, u32, u32)> = vec![];

    let mut line: u32 = 1;
    let mut col: u32 = 0;

compile 函数接收一个字符串,返回 IR 序列。

code 存储已解析的 IR,stk 作为栈,存储左括号的IR位置、源代码行位置、源代码列位置,line 和 col 分别记录行号和列号。

主循环结构

for ch in src.chars() {
    col += 1;
    match ch {
        ...
    }
}

处理换行

'\n' => {
    line += 1;
    col = 0;
}

处理普通指令

'+' => code.push(BfIR::AddVal(1)),
'-' => code.push(BfIR::SubVal(1)),
'>' => code.push(BfIR::AddPtr(1)),
'<' => code.push(BfIR::SubPtr(1)),
',' => code.push(BfIR::GetByte),
'.' => code.push(BfIR::PutByte),

处理左括号,将左括号位置入栈。

'[' => {
    let pos = code.len() as u32;
    stk.push((pos, line, col));
    code.push(BfIR::Jz)
}

处理右括号,从栈中弹出左括号位置,如果栈为空,说明没有匹配的左括号,生成一个编译错误并返回。如果有匹配的左括号,则正常生成 IR。

']' => {
    stk.pop().ok_or(CompileError {
        line,
        col,
        kind: CompileErrorKind::UnexpectedRightBracket,
    })?;

    code.push(BfIR::Jnz)
}

忽略其他字符

_ => {}

循环结束后,如果栈不为空,说明有左括号没有匹配到右括号,弹出左括号位置,生成编译错误。最后返回生成的IR.

if let Some((_, line, col)) = stk.pop() {
    return Err(CompileError {
        line,
        col,
        kind: CompileErrorKind::UnclosedLeftBracket,
    });
}
Ok(code)

完整代码

pub fn compile(src: &str) -> Result<Vec<BfIR>, CompileError> {
    let mut code: Vec<BfIR> = vec![];

    let mut stk: Vec<(u32, u32, u32)> = vec![];

    let mut line: u32 = 1;
    let mut col: u32 = 0;

    for ch in src.chars() {
        col += 1;
        match ch {
            '\n' => {
                line += 1;
                col = 0;
            }
            '+' => code.push(BfIR::AddVal(1)),
            '-' => code.push(BfIR::SubVal(1)),
            '>' => code.push(BfIR::AddPtr(1)),
            '<' => code.push(BfIR::SubPtr(1)),
            ',' => code.push(BfIR::GetByte),
            '.' => code.push(BfIR::PutByte),
            '[' => {
                let pos = code.len() as u32;
                stk.push((pos, line, col));
                code.push(BfIR::Jz)
            }
            ']' => {
                stk.pop().ok_or(CompileError {
                    line,
                    col,
                    kind: CompileErrorKind::UnexpectedRightBracket,
                })?;

                code.push(BfIR::Jnz)
            }
            _ => {}
        }
    }
    if let Some((_, line, col)) = stk.pop() {
        return Err(CompileError {
            line,
            col,
            kind: CompileErrorKind::UnclosedLeftBracket,
        });
    }
    Ok(code)
}

简单优化

brainfuck 代码中经常有连续的算术加减和指针移动,这些操作完全可以折叠起来。

优化函数直接操作 IR 序列,用一次遍历完成折叠,原地操作。时间复杂度 O(n),空间复杂度 O(1)。

这里定义两个宏来避免复制粘贴大量同样的代码。

    let mut i = 0;
    let mut pc = 0;

    macro_rules! _fold_ir {
        ($variant:ident, $x:ident) => {{
            let mut j = i + 1;
            while j < len {
                if let $variant(d) = code[j] {
                    $x = $x.wrapping_add(d);
                } else {
                    break;
                }
                j += 1;
            }
            i = j;
            code[pc] = $variant($x);
            pc += 1;
        }};
    }

    macro_rules! _normal_ir {
        () => {{
            code[pc] = code[i];
            pc += 1;
            i += 1;
        }};
    }

    use BfIR::*;
    while i < len {
        match code[i] {
            AddPtr(mut x) => _fold_ir!(AddPtr, x),
            SubPtr(mut x) => _fold_ir!(SubPtr, x),
            AddVal(mut x) => _fold_ir!(AddVal, x),
            SubVal(mut x) => _fold_ir!(SubVal, x),
            GetByte => _normal_ir!(),
            PutByte => _normal_ir!(),
            Jz => _normal_ir!(),
            Jnz => _normal_ir!(),
        }
    }

    code.truncate(pc);
    code.shrink_to_fit();
}

简单测试

Rust 内置一套单元测试框架,在模块内随手写个函数,标上 #[test],然后运行命令 cargo test。cargo 会收集所有测试,逐个运行,并报告结果。

#[test]
fn test_compile() {
    assert_eq!(
        compile("+[,.]").unwrap(),
        vec![
            BfIR::AddVal(1),
            BfIR::Jz,
            BfIR::GetByte,
            BfIR::PutByte,
            BfIR::Jnz,
        ]
    );

    match compile("[").unwrap_err().kind {
        CompileErrorKind::UnclosedLeftBracket => {}
        _ => panic!(),
    };

    match compile("]").unwrap_err().kind {
        CompileErrorKind::UnexpectedRightBracket => {}
        _ => panic!(),
    };

    let mut code = compile("[+++++]").unwrap();
    optimize(&mut code);
    assert_eq!(code, vec![BfIR::Jz, BfIR::AddVal(5), BfIR::Jnz]);
}