Skip to content

Instantly share code, notes, and snippets.

@mexus
Last active October 18, 2018 17:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mexus/0dbdf5af2367719c00bb70c5288d9cc9 to your computer and use it in GitHub Desktop.
Save mexus/0dbdf5af2367719c00bb70c5288d9cc9 to your computer and use it in GitHub Desktop.
An alternative VM implementation for https://habr.com/post/416505/
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyright (C) 2018 mexus <gilaldpellaeon at gmail dot com>
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//
//! An alternative VM implementation for https://habr.com/post/416505/
#[macro_use]
extern crate failure;
mod utils {
/// Virtual machine error.
#[derive(Debug, Fail, PartialEq)]
pub enum Error {
/// Not enough elements on the stack.
#[fail(display = "Not enough elements on the stack, expected at least {} element(s)", _0)]
NotEnoughElements(usize),
}
/// Takes the last value from a stack, applies a given function on it, pushes the result back to the
/// stack.
pub fn take_apply_and_push1<F>(mut stack: Vec<i32>, f: F) -> Result<Vec<i32>, Error>
where
F: FnOnce(i32) -> i32,
{
let last = stack.pop().ok_or_else(|| Error::NotEnoughElements(1))?;
let result = f(last);
stack.push(result);
Ok(stack)
}
/// Takes the last two values from a stack, applies a given function on them, pushes the result back
/// to the stack.
pub fn take_apply_and_push2<F>(mut stack: Vec<i32>, f: F) -> Result<Vec<i32>, Error>
where
F: FnOnce(i32, i32) -> i32,
{
let err = || Error::NotEnoughElements(2);
let last = stack.pop().ok_or_else(err)?;
let second_to_last = stack.pop().ok_or_else(err)?;
let result = f(second_to_last, last);
stack.push(result);
Ok(stack)
}
/// Extends an [Iterator] trait with an [IterExt::fold_ok] method.
pub trait IterExt: Iterator {
/// Like an [Iterator::fold], but accepts a function that returns a [Result] and proceeds only
/// until [Ok] is returned.
#[inline(always)]
fn fold_ok<T, E, F>(&mut self, initial: T, apply: F) -> Result<T, E>
where
F: Fn(T, <Self as Iterator>::Item) -> Result<T, E>,
{
let mut calculated = initial;
for value in self {
calculated = apply(calculated, value)?;
}
Ok(calculated)
}
}
impl<T> IterExt for T
where
T: Iterator,
{
}
}
mod vm {
use utils::{take_apply_and_push1, take_apply_and_push2, Error, IterExt};
#[derive(Debug, Clone, Copy)]
/// Operation code.
pub enum Opcode {
/// Push a number to the stack.
Push(i32),
/// Pop two latest numbers from the stack, sums them up and push the result to the stack.
Add,
/// Pop the last number from the stack, sum it up with a given one and push the result to the
/// stack.
AddAssign(i32),
/// Pop two latest numbers from the stack, substract the second-to-last number from the last one
/// and push the result to the stack.
Sub,
/// Substract a given number from the last number from the stack and push the result to the
/// stack.
SubAssign(i32),
}
/// A simple virtual machine with a stack.
#[derive(Default, Debug, PartialEq)]
pub struct Vm {
stack: Vec<i32>,
}
impl Vm {
/// A single "turn" of a virtual machine, i.e. processing a single operation.
fn turn(self, operation: Opcode) -> Result<Self, Error> {
let mut state = self.stack;
let new_state = match operation {
Opcode::Push(value) => {
state.push(value);
state
}
Opcode::Add => take_apply_and_push2(state, |x, y| x + y)?,
Opcode::AddAssign(value) => take_apply_and_push1(state, |x| x + value)?,
Opcode::Sub => take_apply_and_push2(state, |x, y| y - x)?,
Opcode::SubAssign(value) => take_apply_and_push1(state, |x| x - value)?,
};
Ok(Vm { stack: new_state })
}
/// Executes given operations on the machine.
pub fn run(self, program: impl IntoIterator<Item = Opcode>) -> Result<Self, Error> {
program.into_iter().fold_ok(self, Vm::turn)
}
#[cfg(test)]
/// Provides a view on the internal stack.
pub fn get_stack(&self) -> &[i32] {
&self.stack
}
}
#[cfg(test)]
mod tests {
use super::*;
impl From<Vec<i32>> for Vm {
fn from(stack: Vec<i32>) -> Vm {
Vm { stack }
}
}
#[test]
fn check_push() {
let vm = Vm::default();
let new_vm = vm
.run(vec![Opcode::Push(1), Opcode::Push(2), Opcode::Push(12)])
.unwrap();
assert_eq!(&[1, 2, 12], new_vm.get_stack());
}
#[test]
fn check_add1() {
let vm = Vm::from(vec![10, 30, -5])
.run(vec![Opcode::AddAssign(5)])
.unwrap();
assert_eq!(&[10, 30, 0], vm.get_stack());
let vm = Vm::from(vec![]);
assert_eq!(
Err(Error::NotEnoughElements(1)),
vm.run(vec![Opcode::AddAssign(1)])
);
}
#[test]
fn check_add2() {
let vm = Vm::from(vec![10, 30, -5]).run(vec![Opcode::Add]).unwrap();
assert_eq!(&[10, 25], vm.get_stack());
let vm = Vm::from(vec![1]);
assert_eq!(Err(Error::NotEnoughElements(2)), vm.run(vec![Opcode::Add]));
}
#[test]
fn check_sub1() {
let vm = Vm::from(vec![1, 2, 3])
.run(vec![Opcode::SubAssign(1)])
.unwrap();
assert_eq!(&[1, 2, 2], vm.get_stack());
let vm = Vm::from(vec![]);
assert_eq!(
Err(Error::NotEnoughElements(1)),
vm.run(vec![Opcode::SubAssign(1)])
);
}
#[test]
fn check_sub2() {
let vm = Vm::from(vec![1, 2, 3]).run(vec![Opcode::Sub]).unwrap();
assert_eq!(&[1, 1], vm.get_stack());
let vm = Vm::from(vec![1]);
assert_eq!(Err(Error::NotEnoughElements(2)), vm.run(vec![Opcode::Sub]));
}
}
}
pub use utils::Error;
pub use vm::{Opcode, Vm};
fn main() -> Result<(), Error> {
let vm = Vm::default();
let res = vm.run(vec![
Opcode::Push(1),
Opcode::Push(2),
Opcode::Add,
Opcode::AddAssign(1),
Opcode::SubAssign(1),
Opcode::Push(5),
Opcode::Sub,
])?;
println!("{:?}", res);
Ok(())
}
@tvladyslav
Copy link

Hi, mexus!
What is the license for this code? Can I copy and modify it?
Thanks.

@mexus
Copy link
Author

mexus commented Oct 18, 2018

Hi, mexus!
What is the license for this code? Can I copy and modify it?
Thanks.

Hi @crypto-universe! sorry, I've somehow missed your comment! It was a very long reply...

I've modified the file and included the WTFPL, so you can copy and modify it at your will :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment