Files
not-python-rust/src/disassemble.rs

181 lines
6.0 KiB
Rust
Raw Normal View History

use crate::obj::function::UserFunction;
use crate::obj::ObjP;
use crate::vm::{Chunk, JumpOpArg, Op};
type Row = (String, String, &'static str, String, String);
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
fn disassemble_chunk(chunk: &Chunk, globals: &Vec<String>, constants: &Vec<ObjP>) {
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());
info = format!("(constant ID {constant_id})");
}
Op::Dup => {
op_str = "DUP";
arg = String::new();
info = String::new();
}
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());
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());
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::BuildList(len) => {
op_str = "BUILD_LIST";
arg = format!("{len}");
info = String::new();
}
Op::Nop => {
op_str = "NOP";
arg = String::new();
info = String::new();
}
Op::EvalModule => {
op_str = "EVAL_MODULE";
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
arg = String::new();
info = String::new();
}
Op::ExitModule => {
op_str = "EXIT_MODULE";
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$}"
);
}
}
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
pub fn disassemble(chunk: &Chunk, globals: &Vec<String>, constants: &Vec<ObjP>) {
println!("== main chunk");
println!();
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
disassemble_chunk(chunk, globals, constants);
for constant in constants {
if let Some(fun) = constant.borrow().as_any().downcast_ref::<UserFunction>() {
println!();
println!(
"== {} starting on line {}",
fun.name(),
fun.chunk().lines[0].0
);
println!();
WIP: Add imports and modules This is a big change because it touches a lot of stuff, but here is the overview: * Import syntax: ``` import foo import bar from foo import bar from "foo.npp" import bar, baz from foo import * from foo import "foo.npp" ``` * These are all valid imports. They should be pretty straightforward, maybe with exception of the last item. If you are importing a path directly, but not importing any members from it, it does not insert anything into the current namespace, and just executes the file. This is probably going to be unused but I want to include it for completeness. We can always remove it later before a hypothetical 1.0 release. * The "from" keyword is only ever used as a keyword here, and I am allowing it to be used as an identifier elsewhere. Don't export it, because that's weird and wrong and won't work. * Modules: * Doing an `import foo` will look for "foo.npp" at compile-time, relative to the importer's directory, parse it, and compile it. The importer will then attempt to execute the module with the new `EnterModule` op. This instruction will execute the module kind of like a function, assigning the module's global namespace to an object that you can pass around. * `import bar from foo` and `import bar from "foo.npp"` et al syntax is not currently implemented in the compiler. * There is a new "Module" object that represents a potentially un-initialized module. This can't be referred to directly in code. * VM: * The VM operates around Module objects now. If you want to "call" a new module, you should call `enter_module`. This is how the main chunk is invoked. * TODOs: * `exit_module` function in the VM * Finish up module implementation in compiler * Built-in modules * Sub-modules - e.g. `import foo.bar` - how does naming work for this? * Module directories. In Python you have `foo/__init__.py` and in Rust you have `foo/mod.rs`. * Probably a "Namespace" object that explicitly denotes "this is an imported module that you're dealing with" * Tests, tests, tests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2024-10-04 10:11:49 -07:00
disassemble_chunk(fun.chunk(), globals, constants);
}
}
}