diff --git a/Cargo.toml b/Cargo.toml index 1c3dddf..4277e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ cfgrammar = "0.9" lrpar = "0.9" lrlex = "0.9" structopt = "0.3" +snafu = "0.6.6" -#snafu = "0.6.6" #lazy_static = "1.4.0" #regex = "1.3.7" #derivative = "2.1.1" diff --git a/runtime/src/obj/attrs.rs b/runtime/src/obj/attrs.rs index e14cd8a..c04051a 100644 --- a/runtime/src/obj/attrs.rs +++ b/runtime/src/obj/attrs.rs @@ -1,14 +1,31 @@ -use crate::{obj::{Obj, ObjRef, sym::Sym}}; -use std::collections::BTreeMap; +use crate::obj::{ObjRef, sym::Sym}; +use shredder::{Gc, Scan}; +use std::{collections::BTreeMap, ops::{Deref, DerefMut}}; pub type Attrs = BTreeMap; +pub type VtableAttrs = Gc; -impl Obj for Attrs { - fn attrs(&self) -> &Attrs { - self - } +#[derive(Scan, Debug, Clone, Default)] +pub struct Vtable { + attrs: VtableAttrs, +} - fn attrs_mut(&mut self) -> Option<&mut Attrs> { - Some(self) +impl Vtable { + pub fn new(attrs: Attrs) -> Self { + Self { attrs: Gc::new(attrs), } + } +} + +impl Deref for Vtable { + type Target = VtableAttrs; + + fn deref(&self) -> &Self::Target { + &self.attrs + } +} + +impl DerefMut for Vtable { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.attrs } } diff --git a/runtime/src/obj/fun.rs b/runtime/src/obj/fun.rs index 381c71e..051caae 100644 --- a/runtime/src/obj/fun.rs +++ b/runtime/src/obj/fun.rs @@ -3,11 +3,38 @@ use once_cell::sync::Lazy; use shredder::{GcSafeWrapper, Scan}; 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> = Lazy::new(|| { + NativeFun::new_obj(Box::new(|_vm, _fun, _args| { + todo!("__call__ function") + })) +}); + // // struct UserFun // #[derive(Scan)] pub struct UserFun { + vtable: Vtable, attrs: Attrs, // Safe because Vec doesn't need to be scanned #[shredder(unsafe_skip)] @@ -27,7 +54,7 @@ impl UserFun { } } -impl_obj!(UserFun, attrs); +impl_obj!(UserFun); impl Debug for UserFun { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { @@ -43,11 +70,14 @@ impl Debug for UserFun { // struct NativeFun // +pub type NativeFunPtr = Box) + Send + Sync>; + #[derive(Scan)] pub struct NativeFun { - #[shredder(skip)] - fun: GcSafeWrapper>, + vtable: Vtable, attrs: Attrs, + #[shredder(skip)] + fun: GcSafeWrapper, } // @@ -55,23 +85,13 @@ pub struct NativeFun { // impl NativeFun { - pub fn new(fun: Box) -> ObjRef { - let obj_ref = ObjRef::new(Self { - fun: GcSafeWrapper::new(fun), + pub fn new_obj(fun: NativeFunPtr) -> ObjRef { + ObjRef::new(Self { + // TODO : vtable for NativeFun + vtable: Default::default(), attrs: Default::default(), - }); - - { - write_obj!(let obj = obj_ref); - obj.set_attr(*CALL_MEMBER_SYM, obj_ref.clone()); - obj.set_attr(*GET_ATTR_MEMBER_SYM, GET_ATTR_MEMBER_FUN.clone()); - } - - obj_ref - } - - pub fn call(&self, vm: &mut Vm) { - (self.fun)(vm) + fun: GcSafeWrapper::new(fun), + }) } } @@ -88,7 +108,7 @@ impl Debug for NativeFun { } } -impl_obj!(NativeFun, attrs); +impl_obj!(NativeFun); // // Native function defs @@ -98,10 +118,8 @@ impl_obj!(NativeFun, attrs); // __get_attr__ *should* always bypass the __access__ function and get an attribute directly pub static GET_ATTR_MEMBER_FUN: Lazy> = Lazy::new(|| { - NativeFun::new(Box::new(|_vm| { + NativeFun::new_obj(Box::new(|_vm, _fun, _args| { /* - * TODO - need SymObj or something like that, which can be used as an ObjRef - since that's - * all we'll have access to at runtime anyway let sym_ref = vm.pop(); let obj_ref = vm.pop(); obj_ref.access() diff --git a/runtime/src/obj/int.rs b/runtime/src/obj/int.rs index c4fbb44..a6ffbf5 100644 --- a/runtime/src/obj/int.rs +++ b/runtime/src/obj/int.rs @@ -1,24 +1,24 @@ -use crate::obj::{names::*, prelude::*}; +use crate::obj::prelude::*; use shredder::Scan; +pub type IntRef = ObjRef; + #[derive(Debug, Scan)] pub struct Int { value: i64, + vtable: Vtable, attrs: Attrs, } impl Int { - pub fn new(value: i64) -> ObjRef { + pub fn new_obj(value: i64) -> ObjRef { + // TODO : vtable for Int let obj_ref = ObjRef::new(Self { value, + vtable: Default::default(), attrs: Default::default(), }); - { - write_obj!(let obj = obj_ref); - obj.set_attr(*GET_ATTR_MEMBER_SYM, GET_ATTR_MEMBER_FUN.clone()); - } - obj_ref } @@ -27,4 +27,4 @@ impl Int { } } -impl_obj!(Int, attrs); +impl_obj_readonly!(Int); diff --git a/runtime/src/obj/macros.rs b/runtime/src/obj/macros.rs index 2466aa9..d6634b3 100644 --- a/runtime/src/obj/macros.rs +++ b/runtime/src/obj/macros.rs @@ -1,17 +1,48 @@ /// Implements `Obj` for a given type and using the given member for attributes. #[macro_export] macro_rules! impl_obj { - ($ty:ty, $attrs:ident) => { + ($ty:ty, $vtable:ident, $attrs:ident) => { impl $crate::obj::Obj for $ty { - fn attrs(&self) -> &Attrs { + fn vtable(&self) -> &$crate::obj::attrs::Vtable { + &self.$vtable + } + + fn attrs(&self) -> &$crate::obj::attrs::Attrs { &self.$attrs } - fn attrs_mut(&mut self) -> Option<&mut Attrs> { + fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> { Some(&mut self.$attrs) } } }; + + ($ty:ty) => { + impl_obj!($ty, vtable, attrs); + } +} + +#[macro_export] +macro_rules! impl_obj_readonly { + ($ty:ty, $vtable:ident, $attrs:ident) => { + impl $crate::obj::Obj for $ty { + fn vtable(&self) -> &$crate::obj::attrs::Vtable { + &self.$vtable + } + + fn attrs(&self) -> &$crate::obj::attrs::Attrs { + &self.$attrs + } + + fn attrs_mut(&mut self) -> Option<&mut $crate::obj::attrs::Attrs> { + None + } + } + }; + + ($ty:ty) => { + impl_obj_readonly!($ty, vtable, attrs); + }; } /// Locks a `ObjRef` type for reading. @@ -43,3 +74,14 @@ macro_rules! attrs { maplit::btreemap! { } }} } + +#[macro_export] +macro_rules! vtable { + ($($key:expr => $value:expr),+ $(,)?) => {{ + $crate::obj::attrs::Vtable::new(maplit::btreemap! { $($key => ($value as _) ),+ }) + }}; + + () => {{ + $crate::obj::attrs::Vtable::new(maplit::btreemap! { }) + }} +} diff --git a/runtime/src/obj/mod.rs b/runtime/src/obj/mod.rs index f01c7e6..a4c53d6 100644 --- a/runtime/src/obj/mod.rs +++ b/runtime/src/obj/mod.rs @@ -8,8 +8,9 @@ pub mod intern; pub mod names; pub mod str; pub mod sym; +#[cfg(test)] +mod test; pub mod ty; -#[cfg(test)] mod test; pub mod prelude { pub use crate::obj::{attrs::*, fun::*, int::*, intern::*, str::*, sym::*, ty::*, Obj, ObjRef}; @@ -17,8 +18,8 @@ pub mod prelude { use shredder::{Gc, Scan}; use std::{ - ops::{Deref, DerefMut, CoerceUnsized}, marker::Unsize, + ops::{CoerceUnsized, Deref, DerefMut}, sync::RwLock, }; @@ -30,11 +31,18 @@ use sym::Sym; // pub trait Obj: Scan + std::fmt::Debug { + fn vtable(&self) -> &Vtable; fn attrs(&self) -> &Attrs; fn attrs_mut(&mut self) -> Option<&mut Attrs>; - fn get_attr(&self, sym: &Sym) -> Option<&ObjRef> { - self.attrs().get(&sym) + fn get_attr(&self, sym: &Sym) -> Option { + self.attrs() + .get(&sym) + .cloned() + .or_else(|| { + let vtable = self.vtable().get(); + vtable.get(&sym).cloned() + }) } fn set_attr(&mut self, sym: Sym, value: ObjRef) -> Option { @@ -47,13 +55,21 @@ pub trait Obj: Scan + std::fmt::Debug { // #[derive(Debug, Scan)] -pub struct ObjRef { +pub struct ObjRef +where + T: Obj + ?Sized + Send + Sync, +{ gc: Gc>, } -impl Clone for ObjRef { +impl Clone for ObjRef +where + T: Obj + ?Sized + Send + Sync, +{ fn clone(&self) -> Self { - ObjRef { gc: self.gc.clone() } + ObjRef { + gc: self.gc.clone(), + } } } @@ -61,7 +77,10 @@ impl Clone for ObjRef { // impl ObjRef // -impl ObjRef { +impl ObjRef +where + T: Obj + ?Sized + Send + Sync, +{ /// Check object reference equality. pub fn ref_eq(&self, other: &Self) -> bool { let lhs: &RwLock = &*self.gc.get(); @@ -70,7 +89,10 @@ impl ObjRef { } } -impl ObjRef { +impl ObjRef +where + T: Obj + Send + Sync + 'static, +{ pub fn new(obj: T) -> Self { ObjRef { gc: Gc::new(RwLock::new(obj)), diff --git a/runtime/src/obj/names.rs b/runtime/src/obj/names.rs index 6135453..5cc0680 100644 --- a/runtime/src/obj/names.rs +++ b/runtime/src/obj/names.rs @@ -1,33 +1,49 @@ -use crate::obj::sym::{Sym, global_sym}; +use crate::obj::sym::{Sym, SymRef, global_sym, global_sym_ref}; use once_cell::sync::Lazy; macro_rules! name { - ($name:ident, $sym_name:ident, $text:expr $(,)?) => { - pub const $name: &str = $text; - pub static $sym_name: Lazy = Lazy::new(|| global_sym($name.to_string())); + ($name:ident, $text:expr $(,)?) => { + pub static $name: Lazy = Lazy::new(|| { + let name = $text; + let sym = global_sym(name.to_string()); + NameInfo { name, sym, } + }); + } +} + +pub struct NameInfo { + pub name: &'static str, + pub sym: Sym, +} + +impl NameInfo { + pub fn sym_ref(&self) -> SymRef { + global_sym_ref(self.name.to_string()) } } // // Types // -name!(INT_TY_NAME, INT_TY_SYM, "Int"); -name!(TY_TY_NAME, TY_TY_SYM, "Type"); -name!(SYM_TY_NAME, SYM_TY_SYM, "Sym"); +name!(INT_NAME, "Int"); +name!(TY_NAME, "Type"); +name!(SYM_NAME, "Sym"); // // Members // -name!(TY_MEMBER_NAME, TY_MEMBER_SYM, "__type__"); -name!(CALL_MEMBER_NAME, CALL_MEMBER_SYM, "__call__"); -name!(NAME_MEMBER_NAME, NAME_MEMBER_SYM, "__name__"); -name!(GET_ATTR_MEMBER_NAME, GET_ATTR_MEMBER_SYM, "__get_attr__"); -name!(SET_ATTR_MEMBER_NAME, SET_ATTR_MEMBER_SYM, "__set_attr__"); +name!(TY_MEMBER_NAME, "__type__"); +name!(CALL_MEMBER_NAME, "__call__"); +name!(NAME_MEMBER_NAME, "__name__"); +name!(GET_ATTR_MEMBER_NAME, "__get_attr__"); +name!(SET_ATTR_MEMBER_NAME, "__set_attr__"); +name!(SELF_MEMBER_NAME, "__self__"); +name!(FUNC_MEMBER_NAME, "__func__"); // // Predefined VM-aware symbols // -name!(LOCAL_NAME, LOCAL_SYM, "__local__"); +name!(SCOPE_NAME, "__scope__"); // // Builtin functions @@ -39,17 +55,17 @@ name!(LOCAL_NAME, LOCAL_SYM, "__local__"); // // Builtin constants // -name!(TRUE_NAME, TRUE_SYM, "true"); -name!(FALSE_NAME, FALSE_SYM, "false"); -name!(NIL_NAME, NIL_SYM, "nil"); +name!(TRUE_NAME, "true"); +name!(FALSE_NAME, "false"); +name!(NIL_NAME, "nil"); // Operator function names -name!(EQ_EQ_OP_NAME, EQ_EQ_OP_SYM, "__eq__"); -name!(LT_OP_NAME, LT_OP_SYM, "__lt__"); -name!(GT_OP_NAME, GT_OP_SYM, "__gt__"); -name!(LT_EQ_OP_NAME, LT_EQ_OP_SYM, "__le__"); -name!(GT_EQ_OP_NAME, GT_EQ_OP_SYM, "__ge__"); -name!(PLUS_OP_NAME, PLUS_OP_SYM, "__add__"); -name!(MINUS_OP_NAME, MINUS_OP_SYM, "__sub__"); -name!(TIMES_OP_NAME, TIMES_OP_SYM, "__mul__"); -name!(DIV_OP_NAME, DIV_OP_SYM, "__div__"); +name!(EQ_EQ_OP_NAME, "__eq__"); +name!(LT_OP_NAME, "__lt__"); +name!(GT_OP_NAME, "__gt__"); +name!(LT_EQ_OP_NAME, "__le__"); +name!(GT_EQ_OP_NAME, "__ge__"); +name!(PLUS_OP_NAME, "__add__"); +name!(MINUS_OP_NAME, "__sub__"); +name!(TIMES_OP_NAME, "__mul__"); +name!(DIV_OP_NAME, "__div__"); diff --git a/runtime/src/obj/str.rs b/runtime/src/obj/str.rs index c61c462..9270e2a 100644 --- a/runtime/src/obj/str.rs +++ b/runtime/src/obj/str.rs @@ -1,11 +1,27 @@ use crate::obj::prelude::*; use shredder::Scan; +pub type StrRef = ObjRef; + #[derive(Debug, Scan)] pub struct Str { - value: String, + vtable: Vtable, attrs: Attrs, + value: String, } -impl_obj!(Str, attrs); +impl Str { + pub fn new_obj(value: String) -> StrRef { + StrRef::new(Str { + vtable: Default::default(), + attrs: Default::default(), + value, + }) + } + pub fn value(&self) -> &String { + &self.value + } +} + +impl_obj_readonly!(Str); diff --git a/runtime/src/obj/sym.rs b/runtime/src/obj/sym.rs index f95fcb5..ca79c0b 100644 --- a/runtime/src/obj/sym.rs +++ b/runtime/src/obj/sym.rs @@ -1,26 +1,37 @@ -use crate::obj::{prelude::*, intern::Interner, names::*}; +use crate::obj::{intern::Interner, names::*, prelude::*}; use once_cell::sync::Lazy; use shredder::Scan; -use std::sync::Mutex; +use std::{collections::BTreeMap, sync::Mutex}; // // struct Sym // +pub type SymRef = ObjRef; + /// A literal name or symbol. #[derive(Scan, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Sym(usize); impl Sym { - pub fn new(sym: usize) -> Self { - Sym(sym) - } - + /// Gets the index of this symbol. pub fn index(&self) -> usize { self.0 } - pub fn new_obj(sym: impl Into) -> ObjRef { + /// Creates a new symbol. + /// + /// Generally, new symbols should be created through the `global_sym` function - take care + /// while using this function. + pub(super) fn new(sym: usize) -> Self { + Sym(sym) + } + + /// Creates a new symbol object. + /// + /// Generally, new symbol objects should be created through the `global_sym_ref` function - + /// take care while using this function. + pub(super) fn new_obj(sym: impl Into) -> SymRef { ObjRef::new(sym.into()) } } @@ -32,63 +43,75 @@ impl From for Sym { } impl Obj for Sym { + fn vtable(&self) -> &Vtable { + &SYM_VTABLE + } + fn attrs(&self) -> &Attrs { &SYM_ATTRS } - fn attrs_mut(&mut self) -> Option<&mut Attrs> { None } + fn attrs_mut(&mut self) -> Option<&mut Attrs> { + None + } +} + +impl std::ops::Index for Vec { + type Output = T; + + fn index(&self, sym: Sym) -> &Self::Output { + &self[sym.index()] + } } // -// Symbol Ty object +// Sym Ty object // -pub static NIL_OBJ: Lazy> = Lazy::new(|| Sym::new_obj(*NIL_SYM)); +pub static SYM_TY: Lazy> = Lazy::new(|| Ty::new_obj(SYM_NAME.sym_ref())); -pub static TRUE_OBJ: Lazy> = Lazy::new(|| Sym::new_obj(*TRUE_SYM)); - -pub static FALSE_OBJ: Lazy> = Lazy::new(|| Sym::new_obj(*FALSE_SYM)); - -pub static SYM_TY: Lazy> = Lazy::new(|| Ty::new(SYM_TY_SYM_OBJ.clone())); - -/// The Sym type's name object (as a symbol). -pub static SYM_TY_SYM_OBJ: Lazy> = Lazy::new(|| Sym::new_obj(*SYM_TY_SYM)); - -pub static SYM_ATTRS: Lazy = Lazy::new(|| attrs! { - *TY_MEMBER_SYM => SYM_TY.clone(), - *NAME_MEMBER_SYM => SYM_TY_SYM_OBJ.clone(), -}); +/// Symbols have no attributes. This is mostly because it's a pain to do cyclic references at this +/// point. +pub static SYM_ATTRS: Lazy = Lazy::new(|| attrs! {}); +pub static SYM_VTABLE: Lazy = Lazy::new(|| vtable! {}); // -// global symbols table +// global interned symbol obj table // +pub(crate) static SYM_REFS: Lazy>> = + Lazy::new(|| Mutex::new(Default::default())); -pub(crate) static SYMBOLS: Lazy> = Lazy::new(|| { - Mutex::new(SymTable::default()) -}); - -/* -pub(crate) fn global_name_lookup(sym: Sym) -> Option> { - let table = SYMBOLS.lock() - .unwrap(); - table.lookup(sym) +/// Access or insert a globally interned symbol object. +/// +/// If the given symbol name doesn't have a corresponding object, it will be created. +pub fn global_sym_ref(s: String) -> SymRef { + let sym = global_sym(s); + let mut refs = SYM_REFS.lock().unwrap(); + if let Some(obj) = refs.get(&sym) { + obj.clone() + } else { + let sym_ref = Sym::new_obj(sym); + refs.insert(sym, sym_ref.clone()); + sym_ref + } } -pub(crate) fn global_sym_lookup(s: String) -> Option { - let table = SYMBOLS.lock() - .unwrap(); - table.lookup_sym(s) -} -*/ +// +// global interned symbol table +// -pub(crate) fn global_sym(s: String) -> Sym { - let mut table = SYMBOLS.lock() - .unwrap(); +pub(crate) static SYMS: Lazy> = Lazy::new(|| Mutex::new(SymTable::default())); + +/// Access or insert a globally interned symbol. +/// +/// If the given symbol doesn't exist, it will be created. +pub fn global_sym(s: String) -> Sym { + let mut table = SYMS.lock().unwrap(); table.insert(s) } // -// SymTable +// SymTable interner types // pub type SymTable = Interner; diff --git a/runtime/src/obj/test.rs b/runtime/src/obj/test.rs index 0608c9a..316dee4 100644 --- a/runtime/src/obj/test.rs +++ b/runtime/src/obj/test.rs @@ -15,7 +15,7 @@ fn test_sym_plumbing() { run_with_gc_cleanup(|| { assert_eq!(number_of_tracked_allocations(), start + 0); - let nil = NIL_OBJ.clone(); + let nil = NIL_NAME.sym_ref(); // nil sym obj assert_eq!(number_of_tracked_allocations(), start + 1); @@ -23,41 +23,32 @@ fn test_sym_plumbing() { { read_obj!(let nil_obj = nil); let sym: Sym = **nil_obj; - assert_eq!(*NIL_SYM, sym); + assert_eq!(NIL_NAME.sym, sym); assert_eq!(number_of_tracked_allocations(), start + 1); - // nil_obj.attrs will initialize: - // - SYM_ATTRS (not an object) - // + SYM_TY - // + SYM_TY_SYM_OBJ - // - SYM_TY_SYM (not an object) - // + TY_MEMBER_SYM - // + TY_TY - // - SYM_TY_SYM_OBJ (already initialized) nil_obj.attrs(); - let ty_sym_obj = SYM_TY_SYM_OBJ.clone(); - assert_eq!(number_of_tracked_allocations(), start + 5); + let ty_sym_obj = SYM_NAME.sym_ref(); + assert_eq!(number_of_tracked_allocations(), start + 2); } - let on = TRUE_OBJ.clone(); + let on = TRUE_NAME.sym_ref(); // true sym obj, sym ty obj shouldn't be duplicated - assert_eq!(number_of_tracked_allocations(), start + 6); + assert_eq!(number_of_tracked_allocations(), start + 3); - let off = FALSE_OBJ.clone(); + let off = FALSE_NAME.sym_ref(); // false sym obj, sym ty obj shouldn't be duplicated - assert_eq!(number_of_tracked_allocations(), start + 7); + assert_eq!(number_of_tracked_allocations(), start + 4); }); // these are *static* values, so there will always remain at least one reference. - assert_eq!(number_of_tracked_allocations(), start + 7); - // TODO ^ prove the above + assert_eq!(number_of_tracked_allocations(), start + 4); } #[test] fn test_dyn_obj_ref_eq() { #[derive(Default, Debug, Scan)] - struct FooObj { attrs: Attrs } + struct FooObj { vtable: Attrs, attrs: Attrs } - impl_obj!(FooObj, attrs); + impl_obj!(FooObj, vtable, attrs); let _guard = TEST_LOCK.lock().unwrap(); let start = number_of_tracked_allocations(); // need 'start' because of static allocations diff --git a/runtime/src/obj/ty.rs b/runtime/src/obj/ty.rs index a93361a..224e604 100644 --- a/runtime/src/obj/ty.rs +++ b/runtime/src/obj/ty.rs @@ -4,40 +4,31 @@ use shredder::Scan; #[derive(Scan, Debug)] pub struct Ty { + vtable: Vtable, attrs: Attrs, } impl Ty { - pub fn new(name: ObjRef) -> ObjRef { + pub fn new_obj(name: ObjRef) -> ObjRef { // Ty objects have these attributes: // __ty__ - always the Type type // __name__ - this type's name as a symbol ObjRef::new(Ty { - attrs: attrs! { - *TY_MEMBER_SYM => TY_TY.clone(), - *NAME_MEMBER_SYM => name.clone(), - } + vtable: vtable! { + TY_MEMBER_NAME.sym => TY_TY.clone(), + NAME_MEMBER_NAME.sym => name.clone(), + }, + attrs: Default::default(), }) } } -pub static TY_TY_SYM_OBJ: Lazy> = Lazy::new(|| { - Sym::new_obj(*TY_TY_SYM) -}); - pub static TY_TY: Lazy> = Lazy::new(|| { - let ty = ObjRef::new(Ty { - attrs: attrs! { - *NAME_MEMBER_SYM => TY_TY_SYM_OBJ.clone(), - } - }); - - // add self-reference - { - write_obj!(let ty_obj = ty); - ty_obj.set_attr(*TY_MEMBER_SYM, ty.clone()); - } - ty + // TODO : vtable for Ty + ObjRef::new(Ty { + vtable: Default::default(), + attrs: Default::default(), + }) }); -impl_obj!(Ty, attrs); +impl_obj!(Ty); diff --git a/runtime/src/vm/consts.rs b/runtime/src/vm/consts.rs new file mode 100644 index 0000000..bfc7c88 --- /dev/null +++ b/runtime/src/vm/consts.rs @@ -0,0 +1,47 @@ +use crate::obj::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ConstHandle(usize); + +impl ConstHandle { + pub fn index(&self) -> usize { + self.0 + } + + pub fn new(handle: usize) -> Self { + ConstHandle(handle) + } +} + +#[derive(Debug, Default)] +pub struct ConstPool { + pool: Vec, +} + +impl ConstPool { + pub fn new() -> Self { + Default::default() + } + + pub fn push(&mut self, value: ObjRef) -> ConstHandle { + let hdl = ConstHandle::new(self.pool.len()); + self.pool.push(value); + hdl + } + + pub fn get(&self, hdl: ConstHandle) -> &ObjRef { + self.pool.get(hdl.index()).unwrap() + } + + pub fn len(&self) -> usize { + self.pool.len() + } +} + +impl std::ops::Index for ConstPool { + type Output = ObjRef; + + fn index(&self, hdl: ConstHandle) -> &Self::Output { + self.get(hdl) + } +} diff --git a/runtime/src/vm/inst.rs b/runtime/src/vm/inst.rs index 1015f12..2041938 100644 --- a/runtime/src/vm/inst.rs +++ b/runtime/src/vm/inst.rs @@ -1,27 +1,33 @@ -use crate::obj::prelude::*; +use crate::{obj::prelude::*, vm::consts::ConstHandle}; #[derive(Debug, PartialEq)] pub enum Inst { /// Push a literal symbol object to the stack. PushSym(Sym), + /// Push a const value reference to the stack. + PushConst(ConstHandle), + + /// Looks up and pushes a local value. + PushLocal(Sym), + /// Pop a value from the stack, possibly into a local symbol. Pop(Option), /// Pops a symbol value and an object reference. /// /// This will get an attr from the object reference pointed to by the symbol. - /// - /// If the object reference is `:__local__`, this instruction will push a local value. - GetAttr, + GetAttr(Sym), - /// Pops an object reference, a symbol value, and another object reference. + /// A target reference and a source reference from the stack. /// - /// This will set an attr of the second object ref to the first object ref, with the symbol - /// value name. + /// The target reference will have the given symbol attribute assigned to the source. /// - /// If the given value is `:__local__`, this instruction will set a local value. - SetAttr, + /// In code, it would look like this: + /// + /// target.symbol = source + /// + SetAttr(Sym), /// Jump to a given address in the current function unconditionally. Jump(usize), @@ -31,11 +37,75 @@ pub enum Inst { /// The condition flag may be set by an internal function. JumpTrue(usize), - /// Pops the top item off of the stack, followed by the number of arguments supplied, and - /// attempts to run the `__call__` attribute of the object. + /// Calls a function with the supplied number of arguments. + /// + /// The stack, from bottom to top, should contain the function followed by the arguments. + /// + /// After the function has returned, the VM will have popped the arguments and function + /// pointer, and the return value will be on top of the stack. Call(usize), + /// Indexes a value, e.g. a list or a dict. + /// + /// The stack, from bottom to top, should have the expression being indexed, and then the + /// indexed value. + Index, + /// Pops the top value from the stack, and returns from the function, using the popped value as /// a return value. Return, + + /// Replaces the top stack value with its negation applied. + UnNeg, + + /// Replaces the top stack value with its absolute value applied. + UnPos, + + /// Pops the top two items off of the stack, and applies the binary addition operator to them, + /// pushing the result to the stack. + BinPlus, + + /// Pops the top two items off of the stack, and applies the binary subtraction operator to + /// them, pushing the result to the stack. + BinMinus, + + /// Pops the top two items off of the stack, and applies the binary multiplication operator to + /// them, pushing the result to the stack. + BinMul, + + /// Pops the top two items off of the stack, and applies the binary division operator to them, + /// pushing the result to the stack. + BinDiv, + + /// Pops the top two items off of the stack, and applies the boolean equality operator to them, + /// pushing the result to the stack. + BinEq, + + /// Pops the top two items off of the stack, and applies the boolean inequality operator to + /// them, pushing the result to the stack. + BinNeq, + + /// Pops the top two items off of the stack, and applies the boolean less-than operator to + /// them, pushing the result to the stack. + BinLt, + + /// Pops the top two items off of the stack, and applies the binary less-than or equals + /// operator to them, pushing the result to the stack. + BinLe, + + /// Pops the top two items off of the stack, and applies the boolean greater-than operator to + /// them, pushing the result to the stack. + BinGt, + + /// Pops the top two items off of the stack, and applies the binary greater-than or equals + /// operator to them, pushing the result to the stack. + BinGe, + + /// Pops the top two items off of the stack, and applies the boolean and operator to them, + /// pushing the result to the stack. + BinAnd, + + /// Pops the top two items off of the stack, and applies the boolean or operator to them, + /// pushing the result to the stack. + BinOr, } diff --git a/runtime/src/vm/mod.rs b/runtime/src/vm/mod.rs index 7567f8a..0ecc3ad 100644 --- a/runtime/src/vm/mod.rs +++ b/runtime/src/vm/mod.rs @@ -1,5 +1,6 @@ mod frame; pub mod inst; +pub mod consts; use crate::obj::prelude::*; diff --git a/src/compile/error.rs b/src/compile/error.rs new file mode 100644 index 0000000..04fd678 --- /dev/null +++ b/src/compile/error.rs @@ -0,0 +1,12 @@ +use snafu::Snafu; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("invalid assignment target"))] + InvalidLhs, + + #[snafu(display("invalid object attribute"))] + InvalidAttr, +} + +pub type Result = std::result::Result; diff --git a/src/compile/mod.rs b/src/compile/mod.rs new file mode 100644 index 0000000..08ec7aa --- /dev/null +++ b/src/compile/mod.rs @@ -0,0 +1,74 @@ +pub mod thunk; +pub mod error; + +use runtime::{obj::prelude::*, vm::consts::*}; +use std::collections::HashMap; + +pub struct Compile<'t> { + text: &'t str, + const_data: ConstData, +} + +impl<'t> Compile<'t> { + pub fn new(text: &'t str) -> Self { + Compile { text, const_data: Default::default() } + } + + pub fn text(&self) -> &'t str { + self.text + } + + pub fn const_data(&self) -> &ConstData { + &self.const_data + } + + pub fn const_data_mut(&mut self) -> &mut ConstData { + &mut self.const_data + } + + /// Gets or inserts a static int reference. + pub fn const_int(&mut self, int: i64) -> (ConstHandle, IntRef) { + self.const_data_mut() + .const_int(int) + } + + /// Gets or inserts a static string reference. + pub fn const_str(&mut self, s: impl AsRef) -> (ConstHandle, StrRef) { + self.const_data_mut() + .const_str(s) + } +} + +#[derive(Debug, Default)] +pub struct ConstData { + ints: HashMap, + strs: HashMap, + const_pool: ConstPool, +} + +impl ConstData { + /// Gets or inserts a static int reference. + pub fn const_int(&mut self, int: i64) -> (ConstHandle, IntRef) { + if let Some(pair) = self.ints.get(&int) { + pair.clone() + } else { + let obj = Int::new_obj(int); + let hdl = self.const_pool.push(obj.clone()); + self.ints.insert(int, (hdl, obj.clone())); + (hdl, obj) + } + } + + /// Gets or inserts a static string reference. + pub fn const_str(&mut self, s: impl AsRef) -> (ConstHandle, StrRef) { + let s = s.as_ref(); + if let Some(pair) = self.strs.get(s) { + pair.clone() + } else { + let obj = Str::new_obj(s.to_string()); + let hdl = self.const_pool.push(obj.clone()); + self.strs.insert(s.to_string(), (hdl, obj.clone())); + (hdl, obj) + } + } +} diff --git a/src/compile/thunk.rs b/src/compile/thunk.rs new file mode 100644 index 0000000..2866320 --- /dev/null +++ b/src/compile/thunk.rs @@ -0,0 +1,228 @@ +use crate::{ + compile::{error::*, Compile}, + syn::{ast::*, visit::*}, +}; +use runtime::{obj::prelude::*, vm::inst::*}; +use std::mem; + +pub enum Thunk { + Body(Vec), + List(Vec), + Branch { + thunk_true: Box, + thunk_false: Box, + }, + Nop, +} + +impl Thunk { + pub fn push(&mut self, append: impl Into) { + self.push_thunk(Thunk::Body(vec![append.into()])) + } + + pub fn push_thunk(&mut self, append: impl Into) { + let append = append.into(); + let inner = mem::replace(self, Thunk::Nop); + + *self = match (inner, append) { + // X + Nop = X + (lhs, Thunk::Nop) => lhs, + (Thunk::Nop, rhs) => rhs, + + // Body + Body = Body + (Thunk::Body(mut lhs), Thunk::Body(rhs)) => { + lhs.extend(rhs); + Thunk::Body(lhs) + } + // List + List = List + (Thunk::List(mut lhs), Thunk::List(rhs)) => { + lhs.extend(rhs); + Thunk::List(lhs) + } + // List + X = List + (Thunk::List(mut lhs), rhs) => { + lhs.push(rhs); + Thunk::List(lhs) + } + // X + List = List + (lhs, Thunk::List(mut rhs)) => { + rhs.insert(0, lhs); + Thunk::List(rhs) + } + // X + X = List + (lhs, rhs) => Thunk::List(vec![lhs, rhs]), + }; + } +} + +impl From for Thunk { + fn from(other: Inst) -> Self { + Self::from(vec![other]) + } +} + +impl From> for Thunk { + fn from(other: Vec) -> Self { + Thunk::Body(other) + } +} + +impl From> for Thunk { + fn from(other: Vec) -> Self { + Thunk::List(other) + } +} + +pub struct CompileBody<'c, 't> { + compile: &'c mut Compile<'t>, +} + +impl<'c, 't> CompileBody<'c, 't> { + pub fn new(compile: &'c mut Compile<'t>) -> Self { + CompileBody { compile } + } + + pub fn compile(&mut self, body: &'c Body) -> Result { + self.visit_body(body) + } +} + +impl Visit for CompileBody<'_, '_> { + type Out = Result; + + fn visit_body(&mut self, body: &Body) -> Self::Out { + let mut thunk = Thunk::Nop; + + for stmt in body.body.iter() { + thunk.push_thunk(stmt.accept(self)?); + } + + Ok(thunk) + } + + fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out { + DefaultAccept::default_accept(stmt, self) + } + + fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out { + // - push rhs + // - push lhs (which handles the assignment) + let mut thunk = self.visit_expr(&assign.rhs)?; + thunk.push_thunk(self.visit_lhs_expr(&assign.lhs)?); + Ok(thunk) + } + + fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out { + // Do different things depending on the LHS + let mut thunk; + match &lhs_expr { + LhsExpr::SetAttr(expr) => { + // - push lhs expression (without accessor) + // - setattr (access) NOTE : rhs should already be on stack + thunk = self.visit_expr(&expr.expr)?; + let attr = global_sym(expr.access.to_string()); + thunk.push(Inst::SetAttr(attr)); + } + LhsExpr::Local(local) => { + let local = global_sym(local.to_string()); + thunk = Inst::Pop(Some(local)).into(); + } + } + Ok(thunk) + } + + fn visit_expr(&mut self, expr: &Expr) -> Self::Out { + DefaultAccept::default_accept(expr, self) + } + + fn visit_bin_expr(&mut self, expr: &BinExpr) -> Self::Out { + // - push lhs + // - push rhs + // - call operator's function + let mut thunk = self.visit_expr(&expr.lhs)?; + thunk.push_thunk(self.visit_expr(&expr.rhs)?); + let inst = match expr.op { + BinOp::Plus => Inst::BinPlus, + BinOp::Minus => Inst::BinMinus, + BinOp::Times => Inst::BinMul, + BinOp::Div => Inst::BinDiv, + BinOp::Eq => Inst::BinEq, + BinOp::Neq => Inst::BinNeq, + BinOp::Lt => Inst::BinLt, + BinOp::Le => Inst::BinLe, + BinOp::Gt => Inst::BinGt, + BinOp::Ge => Inst::BinGe, + BinOp::And => Inst::BinAnd, + BinOp::Or => Inst::BinOr, + }; + + thunk.push(inst); + Ok(thunk) + } + + fn visit_un_expr(&mut self, expr: &UnExpr) -> Self::Out { + // - push expr + // - call operator's function + let mut thunk = self.visit_expr(&expr.expr)?; + match expr.op { + UnOp::Plus => thunk.push(Inst::UnPos), + UnOp::Minus => thunk.push(Inst::UnNeg), + } + Ok(thunk) + } + + fn visit_call_expr(&mut self, expr: &CallExpr) -> Self::Out { + // - push expr + // - push args in order + // - call function + let mut thunk = self.visit_expr(&expr.expr)?; + for arg in expr.args.iter() { + thunk.push_thunk(self.visit_expr(&arg)?); + } + Ok(thunk) + } + + fn visit_index_expr(&mut self, expr: &IndexExpr) -> Self::Out { + // - eval expr + // - eval index + // - index + let mut thunk = self.visit_expr(&expr.expr)?; + thunk.push_thunk(self.visit_expr(&expr.index)?); + thunk.push(Inst::Index); + Ok(thunk) + } + + fn visit_access_expr(&mut self, expr: &AccessExpr) -> Self::Out { + // - eval expr + // - getattr (expr.access) + let mut thunk = self.visit_expr(&expr.expr)?; + thunk.push_thunk(Thunk::Body(vec![ + Inst::GetAttr(global_sym(expr.access.to_string())), + ])); + Ok(thunk) + } + + fn visit_atom(&mut self, atom: &Atom) -> Self::Out { + let thunk = match atom { + Atom::Ident(ident) => { + // get local + Inst::PushLocal(global_sym(ident.to_string())).into() + } + Atom::Sym(sym) => { + // push symbol + Inst::PushSym(global_sym(sym.clone())).into() + } + Atom::Num(num) => { + // push const + let (hdl, _) = self.compile.const_int(*num); + Inst::PushConst(hdl).into() + } + Atom::String(s) => { + // push const + let (hdl, _) = self.compile.const_str(s); + Inst::PushConst(hdl).into() + } + }; + Ok(thunk) + } +}