added basic VM

This commit is contained in:
Erin 2022-08-06 23:20:51 +02:00 committed by ondra05
parent 18fd6a35d5
commit 26a0cce990
6 changed files with 193 additions and 8 deletions

View file

@ -0,0 +1,28 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Instruction {
/// Call a function on top of the stack
/// with n arguments
Call(usize),
/// Unconditional jump by offset
Jump(isize),
/// Jump by offset if value on
/// top of the stack is falsey (nil)
JumpN(isize),
/// Load a value from constants table
LdConst(usize),
/// Load a value from locals table
LdLocal(usize),
/// Store a value to locals table
StLocal(usize),
/// Pop a value from stack
Pop,
/// Duplicate value on stack
Dup,
}

134
src/interpreter/context.rs Normal file
View file

@ -0,0 +1,134 @@
//! Evaluation context for Wisp
use super::{
bytecode::Instruction,
value::{Function, Value},
};
use crate::interpreter::value::NativeFun;
use std::rc::Rc;
use thiserror::Error;
type Result<T, E = Error> = std::result::Result<T, E>;
pub struct Context<'s> {
fun: Rc<Function<'s>>,
stack: Vec<Value<'s>>,
locals: Vec<Value<'s>>,
pc: usize,
}
impl<'s> Context<'s> {
/// Create a new evaluation context
pub fn new(fun: Rc<Function<'s>>) -> Self {
Self {
stack: vec![],
locals: vec![Value::Nil; fun.locals_len],
pc: 0,
fun,
}
}
/// Execute next instruction
pub fn tick(&mut self) -> Result<()> {
use Instruction::*;
match self
.fun
.bytecode
.get(self.pc)
.copied()
.ok_or(IndexOutOfBoundsError::Bytecode)?
{
Call(n) if n >= self.stack.len() => return Err(IndexOutOfBoundsError::Stack.into()),
Call(n) => {
let fun = self.stack_pop()?;
match fun {
Value::Function(_) => todo!("function calls"),
Value::NativeFun(NativeFun(f)) => {
let v = f(&self.stack.split_off(self.stack.len() - n));
self.stack.push(v);
}
_ => unimplemented!("invalid function type"),
}
}
Jump(o) => {
self.jump(o);
return Ok(());
}
JumpN(o) => {
if self.stack_pop()? == Value::Nil {
self.jump(o);
return Ok(());
}
}
LdConst(i) => {
self.stack.push(
self.fun
.consts
.get(i)
.cloned()
.ok_or(IndexOutOfBoundsError::Const)?,
);
}
LdLocal(i) => {
self.stack.push(
self.locals
.get(i)
.cloned()
.ok_or(IndexOutOfBoundsError::Local)?,
);
}
StLocal(i) => {
*self.locals.get_mut(i).ok_or(IndexOutOfBoundsError::Local)? = self.stack_pop()?;
}
Pop => {
self.stack_pop()?;
}
Dup => {
self.stack.push(
self.stack
.last()
.cloned()
.ok_or(IndexOutOfBoundsError::Stack)?,
);
}
}
self.pc += 1;
Ok(())
}
/// Jump by offset
fn jump(&mut self, offset: isize) {
(if offset.is_negative() {
std::ops::SubAssign::sub_assign
} else {
std::ops::AddAssign::add_assign
})(&mut self.pc, offset.unsigned_abs());
}
/// Pop a value from a stack
fn stack_pop(&mut self) -> Result<Value<'s>, IndexOutOfBoundsError> {
self.stack.pop().ok_or(IndexOutOfBoundsError::Stack)
}
}
#[derive(Debug, Clone, Error)]
pub enum Error {
#[error(transparent)]
IndexOutOfBounds(#[from] IndexOutOfBoundsError),
}
#[derive(Debug, Clone, Error)]
pub enum IndexOutOfBoundsError {
#[error("bytecode index out of bounds")]
Bytecode,
#[error("const index out of bounds")]
Const,
#[error("stack index out of bounds")]
Stack,
#[error("locals index out of bounds")]
Local,
}

View file

@ -7,4 +7,6 @@
//! A simple silly compiler + VM intended for testing purposes. //! A simple silly compiler + VM intended for testing purposes.
//! To be replaced by something better soon™ :ferrisClueless: //! To be replaced by something better soon™ :ferrisClueless:
pub mod bytecode;
pub mod context;
pub mod value; pub mod value;

View file

@ -1,8 +1,29 @@
use super::Str; use std::fmt::Debug;
use crate::syntax::ast::{Expr, Spanned};
use super::Value;
use crate::interpreter::bytecode::Instruction;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Function<'s> { pub struct Function<'s> {
params: Box<[Str<'s>]>, pub bytecode: Box<[Instruction]>,
ast: Box<[Spanned<Expr<'s>>]>, pub consts: Box<[Value<'s>]>,
pub locals_len: usize,
} }
#[derive(Clone)]
pub struct NativeFun<'s>(pub fn(&[Value<'s>]) -> Value<'s>);
impl<'s> Debug for NativeFun<'s> {
fn fmt<'a>(&'a self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("NativeFun")
.field(&(self.0 as fn(&'a _) -> _))
.finish()
}
}
impl<'s> PartialEq for NativeFun<'s> {
fn eq<'a>(&'a self, other: &Self) -> bool {
(self.0 as fn(&'a _) -> _) == other.0
}
}
impl<'s> Eq for NativeFun<'s> {}

View file

@ -2,7 +2,7 @@ mod function;
mod pair; mod pair;
mod string; mod string;
pub use function::Function; pub use function::{Function, NativeFun};
pub use pair::DotPair; pub use pair::DotPair;
pub use string::Str; pub use string::Str;
@ -24,7 +24,7 @@ pub enum Value<'s> {
Number(OrderedF64), Number(OrderedF64),
String(Str<'s>), String(Str<'s>),
Function(Rc<Function<'s>>), Function(Rc<Function<'s>>),
NativeFun(fn(&'s [Value<'s>]) -> Value<'s>), NativeFun(NativeFun<'s>),
Macro(Rc<Function<'s>>), Macro(Rc<Function<'s>>),
} }
@ -72,7 +72,7 @@ impl<'s> Display for Value<'s> {
} }
} }
fn fmt_sequence<'s, T>( fn fmt_sequence<T>(
f: &mut std::fmt::Formatter<'_>, f: &mut std::fmt::Formatter<'_>,
s: impl IntoIterator<Item = T>, s: impl IntoIterator<Item = T>,
start: &str, start: &str,

View file

@ -1,4 +1,4 @@
use std::{fmt::Display, ops::Deref, rc::Rc, hash::Hash}; use std::{fmt::Display, hash::Hash, ops::Deref, rc::Rc};
/// A Wisp string type which can hold two variants: /// A Wisp string type which can hold two variants:
/// - Borrowed (usually a reference to source code or built-in literal) /// - Borrowed (usually a reference to source code or built-in literal)