2024-09-24 09:03:34 -07:00
|
|
|
use std::fmt::{self, Debug, Display};
|
|
|
|
|
use std::ptr;
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
|
|
use gc::{Finalize, Trace};
|
|
|
|
|
|
|
|
|
|
use crate::obj::macros::*;
|
2024-09-26 11:07:12 -07:00
|
|
|
use crate::obj::{make_ptr, BaseObj, ObjP, Object};
|
Add function yielding and resuming
Sometimes, a builtin function may need to call out to another function
(user-defined or otherwise). Previously, we were just calling the
function and popping the stack frame, leaving no room for the new
function to be called. This introduces a `FunctionResult` and
`FunctionState` that get passed between these builtin functions. A
builtin function will receive a FunctionState that tells it whether it
is currently beginning or being resumed and can act accordingly. A
builtin function will in turn return a FunctionResult, which can either
be to return and push a value to the stack, return without pushing a
value (value is already on top of the stack), or yield execution back to
the VM (implying that a new stack frame has been pushed with a new
function to execute).
Having to call a new function and resume is a bit unwieldy and
un-ergonomic, and making a macro to help write these would be nice, but
it looks like a procedural macro may be required to really enable this.
For now, we will write these yields by hand and once it becomes truly
too much, we can start looking at writing a macro library to handle this
case.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:34:07 -07:00
|
|
|
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),
|
|
|
|
|
}
|
2024-09-24 09:03:34 -07:00
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2024-09-26 11:07:12 -07:00
|
|
|
// BuiltinFunction
|
2024-09-24 09:03:34 -07:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
Add function yielding and resuming
Sometimes, a builtin function may need to call out to another function
(user-defined or otherwise). Previously, we were just calling the
function and popping the stack frame, leaving no room for the new
function to be called. This introduces a `FunctionResult` and
`FunctionState` that get passed between these builtin functions. A
builtin function will receive a FunctionState that tells it whether it
is currently beginning or being resumed and can act accordingly. A
builtin function will in turn return a FunctionResult, which can either
be to return and push a value to the stack, return without pushing a
value (value is already on top of the stack), or yield execution back to
the VM (implying that a new stack frame has been pushed with a new
function to execute).
Having to call a new function and resume is a bit unwieldy and
un-ergonomic, and making a macro to help write these would be nice, but
it looks like a procedural macro may be required to really enable this.
For now, we will write these yields by hand and once it becomes truly
too much, we can start looking at writing a macro library to handle this
case.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:34:07 -07:00
|
|
|
pub type BuiltinFunctionPtr = fn(vm: &mut Vm, function_state: FunctionState) -> FunctionResult;
|
2024-09-24 09:03:34 -07:00
|
|
|
|
2024-09-26 10:52:47 -07:00
|
|
|
#[derive(Debug, Trace, Finalize)]
|
2024-09-26 11:07:12 -07:00
|
|
|
pub struct BuiltinFunction {
|
|
|
|
|
base: BaseObj,
|
Add function yielding and resuming
Sometimes, a builtin function may need to call out to another function
(user-defined or otherwise). Previously, we were just calling the
function and popping the stack frame, leaving no room for the new
function to be called. This introduces a `FunctionResult` and
`FunctionState` that get passed between these builtin functions. A
builtin function will receive a FunctionState that tells it whether it
is currently beginning or being resumed and can act accordingly. A
builtin function will in turn return a FunctionResult, which can either
be to return and push a value to the stack, return without pushing a
value (value is already on top of the stack), or yield execution back to
the VM (implying that a new stack frame has been pushed with a new
function to execute).
Having to call a new function and resume is a bit unwieldy and
un-ergonomic, and making a macro to help write these would be nice, but
it looks like a procedural macro may be required to really enable this.
For now, we will write these yields by hand and once it becomes truly
too much, we can start looking at writing a macro library to handle this
case.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:34:07 -07:00
|
|
|
#[unsafe_ignore_trace]
|
|
|
|
|
name: Rc<String>,
|
2024-09-24 09:03:34 -07:00
|
|
|
#[unsafe_ignore_trace]
|
|
|
|
|
function: BuiltinFunctionPtr,
|
|
|
|
|
arity: Argc,
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl BuiltinFunction {
|
2024-09-24 09:03:34 -07:00
|
|
|
pub fn new(name: impl ToString, function: BuiltinFunctionPtr, arity: Argc) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
base: Default::default(),
|
Add function yielding and resuming
Sometimes, a builtin function may need to call out to another function
(user-defined or otherwise). Previously, we were just calling the
function and popping the stack frame, leaving no room for the new
function to be called. This introduces a `FunctionResult` and
`FunctionState` that get passed between these builtin functions. A
builtin function will receive a FunctionState that tells it whether it
is currently beginning or being resumed and can act accordingly. A
builtin function will in turn return a FunctionResult, which can either
be to return and push a value to the stack, return without pushing a
value (value is already on top of the stack), or yield execution back to
the VM (implying that a new stack frame has been pushed with a new
function to execute).
Having to call a new function and resume is a bit unwieldy and
un-ergonomic, and making a macro to help write these would be nice, but
it looks like a procedural macro may be required to really enable this.
For now, we will write these yields by hand and once it becomes truly
too much, we can start looking at writing a macro library to handle this
case.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:34:07 -07:00
|
|
|
name: Rc::new(name.to_string()),
|
2024-09-24 09:03:34 -07:00
|
|
|
function,
|
|
|
|
|
arity,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl_create!(
|
|
|
|
|
name: impl ToString,
|
|
|
|
|
function: BuiltinFunctionPtr,
|
|
|
|
|
arity: Argc,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
pub fn name(&self) -> &String {
|
|
|
|
|
&self.name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Display for BuiltinFunction {
|
2024-09-24 09:03:34 -07:00
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Object for BuiltinFunction {
|
2024-09-24 09:03:34 -07:00
|
|
|
fn arity(&self) -> Option<Argc> {
|
|
|
|
|
Some(self.arity)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn call(&self, vm: &mut Vm, argc: Argc) {
|
Add function yielding and resuming
Sometimes, a builtin function may need to call out to another function
(user-defined or otherwise). Previously, we were just calling the
function and popping the stack frame, leaving no room for the new
function to be called. This introduces a `FunctionResult` and
`FunctionState` that get passed between these builtin functions. A
builtin function will receive a FunctionState that tells it whether it
is currently beginning or being resumed and can act accordingly. A
builtin function will in turn return a FunctionResult, which can either
be to return and push a value to the stack, return without pushing a
value (value is already on top of the stack), or yield execution back to
the VM (implying that a new stack frame has been pushed with a new
function to execute).
Having to call a new function and resume is a bit unwieldy and
un-ergonomic, and making a macro to help write these would be nice, but
it looks like a procedural macro may be required to really enable this.
For now, we will write these yields by hand and once it becomes truly
too much, we can start looking at writing a macro library to handle this
case.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:34:07 -07:00
|
|
|
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);
|
2024-09-24 09:03:34 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
fn equals(&self, other: &dyn Object) -> bool {
|
|
|
|
|
// TODO BuiltinFunction::equals : need something more robust than checking addr_eq,
|
2024-09-24 09:03:34 -07:00
|
|
|
// maybe check the self_binding pointer too?
|
2024-09-26 11:07:12 -07:00
|
|
|
if let Some(other) = other.as_any().downcast_ref::<BuiltinFunction>() {
|
2024-09-24 09:03:34 -07:00
|
|
|
ptr::addr_eq(self, other)
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 10:22:03 -07:00
|
|
|
impl_base_obj!(BuiltinFunction);
|
2024-09-24 09:03:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2024-09-26 11:07:12 -07:00
|
|
|
// UserFunction
|
2024-09-24 09:03:34 -07:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2024-09-26 10:52:47 -07:00
|
|
|
#[derive(Debug, Clone, Trace, Finalize)]
|
2024-09-26 11:07:12 -07:00
|
|
|
pub struct UserFunction {
|
|
|
|
|
base: BaseObj,
|
2024-09-24 09:03:34 -07:00
|
|
|
#[unsafe_ignore_trace]
|
|
|
|
|
name: Rc<String>,
|
|
|
|
|
#[unsafe_ignore_trace]
|
|
|
|
|
chunk: Rc<Chunk>,
|
|
|
|
|
arity: Argc,
|
|
|
|
|
captures: Vec<ObjP>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl UserFunction {
|
2024-09-24 09:03:34 -07:00
|
|
|
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) -> &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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Display for UserFunction {
|
2024-09-24 09:03:34 -07:00
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Object for UserFunction {
|
2024-09-24 09:03:34 -07:00
|
|
|
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");
|
Add function yielding and resuming
Sometimes, a builtin function may need to call out to another function
(user-defined or otherwise). Previously, we were just calling the
function and popping the stack frame, leaving no room for the new
function to be called. This introduces a `FunctionResult` and
`FunctionState` that get passed between these builtin functions. A
builtin function will receive a FunctionState that tells it whether it
is currently beginning or being resumed and can act accordingly. A
builtin function will in turn return a FunctionResult, which can either
be to return and push a value to the stack, return without pushing a
value (value is already on top of the stack), or yield execution back to
the VM (implying that a new stack frame has been pushed with a new
function to execute).
Having to call a new function and resume is a bit unwieldy and
un-ergonomic, and making a macro to help write these would be nice, but
it looks like a procedural macro may be required to really enable this.
For now, we will write these yields by hand and once it becomes truly
too much, we can start looking at writing a macro library to handle this
case.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-24 11:34:07 -07:00
|
|
|
let new_frame = Frame::new(
|
|
|
|
|
Rc::clone(&self.name),
|
|
|
|
|
Function::Chunk(Rc::clone(&self.chunk)),
|
|
|
|
|
vm.stack().len() - (argc as usize),
|
|
|
|
|
);
|
2024-09-24 09:03:34 -07:00
|
|
|
vm.push_frame(new_frame);
|
|
|
|
|
for capture in &self.captures {
|
|
|
|
|
vm.push(capture.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
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.
|
2024-09-24 09:03:34 -07:00
|
|
|
ptr::addr_eq(self, other)
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 10:22:03 -07:00
|
|
|
impl_base_obj!(UserFunction);
|
2024-09-24 09:03:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2024-09-26 11:07:12 -07:00
|
|
|
// Method
|
2024-09-24 09:03:34 -07:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2024-09-26 10:52:47 -07:00
|
|
|
#[derive(Trace, Finalize)]
|
2024-09-26 11:07:12 -07:00
|
|
|
pub struct Method {
|
|
|
|
|
base: BaseObj,
|
2024-09-24 09:03:34 -07:00
|
|
|
self_binding: ObjP,
|
|
|
|
|
function: ObjP,
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Debug for Method {
|
2024-09-24 12:30:26 -07:00
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
2024-09-26 11:07:12 -07:00
|
|
|
fmt.debug_struct("Method")
|
2024-09-24 12:30:26 -07:00
|
|
|
.field("base", &self.base)
|
|
|
|
|
.field("self_binding", &format!("{}", self.self_binding.borrow()))
|
|
|
|
|
.field("function", &self.function)
|
|
|
|
|
.finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Method {
|
2024-09-24 09:03:34 -07:00
|
|
|
pub fn new(self_binding: ObjP, function: ObjP) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
base: Default::default(),
|
|
|
|
|
self_binding,
|
|
|
|
|
function,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 10:22:03 -07:00
|
|
|
pub fn create(self_binding: ObjP, function: ObjP) -> ObjP {
|
2024-09-24 09:03:34 -07:00
|
|
|
let ptr = make_ptr(Self::new(self_binding, function));
|
2024-09-25 10:22:03 -07:00
|
|
|
ptr.borrow_mut().instantiate();
|
2024-09-24 09:03:34 -07:00
|
|
|
ptr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn self_binding(&self) -> &ObjP {
|
|
|
|
|
&self.self_binding
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Display for Method {
|
2024-09-24 09:03:34 -07:00
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
write!(fmt, "{}", self.function.borrow())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
impl Object for Method {
|
2024-09-24 09:03:34 -07:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 11:07:12 -07:00
|
|
|
fn equals(&self, other: &dyn Object) -> bool {
|
|
|
|
|
if let Some(other) = other.as_any().downcast_ref::<Method>() {
|
2024-09-24 09:03:34 -07:00
|
|
|
ptr::addr_eq(&*self.self_binding, &*other.self_binding)
|
|
|
|
|
&& ptr::addr_eq(&*self.function, &*other.function)
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 10:22:03 -07:00
|
|
|
impl_base_obj!(Method);
|
2024-09-24 09:03:34 -07:00
|
|
|
}
|