Let's talk about to_repr and to_str. to_repr tries to do what Python's `repr` function does - that is, it converts an object into a developer-readable (but maybe not human-readable) string. This function is implemented for every object, and may very well just write out "<MyType at 0x12345678>". to_str, on the other hand, tries to turn an object into an explicitly human-readable format. In Python (which we are modeling a lot of our design after), the str() function usually will end up calling `repr()` itself, if no other implementation has been provided. Previously in our implementation, there was a bit of a disconnect between `to_repr` and `to_str`, versus `Debug` and `Display`. `to_repr` would kind of do its own thing, and then maybe call either `Display` or `Debug` to format an object. Consequently, `to_str` would kind of do its own thing too - usually calling `to_repr` but not always. This change attempts to strengthen the definitions of `to_repr` and `to_str`. *In general*, a call to `to_repr` should be calling an object's `Debug::fmt` function, and *in general* a call to `to_str()` should be calling an object's `Display::fmt` function. Often, the `Display::fmt` will just end up calling `Debug::fmt` itself, but now the `to_str()` and `to_repr()` interfaces are much better defined than they used to be. The only major downside is that we are giving up the `Debug` implementation for language logic, rather than debugging-the-language-itself logic. I can see this biting us down the road if we ever need a Rust-style `Debug` implementation, but for now, I think this is going to serve our needs just fine. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
319 lines
8.6 KiB
Rust
319 lines
8.6 KiB
Rust
use std::fmt::{self, Debug, Display};
|
|
use std::ptr;
|
|
use std::rc::Rc;
|
|
|
|
use gc::{Finalize, Trace};
|
|
|
|
use crate::obj::macros::*;
|
|
use crate::obj::{make_ptr, BaseObj, ObjP, Object};
|
|
use crate::vm::{Argc, Chunk, Frame, Function, Vm};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// FunctionResult, FunctionState
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// A result that instructs the VM what to do after a function finishes its execution.
|
|
#[derive(Debug)]
|
|
pub enum FunctionResult {
|
|
/// Take this value and push it on the stack as its return value.
|
|
ReturnPush(ObjP),
|
|
|
|
/// Return value has already been pushed to the stack.
|
|
Return,
|
|
|
|
/// Yield control to the VM with a state marker, and resume execution.
|
|
///
|
|
/// This means that a new stack frame should have been pushed to the VM and resume execution
|
|
/// starting from there.
|
|
Yield(usize),
|
|
}
|
|
|
|
impl From<ObjP> for FunctionResult {
|
|
fn from(other: ObjP) -> Self {
|
|
FunctionResult::ReturnPush(other)
|
|
}
|
|
}
|
|
|
|
/// A function's resume state.
|
|
///
|
|
/// When a builtin function is called, it may need to set up a stack frame for a new function,
|
|
/// yielding its control back to the VM. If it does this, then presumably, that function would like
|
|
/// to resume its execution where it left off. There's not really a way to capture a native
|
|
/// function's execution state in Rust (without tons of unsafe and not-really-worth-it black
|
|
/// magic), so instead a builtin function will accept a `FunctionState` value to give it a hint of
|
|
/// where it left off.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum FunctionState {
|
|
Begin,
|
|
Resume(usize),
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// BuiltinFunction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
pub type BuiltinFunctionPtr = fn(vm: &mut Vm, function_state: FunctionState) -> FunctionResult;
|
|
|
|
#[derive(Trace, Finalize)]
|
|
pub struct BuiltinFunction {
|
|
base: BaseObj,
|
|
#[unsafe_ignore_trace]
|
|
name: Rc<String>,
|
|
#[unsafe_ignore_trace]
|
|
function: BuiltinFunctionPtr,
|
|
arity: Argc,
|
|
}
|
|
|
|
impl BuiltinFunction {
|
|
pub fn new(name: impl ToString, function: BuiltinFunctionPtr, arity: Argc) -> Self {
|
|
Self {
|
|
base: Default::default(),
|
|
name: Rc::new(name.to_string()),
|
|
function,
|
|
arity,
|
|
}
|
|
}
|
|
|
|
impl_create!(
|
|
name: impl ToString,
|
|
function: BuiltinFunctionPtr,
|
|
arity: Argc,
|
|
);
|
|
|
|
pub fn name(&self) -> &Rc<String> {
|
|
&self.name
|
|
}
|
|
}
|
|
|
|
impl Display for BuiltinFunction {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
Debug::fmt(self, fmt)
|
|
}
|
|
}
|
|
|
|
impl Debug for BuiltinFunction {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
fmt,
|
|
"<BuiltinFunction {}/{} at 0x{:x}>",
|
|
self.name(),
|
|
self.arity().unwrap(),
|
|
self.function as *const BuiltinFunctionPtr as usize
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Object for BuiltinFunction {
|
|
fn arity(&self) -> Option<Argc> {
|
|
Some(self.arity)
|
|
}
|
|
|
|
fn call(&self, vm: &mut Vm, argc: Argc) {
|
|
let new_frame = Frame::new(
|
|
Rc::clone(&self.name),
|
|
Function::Builtin(self.function, FunctionState::Begin),
|
|
vm.stack().len() - (argc as usize),
|
|
);
|
|
vm.push_frame(new_frame);
|
|
}
|
|
|
|
fn equals(&self, other: &dyn Object) -> bool {
|
|
// TODO BuiltinFunction::equals : need something more robust than checking addr_eq,
|
|
// maybe check the self_binding pointer too?
|
|
if let Some(other) = other.as_any().downcast_ref::<BuiltinFunction>() {
|
|
ptr::addr_eq(self, other)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
impl_base_obj!(BuiltinFunction);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// UserFunction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#[derive(Clone, Trace, Finalize)]
|
|
pub struct UserFunction {
|
|
base: BaseObj,
|
|
#[unsafe_ignore_trace]
|
|
name: Rc<String>,
|
|
#[unsafe_ignore_trace]
|
|
chunk: Rc<Chunk>,
|
|
arity: Argc,
|
|
captures: Vec<ObjP>,
|
|
}
|
|
|
|
impl UserFunction {
|
|
pub fn new(chunk: Chunk, arity: Argc) -> Self {
|
|
Self {
|
|
base: Default::default(),
|
|
name: Rc::new("(anonymous)".to_string()),
|
|
chunk: Rc::new(chunk),
|
|
arity,
|
|
captures: Default::default(),
|
|
}
|
|
}
|
|
|
|
impl_create!(chunk: Chunk, arity: Argc);
|
|
|
|
pub fn name(&self) -> &Rc<String> {
|
|
&self.name
|
|
}
|
|
|
|
pub fn set_name(&mut self, name: Rc<String>) {
|
|
self.name = name;
|
|
}
|
|
|
|
pub fn chunk(&self) -> &Chunk {
|
|
&self.chunk
|
|
}
|
|
|
|
pub fn push_capture(&mut self, value: ObjP) {
|
|
self.captures.push(value);
|
|
}
|
|
}
|
|
|
|
impl Display for UserFunction {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
Debug::fmt(self, fmt)
|
|
}
|
|
}
|
|
|
|
impl Debug for UserFunction {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
fmt,
|
|
"<UserFunction {}/{} at 0x{:x}>",
|
|
self.name(),
|
|
self.arity().unwrap(),
|
|
self as *const _ as usize
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Object for UserFunction {
|
|
fn arity(&self) -> Option<Argc> {
|
|
Some(self.arity)
|
|
}
|
|
|
|
fn call(&self, vm: &mut Vm, argc: Argc) {
|
|
assert_eq!(argc, self.arity, "argc must match arity");
|
|
let new_frame = Frame::new(
|
|
Rc::clone(&self.name),
|
|
Function::Chunk(Rc::clone(&self.chunk)),
|
|
vm.stack().len() - (argc as usize),
|
|
);
|
|
vm.push_frame(new_frame);
|
|
for capture in &self.captures {
|
|
vm.push(capture.clone());
|
|
}
|
|
}
|
|
|
|
fn equals(&self, other: &dyn Object) -> bool {
|
|
if let Some(other) = other.as_any().downcast_ref::<UserFunction>() {
|
|
// TODO UserFunction::equals : need something more robust than checking addr_eq.
|
|
ptr::addr_eq(self, other)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
impl_base_obj!(UserFunction);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Method
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#[derive(Trace, Finalize)]
|
|
pub struct Method {
|
|
base: BaseObj,
|
|
self_binding: ObjP,
|
|
function: ObjP,
|
|
}
|
|
|
|
impl Method {
|
|
pub fn new(self_binding: ObjP, function: ObjP) -> Self {
|
|
Self {
|
|
base: Default::default(),
|
|
self_binding,
|
|
function,
|
|
}
|
|
}
|
|
|
|
pub fn create(self_binding: ObjP, function: ObjP) -> ObjP {
|
|
let ptr = make_ptr(Self::new(self_binding, function));
|
|
ptr.borrow_mut().instantiate();
|
|
ptr
|
|
}
|
|
|
|
pub fn self_binding(&self) -> &ObjP {
|
|
&self.self_binding
|
|
}
|
|
}
|
|
|
|
impl Display for Method {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
Debug::fmt(self, fmt)
|
|
}
|
|
}
|
|
|
|
impl Debug for Method {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
let function_name: Rc<_> = if let Some(function) = self
|
|
.function
|
|
.borrow()
|
|
.as_any()
|
|
.downcast_ref::<BuiltinFunction>()
|
|
{
|
|
Rc::clone(&function.name())
|
|
} else if let Some(function) = self
|
|
.function
|
|
.borrow()
|
|
.as_any()
|
|
.downcast_ref::<UserFunction>()
|
|
{
|
|
function.name().clone()
|
|
} else {
|
|
unreachable!()
|
|
};
|
|
write!(
|
|
fmt,
|
|
"<Method {}.{}/{} at {}>",
|
|
self.self_binding.borrow().ty_name(),
|
|
function_name,
|
|
self.function.borrow().arity().unwrap(),
|
|
self as *const _ as usize
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Object for Method {
|
|
fn arity(&self) -> Option<Argc> {
|
|
// Subtract one from the arity - this is because the VM uses arity() to check against the
|
|
// number of arguments passed.
|
|
self.function.borrow().arity().map(|arity| arity - 1)
|
|
}
|
|
|
|
fn call(&self, vm: &mut Vm, mut argc: Argc) {
|
|
let self_pos = vm.stack().len() - (argc as usize);
|
|
vm.stack_mut().insert(self_pos, self.self_binding.clone());
|
|
argc += 1;
|
|
|
|
self.function.borrow().call(vm, argc)
|
|
}
|
|
|
|
fn equals(&self, other: &dyn Object) -> bool {
|
|
if let Some(other) = other.as_any().downcast_ref::<Method>() {
|
|
ptr::addr_eq(&*self.self_binding, &*other.self_binding)
|
|
&& ptr::addr_eq(&*self.function, &*other.function)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
impl_base_obj!(Method);
|
|
}
|