General changes across the runtime crate in support of compile module

* Compile module is able to compile bytecode (or so it seems...)
* Runtime crate has had some new stuff added to it, mostly with objects
  and vtables. Still not 100% on the object method function call story,
  but I guess it'll be tackled when we get there.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-09-14 14:09:29 -07:00
parent e2c43dc911
commit 8e2cbb10a4
17 changed files with 743 additions and 175 deletions

View File

@@ -16,8 +16,8 @@ cfgrammar = "0.9"
lrpar = "0.9" lrpar = "0.9"
lrlex = "0.9" lrlex = "0.9"
structopt = "0.3" structopt = "0.3"
snafu = "0.6.6"
#snafu = "0.6.6"
#lazy_static = "1.4.0" #lazy_static = "1.4.0"
#regex = "1.3.7" #regex = "1.3.7"
#derivative = "2.1.1" #derivative = "2.1.1"

View File

@@ -1,14 +1,31 @@
use crate::{obj::{Obj, ObjRef, sym::Sym}}; use crate::obj::{ObjRef, sym::Sym};
use std::collections::BTreeMap; use shredder::{Gc, Scan};
use std::{collections::BTreeMap, ops::{Deref, DerefMut}};
pub type Attrs = BTreeMap<Sym, ObjRef>; pub type Attrs = BTreeMap<Sym, ObjRef>;
pub type VtableAttrs = Gc<Attrs>;
impl Obj for Attrs { #[derive(Scan, Debug, Clone, Default)]
fn attrs(&self) -> &Attrs { pub struct Vtable {
self attrs: VtableAttrs,
} }
fn attrs_mut(&mut self) -> Option<&mut Attrs> { impl Vtable {
Some(self) 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
} }
} }

View File

