Initial commit

* Parser, compiler, objects, and VM base implementations
* Stuff will print out, functions called, etc
* There are probably plenty of bugs but this is a good starting point to
  start committing changes into git

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2023-04-01 22:34:35 -07:00
commit bfe7ca33bd
26 changed files with 13853 additions and 0 deletions

137
src/obj/builtin_fun.rs Normal file
View File

@@ -0,0 +1,137 @@
use std::fmt;
use crate::vm::frame::Frame;
use crate::{obj::prelude::*, vm::Vm};
pub type BuiltinFunPtr = fn(BuiltinFunVm, Vec<ObjPtr>, BuiltinFunState) -> BuiltinFunResult;
#[macro_export]
macro_rules! builtin_fun {
($name:expr, $ptr:expr) => {
$crate::obj::builtin_fun::BuiltinFun::new($name.into(), $ptr)
};
}
#[macro_export]
macro_rules! builtin_fun_ptr {
($($tt:tt)+) => { $crate::obj::ObjPtr::new(builtin_fun!($($tt)+)) };
}
pub use builtin_fun;
pub use builtin_fun_ptr;
/// A result from a builtin function.
#[derive(Debug)]
pub enum BuiltinFunResult {
/// Return the given optional object.
Return(Option<ObjPtr>),
/// Yield function execution to the given stack frame.
Yield(Frame),
}
/// A builtin function may be resumed after it's done calling a user-defined
/// function. This enum determines what the function execution context is.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinFunState {
/// This function is being called for the first time.
Begin,
/// This function is being resumed after yielding control to some other function.
Resume(usize),
}
impl BuiltinFunState {
pub fn next(&self) -> Self {
match self {
BuiltinFunState::Begin => BuiltinFunState::Resume(0),
BuiltinFunState::Resume(n) => BuiltinFunState::Resume(n + 1),
}
}
}
/// This is a wrapper structure around the VM that provides an API to a
/// `BuiltinFun` function call.
///
/// It has some extra checks that prevent the VM being used in a weird/unexpected way.
pub struct BuiltinFunVm<'vm> {
vm: &'vm mut Vm,
last_sp: usize,
}
impl<'vm> BuiltinFunVm<'vm> {
pub fn new(vm: &'vm mut Vm, last_sp: usize) -> Self {
Self { vm, last_sp }
}
pub fn stack(&self) -> &Vec<ObjPtr> {
self.vm.stack()
}
pub fn make_frame(&self, fun: ObjPtr, args: Vec<ObjPtr>) -> Option<Frame> {
self.vm.make_frame(fun, args)
}
pub fn push_stack(&mut self, value: ObjPtr) {
self.vm.push_stack(value);
}
pub fn pop_stack(&mut self) -> Option<ObjPtr> {
if self.stack().len() <= self.last_sp {
panic!("Tried to underflow stack in BuiltinFun call ");
}
self.pop_stack_unchecked()
}
pub fn pop_stack_unchecked(&mut self) -> Option<ObjPtr> {
self.vm.pop_stack()
}
}
pub struct BuiltinFun {
name: String,
fun: BuiltinFunPtr,
attrs: AttrsPtr,
}
impl fmt::Debug for BuiltinFun {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("BuiltinFun")
.field("name", &self.name)
.field(
"fun",
&format!(
"<builtin function at {:#016x}>",
(&self.fun as *const _) as usize
),
)
.field("attrs", &self.attrs)
.finish()
}
}
impl BuiltinFun {
pub fn new(name: String, fun: BuiltinFunPtr) -> Self {
Self {
name,
fun,
attrs: Default::default(),
}
}
pub fn fun(&self) -> BuiltinFunPtr {
self.fun
}
pub fn name(&self) -> &String {
&self.name
}
}
impl Obj for BuiltinFun {
fn attrs(&self) -> AttrsPtr {
AttrsPtr::clone(&self.attrs)
}
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
self
}
}

55
src/obj/builtins.rs Normal file
View File

