Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::rc::Rc;
|
2024-09-20 16:04:30 -07:00
|
|
|
|
|
|
|
|
use crate::obj::*;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
pub enum Op {
|
|
|
|
|
// Stack functions
|
|
|
|
|
Pop,
|
|
|
|
|
PushConstant(LongOpArg),
|
|
|
|
|
|
|
|
|
|
// Variables
|
|
|
|
|
GetLocal(LocalIndex),
|
|
|
|
|
SetLocal(LocalIndex),
|
|
|
|
|
GetGlobal(GlobalId),
|
|
|
|
|
SetGlobal(GlobalId),
|
|
|
|
|
|
|
|
|
|
// Attributes
|
|
|
|
|
GetAttr(ConstantId),
|
|
|
|
|
SetAttr(ConstantId),
|
|
|
|
|
|
|
|
|
|
// Jumps
|
|
|
|
|
Jump(JumpOpArg),
|
|
|
|
|
JumpFalse(JumpOpArg),
|
|
|
|
|
JumpTrue(JumpOpArg),
|
|
|
|
|
|
|
|
|
|
// Functions
|
|
|
|
|
Call(Argc),
|
|
|
|
|
Return,
|
|
|
|
|
CloseOver { depth: ShortOpArg, slot: ShortOpArg },
|
|
|
|
|
|
|
|
|
|
// VM control
|
|
|
|
|
Halt,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub type LineRange = (usize, usize);
|
|
|
|
|
|
|
|
|
|
type ShortOpArg = u16;
|
|
|
|
|
type LongOpArg = u32;
|
|
|
|
|
|
|
|
|
|
pub type JumpOpArg = i32;
|
|
|
|
|
pub type LocalIndex = LongOpArg;
|
|
|
|
|
pub type LocalSlot = ShortOpArg;
|
|
|
|
|
pub type ConstantId = LongOpArg;
|
|
|
|
|
pub type GlobalId = LongOpArg;
|
|
|
|
|
pub type Argc = LongOpArg;
|
|
|
|
|
pub type FrameDepth = ShortOpArg;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct Local {
|
|
|
|
|
pub(crate) slot: LocalSlot,
|
|
|
|
|
pub(crate) index: LocalIndex,
|
|
|
|
|
pub(crate) name: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
|
|
|
pub struct Chunk {
|
|
|
|
|
pub(crate) code: Vec<Op>,
|
|
|
|
|
pub(crate) lines: Vec<LineRange>,
|
|
|
|
|
pub(crate) locals: Vec<Local>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct Frame {
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
pub(crate) name: Rc<String>,
|
|
|
|
|
pub(crate) chunk: Rc<Chunk>,
|
2024-09-20 16:04:30 -07:00
|
|
|
pub(crate) ip: usize,
|
|
|
|
|
pub(crate) stack_base: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Frame {
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
pub fn new(name: Rc<String>, chunk: Rc<Chunk>, stack_base: usize) -> Self {
|
2024-09-20 16:04:30 -07:00
|
|
|
Self {
|
|
|
|
|
name,
|
|
|
|
|
chunk,
|
|
|
|
|
ip: 0,
|
|
|
|
|
stack_base,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Vm {
|
|
|
|
|
constants: Vec<ObjP>,
|
|
|
|
|
//global_names: Vec<String>,
|
|
|
|
|
globals: Vec<ObjP>,
|
|
|
|
|
stack: Vec<ObjP>,
|
|
|
|
|
frames: Vec<Frame>,
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
builtins: HashMap<String, ObjP>,
|
2024-09-20 16:04:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Vm {
|
|
|
|
|
/// Create a new virtual machine with the given chunk, constants, and global names.
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
pub fn new(
|
|
|
|
|
chunk: Rc<Chunk>,
|
|
|
|
|
constants: Vec<ObjP>,
|
|
|
|
|
global_names: Vec<String>,
|
|
|
|
|
builtins: HashMap<String, ObjP>,
|
|
|
|
|
) -> Self {
|
2024-09-20 16:04:30 -07:00
|
|
|
// set up globals
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
let nil = builtins.create_nil();
|
|
|
|
|
let mut globals: Vec<_> = global_names.iter().map(|_| ObjP::clone(&nil)).collect();
|
2024-09-20 16:04:30 -07:00
|
|
|
|
|
|
|
|
let mut register_global = |name: &str, value: ObjP| {
|
|
|
|
|
let index = global_names
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|global| global == name)
|
|
|
|
|
.expect("could not find global");
|
|
|
|
|
globals[index] = value;
|
|
|
|
|
};
|
|
|
|
|
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
for (name, builtin) in builtins.iter() {
|
|
|
|
|
register_global(&name, ObjP::clone(&builtin));
|
|
|
|
|
}
|
2024-09-20 16:04:30 -07:00
|
|
|
|
|
|
|
|
// stack and frames
|
|
|
|
|
let stack = Vec::new();
|
|
|
|
|
let frames = vec![Frame::new("__main__".to_string().into(), chunk, 0)];
|
|
|
|
|
|
|
|
|
|
Vm {
|
|
|
|
|
constants,
|
|
|
|
|
//global_names,
|
|
|
|
|
globals,
|
|
|
|
|
stack,
|
|
|
|
|
frames,
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
builtins,
|
2024-09-20 16:04:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the stack.
|
|
|
|
|
pub fn stack(&self) -> &Vec<ObjP> {
|
|
|
|
|
&self.stack
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Current stack frame.
|
|
|
|
|
pub fn frame(&self) -> &Frame {
|
|
|
|
|
self.frames.last().unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Current stack frame, mutably.
|
|
|
|
|
pub fn frame_mut(&mut self) -> &mut Frame {
|
|
|
|
|
self.frames.last_mut().unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Push a new stack frame.
|
|
|
|
|
pub fn push_frame(&mut self, frame: Frame) {
|
|
|
|
|
self.frames.push(frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pop the current stack frame.
|
|
|
|
|
pub fn pop_frame(&mut self) -> Frame {
|
|
|
|
|
self.frames.pop().expect("no frame")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the chunk of the currently executing frame.
|
|
|
|
|
pub fn chunk(&self) -> &Chunk {
|
|
|
|
|
&self.frame().chunk
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Instruction pointer of the current frame.
|
|
|
|
|
pub fn ip(&self) -> usize {
|
|
|
|
|
self.frame().ip
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Update the current instruction pointer.
|
|
|
|
|
pub fn set_ip(&mut self, ip: usize) {
|
|
|
|
|
self.frame_mut().ip = ip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
/// Gets the line of the current instruction.
|
|
|
|
|
fn line(&self, offset: isize) -> LineRange {
|
|
|
|
|
let index = (((self.ip() as isize) + offset).max(0) as usize).min(self.chunk().lines.len());
|
|
|
|
|
self.chunk().lines[index]
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/// Get the current instruction and advance the IP.
|
|
|
|
|
fn next(&mut self) -> Op {
|
|
|
|
|
let ip = self.ip();
|
|
|
|
|
self.set_ip(ip + 1);
|
|
|
|
|
self.chunk().code[ip]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pop a value from the stack.
|
|
|
|
|
pub fn pop(&mut self) -> ObjP {
|
|
|
|
|
self.stack.pop().expect("stack empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Peek the top value of the stack.
|
|
|
|
|
pub fn peek(&self) -> ObjP {
|
|
|
|
|
self.stack.last().map(Ptr::clone).expect("stack empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Push a value to the stack.
|
|
|
|
|
pub fn push(&mut self, value: ObjP) {
|
|
|
|
|
self.stack.push(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run(&mut self) {
|
2024-09-23 20:59:00 -07:00
|
|
|
// Cached pointers that we just always want to have on hand
|
|
|
|
|
let method_type = self.builtins().get("Method").unwrap().clone();
|
|
|
|
|
|
2024-09-20 16:04:30 -07:00
|
|
|
loop {
|
|
|
|
|
match self.next() {
|
|
|
|
|
Op::Pop => {
|
|
|
|
|
self.pop();
|
|
|
|
|
}
|
|
|
|
|
Op::PushConstant(constant_id) => {
|
|
|
|
|
let constant = Ptr::clone(&self.constants[constant_id as usize]);
|
|
|
|
|
self.push(constant);
|
|
|
|
|
}
|
|
|
|
|
Op::GetLocal(local_index) => {
|
|
|
|
|
let local = &self.chunk().locals[local_index as usize];
|
|
|
|
|
let value =
|
|
|
|
|
Ptr::clone(&self.stack[self.frame().stack_base + local.slot as usize]);
|
|
|
|
|
self.push(value);
|
|
|
|
|
}
|
|
|
|
|
Op::SetLocal(local_index) => {
|
|
|
|
|
let value = self.pop();
|
|
|
|
|
let local = &self.chunk().locals[local_index as usize];
|
|
|
|
|
let index = self.frame().stack_base + local.slot as usize;
|
|
|
|
|
self.stack[index] = value;
|
|
|
|
|
}
|
|
|
|
|
Op::GetGlobal(global_index) => {
|
|
|
|
|
let value = Ptr::clone(&self.globals[global_index as usize]);
|
|
|
|
|
self.push(value);
|
|
|
|
|
}
|
|
|
|
|
Op::SetGlobal(global_index) => {
|
|
|
|
|
let value = self.pop();
|
|
|
|
|
self.globals[global_index as usize] = value;
|
|
|
|
|
}
|
|
|
|
|
Op::GetAttr(constant_id) => {
|
|
|
|
|
// need both declarations to borrow cell value
|
|
|
|
|
let name_obj = Ptr::clone(&self.constants[constant_id as usize]);
|
|
|
|
|
let name =
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
with_obj_downcast(name_obj, |name: &StrInst| Rc::clone(&name.str_value()));
|
2024-09-20 16:04:30 -07:00
|
|
|
let owner = self.pop();
|
2024-09-23 20:59:00 -07:00
|
|
|
let value =
|
|
|
|
|
owner
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.get_attr_lazy(owner.clone(), method_type.clone(), &name);
|
2024-09-20 16:04:30 -07:00
|
|
|
if let Some(value) = value {
|
|
|
|
|
self.push(value);
|
|
|
|
|
} else {
|
|
|
|
|
// TODO Vm::run, Op::GetAttr - throw an exception when the attribute
|
|
|
|
|
// doesn't exist
|
|
|
|
|
// BLOCKED-ON: exceptions
|
|
|
|
|
todo!(
|
|
|
|
|
"throw an error because we couldn't read attr '{}' on '{}'",
|
|
|
|
|
name,
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
owner.borrow(),
|
2024-09-20 16:04:30 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Op::SetAttr(constant_id) => {
|
|
|
|
|
let name_obj = Ptr::clone(&self.constants[constant_id as usize]);
|
|
|
|
|
let name =
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
with_obj_downcast(name_obj, |name: &StrInst| Rc::clone(&name.str_value()));
|
2024-09-20 16:04:30 -07:00
|
|
|
let value = self.pop();
|
|
|
|
|
let target = self.pop();
|
|
|
|
|
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
target.borrow_mut().set_attr(&name, value);
|
2024-09-20 16:04:30 -07:00
|
|
|
}
|
|
|
|
|
Op::Jump(offset) => {
|
|
|
|
|
let base = (self.ip() - 1) as JumpOpArg;
|
|
|
|
|
assert!(base + offset > 0, "tried to jump to negative IP");
|
|
|
|
|
self.set_ip((base + offset) as usize);
|
|
|
|
|
}
|
|
|
|
|
Op::JumpFalse(offset) => {
|
|
|
|
|
let base = (self.ip() - 1) as JumpOpArg;
|
|
|
|
|
let value = self.peek();
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
if !value.borrow().is_truthy() {
|
2024-09-20 16:04:30 -07:00
|
|
|
self.set_ip((base + offset) as usize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Op::JumpTrue(offset) => {
|
|
|
|
|
let base = (self.ip() - 1) as JumpOpArg;
|
|
|
|
|
let value = self.peek();
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
if value.borrow().is_truthy() {
|
2024-09-20 16:04:30 -07:00
|
|
|
self.set_ip((base + offset) as usize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Op::Call(argc) => {
|
|
|
|
|
let argc = argc as usize;
|
|
|
|
|
let index = self.stack.len() - argc - 1;
|
|
|
|
|
let fun_ptr = Ptr::clone(&self.stack[index]);
|
|
|
|
|
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
let arity = if let Some(arity) = fun_ptr.borrow().arity() {
|
2024-09-20 16:04:30 -07:00
|
|
|
arity as usize
|
|
|
|
|
} else {
|
|
|
|
|
// TODO Vm::run, Op::Call - throw an exception when the value isn't
|
|
|
|
|
// callable
|
|
|
|
|
// BLOCKED-ON: exceptions
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
todo!(
|
|
|
|
|
"throw an error because we couldn't call {}",
|
|
|
|
|
fun_ptr.borrow()
|
|
|
|
|
);
|
2024-09-20 16:04:30 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Methods with bound "self" parameter
|
|
|
|
|
// argc may be mutated
|
|
|
|
|
let mut argc = argc;
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
if let Some(method) = fun_ptr.borrow().as_any().downcast_ref::<MethodInst>() {
|
2024-09-20 16:04:30 -07:00
|
|
|
// shift all of the arguments over by one
|
|
|
|
|
// (duplicate the last item on the stack and then shift everyone else over)
|
|
|
|
|
self.stack
|
|
|
|
|
.insert(self.stack.len() - argc, Ptr::clone(method.self_binding()));
|
|
|
|
|
// also increment argc since we're specifying another arg
|
|
|
|
|
argc += 1;
|
|
|
|
|
}
|
|
|
|
|
// remove mutability
|
|
|
|
|
let argc = argc;
|
|
|
|
|
|
|
|
|
|
if arity != argc {
|
|
|
|
|
// TODO Vm::run, Op::Call - throw an exception when the number of arguments
|
|
|
|
|
// does not match the function's arity
|
|
|
|
|
// BLOCKED-ON: exceptions
|
|
|
|
|
todo!(
|
|
|
|
|
"throw an error because we passed the wrong number of arguments to {}",
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
fun_ptr.borrow()
|
2024-09-20 16:04:30 -07:00
|
|
|
);
|
|
|
|
|
}
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
fun_ptr.borrow().call(self, argc as Argc);
|
2024-09-20 16:04:30 -07:00
|
|
|
}
|
|
|
|
|
Op::Return => {
|
|
|
|
|
let return_value = self.pop();
|
|
|
|
|
let old_frame = self.frames.pop().unwrap();
|
|
|
|
|
// stack_base is always going to be <= current stack size
|
|
|
|
|
self.stack
|
|
|
|
|
.resize_with(old_frame.stack_base, || unreachable!());
|
|
|
|
|
// also pop the function object off of the stack
|
|
|
|
|
self.stack.pop();
|
|
|
|
|
self.push(return_value);
|
|
|
|
|
}
|
|
|
|
|
Op::CloseOver { depth, slot } => {
|
|
|
|
|
// since we're closing over a value, and functions ultimately come from
|
|
|
|
|
// constants, we want to deep-clone this object so we don't alter any live
|
|
|
|
|
// objects.
|
|
|
|
|
// there is some room for optimization here so we aren't cloning the entire
|
|
|
|
|
// UserFunctionInst for every individual capture in a function.
|
|
|
|
|
let fun_ptr = self.pop();
|
|
|
|
|
let mut fun: UserFunctionInst =
|
|
|
|
|
with_obj_downcast(fun_ptr, UserFunctionInst::clone);
|
|
|
|
|
let frame_index = self.frames.len() - (depth as usize) - 1;
|
|
|
|
|
let stack_base = self.frames[frame_index].stack_base;
|
|
|
|
|
let value = Ptr::clone(&self.stack[stack_base + (slot as usize)]);
|
|
|
|
|
fun.push_capture(value);
|
2024-09-23 20:59:00 -07:00
|
|
|
self.push(make_ptr(fun));
|
2024-09-20 16:04:30 -07:00
|
|
|
}
|
|
|
|
|
Op::Halt => {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
Revamp object system, start using `gc` crate
Wow, what a ride. I think everything should be working now. In short:
* Objects use the `gc` crate, which as a `Gc` garbage-collected pointer
type. I may choose to implement my own in contiguous memory in the
future. We will see.
* The type system is no longer global. This is a bit of a burden,
because now, whenever you want to create a new object, you need to
pass its type object into the `Obj::instantiate` method, as well as
its `::create` static method.
* This burden is somewhat alleviated by the `ObjFactory` trait, which
helps create new objects as long as you have access to a `builtins`
hashmap. So something that would normally look like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(&builtins.get("BuiltinFunction").unwrap()),
"print",
print,
1
);
builtins.insert("print".to_string(), print_builtin)
// other builtins inserted here...
}
now looks like this:
fn init_builtins(builtins: &mut HashMap<String, ObjP>) {
let print_builtin = builtins.create_builtin_function("print", print, 1);
builtins.insert("print".to_string(), print_builtin);
}
(turns out, if all you need is a HashMap<String, ObjP>, you can
implement ObjFactory for HashMap<String, ObjP> itself(!))
Overall, I'm happier with this design, and I think this is what is going
to get merged. It's a little weird to be querying type names that are
used in the language itself to get those type objects, but whatever
works, I guess.
Next up is vtables.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-23 18:12:32 -07:00
|
|
|
|
|
|
|
|
impl ObjFactory for Vm {
|
|
|
|
|
fn builtins(&self) -> &HashMap<String, ObjP> {
|
|
|
|
|
&self.builtins
|
|
|
|
|
}
|
|
|
|
|
}
|