@@ -3,11 +3,38 @@ 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
// //
#[derive(Scan)] #[derive(Scan)]
pub struct UserFun { pub struct UserFun {
vtable: Vtable,
attrs: Attrs, attrs: Attrs,
// Safe because Vec<Inst> doesn't need to be scanned // Safe because Vec<Inst> doesn't need to be scanned
#[shredder(unsafe_skip)] #[shredder(unsafe_skip)]
@@ -27,7 +54,7 @@ impl UserFun {
} }
} }
impl_obj!(UserFun, attrs); impl_obj!(UserFun);
impl Debug for UserFun { impl Debug for UserFun {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
@@ -43,11 +70,14 @@ impl Debug for UserFun {
// struct NativeFun // struct NativeFun
// //
pub type NativeFunPtr = Box<dyn Fn(&mut Vm, ObjRef, Vec<ObjRef>) + Send + Sync>;
#[derive(Scan)] #[derive(Scan)]
pub struct NativeFun { pub struct NativeFun {
#[shredder(skip)] vtable: Vtable,
fun: GcSafeWrapper<Box<dyn Fn(&mut Vm) + Send + Sync>>,
attrs: Attrs, attrs: Attrs,
#[shredder(skip)]
fun: GcSafeWrapper<NativeFunPtr>,
} }
// //
@@ -55,23 +85,13 @@ pub struct NativeFun {
// //
impl NativeFun { impl NativeFun {
pub fn new(fun: Box<dyn Fn(&mut Vm) + Send + Sync>) -> ObjRef<Self> { pub fn new_obj(fun: NativeFunPtr) -> ObjRef<Self> {
let obj_ref = ObjRef::new(Self { ObjRef::new(Self {
fun: GcSafeWrapper::new(fun), // TODO : vtable for NativeFun
vtable: Default::default(),
attrs: Default::default(), attrs: Default::default(),
}); fun: GcSafeWrapper::new(fun),
})
{
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)
} }
} }
@@ -88,7 +108,7 @@ impl Debug for NativeFun {
} }
} }
impl_obj!(NativeFun, attrs); impl_obj!(NativeFun);
// //
// Native function defs // Native function defs
@@ -98,10 +118,8 @@ impl_obj!(NativeFun, attrs);
// __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(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 sym_ref = vm.pop();
let obj_ref = vm.pop(); let obj_ref = vm.pop();
obj_ref.access() obj_ref.access()

View File

@@ -1,24 +1,24 @@
use crate::obj::{names::*, prelude::*}; use crate::obj::prelude::*;
use shredder::Scan; use shredder::Scan;
pub type IntRef = ObjRef<Int>;
#[derive(Debug, Scan)] #[derive(Debug, Scan)]
pub struct Int { pub struct Int {
value: i64, value: i64,
vtable: Vtable,
attrs: Attrs, attrs: Attrs,
} }
impl Int { impl Int {
pub fn new(value: i64) -> ObjRef<Self> { pub fn new_obj(value: i64) -> ObjRef<Self> {
// TODO : vtable for Int
let obj_ref = ObjRef::new(Self { let obj_ref = ObjRef::new(Self {
value, value,
vtable: Default::default(),
attrs: 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 obj_ref
} }
@@ -27,4 +27,4 @@ impl Int {
} }
} }
impl_obj!(Int, attrs); impl_obj_readonly!(Int);

View File

@@ -1,17 +1,48 @@
/// Implements `Obj` for a given type and using the given member for attributes. /// Implements `Obj` for a given type and using the given member for attributes.
#[macro_export] #[macro_export]
macro_rules! impl_obj { macro_rules! impl_obj {
($ty:ty, $attrs:ident) => { ($ty:ty, $vtable:ident, $attrs:ident) => {
impl $crate::obj::Obj for $ty { 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 &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) 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. /// Locks a `ObjRef` type for reading.
@@ -43,3 +74,14 @@ macro_rules! attrs {
maplit::btreemap! { } 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! { })
}}
}

View File

@@ -8,8 +8,9 @@ pub mod intern;
pub mod names; pub mod names;
pub mod str; pub mod str;
pub mod sym; pub mod sym;
#[cfg(test)]
mod test;
pub mod ty; pub mod ty;
#[cfg(test)] mod test;
pub mod prelude { pub mod prelude {
pub use crate::obj::{attrs::*, fun::*, int::*, intern::*, str::*, sym::*, ty::*, Obj, ObjRef}; 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 shredder::{Gc, Scan};
use std::{ use std::{
ops::{Deref, DerefMut, CoerceUnsized},
marker::Unsize, marker::Unsize,
ops::{CoerceUnsized, Deref, DerefMut},
sync::RwLock, sync::RwLock,
}; };
@@ -30,11 +31,18 @@ use sym::Sym;
// //
pub trait Obj: Scan + std::fmt::Debug { pub trait Obj: Scan + std::fmt::Debug {
fn vtable(&self) -> &Vtable;
fn attrs(&self) -> &Attrs; fn attrs(&self) -> &Attrs;
fn attrs_mut(&mut self) -> Option<&mut Attrs>; fn attrs_mut(&mut self) -> Option<&mut Attrs>;
fn get_attr(&self, sym: &Sym) -> Option<&ObjRef> { fn get_attr(&self, sym: &Sym) -> Option<ObjRef> {
self.attrs().get(&sym) 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<ObjRef> { fn set_attr(&mut self, sym: Sym, value: ObjRef) -> Option<ObjRef> {
@@ -47,13 +55,21 @@ pub trait Obj: Scan + std::fmt::Debug {
// //
#[derive(Debug, Scan)] #[derive(Debug, Scan)]
pub struct ObjRef<T: Obj + ?Sized + Send + Sync = (dyn Obj + Send + Sync + 'static)> { pub struct ObjRef<T = (dyn Obj + Send + Sync + 'static)>
where
T: Obj + ?Sized + Send + Sync,
{
gc: Gc<RwLock<T>>, gc: Gc<RwLock<T>>,
} }
impl<T: Obj + ?Sized + Send + Sync> Clone for ObjRef<T> { impl<T> Clone for ObjRef<T>
where
T: Obj + ?Sized + Send + Sync,
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
ObjRef { gc: self.gc.clone() } ObjRef {
gc: self.gc.clone(),
}
} }
} }
@@ -61,7 +77,10 @@ impl<T: Obj + ?Sized + Send + Sync> Clone for ObjRef<T> {
// impl ObjRef // impl ObjRef
// //
impl<T: Obj + ?Sized + Send + Sync> ObjRef<T> { impl<T> ObjRef<T>
where
T: Obj + ?Sized + Send + Sync,
{
/// Check object reference equality. /// Check object reference equality.
pub fn ref_eq(&self, other: &Self) -> bool { pub fn ref_eq(&self, other: &Self) -> bool {
let lhs: &RwLock<T> = &*self.gc.get(); let lhs: &RwLock<T> = &*self.gc.get();
@@ -70,7 +89,10 @@ impl<T: Obj + ?Sized + Send + Sync> ObjRef<T> {
} }
} }
impl<T: Obj + Send + Sync + 'static> ObjRef<T> { impl<T> ObjRef<T>
where
T: Obj + Send + Sync + 'static,
{
pub fn new(obj: T) -> Self { pub fn new(obj: T) -> Self {
ObjRef { ObjRef {
gc: Gc::new(RwLock::new(obj)), gc: Gc::new(RwLock::new(obj)),

View File

@@ -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; use once_cell::sync::Lazy;
macro_rules! name { macro_rules! name {
($name:ident, $sym_name:ident, $text:expr $(,)?) => { ($name:ident, $text:expr $(,)?) => {
pub const $name: &str = $text; pub static $name: Lazy<NameInfo> = Lazy::new(|| {
pub static $sym_name: Lazy<Sym> = Lazy::new(|| global_sym($name.to_string())); 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 // Types
// //
name!(INT_TY_NAME, INT_TY_SYM, "Int"); name!(INT_NAME, "Int");
name!(TY_TY_NAME, TY_TY_SYM, "Type"); name!(TY_NAME, "Type");
name!(SYM_TY_NAME, SYM_TY_SYM, "Sym"); name!(SYM_NAME, "Sym");
// //
// Members // Members
// //
name!(TY_MEMBER_NAME, TY_MEMBER_SYM, "__type__"); name!(TY_MEMBER_NAME, "__type__");
name!(CALL_MEMBER_NAME, CALL_MEMBER_SYM, "__call__"); name!(CALL_MEMBER_NAME, "__call__");
name!(NAME_MEMBER_NAME, NAME_MEMBER_SYM, "__name__"); name!(NAME_MEMBER_NAME, "__name__");
name!(GET_ATTR_MEMBER_NAME, GET_ATTR_MEMBER_SYM, "__get_attr__"); name!(GET_ATTR_MEMBER_NAME, "__get_attr__");
name!(SET_ATTR_MEMBER_NAME, SET_ATTR_MEMBER_SYM, "__set_attr__"); name!(SET_ATTR_MEMBER_NAME, "__set_attr__");
name!(SELF_MEMBER_NAME, "__self__");
name!(FUNC_MEMBER_NAME, "__func__");
// //
// Predefined VM-aware symbols // Predefined VM-aware symbols
// //
name!(LOCAL_NAME, LOCAL_SYM, "__local__"); name!(SCOPE_NAME, "__scope__");
// //
// Builtin functions // Builtin functions
@@ -39,17 +55,17 @@ name!(LOCAL_NAME, LOCAL_SYM, "__local__");
// //
// Builtin constants // Builtin constants
// //
name!(TRUE_NAME, TRUE_SYM, "true"); name!(TRUE_NAME, "true");
name!(FALSE_NAME, FALSE_SYM, "false"); name!(FALSE_NAME, "false");
name!(NIL_NAME, NIL_SYM, "nil"); name!(NIL_NAME, "nil");
// Operator function names // Operator function names
name!(EQ_EQ_OP_NAME, EQ_EQ_OP_SYM, "__eq__"); name!(EQ_EQ_OP_NAME, "__eq__");
name!(LT_OP_NAME, LT_OP_SYM, "__lt__"); name!(LT_OP_NAME, "__lt__");
name!(GT_OP_NAME, GT_OP_SYM, "__gt__"); name!(GT_OP_NAME, "__gt__");
name!(LT_EQ_OP_NAME, LT_EQ_OP_SYM, "__le__"); name!(LT_EQ_OP_NAME, "__le__");
name!(GT_EQ_OP_NAME, GT_EQ_OP_SYM, "__ge__"); name!(GT_EQ_OP_NAME, "__ge__");
name!(PLUS_OP_NAME, PLUS_OP_SYM, "__add__"); name!(PLUS_OP_NAME, "__add__");
name!(MINUS_OP_NAME, MINUS_OP_SYM, "__sub__"); name!(MINUS_OP_NAME, "__sub__");
name!(TIMES_OP_NAME, TIMES_OP_SYM, "__mul__"); name!(TIMES_OP_NAME, "__mul__");
name!(DIV_OP_NAME, DIV_OP_SYM, "__div__"); name!(DIV_OP_NAME, "__div__");

View File

@@ -1,11 +1,27 @@
use crate::obj::prelude::*; use crate::obj::prelude::*;
use shredder::Scan; use shredder::Scan;
pub type StrRef = ObjRef<Str>;
#[derive(Debug, Scan)] #[derive(Debug, Scan)]
pub struct Str { pub struct Str {
value: String, vtable: Vtable,
attrs: Attrs, 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);

View File

@@ -1,26 +1,37 @@
use crate::obj::{prelude::*, intern::Interner, names::*}; use crate::obj::{intern::Interner, names::*, prelude::*};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use shredder::Scan; use shredder::Scan;
use std::sync::Mutex; use std::{collections::BTreeMap, sync::Mutex};
// //
// struct Sym // struct Sym
// //
pub type SymRef = ObjRef<Sym>;
/// A literal name or symbol. /// A literal name or symbol.
#[derive(Scan, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Scan, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Sym(usize); pub struct Sym(usize);
impl Sym { impl Sym {
pub fn new(sym: usize) -> Self { /// Gets the index of this symbol.
Sym(sym)
}
pub fn index(&self) -> usize { pub fn index(&self) -> usize {
self.0 self.0
} }
pub fn new_obj(sym: impl Into<Sym>) -> ObjRef<Sym> { /// 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<Sym>) -> SymRef {
ObjRef::new(sym.into()) ObjRef::new(sym.into())
} }
} }
@@ -32,63 +43,75 @@ impl From<usize> for Sym {
} }
impl Obj for Sym { impl Obj for Sym {
fn vtable(&self) -> &Vtable {
&SYM_VTABLE
}
fn attrs(&self) -> &Attrs { fn attrs(&self) -> &Attrs {
&SYM_ATTRS &SYM_ATTRS
} }
fn attrs_mut(&mut self) -> Option<&mut Attrs> { None } fn attrs_mut(&mut self) -> Option<&mut Attrs> {
None
}
}
impl<T> std::ops::Index<Sym> for Vec<T> {
type Output = T;
fn index(&self, sym: Sym) -> &Self::Output {
&self[sym.index()]
}
} }
// //
// Symbol Ty object // Sym Ty object
// //
pub static NIL_OBJ: Lazy<ObjRef<Sym>> = Lazy::new(|| Sym::new_obj(*NIL_SYM)); pub static SYM_TY: Lazy<ObjRef<Ty>> = Lazy::new(|| Ty::new_obj(SYM_NAME.sym_ref()));
pub static TRUE_OBJ: Lazy<ObjRef<Sym>> = Lazy::new(|| Sym::new_obj(*TRUE_SYM)); /// Symbols have no attributes. This is mostly because it's a pain to do cyclic references at this
/// point.
pub static FALSE_OBJ: Lazy<ObjRef<Sym>> = Lazy::new(|| Sym::new_obj(*FALSE_SYM)); pub static SYM_ATTRS: Lazy<Attrs> = Lazy::new(|| attrs! {});
pub static SYM_VTABLE: Lazy<Vtable> = Lazy::new(|| vtable! {});
pub static SYM_TY: Lazy<ObjRef<Ty>> = 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<ObjRef<Sym>> = Lazy::new(|| Sym::new_obj(*SYM_TY_SYM));
pub static SYM_ATTRS: Lazy<Attrs> = Lazy::new(|| attrs! {
*TY_MEMBER_SYM => SYM_TY.clone(),
*NAME_MEMBER_SYM => SYM_TY_SYM_OBJ.clone(),
});
// //
// global symbols table // global interned symbol obj table
// //
pub(crate) static SYM_REFS: Lazy<Mutex<BTreeMap<Sym, SymRef>>> =
Lazy::new(|| Mutex::new(Default::default()));
pub(crate) static SYMBOLS: Lazy<Mutex<SymTable>> = Lazy::new(|| { /// Access or insert a globally interned symbol object.
Mutex::new(SymTable::default()) ///
}); /// 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);
pub(crate) fn global_name_lookup(sym: Sym) -> Option<Arc<String>> { let mut refs = SYM_REFS.lock().unwrap();
let table = SYMBOLS.lock() if let Some(obj) = refs.get(&sym) {
.unwrap(); obj.clone()
table.lookup(sym) } 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<Sym> { //
let table = SYMBOLS.lock() // global interned symbol table
.unwrap(); //
table.lookup_sym(s)
}
*/
pub(crate) fn global_sym(s: String) -> Sym { pub(crate) static SYMS: Lazy<Mutex<SymTable>> = Lazy::new(|| Mutex::new(SymTable::default()));
let mut table = SYMBOLS.lock()
.unwrap(); /// 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) table.insert(s)
} }
// //
// SymTable // SymTable interner types
// //
pub type SymTable = Interner<String>; pub type SymTable = Interner<String>;

View File

@@ -15,7 +15,7 @@ fn test_sym_plumbing() {
run_with_gc_cleanup(|| { run_with_gc_cleanup(|| {
assert_eq!(number_of_tracked_allocations(), start + 0); assert_eq!(number_of_tracked_allocations(), start + 0);
let nil = NIL_OBJ.clone(); let nil = NIL_NAME.sym_ref();
// nil sym obj // nil sym obj
assert_eq!(number_of_tracked_allocations(), start + 1); assert_eq!(number_of_tracked_allocations(), start + 1);
@@ -23,41 +23,32 @@ fn test_sym_plumbing() {
{ {
read_obj!(let nil_obj = nil); read_obj!(let nil_obj = nil);
let sym: Sym = **nil_obj; let sym: Sym = **nil_obj;
assert_eq!(*NIL_SYM, sym); assert_eq!(NIL_NAME.sym, sym);
assert_eq!(number_of_tracked_allocations(), start + 1); 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(); nil_obj.attrs();
let ty_sym_obj = SYM_TY_SYM_OBJ.clone(); let ty_sym_obj = SYM_NAME.sym_ref();
assert_eq!(number_of_tracked_allocations(), start + 5); 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 // 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 // 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. // these are *static* values, so there will always remain at least one reference.
assert_eq!(number_of_tracked_allocations(), start + 7); assert_eq!(number_of_tracked_allocations(), start + 4);
// TODO ^ prove the above
} }
#[test] #[test]
fn test_dyn_obj_ref_eq() { fn test_dyn_obj_ref_eq() {
#[derive(Default, Debug, Scan)] #[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 _guard = TEST_LOCK.lock().unwrap();
let start = number_of_tracked_allocations(); // need 'start' because of static allocations let start = number_of_tracked_allocations(); // need 'start' because of static allocations

View File

@@ -4,40 +4,31 @@ use shredder::Scan;
#[derive(Scan, Debug)] #[derive(Scan, Debug)]
pub struct Ty { pub struct Ty {
vtable: Vtable,
attrs: Attrs, attrs: Attrs,
} }
impl Ty { impl Ty {
pub fn new(name: ObjRef) -> ObjRef<Self> { pub fn new_obj(name: ObjRef) -> ObjRef<Self> {
// Ty objects have these attributes: // Ty objects have these attributes:
// __ty__ - always the Type type // __ty__ - always the Type type
// __name__ - this type's name as a symbol // __name__ - this type's name as a symbol
ObjRef::new(Ty { ObjRef::new(Ty {
attrs: attrs! { vtable: vtable! {
*TY_MEMBER_SYM => TY_TY.clone(), TY_MEMBER_NAME.sym => TY_TY.clone(),
*NAME_MEMBER_SYM => name.clone(), NAME_MEMBER_NAME.sym => name.clone(),
} },
attrs: Default::default(),
}) })
} }
} }
pub static TY_TY_SYM_OBJ: Lazy<ObjRef<Sym>> = Lazy::new(|| {
Sym::new_obj(*TY_TY_SYM)
});
pub static TY_TY: Lazy<ObjRef<Ty>> = Lazy::new(|| { pub static TY_TY: Lazy<ObjRef<Ty>> = Lazy::new(|| {
let ty = ObjRef::new(Ty { // TODO : vtable for Ty
attrs: attrs! { ObjRef::new(Ty {
*NAME_MEMBER_SYM => TY_TY_SYM_OBJ.clone(), vtable: Default::default(),
} attrs: Default::default(),
}); })
// add self-reference
{
write_obj!(let ty_obj = ty);
ty_obj.set_attr(*TY_MEMBER_SYM, ty.clone());
}
ty
}); });
impl_obj!(Ty, attrs); impl_obj!(Ty);

47
runtime/src/vm/consts.rs Normal file
View File

@@ -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<ObjRef>,
}
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<ConstHandle> for ConstPool {
type Output = ObjRef;
fn index(&self, hdl: ConstHandle) -> &Self::Output {
self.get(hdl)
}
}

View File

@@ -1,27 +1,33 @@
use crate::obj::prelude::*; use crate::{obj::prelude::*, vm::consts::ConstHandle};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Inst { pub enum Inst {
/// Push a literal symbol object to the stack. /// Push a literal symbol object to the stack.
PushSym(Sym), 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 a value from the stack, possibly into a local symbol.
Pop(Option<Sym>), Pop(Option<Sym>),
/// Pops a symbol value and an object reference. /// Pops a symbol value and an object reference.
/// ///
/// 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),
/// If the object reference is `:__local__`, this instruction will push a local value.
GetAttr,
/// 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 /// The target reference will have the given symbol attribute assigned to the source.
/// value name.
/// ///
/// If the given value is `:__local__`, this instruction will set a local value. /// In code, it would look like this:
SetAttr, ///
/// target.symbol = source
///
SetAttr(Sym),
/// Jump to a given address in the current function unconditionally. /// Jump to a given address in the current function unconditionally.
Jump(usize), Jump(usize),
@@ -31,11 +37,75 @@ pub enum Inst {
/// The condition flag may be set by an internal function. /// The condition flag may be set by an internal function.
JumpTrue(usize), JumpTrue(usize),
/// Pops the top item off of the stack, followed by the number of arguments supplied, and /// Calls a function with the supplied number of arguments.
/// attempts to run the `__call__` attribute of the object. ///
/// 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), 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 /// Pops the top value from the stack, and returns from the function, using the popped value as
/// a return value. /// a return value.
Return, 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,
} }

View File

@@ -1,5 +1,6 @@
mod frame; mod frame;
pub mod inst; pub mod inst;
pub mod consts;
use crate::obj::prelude::*; use crate::obj::prelude::*;

12
src/compile/error.rs Normal file
View File

@@ -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<T, E = Error> = std::result::Result<T, E>;

74
src/compile/mod.rs Normal file
View File

@@ -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<str>) -> (ConstHandle, StrRef) {
self.const_data_mut()
.const_str(s)
}
}
#[derive(Debug, Default)]
pub struct ConstData {
ints: HashMap<i64, (ConstHandle, IntRef)>,
strs: HashMap<String, (ConstHandle, StrRef)>,
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<str>) -> (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)
}
}
}

228
src/compile/thunk.rs Normal file
View File

@@ -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<Inst>),
List(Vec<Thunk>),
Branch {
thunk_true: Box<Thunk>,
thunk_false: Box<Thunk>,
},
Nop,
}
impl Thunk {
pub fn push(&mut self, append: impl Into<Inst>) {
self.push_thunk(Thunk::Body(vec![append.into()]))
}
pub fn push_thunk(&mut self, append: impl Into<Thunk>) {
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<Inst> for Thunk {
fn from(other: Inst) -> Self {
Self::from(vec![other])
}
}
impl From<Vec<Inst>> for Thunk {
fn from(other: Vec<Inst>) -> Self {
Thunk::Body(other)
}
}
impl From<Vec<Thunk>> for Thunk {
fn from(other: Vec<Thunk>) -> 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<Thunk> {
self.visit_body(body)
}
}
impl Visit for CompileBody<'_, '_> {
type Out = Result<Thunk>;
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)
}
}