Functions are implemented and VM should be able to handle function calls
* VM Signals are used by running functions to dictate whether a function should return, or if it should call another function. These signals can be injected at any time allowing for user functions to inject themselves at runtime. * obj::Method is gone since it's not being used yet. * Obj impls must implement as_any(&self) -> &dyn Any now. This allows for UserFun and NativeFun to be explicitly cast (among other things, in the future). Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -1,34 +1,8 @@
|
|||||||
use crate::{obj::{reserved::*, prelude::*}, vm::{inst::Inst, Vm}};
|
use crate::{obj::{reserved::*, prelude::*}, vm::{inst::Inst, signal::*, Vm}};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use shredder::{GcSafeWrapper, Scan};
|
use shredder::{GcSafeWrapper, Scan};
|
||||||
use std::fmt::{Debug, Formatter, self};
|
use std::fmt::{Debug, Formatter, self};
|
||||||
|
|
||||||
#[derive(Debug, Scan)]
|
|
||||||
pub struct Method {
|
|
||||||
vtable: Vtable,
|
|
||||||
attrs: Attrs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Method {
|
|
||||||
pub fn new(this: ObjRef, func: ObjRef) -> Self {
|
|
||||||
Self {
|
|
||||||
vtable: Default::default(),
|
|
||||||
attrs: attrs! {
|
|
||||||
SELF_MEMBER_NAME.sym => this,
|
|
||||||
FUNC_MEMBER_NAME.sym => func,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_obj!(Method);
|
|
||||||
|
|
||||||
pub static CALL_METHOD_WRAPPER_FUN: Lazy<ObjRef<NativeFun>> = Lazy::new(|| {
|
|
||||||
NativeFun::new_obj(Box::new(|_vm, _fun, _args| {
|
|
||||||
todo!("__call__ function")
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// struct UserFun
|
// struct UserFun
|
||||||
//
|
//
|
||||||
@@ -48,6 +22,27 @@ pub struct UserFun {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UserFun {
|
impl UserFun {
|
||||||
|
pub fn new_obj(code: Vec<Inst>, locals: Names) -> UserFunRef {
|
||||||
|
let obj_ref = ObjRef::new(UserFun {
|
||||||
|
vtable: Default::default(), // this is a placeholder for the real vtable
|
||||||
|
attrs: Default::default(),
|
||||||
|
code,
|
||||||
|
locals,
|
||||||
|
});
|
||||||
|
|
||||||
|
// XXX replace the vtable before returning, since __call__ member points to the object
|
||||||
|
// itself
|
||||||
|
{
|
||||||
|
write_obj!(let obj = obj_ref);
|
||||||
|
obj.vtable = vtable! {
|
||||||
|
TY_MEMBER_NAME.sym => USER_FUN_TY.clone(),
|
||||||
|
CALL_MEMBER_NAME.sym => obj_ref.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
obj_ref
|
||||||
|
}
|
||||||
|
|
||||||
pub fn code(&self) -> &Vec<Inst> {
|
pub fn code(&self) -> &Vec<Inst> {
|
||||||
&self.code
|
&self.code
|
||||||
}
|
}
|
||||||
@@ -69,11 +64,14 @@ impl Debug for UserFun {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static USER_FUN_TY: Lazy<ObjRef<Ty>> = Lazy::new(|| Ty::new_obj(USER_FUN_NAME.sym_ref()));
|
||||||
|
|
||||||
//
|
//
|
||||||
// struct NativeFun
|
// struct NativeFun
|
||||||
//
|
//
|
||||||
|
|
||||||
pub type NativeFunPtr = Box<dyn Fn(&mut Vm, ObjRef, Vec<ObjRef>) + Send + Sync>;
|
pub type NativeFunPtr = Box<dyn (Fn(ObjRef, &mut Vm, Vec<ObjRef>) -> Signal) + Send + Sync>;
|
||||||
|
pub type NativeFunRef = ObjRef<NativeFun>;
|
||||||
|
|
||||||
#[derive(Scan)]
|
#[derive(Scan)]
|
||||||
pub struct NativeFun {
|
pub struct NativeFun {
|
||||||
@@ -89,12 +87,29 @@ pub struct NativeFun {
|
|||||||
|
|
||||||
impl NativeFun {
|
impl NativeFun {
|
||||||
pub fn new_obj(fun: NativeFunPtr) -> ObjRef<Self> {
|
pub fn new_obj(fun: NativeFunPtr) -> ObjRef<Self> {
|
||||||
ObjRef::new(Self {
|
let obj_ref = ObjRef::new(Self {
|
||||||
// TODO : vtable for NativeFun
|
|
||||||
vtable: Default::default(),
|
vtable: Default::default(),
|
||||||
attrs: Default::default(),
|
attrs: Default::default(),
|
||||||
fun: GcSafeWrapper::new(fun),
|
fun: GcSafeWrapper::new(fun),
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// XXX replace the vtable before returning, since __call__ member points to the object
|
||||||
|
// itself
|
||||||
|
{
|
||||||
|
write_obj!(let obj = obj_ref);
|
||||||
|
obj.vtable = vtable! {
|
||||||
|
TY_MEMBER_NAME.sym => NATIVE_FUN_TY.clone(),
|
||||||
|
CALL_MEMBER_NAME.sym => obj_ref.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
obj_ref
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the native function that can be invoked.
|
||||||
|
pub fn fun(&self) -> &NativeFunPtr {
|
||||||
|
&self.fun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +128,8 @@ impl Debug for NativeFun {
|
|||||||
|
|
||||||
impl_obj!(NativeFun);
|
impl_obj!(NativeFun);
|
||||||
|
|
||||||
|
pub static NATIVE_FUN_TY: Lazy<ObjRef<Ty>> = Lazy::new(|| Ty::new_obj(NATIVE_FUN_NAME.sym_ref()));
|
||||||
|
|
||||||
//
|
//
|
||||||
// Native function defs
|
// Native function defs
|
||||||
//
|
//
|
||||||
@@ -121,7 +138,7 @@ impl_obj!(NativeFun);
|
|||||||
// __get_attr__ *should* always bypass the __access__ function and get an attribute directly
|
// __get_attr__ *should* always bypass the __access__ function and get an attribute directly
|
||||||
|
|
||||||
pub static GET_ATTR_MEMBER_FUN: Lazy<ObjRef<NativeFun>> = Lazy::new(|| {
|
pub static GET_ATTR_MEMBER_FUN: Lazy<ObjRef<NativeFun>> = Lazy::new(|| {
|
||||||
NativeFun::new_obj(Box::new(|_vm, _fun, _args| {
|
NativeFun::new_obj(Box::new(|_caller, _vm, _args| {
|
||||||
/*
|
/*
|
||||||
let sym_ref = vm.pop();
|
let sym_ref = vm.pop();
|
||||||
let obj_ref = vm.pop();
|
let obj_ref = vm.pop();
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ macro_rules! impl_obj {
|
|||||||
fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> {
|
fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> {
|
||||||
Some(&mut self.$attrs)
|
Some(&mut self.$attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +41,10 @@ macro_rules! impl_obj_readonly {
|
|||||||
fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> {
|
fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,8 +57,9 @@ macro_rules! impl_obj_readonly {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! read_obj {
|
macro_rules! read_obj {
|
||||||
(let $lhs:ident $(: &$ty:ty)? = $obj:expr) => {
|
(let $lhs:ident $(: &$ty:ty)? = $obj:expr) => {
|
||||||
let __obj $(: &std::sync::RwLock<$ty>)? = &*$obj.get();
|
let __obj = &*$obj.get();
|
||||||
let $lhs $(: &$ty)? = &__obj.read().unwrap();
|
let __obj_deref = &__obj.read().unwrap();
|
||||||
|
let $lhs $(: &$ty)? = std::ops::Deref::deref(__obj_deref);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -60,7 +69,8 @@ macro_rules! read_obj {
|
|||||||
macro_rules! write_obj {
|
macro_rules! write_obj {
|
||||||
(let $lhs:ident $(: &$ty:ty)? = $obj:expr) => {
|
(let $lhs:ident $(: &$ty:ty)? = $obj:expr) => {
|
||||||
let __obj $(: &std::sync::RwLock<$ty>)? = &*$obj.get();
|
let __obj $(: &std::sync::RwLock<$ty>)? = &*$obj.get();
|
||||||
let $lhs $(: &$ty)? = &mut __obj.write().unwrap();
|
let __obj_deref = &mut __obj.write().unwrap();
|
||||||
|
let $lhs $(: &$ty)? = std::ops::DerefMut::deref_mut(__obj_deref);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ pub trait Obj: Scan + std::fmt::Debug {
|
|||||||
vtable.get(&sym).cloned()
|
vtable.get(&sym).cloned()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ impl NameInfo {
|
|||||||
name!(INT_NAME, "Int");
|
name!(INT_NAME, "Int");
|
||||||
name!(TY_NAME, "Type");
|
name!(TY_NAME, "Type");
|
||||||
name!(SYM_NAME, "Sym");
|
name!(SYM_NAME, "Sym");
|
||||||
|
name!(USER_FUN_NAME, "Fun");
|
||||||
|
name!(NATIVE_FUN_NAME, "NativeFun");
|
||||||
|
|
||||||
//
|
//
|
||||||
// Members
|
// Members
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ impl Obj for Sym {
|
|||||||
fn attrs_mut(&mut self) -> Option<&mut Attrs> {
|
fn attrs_mut(&mut self) -> Option<&mut Attrs> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> std::ops::Index<Sym> for Vec<T> {
|
impl<T> std::ops::Index<Sym> for Vec<T> {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ pub struct ConstPool {
|
|||||||
pool: Vec<ObjRef>,
|
pool: Vec<ObjRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constant pools are constant, and live for the lifetime of the entire program.
|
||||||
|
impl shredder::EmptyScan for ConstPool { }
|
||||||
|
|
||||||
impl ConstPool {
|
impl ConstPool {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use shredder::{GcSafe, Scan, Scanner};
|
|||||||
/// A stack call frame.
|
/// A stack call frame.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FrameKind {
|
pub enum FrameKind {
|
||||||
Root,
|
Native(NativeFunRef, Vec<ObjRef>),
|
||||||
Native,
|
|
||||||
User {
|
User {
|
||||||
last_pc: usize,
|
last_pc: usize,
|
||||||
stack_base: usize,
|
stack_base: usize,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{obj::prelude::*, vm::consts::*};
|
use crate::{obj::prelude::*, vm::consts::*};
|
||||||
|
use shredder::EmptyScan;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum Inst {
|
pub enum Inst {
|
||||||
@@ -19,7 +20,7 @@ pub enum Inst {
|
|||||||
/// This will get an attr from the object reference pointed to by the symbol.
|
/// This will get an attr from the object reference pointed to by the symbol.
|
||||||
GetAttr(Sym),
|
GetAttr(Sym),
|
||||||
|
|
||||||
/// A target reference and a source reference from the stack.
|
/// Pops a target reference and a source reference from the stack.
|
||||||
///
|
///
|
||||||
/// The target reference will have the given symbol attribute assigned to the source.
|
/// The target reference will have the given symbol attribute assigned to the source.
|
||||||
///
|
///
|
||||||
@@ -144,3 +145,5 @@ impl Inst {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EmptyScan for Inst { }
|
||||||
|
|||||||
176
src/vm/mod.rs
176
src/vm/mod.rs
@@ -1,14 +1,14 @@
|
|||||||
pub mod consts;
|
pub mod consts;
|
||||||
pub mod error;
|
pub mod error; // TODO : not needed?
|
||||||
pub mod frame;
|
pub mod frame;
|
||||||
pub mod inst;
|
pub mod inst;
|
||||||
pub mod package;
|
pub mod package;
|
||||||
|
pub(crate) mod signal;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
obj::{reserved::*, prelude::*},
|
obj::{reserved::*, prelude::*},
|
||||||
vm::{error::*, frame::*, inst::*, package::*},
|
vm::{frame::*, inst::*, package::*, signal::*},
|
||||||
};
|
};
|
||||||
use shredder::{GcSafe, Scanner, Scan};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Vm<'p> {
|
pub struct Vm<'p> {
|
||||||
@@ -19,15 +19,6 @@ pub struct Vm<'p> {
|
|||||||
package: &'p Package,
|
package: &'p Package,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Scan for Vm<'_> {
|
|
||||||
fn scan(&self, scanner: &mut Scanner) {
|
|
||||||
scanner.scan(&self.stack);
|
|
||||||
scanner.scan(&self.frames);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl GcSafe for Vm<'_> {}
|
|
||||||
|
|
||||||
impl<'p> Vm<'p> {
|
impl<'p> Vm<'p> {
|
||||||
pub fn new(package: &'p Package) -> Self {
|
pub fn new(package: &'p Package) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -101,20 +92,92 @@ impl<'p> Vm<'p> {
|
|||||||
self.condition = condition;
|
self.condition = condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a single instruction.
|
/// Resumes execution of the current program.
|
||||||
pub fn tick(&mut self) -> Result<()> {
|
pub fn resume(&mut self) {
|
||||||
self.do_tick()
|
while let Some(frame) = self.frame().cloned() {
|
||||||
|
let signal = match frame.kind() {
|
||||||
|
FrameKind::Native(fun, args) => {
|
||||||
|
read_obj!(let fun_obj = fun);
|
||||||
|
(fun_obj.fun())(fun.clone(), self, args.clone())
|
||||||
|
}
|
||||||
|
FrameKind::User { .. } => {
|
||||||
|
// run the user function until it returns control to the VM
|
||||||
|
loop {
|
||||||
|
if let Some(signal) = self.tick() {
|
||||||
|
break signal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.handle_signal(signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles a signal targeting the VM.
|
||||||
|
///
|
||||||
|
/// The signal may originate from the VM itself, or from an external location. Signals
|
||||||
|
/// generally interrupt control flow of a program.
|
||||||
|
pub fn handle_signal(&mut self, signal: Signal) {
|
||||||
|
match signal {
|
||||||
|
Signal::Call(caller, args) => {
|
||||||
|
self.begin_call(caller, args);
|
||||||
|
}
|
||||||
|
Signal::Return => {
|
||||||
|
let retval = self.stack.pop()
|
||||||
|
.expect("return value");
|
||||||
|
match self.frames.pop().unwrap().kind() {
|
||||||
|
// no-op; return value should be the TOS
|
||||||
|
FrameKind::Native(_, _) => { },
|
||||||
|
FrameKind::User { last_pc, stack_base, fun: _, } => {
|
||||||
|
// pop return value, reset PC, clean stack, and push return value
|
||||||
|
self.set_pc(*last_pc);
|
||||||
|
self.stack.truncate(*stack_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.stack.push(retval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up the stack for a function call.
|
||||||
|
fn begin_call(&mut self, caller: ObjRef, args: Vec<ObjRef>) {
|
||||||
|
// create stack frame
|
||||||
|
let stack_frame = if let Some(user_fun) = std::any::Any::downcast_ref::<UserFunRef>(&caller) {
|
||||||
|
let locals = {
|
||||||
|
read_obj!(let fun_ref = user_fun);
|
||||||
|
fun_ref.locals()
|
||||||
|
.iter()
|
||||||
|
.zip(args.into_iter())
|
||||||
|
.map(|((k, _), v)| (*k, v))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
Frame::new(locals, FrameKind::User {
|
||||||
|
last_pc: self.pc(),
|
||||||
|
stack_base: self.stack().len(),
|
||||||
|
fun: user_fun.clone(),
|
||||||
|
})
|
||||||
|
} else if let Some(native_fun) = std::any::Any::downcast_ref::<NativeFunRef>(&caller) {
|
||||||
|
Frame::new(Default::default(), FrameKind::Native(native_fun.clone(), args))
|
||||||
|
} else {
|
||||||
|
// TODO : throw an error when error handling is figured out
|
||||||
|
panic!("can't call object {:?}", caller);
|
||||||
|
};
|
||||||
|
|
||||||
|
// push a new stack frame
|
||||||
|
self.frames.push(stack_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current instruction.
|
/// Gets the current instruction.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn load_inst(&self) -> Inst {
|
fn load_inst(&self) -> Inst {
|
||||||
if let FrameKind::User { fun, .. } = self.frame().expect("frame").kind() {
|
let user_fun = if let FrameKind::User { fun, .. } = self.frame().expect("frame").kind() {
|
||||||
read_obj!(let fun = fun);
|
fun
|
||||||
fun.code()[self.pc()]
|
|
||||||
} else {
|
} else {
|
||||||
panic!("invalid stack frame");
|
panic!("expected user function frame")
|
||||||
}
|
};
|
||||||
|
read_obj!(let user_fun = user_fun);
|
||||||
|
user_fun.code()[self.pc()]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a single instruction - inlined version.
|
/// Run a single instruction - inlined version.
|
||||||
@@ -123,9 +186,10 @@ impl<'p> Vm<'p> {
|
|||||||
/// exposed API function `tick()` calls this function, but does not inline itself (unless the
|
/// exposed API function `tick()` calls this function, but does not inline itself (unless the
|
||||||
/// optimizer thinks it's a good idea to do so).
|
/// optimizer thinks it's a good idea to do so).
|
||||||
#[inline]
|
#[inline]
|
||||||
fn do_tick(&mut self) -> Result<()> {
|
fn tick(&mut self) -> Option<Signal> {
|
||||||
let inst = self.load_inst();
|
let inst = self.load_inst();
|
||||||
let mut next_pc = self.pc() + 1;
|
let mut next_pc = self.pc() + 1;
|
||||||
|
let mut signal = None;
|
||||||
match inst {
|
match inst {
|
||||||
Inst::PushSym(sym) => {
|
Inst::PushSym(sym) => {
|
||||||
let sym_ref = global_sym_ref(sym);
|
let sym_ref = global_sym_ref(sym);
|
||||||
@@ -138,9 +202,28 @@ impl<'p> Vm<'p> {
|
|||||||
.clone();
|
.clone();
|
||||||
self.push(obj_ref);
|
self.push(obj_ref);
|
||||||
}
|
}
|
||||||
Inst::LoadName(_sym) => todo!(),
|
Inst::LoadName(name) => {
|
||||||
Inst::Pop(Some(_sym)) => todo!(),
|
let obj_ref = self.frames()
|
||||||
Inst::Pop(None) => todo!(),
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|frame| frame.locals().get(&name))
|
||||||
|
.cloned()
|
||||||
|
.next();
|
||||||
|
if let Some(obj_ref) = obj_ref {
|
||||||
|
self.push(obj_ref);
|
||||||
|
} else {
|
||||||
|
todo!("TODO: implement \"name value not found\" lookup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Inst::Pop(name) => {
|
||||||
|
let tos = self.pop()
|
||||||
|
.expect("stack underflow");
|
||||||
|
// pop into name
|
||||||
|
if let Some(name) = name {
|
||||||
|
self.set_local(name, tos);
|
||||||
|
}
|
||||||
|
// else discard
|
||||||
|
}
|
||||||
Inst::GetAttr(sym) => {
|
Inst::GetAttr(sym) => {
|
||||||
let obj_ref = self.pop().expect("getattr object");
|
let obj_ref = self.pop().expect("getattr object");
|
||||||
read_obj!(let obj = obj_ref);
|
read_obj!(let obj = obj_ref);
|
||||||
@@ -149,7 +232,18 @@ impl<'p> Vm<'p> {
|
|||||||
.unwrap_or_else(|| global_sym_ref(NIL_NAME.sym));
|
.unwrap_or_else(|| global_sym_ref(NIL_NAME.sym));
|
||||||
self.push(attr);
|
self.push(attr);
|
||||||
}
|
}
|
||||||
Inst::SetAttr(_sym) => todo!(),
|
Inst::SetAttr(sym) => {
|
||||||
|
let target = self.pop()
|
||||||
|
.expect("no target available for SetAttr");
|
||||||
|
let source = self.pop()
|
||||||
|
.expect("no source available for SetAttr");
|
||||||
|
write_obj!(let target = target);
|
||||||
|
if let Some(attrs) = target.attrs_mut() {
|
||||||
|
attrs.insert(sym, source);
|
||||||
|
} else {
|
||||||
|
todo!("TODO: throw an error for attributes that can't be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
Inst::Jump(addr) => {
|
Inst::Jump(addr) => {
|
||||||
next_pc = addr;
|
next_pc = addr;
|
||||||
}
|
}
|
||||||
@@ -158,9 +252,14 @@ impl<'p> Vm<'p> {
|
|||||||
next_pc = addr;
|
next_pc = addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Inst::Call(_argc) => todo!(),
|
Inst::Call(argc) => {
|
||||||
|
let stack_top = self.stack.len() - argc;
|
||||||
|
let args = self.stack.split_off(stack_top);
|
||||||
|
let caller = self.pop().unwrap();
|
||||||
|
signal = Some(Signal::Call(caller, args));
|
||||||
|
}
|
||||||
Inst::Index => todo!(),
|
Inst::Index => todo!(),
|
||||||
Inst::Return => todo!(),
|
Inst::Return => { signal = Some(Signal::Return); }
|
||||||
Inst::UnNeg => todo!(),
|
Inst::UnNeg => todo!(),
|
||||||
Inst::UnPos => todo!(),
|
Inst::UnPos => todo!(),
|
||||||
Inst::BinPlus => todo!(),
|
Inst::BinPlus => todo!(),
|
||||||
@@ -177,7 +276,26 @@ impl<'p> Vm<'p> {
|
|||||||
Inst::BinOr => todo!(),
|
Inst::BinOr => todo!(),
|
||||||
}
|
}
|
||||||
self.set_pc(next_pc);
|
self.set_pc(next_pc);
|
||||||
//let mut next_index = self.
|
|
||||||
Ok(())
|
signal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_local(&mut self, name: Name, value: ObjRef) {
|
||||||
|
for frame in self.frames.iter_mut().rev() {
|
||||||
|
let locals = frame.locals_mut();
|
||||||
|
if locals.contains_key(&name) {
|
||||||
|
locals.insert(name, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!("unregistered name {:?}", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_local(&mut self, name: Name) -> Option<ObjRef> {
|
||||||
|
self.frames.iter()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|frame| frame.locals().get(&name))
|
||||||
|
.next()
|
||||||
|
.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::{obj::prelude::*, vm::{consts::ConstPool, inst::Inst}};
|
use crate::{obj::prelude::*, vm::{consts::ConstPool, inst::Inst}};
|
||||||
|
use shredder::Scan;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
/// A compiled package that can be executed by a VM.
|
/// A compiled package that can be executed by a VM.
|
||||||
#[derive(Debug)]
|
#[derive(Scan, Debug)]
|
||||||
pub struct Package {
|
pub struct Package {
|
||||||
names: Vec<Sym>, // local names mappings
|
names: Vec<Sym>, // local names mappings
|
||||||
const_pool: ConstPool,
|
const_pool: ConstPool,
|
||||||
@@ -31,6 +32,7 @@ impl Package {
|
|||||||
&self.code
|
&self.code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dumps a debug output of this package to the given writer.
|
||||||
pub fn dump(&self, writer: &mut dyn Write) -> io::Result<()> {
|
pub fn dump(&self, writer: &mut dyn Write) -> io::Result<()> {
|
||||||
// column widths
|
// column widths
|
||||||
let addr_w = num_digits(self.code().len(), 16).max(4);
|
let addr_w = num_digits(self.code().len(), 16).max(4);
|
||||||
@@ -48,9 +50,6 @@ impl Package {
|
|||||||
{
|
{
|
||||||
let obj_ref = self.const_pool().get(*hdl);
|
let obj_ref = self.const_pool().get(*hdl);
|
||||||
read_obj!(let obj = obj_ref);
|
read_obj!(let obj = obj_ref);
|
||||||
// XXX weirdness with coercion, can't deref as a &dyn Obj because
|
|
||||||
// RwReadLockGuard is not Obj - but using Deref::deref works
|
|
||||||
let obj: &dyn Obj = std::ops::Deref::deref(obj);
|
|
||||||
format!("{:?}", obj)
|
format!("{:?}", obj)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
8
src/vm/signal.rs
Normal file
8
src/vm/signal.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use crate::obj::ObjRef;
|
||||||
|
|
||||||
|
/// A signal from executing code in the VM.
|
||||||
|
pub enum Signal {
|
||||||
|
Call(ObjRef, Vec<ObjRef>),
|
||||||
|
Return,
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user