2024-09-24 09:03:34 -07:00
|
|
|
use crate::obj::function::UserFunctionInst;
|
|
|
|
|
use crate::obj::ObjP;
|
2024-09-20 16:04:30 -07:00
|
|
|
use crate::vm::{Chunk, JumpOpArg, Op};
|
|
|
|
|
|
|
|
|
|
type Row = (String, String, &'static str, String, String);
|
|
|
|
|
|
|
|
|
|
fn disassemble_chunk(chunk: &Chunk, constants: &Vec<ObjP>, globals: &Vec<String>) {
|
|
|
|
|
let mut rows: Vec<Row> = vec![(
|
|
|
|
|
"ADDR".into(),
|
|
|
|
|
"LINE".into(),
|
|
|
|
|
"OP".into(),
|
|
|
|
|
"ARG".into(),
|
|
|
|
|
"INFO".into(),
|
|
|
|
|
)];
|
|
|
|
|
|
|
|
|
|
for (index, op) in chunk.code.iter().enumerate() {
|
|
|
|
|
let (start_line, end_line) = chunk.lines[index];
|
|
|
|
|
|
|
|
|
|
let addr: String = index.to_string();
|
|
|
|
|
let line = if start_line == end_line {
|
|
|
|
|
start_line.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
format!("{start_line}-{end_line}")
|
|
|
|
|
};
|
|
|
|
|
let op_str: &'static str;
|
|
|
|
|
let arg: String;
|
|
|
|
|
let info: String;
|
|
|
|
|
|
|
|
|
|
match op {
|
|
|
|
|
Op::Pop => {
|
|
|
|
|
op_str = "POP";
|
|
|
|
|
arg = String::new();
|
|
|
|
|
info = String::new();
|
|
|
|
|
}
|
|
|
|
|
Op::PushConstant(constant_id) => {
|
|
|
|
|
op_str = "PUSH_CONSTANT";
|
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
|
|
|
arg = format!("{}", &constants[*constant_id as usize].borrow());
|
2024-09-20 16:04:30 -07:00
|
|
|
info = format!("(constant ID {constant_id})");
|
|
|
|
|
}
|
|
|
|
|
Op::GetLocal(local_id) => {
|
|
|
|
|
op_str = "GET_LOCAL";
|
|
|
|
|
let local = &chunk.locals[*local_id as usize];
|
|
|
|
|
arg = local.name.to_string();
|
|
|
|
|
info = format!("(slot {}, local ID {})", local.slot, local.index);
|
|
|
|
|
}
|
|
|
|
|
Op::SetLocal(local_id) => {
|
|
|
|
|
op_str = "SET_LOCAL";
|
|
|
|
|
let local = &chunk.locals[*local_id as usize];
|
|
|
|
|
arg = local.name.to_string();
|
|
|
|
|
info = format!("(slot {}, local ID {})", local.slot, local.index);
|
|
|
|
|
}
|
|
|
|
|
Op::GetGlobal(global_id) => {
|
|
|
|
|
op_str = "GET_GLOBAL";
|
|
|
|
|
arg = globals[*global_id as usize].clone();
|
|
|
|
|
info = format!("(global ID {global_id})");
|
|
|
|
|
}
|
|
|
|
|
Op::SetGlobal(global_id) => {
|
|
|
|
|
op_str = "SET_GLOBAL";
|
|
|
|
|
arg = globals[*global_id as usize].clone();
|
|
|
|
|
info = format!("(global ID {global_id})");
|
|
|
|
|
}
|
|
|
|
|
Op::GetAttr(constant_id) => {
|
|
|
|
|
op_str = "GET_ATTR";
|
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
|
|
|
arg = format!("{}", &constants[*constant_id as usize].borrow());
|
2024-09-20 16:04:30 -07:00
|
|
|
info = format!("(constant ID {constant_id})");
|
|
|
|
|
}
|
|
|
|
|
Op::SetAttr(constant_id) => {
|
|
|
|
|
op_str = "SET_ATTR";
|
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
|
|
|
arg = format!("{}", &constants[*constant_id as usize].borrow());
|
2024-09-20 16:04:30 -07:00
|
|
|
info = format!("(constant ID {constant_id})");
|
|
|
|
|
}
|
|
|
|
|
Op::Jump(jump_offset) => {
|
|
|
|
|
op_str = "JUMP";
|
|
|
|
|
arg = format!("{}", jump_offset);
|
|
|
|
|
info = format!("(address {})", (index as JumpOpArg) + *jump_offset);
|
|
|
|
|
}
|
|
|
|
|
Op::JumpFalse(jump_offset) => {
|
|
|
|
|
op_str = "JUMP_FALSE";
|
|
|
|
|
arg = format!("{}", jump_offset);
|
|
|
|
|
info = format!("(address {})", (index as JumpOpArg) + *jump_offset);
|
|
|
|
|
}
|
|
|
|
|
Op::JumpTrue(jump_offset) => {
|
|
|
|
|
op_str = "JUMP_FALSE";
|
|
|
|
|
arg = format!("{}", jump_offset);
|
|
|
|
|
info = format!("(address {})", (index as JumpOpArg) + *jump_offset);
|
|
|
|
|
}
|
|
|
|
|
Op::Call(argc) => {
|
|
|
|
|
op_str = "CALL";
|
|
|
|
|
arg = format!("argc {argc}");
|
|
|
|
|
info = String::new();
|
|
|
|
|
}
|
|
|
|
|
Op::Return => {
|
|
|
|
|
op_str = "RETURN";
|
|
|
|
|
arg = String::new();
|
|
|
|
|
info = String::new();
|
|
|
|
|
}
|
|
|
|
|
Op::CloseOver { depth, slot } => {
|
|
|
|
|
op_str = "CLOSE_OVER";
|
|
|
|
|
arg = format!("{depth}");
|
|
|
|
|
info = format!("slot {slot} (name unknown)");
|
|
|
|
|
}
|
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
|
|
|
Op::Nop => {
|
|
|
|
|
op_str = "NOP";
|
|
|
|
|
arg = String::new();
|
|
|
|
|
info = String::new();
|
|
|
|
|
}
|
2024-09-20 16:04:30 -07:00
|
|
|
Op::Halt => {
|
|
|
|
|
op_str = "HALT";
|
|
|
|
|
arg = String::new();
|
|
|
|
|
info = String::new();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows.push((addr, line, op_str, arg, info));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
display_rows(&rows);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn display_rows(rows: &Vec<Row>) {
|
|
|
|
|
// get the longest width of each row
|
|
|
|
|
let mut addr_width = 0;
|
|
|
|
|
let mut line_width = 0;
|
|
|
|
|
let mut op_width = 0;
|
|
|
|
|
let mut arg_width = 0;
|
|
|
|
|
let mut info_width = 0;
|
|
|
|
|
for (addr, line, op, arg, info) in rows {
|
|
|
|
|
addr_width = addr_width.max(addr.len());
|
|
|
|
|
line_width = line_width.max(line.len());
|
|
|
|
|
op_width = op_width.max(op.len());
|
|
|
|
|
arg_width = arg_width.max(arg.len());
|
|
|
|
|
info_width = info_width.max(info.len());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addr_width += 2;
|
|
|
|
|
line_width += 2;
|
|
|
|
|
op_width += 2;
|
|
|
|
|
arg_width += 2;
|
|
|
|
|
info_width += 2;
|
|
|
|
|
|
|
|
|
|
for (addr, line, op, arg, info) in rows {
|
|
|
|
|
println!(
|
|
|
|
|
"{addr:>addr_width$} {line:>line_width$} {op:>op_width$} {arg:arg_width$} {info:info_width$}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn disassemble(chunk: &Chunk, constants: &Vec<ObjP>, globals: &Vec<String>) {
|
|
|
|
|
println!("== main chunk");
|
|
|
|
|
println!();
|
|
|
|
|
disassemble_chunk(chunk, constants, globals);
|
|
|
|
|
|
|
|
|
|
for constant in constants {
|
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(fun) = constant
|
|
|
|
|
.borrow()
|
|
|
|
|
.as_any()
|
|
|
|
|
.downcast_ref::<UserFunctionInst>()
|
|
|
|
|
{
|
2024-09-20 16:04:30 -07:00
|
|
|
println!();
|
|
|
|
|
println!(
|
|
|
|
|
"== {} starting on line {}",
|
|
|
|
|
fun.name(),
|
|
|
|
|
fun.chunk().lines[0].0
|
|
|
|
|
);
|
|
|
|
|
println!();
|
|
|
|
|
disassemble_chunk(fun.chunk(), constants, globals);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|