Initial commit

Includes: runtime base from a previous project, syn(tax) module with
parser and lexer

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-09-01 17:32:48 -07:00
commit 178ed4a952
24 changed files with 1139 additions and 0 deletions

15
runtime/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "runtime"
version = "0.1.0"
authors = ["Alek Ratzloff <alekratz@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
maplit = "1.0.2"
once_cell = "1.4.1"
[dependencies.shredder]
git = "https://github.com/Others/shredder"
features = ["nightly-features"]

4
runtime/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
#![feature(unsize, coerce_unsized)]
#[macro_use] pub mod obj;
#[macro_use] pub mod vm;

14
runtime/src/obj/attrs.rs Normal file
View File

@@ -0,0 +1,14 @@
use crate::{obj::{Obj, ObjRef, sym::Sym}};
use std::collections::BTreeMap;
pub type Attrs = BTreeMap<Sym, ObjRef>;
impl Obj for Attrs {
fn attrs(&self) -> &Attrs {
self
}
fn attrs_mut(&mut self) -> Option<&mut Attrs> {
Some(self)
}
}

111
runtime/src/obj/fun.rs Normal file
View File

@@ -0,0 +1,111 @@
use crate::{obj::{names::*, prelude::*}, vm::{inst::Inst, Vm}};
use once_cell::sync::Lazy;
use shredder::{GcSafeWrapper, Scan};
use std::fmt::{Debug, Formatter, self};
//
// struct UserFun
//
#[derive(Scan)]
pub struct UserFun {
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, attrs);
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
//
#[derive(Scan)]
pub struct NativeFun {
#[shredder(skip)]
fun: GcSafeWrapper<Box<dyn Fn(&mut Vm) + Send + Sync>>,
attrs: Attrs,
}
//
// impl NativeFun
//
impl NativeFun {
pub fn new(fun: Box<dyn Fn(&mut Vm) + Send + Sync>) -> ObjRef<Self> {
let obj_ref = ObjRef::new(Self {
fun: GcSafeWrapper::new(fun),
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)
}
}
//
// 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, attrs);
//
// 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(Box::new(|_vm| {
/*
* 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()
*/
todo!("__get_attr__ function")
}))
});

30
runtime/src/obj/int.rs Normal file
View File

@@ -0,0 +1,30 @@
use crate::obj::{names::*, prelude::*};
use shredder::Scan;
#[derive(Debug, Scan)]
pub struct Int {
value: i64,
attrs: Attrs,
}
impl Int {
pub fn new(value: i64) -> ObjRef<Self> {
let obj_ref = ObjRef::new(Self {
value,
attrs: Default::default(),
});
{
write_obj!(let obj = obj_ref);
obj.set_attr(*GET_ATTR_MEMBER_SYM, GET_ATTR_MEMBER_FUN.clone());
}
obj_ref
}
pub fn value(&self) -> i64 {
self.value
}
}
impl_obj!(Int, attrs);

58
runtime/src/obj/intern.rs Normal file
View 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(), }
}
}

45
runtime/src/obj/macros.rs Normal file
View File

