2024-09-20 16:04:30 -07:00
|
|
|
use crate::obj::{ObjP, UserFunctionInst};
|
|
|
|
|
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";
|
WIP: move mutability to be internal to the object instead of the pointer
I'm not super happy with this. But, the RwLock has been moved to the
`BaseObjInst::attrs` member. Although this is not exactly how it appears
in code, it basically does this:
type Ptr<T> = Arc<RwLock<T>>;
struct BaseObjInst {
attr: HashMap<String, Ptr<dyn Obj>>,
// etc
}
becomes
type Ptr<T> = Arc<T>;
struct BaseObjInst {
attr: RwLock<HashMap<String, ObjP>>,
// etc
}
This makes things a lot more ergonomic (don't have to use try_read() and
try_write() everywhere), but it also eliminates compile-time errors that
would catch mutability errors. This is currently rearing its ugly head
when initializing the typesystem, since `Type` needs to hold a circular
reference itself (which it already shouldn't be doing since it's a
reference-counted pointer!). Currently, all tests are failing because of
this limitation.
There are a couple of ways around this limitation.
The first solution would be just copying all of the object
instantiation code into the `init_types` function and avoid calling
`some_base_type.instantiate()`. This would probably be literal
copy-pasting, or maybe an (ugly) macro, and probably a nightmare to
maintain long-term. I don't like this option, but it would make
everything "just work" with reference-counted pointers.
The second solution would be to write our own garbage collector, which
would allow for circular references and (hypothetically) mutably
updating these references. This is something that I am looking into,
because I really want a RefCell that you can pass around in a more
ergonomic way.
I think the fundamental error that I'm running into is trying to borrow
the same value multiple times mutably, which you *really* shouldn't be
doing. I believe I need to write better code and does the same thing.
The only unsolved problem is circular references. This is not a problem
right now because I'm not writing code that has circular references
besides the base typesystem (which is not a problem because they need to
live the entire lifetime of the program), but it will be a latent
problem until it gets fixed.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-22 20:40:15 -07:00
|
|
|
arg = format!("{}", &constants[*constant_id as usize]);
|
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";
|
WIP: move mutability to be internal to the object instead of the pointer
I'm not super happy with this. But, the RwLock has been moved to the
`BaseObjInst::attrs` member. Although this is not exactly how it appears
in code, it basically does this:
type Ptr<T> = Arc<RwLock<T>>;
struct BaseObjInst {
attr: HashMap<String, Ptr<dyn Obj>>,
// etc
}
becomes
type Ptr<T> = Arc<T>;
struct BaseObjInst {
attr: RwLock<HashMap<String, ObjP>>,
// etc
}
This makes things a lot more ergonomic (don't have to use try_read() and
try_write() everywhere), but it also eliminates compile-time errors that
would catch mutability errors. This is currently rearing its ugly head
when initializing the typesystem, since `Type` needs to hold a circular
reference itself (which it already shouldn't be doing since it's a
reference-counted pointer!). Currently, all tests are failing because of
this limitation.
There are a couple of ways around this limitation.
The first solution would be just copying all of the object
instantiation code into the `init_types` function and avoid calling
`some_base_type.instantiate()`. This would probably be literal
copy-pasting, or maybe an (ugly) macro, and probably a nightmare to
maintain long-term. I don't like this option, but it would make
everything "just work" with reference-counted pointers.
The second solution would be to write our own garbage collector, which
would allow for circular references and (hypothetically) mutably
updating these references. This is something that I am looking into,
because I really want a RefCell that you can pass around in a more
ergonomic way.
I think the fundamental error that I'm running into is trying to borrow
the same value multiple times mutably, which you *really* shouldn't be
doing. I believe I need to write better code and does the same thing.
The only unsolved problem is circular references. This is not a problem
right now because I'm not writing code that has circular references
besides the base typesystem (which is not a problem because they need to
live the entire lifetime of the program), but it will be a latent
problem until it gets fixed.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-22 20:40:15 -07:00
|
|
|
arg = format!("{}", &constants[*constant_id as usize]);
|
2024-09-20 16:04:30 -07:00
|
|
|
info = format!("(constant ID {constant_id})");
|
|
|
|
|
}
|
|
|
|
|
Op::SetAttr(constant_id) => {
|
|
|
|
|
op_str = "SET_ATTR";
|
WIP: move mutability to be internal to the object instead of the pointer
I'm not super happy with this. But, the RwLock has been moved to the
`BaseObjInst::attrs` member. Although this is not exactly how it appears
in code, it basically does this:
type Ptr<T> = Arc<RwLock<T>>;
struct BaseObjInst {
attr: HashMap<String, Ptr<dyn Obj>>,
// etc
}
becomes
type Ptr<T> = Arc<T>;
struct BaseObjInst {
attr: RwLock<HashMap<String, ObjP>>,
// etc
}
This makes things a lot more ergonomic (don't have to use try_read() and
try_write() everywhere), but it also eliminates compile-time errors that
would catch mutability errors. This is currently rearing its ugly head
when initializing the typesystem, since `Type` needs to hold a circular
reference itself (which it already shouldn't be doing since it's a
reference-counted pointer!). Currently, all tests are failing because of
this limitation.
There are a couple of ways around this limitation.
The first solution would be just copying all of the object
instantiation code into the `init_types` function and avoid calling
`some_base_type.instantiate()`. This would probably be literal
copy-pasting, or maybe an (ugly) macro, and probably a nightmare to
maintain long-term. I don't like this option, but it would make
everything "just work" with reference-counted pointers.
The second solution would be to write our own garbage collector, which
would allow for circular references and (hypothetically) mutably
updating these references. This is something that I am looking into,
because I really want a RefCell that you can pass around in a more
ergonomic way.
I think the fundamental error that I'm running into is trying to borrow
the same value multiple times mutably, which you *really* shouldn't be
doing. I believe I need to write better code and does the same thing.
The only unsolved problem is circular references. This is not a problem
right now because I'm not writing code that has circular references
besides the base typesystem (which is not a problem because they need to
live the entire lifetime of the program), but it will be a latent
problem until it gets fixed.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-22 20:40:15 -07:00
|
|
|
arg = format!("{}", &constants[*constant_id as usize]);
|
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)");
|
|
|
|
|
}
|
|
|
|
|
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 {
|
WIP: move mutability to be internal to the object instead of the pointer
I'm not super happy with this. But, the RwLock has been moved to the
`BaseObjInst::attrs` member. Although this is not exactly how it appears
in code, it basically does this:
type Ptr<T> = Arc<RwLock<T>>;
struct BaseObjInst {
attr: HashMap<String, Ptr<dyn Obj>>,
// etc
}
becomes
type Ptr<T> = Arc<T>;
struct BaseObjInst {
attr: RwLock<HashMap<String, ObjP>>,
// etc
}
This makes things a lot more ergonomic (don't have to use try_read() and
try_write() everywhere), but it also eliminates compile-time errors that
would catch mutability errors. This is currently rearing its ugly head
when initializing the typesystem, since `Type` needs to hold a circular
reference itself (which it already shouldn't be doing since it's a
reference-counted pointer!). Currently, all tests are failing because of
this limitation.
There are a couple of ways around this limitation.
The first solution would be just copying all of the object
instantiation code into the `init_types` function and avoid calling
`some_base_type.instantiate()`. This would probably be literal
copy-pasting, or maybe an (ugly) macro, and probably a nightmare to
maintain long-term. I don't like this option, but it would make
everything "just work" with reference-counted pointers.
The second solution would be to write our own garbage collector, which
would allow for circular references and (hypothetically) mutably
updating these references. This is something that I am looking into,
because I really want a RefCell that you can pass around in a more
ergonomic way.
I think the fundamental error that I'm running into is trying to borrow
the same value multiple times mutably, which you *really* shouldn't be
doing. I believe I need to write better code and does the same thing.
The only unsolved problem is circular references. This is not a problem
right now because I'm not writing code that has circular references
besides the base typesystem (which is not a problem because they need to
live the entire lifetime of the program), but it will be a latent
problem until it gets fixed.
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-09-22 20:40:15 -07:00
|
|
|
if let Some(fun) = constant.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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|