@@ -0,0 +1,55 @@
//! Builtin functions and objects.
use crate::obj::prelude::*;
pub const BUILTIN_FUNS: &[(&str, BuiltinFunPtr)] = &[
//
("println", println),
("print", print),
];
fn println(vm: BuiltinFunVm, args: Vec<ObjPtr>, state: BuiltinFunState) -> BuiltinFunResult {
do_print(vm, args, state, "\n")
}
fn print(vm: BuiltinFunVm, args: Vec<ObjPtr>, state: BuiltinFunState) -> BuiltinFunResult {
do_print(vm, args, state, "")
}
/// Shared logic of `print` and `println`.
/// It can append a string to the end of whatever is being printed.
fn do_print(
mut vm: BuiltinFunVm,
args: Vec<ObjPtr>,
state: BuiltinFunState,
end: &str,
) -> BuiltinFunResult {
const POST_CALL_STR: BuiltinFunState = BuiltinFunState::Resume(0);
if state == BuiltinFunState::Begin {
if args.len() == 0 {
println!();
return BuiltinFunResult::Return(None);
} else if args.len() != 1 {
todo!("Throw exception: arity error");
}
let arg = ObjPtr::clone(&args[0]);
let str_fun = arg.get("__str__").expect("No __str__ function?");
let frame = vm
.make_frame(str_fun, vec![arg])
.expect("__str__ attr was not a function?");
BuiltinFunResult::Yield(frame)
} else if state == POST_CALL_STR {
// Get return value, cast it as an `Any`, and then downcast to a string.
let result = vm.pop_stack().expect("No return value from __str__?");
let result_any = result.as_any();
if let Some(value) = result_any.downcast_ref::<Str>() {
print!("{}{}", value.value(), end);
} else {
todo!("Throw exception: __str__ function didn't return a Str?");
}
BuiltinFunResult::Return(None)
} else {
panic!("unexpected state: {state:?}");
}
}

53
src/obj/int.rs Normal file
View File

@@ -0,0 +1,53 @@
use crate::obj::prelude::*;
use std::{any::Any, sync::LazyLock};
pub type IntValue = i64;
static INT_ATTRS: LazyLock<AttrsPtr> = LazyLock::new(|| {
attrs_ptr! {
//
"__str__" => builtin_fun_ptr!("__str__", |_vm, args, _state| {
if args.len() != 1 {
todo!("Throw exception: arity error");
}
let arg_any = &args[0].as_any();
let obj = arg_any.downcast_ref::<Int>().expect("Int.__str__ did not receive Int?");
BuiltinFunResult::Return(Some(ObjPtr::new(Str::new(obj.value().to_string()))))
}),
}
});
#[derive(Debug, Clone)]
pub struct Int {
attrs: AttrsPtr,
value: IntValue,
}
impl Int {
pub fn new(value: IntValue) -> Self {
let attrs = AttrsPtr::clone(&INT_ATTRS);
// TODO intern
Int { attrs, value }
}
pub fn value(&self) -> IntValue {
self.value
}
}
impl Obj for Int {
fn attrs(&self) -> AttrsPtr {
AttrsPtr::clone(&self.attrs)
}
fn as_any(&self) -> &(dyn Any + 'static) {
self
}
}
impl ToString for Int {
fn to_string(&self) -> String {
format!("{}", self.value())
}
}

73
src/obj/mod.rs Normal file
View File

