Fold runtime/ crate into this source tree
While I like the idea of having a runtime completely decoupled from the syntax and compiler, I don't think this is that big of a project for that to be necessary or even useful yet. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
pub mod thunk;
|
||||
pub mod error;
|
||||
|
||||
use runtime::{obj::prelude::*, vm::consts::*};
|
||||
use crate::{obj::prelude::*, vm::consts::*};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Compile<'t> {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
compile::{error::*, Compile},
|
||||
syn::{ast::*, visit::*},
|
||||
obj::prelude::*,
|
||||
vm::inst::*
|
||||
};
|
||||
use runtime::{obj::prelude::*, vm::inst::*};
|
||||
use std::mem;
|
||||
|
||||
pub enum Thunk {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#![feature(unsize, coerce_unsized)]
|
||||
|
||||
pub mod syn;
|
||||
pub mod compile;
|
||||
|
||||
pub use runtime;
|
||||
pub mod obj;
|
||||
pub mod vm;
|
||||
|
||||
31
src/obj/attrs.rs
Normal file
31
src/obj/attrs.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::obj::{ObjRef, sym::Sym};
|
||||
use shredder::{Gc, Scan};
|
||||
use std::{collections::BTreeMap, ops::{Deref, DerefMut}};
|
||||
|
||||
pub type Attrs = BTreeMap<Sym, ObjRef>;
|
||||
pub type VtableAttrs = Gc<Attrs>;
|
||||
|
||||
#[derive(Scan, Debug, Clone, Default)]
|
||||
pub struct Vtable {
|
||||
attrs: VtableAttrs,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
129
src/obj/fun.rs
Normal file
129
src/obj/fun.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use crate::{obj::{names::*, prelude::*}, vm::{inst::Inst, Vm}};
|
||||
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<ObjRef<NativeFun>> = 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<Inst> doesn't need to be scanned
|
||||
#[shredder(unsafe_skip)]
|
||||
code: Vec<Inst>,
|
||||
// Safe because this is just an interner that points to symbols, which aren't GC'd
|
||||
#[shredder(unsafe_skip)]
|
||||
locals: Locals,
|
||||
}
|
||||
|
||||
impl UserFun {
|
||||
pub fn code(&self) -> &Vec<Inst> {
|
||||
&self.code
|
||||
}
|
||||
|
||||
pub fn locals(&self) -> &Locals {
|
||||
&self.locals
|
||||
}
|
||||
}
|
||||
|
||||
impl_obj!(UserFun);
|
||||
|
||||
impl Debug for UserFun {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("UserFun")
|
||||
.field("attrs", &self.attrs)
|
||||
.field("code", &self.code)
|
||||
.field("locals", &self.locals)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// struct NativeFun
|
||||
//
|
||||
|
||||
pub type NativeFunPtr = Box<dyn Fn(&mut Vm, ObjRef, Vec<ObjRef>) + Send + Sync>;
|
||||
|
||||
#[derive(Scan)]
|
||||
pub struct NativeFun {
|
||||
vtable: Vtable,
|
||||
attrs: Attrs,
|
||||
#[shredder(skip)]
|
||||
fun: GcSafeWrapper<NativeFunPtr>,
|
||||
}
|
||||
|
||||
//
|
||||
// impl NativeFun
|
||||
//
|
||||
|
||||
impl NativeFun {
|
||||
pub fn new_obj(fun: NativeFunPtr) -> ObjRef<Self> {
|
||||
ObjRef::new(Self {
|
||||
// TODO : vtable for NativeFun
|
||||
vtable: Default::default(),
|
||||
attrs: Default::default(),
|
||||
fun: GcSafeWrapper::new(fun),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// impl Debug for NativeFun
|
||||
//
|
||||
|
||||
impl Debug for NativeFun {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("NativeFun")
|
||||
.field("fun", &format!("(function at {:x})", &self.fun as *const _ as usize))
|
||||
.field("attrs", &self.attrs)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl_obj!(NativeFun);
|
||||
|
||||
//
|
||||
// Native function defs
|
||||
//
|
||||
|
||||
// __access__ is what the "dot" operator calls
|
||||
// __get_attr__ *should* always bypass the __access__ function and get an attribute directly
|
||||
|
||||
pub static GET_ATTR_MEMBER_FUN: Lazy<ObjRef<NativeFun>> = Lazy::new(|| {
|
||||
NativeFun::new_obj(Box::new(|_vm, _fun, _args| {
|
||||
/*
|
||||
let sym_ref = vm.pop();
|
||||
let obj_ref = vm.pop();
|
||||
obj_ref.access()
|
||||
*/
|
||||
todo!("__get_attr__ function")
|
||||
}))
|
||||
});
|
||||
30
src/obj/int.rs
Normal file
30
src/obj/int.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::obj::prelude::*;
|
||||
use shredder::Scan;
|
||||
|
||||
pub type IntRef = ObjRef<Int>;
|
||||
|
||||
#[derive(Debug, Scan)]
|
||||
pub struct Int {
|
||||
value: i64,
|
||||
vtable: Vtable,
|
||||
attrs: Attrs,
|
||||
}
|
||||
|
||||
impl Int {
|
||||
pub fn new_obj(value: i64) -> ObjRef<Self> {
|
||||
// TODO : vtable for Int
|
||||
let obj_ref = ObjRef::new(Self {
|
||||
value,
|
||||
vtable: Default::default(),
|
||||
attrs: Default::default(),
|
||||
});
|
||||
|
||||
obj_ref
|
||||
}
|
||||
|
||||
pub fn value(&self) -> i64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl_obj_readonly!(Int);
|
||||
58
src/obj/intern.rs
Normal file
58
src/obj/intern.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use crate::obj::sym::*;
|
||||
use std::{collections::HashMap, hash::Hash, sync::Arc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Interner<T: Hash + Eq> {
|
||||
forward: Vec<Arc<T>>,
|
||||
reverse: HashMap<Arc<T>, Sym>,
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq> Interner<T> {
|
||||
pub fn new(forward: Vec<Arc<T>>) -> Self {
|
||||
let reverse = forward.iter()
|
||||
.enumerate()
|
||||
.map(|(i, s)| (Arc::clone(s), Sym::new(i)))
|
||||
.collect();
|
||||
Self {
|
||||
forward,
|
||||
reverse,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forward(&self) -> &Vec<Arc<T>> {
|
||||
&self.forward
|
||||
}
|
||||
|
||||
pub fn reverse(&self) -> &HashMap<Arc<T>, Sym> {
|
||||
&self.reverse
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: T) -> Sym {
|
||||
if let Some(index) = self.reverse().get(&value) {
|
||||
*index
|
||||
} else {
|
||||
let next_sym = Sym::new(self.forward().len());
|
||||
let ptr = Arc::new(value.into());
|
||||
self.forward.push(Arc::clone(&ptr));
|
||||
self.reverse.insert(ptr, next_sym);
|
||||
assert_eq!(self.forward().len(), self.reverse().len());
|
||||
next_sym
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(&self, sym: Sym) -> Option<Arc<T>> {
|
||||
self.forward.get(sym.index())
|
||||
.map(Arc::clone)
|
||||
}
|
||||
|
||||
pub fn lookup_sym(&self, value: impl std::borrow::Borrow<T>) -> Option<Sym> {
|
||||
self.reverse.get(value.borrow())
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq> Default for Interner<T> {
|
||||
fn default() -> Self {
|
||||
Interner { forward: Default::default(), reverse: Default::default(), }
|
||||
}
|
||||
}
|
||||
87
src/obj/macros.rs
Normal file
87
src/obj/macros.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
/// Implements `Obj` for a given type and using the given member for attributes.
|
||||
#[macro_export]
|
||||
macro_rules! impl_obj {
|
||||
($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> {
|
||||
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.
|
||||
#[macro_export]
|
||||
macro_rules! read_obj {
|
||||
(let $lhs:ident $(: &$ty:ty)? = $obj:expr) => {
|
||||
let __obj $(: &std::sync::RwLock<$ty>)? = &*$obj.get();
|
||||
let $lhs $(: &$ty)? = &__obj.read().unwrap();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/// Locks a `ObjRef` type for writing.
|
||||
#[macro_export]
|
||||
macro_rules! write_obj {
|
||||
(let $lhs:ident $(: &$ty:ty)? = $obj:expr) => {
|
||||
let __obj $(: &std::sync::RwLock<$ty>)? = &*$obj.get();
|
||||
let $lhs $(: &$ty)? = &mut __obj.write().unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! attrs {
|
||||
($($key:expr => $value:expr),+ $(,)?) => {{
|
||||
maplit::btreemap! { $($key => ($value as _) ),+ }
|
||||
}};
|
||||
|
||||
() => {{
|
||||
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! { })
|
||||
}}
|
||||
}
|
||||
130
src/obj/mod.rs
Normal file
130
src/obj/mod.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod attrs;
|
||||
pub mod fun;
|
||||
pub mod int;
|
||||
pub mod intern;
|
||||
pub mod names;
|
||||
pub mod str;
|
||||
pub mod sym;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
pub mod ty;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::obj::{attrs::*, fun::*, int::*, intern::*, str::*, sym::*, ty::*, Obj, ObjRef};
|
||||
}
|
||||
|
||||
use shredder::{Gc, Scan};
|
||||
use std::{
|
||||
marker::Unsize,
|
||||
ops::{CoerceUnsized, Deref, DerefMut},
|
||||
sync::RwLock,
|
||||
};
|
||||
|
||||
use attrs::*;
|
||||
use sym::Sym;
|
||||
|
||||
//
|
||||
// trait Obj
|
||||
//
|
||||
|
||||
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)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
let vtable = self.vtable().get();
|
||||
vtable.get(&sym).cloned()
|
||||
})
|
||||
}
|
||||
|
||||
fn set_attr(&mut self, sym: Sym, value: ObjRef) -> Option<ObjRef> {
|
||||
self.attrs_mut()?.insert(sym, value)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// struct ObjRef
|
||||
//
|
||||
|
||||
#[derive(Debug, Scan)]
|
||||
pub struct ObjRef<T = (dyn Obj + Send + Sync + 'static)>
|
||||
where
|
||||
T: Obj + ?Sized + Send + Sync,
|
||||
{
|
||||
gc: Gc<RwLock<T>>,
|
||||
}
|
||||
|
||||
impl<T> Clone for ObjRef<T>
|
||||
where
|
||||
T: Obj + ?Sized + Send + Sync,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
ObjRef {
|
||||
gc: self.gc.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// impl ObjRef
|
||||
//
|
||||
|
||||
impl<T> ObjRef<T>
|
||||
where
|
||||
T: Obj + ?Sized + Send + Sync,
|
||||
{
|
||||
/// Check object reference equality.
|
||||
pub fn ref_eq(&self, other: &Self) -> bool {
|
||||
let lhs: &RwLock<T> = &*self.gc.get();
|
||||
let rhs: &RwLock<T> = &*other.get();
|
||||
std::ptr::eq::<RwLock<T>>(lhs, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ObjRef<T>
|
||||
where
|
||||
T: Obj + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(obj: T) -> Self {
|
||||
ObjRef {
|
||||
gc: Gc::new(RwLock::new(obj)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> CoerceUnsized<ObjRef<U>> for ObjRef<T>
|
||||
where
|
||||
T: Obj + Send + Sync + ?Sized + Unsize<U>,
|
||||
U: Obj + Send + Sync + ?Sized,
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// impl Deref for ObjRef
|
||||
//
|
||||
|
||||
impl<T: Obj + ?Sized + Send + Sync> Deref for ObjRef<T> {
|
||||
type Target = Gc<RwLock<T>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.gc
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// impl DerefMut for ObjRef
|
||||
//
|
||||
|
||||
impl<T: Obj + ?Sized + Send + Sync> DerefMut for ObjRef<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.gc
|
||||
}
|
||||
}
|
||||
71
src/obj/names.rs
Normal file
71
src/obj/names.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use crate::obj::sym::{Sym, SymRef, global_sym, global_sym_ref};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
macro_rules! name {
|
||||
($name:ident, $text:expr $(,)?) => {
|
||||
pub static $name: Lazy<NameInfo> = 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_NAME, "Int");
|
||||
name!(TY_NAME, "Type");
|
||||
name!(SYM_NAME, "Sym");
|
||||
|
||||
//
|
||||
// Members
|
||||
//
|
||||
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!(SCOPE_NAME, "__scope__");
|
||||
|
||||
//
|
||||
// Builtin functions
|
||||
//
|
||||
//name!(REPR_FUN_NAME, REPR_FUN_SYM, "repr");
|
||||
//name!(GET_LOCAL_FUN_NAME, GET_LOCAL_FUN_SYM, "get_local");
|
||||
//name!(SET_LOCAL_FUN_NAME, SET_LOCAL_FUN_SYM, "set_local");
|
||||
|
||||
//
|
||||
// Builtin constants
|
||||
//
|
||||
name!(TRUE_NAME, "true");
|
||||
name!(FALSE_NAME, "false");
|
||||
name!(NIL_NAME, "nil");
|
||||
|
||||
// Operator function names
|
||||
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__");
|
||||
27
src/obj/str.rs
Normal file
27
src/obj/str.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::obj::prelude::*;
|
||||
use shredder::Scan;
|
||||
|
||||
pub type StrRef = ObjRef<Str>;
|
||||
|
||||
#[derive(Debug, Scan)]
|
||||
pub struct Str {
|
||||
vtable: Vtable,
|
||||
attrs: Attrs,
|
||||
value: String,
|
||||
}
|
||||
|
||||
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);
|
||||
118
src/obj/sym.rs
Normal file
118
src/obj/sym.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use crate::obj::{intern::Interner, names::*, prelude::*};
|
||||
use once_cell::sync::Lazy;
|
||||
use shredder::Scan;
|
||||
use std::{collections::BTreeMap, sync::Mutex};
|
||||
|
||||
//
|
||||
// struct Sym
|
||||
//
|
||||
|
||||
pub type SymRef = ObjRef<Sym>;
|
||||
|
||||
/// A literal name or symbol.
|
||||
#[derive(Scan, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Sym(usize);
|
||||
|
||||
impl Sym {
|
||||
/// Gets the index of this symbol.
|
||||
pub fn index(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Sym {
|
||||
fn from(other: usize) -> Sym {
|
||||
Sym(other)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Index<Sym> for Vec<T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, sym: Sym) -> &Self::Output {
|
||||
&self[sym.index()]
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Sym Ty object
|
||||
//
|
||||
|
||||
pub static SYM_TY: Lazy<ObjRef<Ty>> = Lazy::new(|| Ty::new_obj(SYM_NAME.sym_ref()));
|
||||
|
||||
/// Symbols have no attributes. This is mostly because it's a pain to do cyclic references at this
|
||||
/// point.
|
||||
pub static SYM_ATTRS: Lazy<Attrs> = Lazy::new(|| attrs! {});
|
||||
pub static SYM_VTABLE: Lazy<Vtable> = Lazy::new(|| vtable! {});
|
||||
|
||||
//
|
||||
// global interned symbol obj table
|
||||
//
|
||||
pub(crate) static SYM_REFS: Lazy<Mutex<BTreeMap<Sym, SymRef>>> =
|
||||
Lazy::new(|| Mutex::new(Default::default()));
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// global interned symbol table
|
||||
//
|
||||
|
||||
pub(crate) static SYMS: Lazy<Mutex<SymTable>> = 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 interner types
|
||||
//
|
||||
|
||||
pub type SymTable = Interner<String>;
|
||||
pub type Locals = Interner<Sym>;
|
||||
64
src/obj/test.rs
Normal file
64
src/obj/test.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use crate::obj::{names::*, prelude::*};
|
||||
use once_cell::sync::Lazy;
|
||||
use shredder::*;
|
||||
use std::sync::Mutex;
|
||||
|
||||
static TEST_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
||||
|
||||
#[test]
|
||||
fn test_sym_plumbing() {
|
||||
// Most of this test is making sure we are free of runtime infinite recursion issues with
|
||||
// initializing static data (NIL_OBJ, SYM_TY, SYM_ATTRS)
|
||||
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
let start = number_of_tracked_allocations(); // need 'start' because of static allocations
|
||||
run_with_gc_cleanup(|| {
|
||||
assert_eq!(number_of_tracked_allocations(), start + 0);
|
||||
|
||||
let nil = NIL_NAME.sym_ref();
|
||||
|
||||
// nil sym obj
|
||||
assert_eq!(number_of_tracked_allocations(), start + 1);
|
||||
|
||||
{
|
||||
read_obj!(let nil_obj = nil);
|
||||
let sym: Sym = **nil_obj;
|
||||
assert_eq!(NIL_NAME.sym, sym);
|
||||
assert_eq!(number_of_tracked_allocations(), start + 1);
|
||||
|
||||
nil_obj.attrs();
|
||||
let ty_sym_obj = SYM_NAME.sym_ref();
|
||||
assert_eq!(number_of_tracked_allocations(), start + 2);
|
||||
}
|
||||
|
||||
let on = TRUE_NAME.sym_ref();
|
||||
// true sym obj, sym ty obj shouldn't be duplicated
|
||||
assert_eq!(number_of_tracked_allocations(), start + 3);
|
||||
|
||||
let off = FALSE_NAME.sym_ref();
|
||||
// false sym obj, sym ty obj shouldn't be duplicated
|
||||
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 + 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dyn_obj_ref_eq() {
|
||||
#[derive(Default, Debug, Scan)]
|
||||
struct FooObj { vtable: Attrs, attrs: Attrs }
|
||||
|
||||
impl_obj!(FooObj, vtable, attrs);
|
||||
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
let start = number_of_tracked_allocations(); // need 'start' because of static allocations
|
||||
run_with_gc_cleanup(|| {
|
||||
let rf1: ObjRef = ObjRef::new(FooObj::default());
|
||||
let rf2 = rf1.clone();
|
||||
|
||||
assert!(rf1.ref_eq(&rf2));
|
||||
assert!(rf2.ref_eq(&rf1));
|
||||
assert_eq!(number_of_tracked_allocations(), start + 1);
|
||||
});
|
||||
assert_eq!(number_of_tracked_allocations(), start + 0);
|
||||
}
|
||||
34
src/obj/ty.rs
Normal file
34
src/obj/ty.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::obj::{names::*, prelude::*};
|
||||
use once_cell::sync::Lazy;
|
||||
use shredder::Scan;
|
||||
|
||||
#[derive(Scan, Debug)]
|
||||
pub struct Ty {
|
||||
vtable: Vtable,
|
||||
attrs: Attrs,
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
pub fn new_obj(name: ObjRef) -> ObjRef<Self> {
|
||||
// Ty objects have these attributes:
|
||||
// __ty__ - always the Type type
|
||||
// __name__ - this type's name as a symbol
|
||||
ObjRef::new(Ty {
|
||||
vtable: vtable! {
|
||||
TY_MEMBER_NAME.sym => TY_TY.clone(),
|
||||
NAME_MEMBER_NAME.sym => name.clone(),
|
||||
},
|
||||
attrs: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub static TY_TY: Lazy<ObjRef<Ty>> = Lazy::new(|| {
|
||||
// TODO : vtable for Ty
|
||||
ObjRef::new(Ty {
|
||||
vtable: Default::default(),
|
||||
attrs: Default::default(),
|
||||
})
|
||||
});
|
||||
|
||||
impl_obj!(Ty);
|
||||
47
src/vm/consts.rs
Normal file
47
src/vm/consts.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
6
src/vm/frame.rs
Normal file
6
src/vm/frame.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
/// A stack call frame.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Frame {
|
||||
last_pc: usize,
|
||||
stack_base: usize,
|
||||
}
|
||||
111
src/vm/inst.rs
Normal file
111
src/vm/inst.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
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<Sym>),
|
||||
|
||||
/// Pops a symbol value and an object reference.
|
||||
///
|
||||
/// This will get an attr from the object reference pointed to by the symbol.
|
||||
GetAttr(Sym),
|
||||
|
||||
/// A target reference and a source reference from the stack.
|
||||
///
|
||||
/// The target reference will have the given symbol attribute assigned to the source.
|
||||
///
|
||||
/// In code, it would look like this:
|
||||
///
|
||||
/// target.symbol = source
|
||||
///
|
||||
SetAttr(Sym),
|
||||
|
||||
/// Jump to a given address in the current function unconditionally.
|
||||
Jump(usize),
|
||||
|
||||
/// Jump to a given address in the current function if the condition flag is true.
|
||||
///
|
||||
/// The condition flag may be set by an internal function.
|
||||
JumpTrue(usize),
|
||||
|
||||
/// 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,
|
||||
}
|
||||
52
src/vm/mod.rs
Normal file
52
src/vm/mod.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
mod frame;
|
||||
pub mod inst;
|
||||
pub mod consts;
|
||||
|
||||
use crate::obj::prelude::*;
|
||||
|
||||
use frame::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Vm {
|
||||
stack: Vec<ObjRef>,
|
||||
frames: Vec<Frame>,
|
||||
pc: usize,
|
||||
condition: bool,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stack: Default::default(),
|
||||
frames: vec![Default::default()], // Start with a root stack frame
|
||||
pc: 0,
|
||||
condition: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack(&self) -> &Vec<ObjRef> {
|
||||
&self.stack
|
||||
}
|
||||
|
||||
pub fn stack_mut(&mut self) -> &mut Vec<ObjRef> {
|
||||
&mut self.stack
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: ObjRef) {
|
||||
self.stack_mut().push(value);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<ObjRef> {
|
||||
self.stack_mut().pop()
|
||||
}
|
||||
|
||||
pub fn pc(&self) -> usize {
|
||||
self.pc
|
||||
}
|
||||
|
||||
pub fn condition(&self) -> bool {
|
||||
self.condition
|
||||
}
|
||||
|
||||
//pub fn new_local(&mut self, name: String,
|
||||
}
|
||||
Reference in New Issue
Block a user