0

Came across this while trying to follow the excellent Crafting Interpreters book but using Rust instead of C.

The book builds a stack based virtual machine, of which I have a simple Rust version that looks something like:

struct VM {
  stack: Vec<Value>
}

impl VM {
//...
pub fn interpret(op: OpCode) {
  match op {
    Op::Add => {
      let r = self.stack.pop();
      let l = self.stack.pop();
      self.stack.push(l + r);
    }
    // Repeated for -, *, and /
  }
}
}

The book uses a C style macro to prevent copy/pasting the same code for all binops.

#define BINOP(op) \
    do { \
      double r = pop(); \
      double l = pop(); \
      push(l op r); \
    } while (false)

void interpret(VM *vm, OpCode op) {
  switch op {
    case OP_ADD: BINOP(+); break;
    case OP_SUB: BINOP(-); break;
    case OP_MUL: BINOP(*); break;
    case OP_DIV: BINOP(/); break;
  }

I tried to do something similar using a Rust macro

macro_rules! binop {
    ($op:tt) => {
        {
            let l = self.stack.pop(); 
            let r = self.stack.pop(); 
            self.stack.push(l $op r) };
    }
}

// ... inside VM impl ...

pub fn interpret(&mut self, op: OpCode) {
  match op {
    Op::Add => binop!(+)
    // Repeated for -, *, and /
  }
}

but the macro does know what self is and I am not sure what the best way to make the macro aware of self.

Is there a way to achieve something similar to the C macro in Rust?

Herohtar
  • 5,347
  • 4
  • 31
  • 41
Increasingly Idiotic
  • 5,700
  • 5
  • 35
  • 73

1 Answers1

3

You can pass self as a parameter for your macro:

macro_rules! binop {
    ($self:expr, $op:tt) => {{
        let l = $self.stack.pop(); 
        let r = $self.stack.pop(); 
        $self.stack.push(l $op r);
    }};
}

Or you can define the macro inside the function if you do not need it elsewhere:

pub fn interpret(&mut self, op: OpCode) {
    macro_rules! binop {
        ($op:tt) => {{
            let l = self.stack.pop(); 
            let r = self.stack.pop(); 
            self.stack.push(l $op r);
        }};
    }

    match op {
        Op::Add => binop!(+)
        // Repeated for -, *, and /
    }
}

Another option could be to use a closure instead of a macro:

impl VM {
    pub fn interpret(&mut self, op: OpCode) {
        match op {
            OpCode::Add => self.binop(|l, r| l + r),
            OpCode::Sub => self.binop(|l, r| l - r),
            OpCode::Mul => self.binop(|l, r| l * r),
            OpCode::Div => self.binop(|l, r| l / r),
        }
    }

    pub fn binop(&mut self, f: impl Fn(Value, Value) -> Value) {
        // Of course, in a real case you should handle unwrap:
        let l = self.stack.pop().unwrap();
        let r = self.stack.pop().unwrap();
        self.stack.push(f(l, r));
    }
}
yolenoyer
  • 8,797
  • 2
  • 27
  • 61