@@ -0,0 +1,73 @@
pub mod builtin_fun;
pub mod builtins;
pub mod int;
pub mod none;
pub mod str;
pub mod user_fun;
pub mod prelude {
// Module types
pub use super::{builtin_fun::*, int::*, none::*, str::*, user_fun::*};
// Local types
pub use super::{Attrs, AttrsPtr, Obj, ObjPtr};
// Macros
pub use super::{attrs, attrs_ptr};
// Builtins
pub use super::builtins::*;
}
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
// Macro definitions
#[macro_export]
macro_rules! attrs {
($($key:expr => $value:expr),* $(,)?) => {{
let mut __attrs = $crate::obj::Attrs::new();
$(
__attrs.insert($key.into(), $value);
)*
__attrs
}};
}
#[macro_export]
macro_rules! attrs_ptr {
($($tt:tt)*) => {{
$crate::obj::AttrsPtr::new(
std::sync::RwLock::new(
attrs! {
$($tt)*
}
)
)
}};
}
pub use attrs;
pub use attrs_ptr;
pub type ObjPtr<T = dyn Obj + 'static> = Arc<T>;
pub type Attrs = HashMap<String, ObjPtr>;
pub type AttrsPtr = Arc<RwLock<Attrs>>;
pub trait Obj: Debug + Sync + Send {
fn attrs(&self) -> AttrsPtr;
fn as_any(&self) -> &(dyn Any + 'static);
fn get(&self, key: &str) -> Option<ObjPtr> {
let attrs_ptr = self.attrs();
let attrs = attrs_ptr.read().unwrap();
attrs.get(key).cloned()
}
fn set(&mut self, key: String, value: ObjPtr) -> Option<ObjPtr> {
let attrs_ptr = self.attrs();
let mut attrs = attrs_ptr.write().unwrap();
attrs.insert(key, value)
}
}

39
src/obj/none.rs Normal file
View File

@@ -0,0 +1,39 @@
use crate::obj::prelude::*;
use std::{any::Any, sync::LazyLock};
pub static NONE_INST: LazyLock<ObjPtr> = LazyLock::new(|| ObjPtr::new(None::new()));
static NONE_STR: LazyLock<ObjPtr> = LazyLock::new(|| ObjPtr::new(Str::new("None")));
static NONE_ATTRS: LazyLock<AttrsPtr> = LazyLock::new(|| {
attrs_ptr! {
//
"__str__" => builtin_fun_ptr!("__str__", |_vm, args, _state| {
if args.len() != 1 {
todo!("Throw exception: arity error");
}
BuiltinFunResult::Return(Some(ObjPtr::clone(&NONE_STR)))
}),
}
});
#[derive(Debug)]
pub struct None {
attrs: AttrsPtr,
}
impl None {
fn new() -> Self {
let attrs = AttrsPtr::clone(&NONE_ATTRS);
Self { attrs }
}
}
impl Obj for None {
fn attrs(&self) -> AttrsPtr {
AttrsPtr::clone(&self.attrs)
}
fn as_any(&self) -> &(dyn Any + 'static) {
self
}
}

53
src/obj/str.rs Normal file
View File

@@ -0,0 +1,53 @@
use crate::obj::prelude::*;
use std::{any::Any, sync::LazyLock};
pub type StrValue = String;
static STR_ATTRS: LazyLock<AttrsPtr> = LazyLock::new(|| {
attrs_ptr! {
//
"__str__" => builtin_fun_ptr!("__str__", |_vm, mut args, _state| {
if args.len() != 1 {
todo!("Throw exception: arity error");
}
BuiltinFunResult::Return(Some(args.pop().unwrap()))
}),
}
});
#[derive(Debug, Clone)]
pub struct Str {
attrs: AttrsPtr,
value: StrValue,
}
impl Str {
pub fn new(value: impl Into<StrValue>) -> Self {
let attrs = AttrsPtr::clone(&STR_ATTRS);
// TODO intern
Str {
attrs,
value: value.into(),
}
}
pub fn value(&self) -> &StrValue {
&self.value
}
}
impl Obj for Str {
fn attrs(&self) -> AttrsPtr {
AttrsPtr::clone(&self.attrs)
}
fn as_any(&self) -> &(dyn Any + 'static) {
self
}
}
impl ToString for Str {
fn to_string(&self) -> String {
self.value().clone()
}
}

50
src/obj/user_fun.rs Normal file
View File

@@ -0,0 +1,50 @@
use crate::obj::prelude::*;
use crate::vm::{inst::Inst, name::Name};
#[derive(Debug)]
pub struct UserFun {
name: Option<Name>,
params: Vec<Name>,
locals: Vec<Name>,
body: Vec<Inst>,
attrs: AttrsPtr,
}
impl UserFun {
pub fn new(name: Option<Name>, params: Vec<Name>, locals: Vec<Name>, body: Vec<Inst>) -> Self {
Self {
name,
params,
locals,
body,
attrs: Default::default(),
}
}
/// Returns the name of this function, `None` if it is anonymous.
pub fn name(&self) -> Option<Name> {
self.name
}
pub fn params(&self) -> &Vec<Name> {
&self.params
}
pub fn locals(&self) -> &Vec<Name> {
&self.locals
}
pub fn body(&self) -> &Vec<Inst> {
&self.body
}
}
impl Obj for UserFun {
fn attrs(&self) -> AttrsPtr {
AttrsPtr::clone(&self.attrs)
}
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
self
}
}