Last active
October 18, 2018 17:41
-
-
Save mexus/0dbdf5af2367719c00bb70c5288d9cc9 to your computer and use it in GitHub Desktop.
An alternative VM implementation for https://habr.com/post/416505/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(()) | |
} |
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
Hi, mexus!
What is the license for this code? Can I copy and modify it?
Thanks.