@@ -0,0 +1,45 @@
/// Implements `Obj` for a given type and using the given member for attributes.
#[macro_export]
macro_rules! impl_obj {
($ty:ty, $attrs:ident) => {
impl $crate::obj::Obj for $ty {
fn attrs(&self) -> &Attrs {
&self.$attrs
}
fn attrs_mut(&mut self) -> Option<&mut Attrs> {
Some(&mut self.$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! { }
}}
}

108
runtime/src/obj/mod.rs Normal file
View File

@@ -0,0 +1,108 @@
#[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;
pub mod ty;
#[cfg(test)] mod test;
pub mod prelude {
pub use crate::obj::{attrs::*, fun::*, int::*, intern::*, str::*, sym::*, ty::*, Obj, ObjRef};
}
use shredder::{Gc, Scan};
use std::{
ops::{Deref, DerefMut, CoerceUnsized},
marker::Unsize,
sync::RwLock,
};
use attrs::*;
use sym::Sym;
//
// trait Obj
//
pub trait Obj: Scan + std::fmt::Debug {
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 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: Obj + ?Sized + Send + Sync = (dyn Obj + Send + Sync + 'static)> {
gc: Gc<RwLock<T>>,
}
impl<T: Obj + ?Sized + Send + Sync> Clone for ObjRef<T> {
fn clone(&self) -> Self {
ObjRef { gc: self.gc.clone() }
}
}
//
// impl ObjRef
//
impl<T: Obj + ?Sized + Send + Sync> ObjRef<T> {
/// 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: Obj + Send + Sync + 'static> ObjRef<T> {
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
}
}

55
runtime/src/obj/names.rs Normal file
View File

@@ -0,0 +1,55 @@
use crate::obj::sym::{Sym, global_sym};
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<Sym> = Lazy::new(|| global_sym($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");
//
// 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__");
//
// Predefined VM-aware symbols
//
name!(LOCAL_NAME, LOCAL_SYM, "__local__");
//
// 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_SYM, "true");
name!(FALSE_NAME, FALSE_SYM, "false");
name!(NIL_NAME, NIL_SYM, "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__");

11
runtime/src/obj/str.rs Normal file
View File

@@ -0,0 +1,11 @@
use crate::obj::prelude::*;
use shredder::Scan;
#[derive(Debug, Scan)]
pub struct Str {
value: String,
attrs: Attrs,
}
impl_obj!(Str, attrs);

95
runtime/src/obj/sym.rs Normal file
View File

@@ -0,0 +1,95 @@
use crate::obj::{prelude::*, intern::Interner, names::*};
use once_cell::sync::Lazy;
use shredder::Scan;
use std::sync::Mutex;
//
// struct Sym
//
/// 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)
}
pub fn index(&self) -> usize {
self.0
}
pub fn new_obj(sym: impl Into<Sym>) -> ObjRef<Sym> {
ObjRef::new(sym.into())
}
}
impl From<usize> for Sym {
fn from(other: usize) -> Sym {
Sym(other)
}
}
impl Obj for Sym {
fn attrs(&self) -> &Attrs {
&SYM_ATTRS
}
fn attrs_mut(&mut self) -> Option<&mut Attrs> { None }
}
//
// Symbol Ty object
//
pub static NIL_OBJ: Lazy<ObjRef<Sym>> = Lazy::new(|| Sym::new_obj(*NIL_SYM));
pub static TRUE_OBJ: Lazy<ObjRef<Sym>> = Lazy::new(|| Sym::new_obj(*TRUE_SYM));
pub static FALSE_OBJ: Lazy<ObjRef<Sym>> = Lazy::new(|| Sym::new_obj(*FALSE_SYM));
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
//
pub(crate) static SYMBOLS: Lazy<Mutex<SymTable>> = Lazy::new(|| {
Mutex::new(SymTable::default())
});
/*
pub(crate) fn global_name_lookup(sym: Sym) -> Option<Arc<String>> {
let table = SYMBOLS.lock()
.unwrap();
table.lookup(sym)
}
pub(crate) fn global_sym_lookup(s: String) -> Option<Sym> {
let table = SYMBOLS.lock()
.unwrap();
table.lookup_sym(s)
}
*/
pub(crate) fn global_sym(s: String) -> Sym {
let mut table = SYMBOLS.lock()
.unwrap();
table.insert(s)
}
//
// SymTable
//
pub type SymTable = Interner<String>;
pub type Locals = Interner<Sym>;

73
runtime/src/obj/test.rs Normal file
View File

@@ -0,0 +1,73 @@
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_OBJ.clone();
// 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_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 on = TRUE_OBJ.clone();
// true sym obj, sym ty obj shouldn't be duplicated
assert_eq!(number_of_tracked_allocations(), start + 6);
let off = FALSE_OBJ.clone();
// false sym obj, sym ty obj shouldn't be duplicated
assert_eq!(number_of_tracked_allocations(), start + 7);
});
// 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
}
#[test]
fn test_dyn_obj_ref_eq() {
#[derive(Default, Debug, Scan)]
struct FooObj { attrs: Attrs }
impl_obj!(FooObj, 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);
}

43
runtime/src/obj/ty.rs Normal file
View File

@@ -0,0 +1,43 @@
use crate::obj::{names::*, prelude::*};
use once_cell::sync::Lazy;
use shredder::Scan;
#[derive(Scan, Debug)]
pub struct Ty {
attrs: Attrs,
}
impl Ty {
pub fn new(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 {
attrs: attrs! {
*TY_MEMBER_SYM => TY_TY.clone(),
*NAME_MEMBER_SYM => name.clone(),
}
})
}
}
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(|| {
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
});
impl_obj!(Ty, attrs);

6
runtime/src/vm/frame.rs Normal file
View File

@@ -0,0 +1,6 @@
/// A stack call frame.
#[derive(Default, Debug, Clone)]
pub struct Frame {
last_pc: usize,
stack_base: usize,
}

41
runtime/src/vm/inst.rs Normal file
View File

@@ -0,0 +1,41 @@
use crate::obj::prelude::*;
#[derive(Debug, PartialEq)]
pub enum Inst {
/// Push a literal symbol object to the stack.
PushSym(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.
///
/// 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.
///
/// This will set an attr of the second object ref to the first object ref, with the symbol
/// value name.
///
/// If the given value is `:__local__`, this instruction will set a local value.
SetAttr,
/// 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),
/// 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.
Call(usize),
/// Pops the top value from the stack, and returns from the function, using the popped value as
/// a return value.
Return,
}

51
runtime/src/vm/mod.rs Normal file
View File

@@ -0,0 +1,51 @@
mod frame;
pub mod inst;
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,
}