diff --git a/forge_core/src/ast.rs b/forge_core/src/ast.rs index 6f79a01..704c306 100644 --- a/forge_core/src/ast.rs +++ b/forge_core/src/ast.rs @@ -224,3 +224,20 @@ impl From<&str> for Expr { Self::Name(String::from(value)) } } + +impl Statement { + pub fn description(&self) -> String { + String::from( + match self { + Statement::Return(_) => "return", + Statement::Assignment(_) => "assignment", + Statement::Expr(_) => "expr", + Statement::VarDecl(_) => "varDecl", + Statement::Conditional(_) => "conditional", + Statement::WhileLoop(_) => "whileLoop", + Statement::RepeatLoop(_) => "repeatLoop", + Statement::Asm(_) => "asm" + } + ) + } +} \ No newline at end of file diff --git a/forge_core/src/compiler/ast_nodes/block.rs b/forge_core/src/compiler/ast_nodes/block.rs index 89f1024..d2ce1a3 100644 --- a/forge_core/src/compiler/ast_nodes/block.rs +++ b/forge_core/src/compiler/ast_nodes/block.rs @@ -13,6 +13,7 @@ impl Compilable for Block { // Compile each statement: for stmt in self.0 { let loc = stmt.location; + sig.emit_comment(format!(";; {}:{} :: {}", loc.line, loc.col, stmt.ast.description())); match stmt.ast { Statement::Return(ret) => { ret.process(state, Some(sig), loc)?; diff --git a/forge_core/src/compiler/ast_nodes/function.rs b/forge_core/src/compiler/ast_nodes/function.rs index d234fbe..35641c4 100644 --- a/forge_core/src/compiler/ast_nodes/function.rs +++ b/forge_core/src/compiler/ast_nodes/function.rs @@ -1,6 +1,6 @@ use crate::ast::{Function, Location}; use crate::compiler::compilable::Compilable; -use crate::compiler::compiled_fn::CompiledFn; +use crate::compiler::compiled_fn::{CompiledFn}; use crate::compiler::CompileError; use crate::compiler::state::State; @@ -11,6 +11,7 @@ impl Compilable for Function { // The CompiledFn for this function, which will eventually get stuff populated into it: let mut sig = CompiledFn { + comments: state.comments, label, end_label, ..Default::default() diff --git a/forge_core/src/compiler/compiled_fn.rs b/forge_core/src/compiler/compiled_fn.rs index 894da6a..b9a18ee 100644 --- a/forge_core/src/compiler/compiled_fn.rs +++ b/forge_core/src/compiler/compiled_fn.rs @@ -50,6 +50,7 @@ use crate::compiler::utils::{Label, Scope, Variable}; /// with an interface in the zero page. #[derive(Clone, PartialEq, Debug, Default)] pub struct CompiledFn { + pub comments: bool, pub label: Label, pub end_label: Label, pub max_frame_size: usize, @@ -96,6 +97,12 @@ impl CompiledFn { self.body.emit_arg(opcode, arg) } + pub(crate) fn emit_comment(&mut self, arg: T) { + if self.comments { + self.body.emit(format!("{}", arg).as_str()) + } + } + /// The (current) size of the local scope in bytes. This increases as variables are declared, /// decreases as they leave scope pub(crate) fn frame_size(&self) -> usize { diff --git a/forge_core/src/compiler/mod.rs b/forge_core/src/compiler/mod.rs index 95d624b..d921c85 100644 --- a/forge_core/src/compiler/mod.rs +++ b/forge_core/src/compiler/mod.rs @@ -20,8 +20,12 @@ mod text; /// Turn a `Program` into a list of assembly lines that can be assembled to run it. /// There are various ways to build a program, this one is the simplest: it builds it as a /// complete ROM that will place a jmp to main() at 0x400, so a Vulcan can boot from it. -pub fn build_boot(src: &str) -> Result, CompileError> { - let mut state = State::default(); +pub fn build_boot(src: &str, include_comments: bool) -> Result, CompileError> { + let mut state = if include_comments { + State::with_comments() + } else { + State::default() + }; let ast = parse(src).map_err(CompileError::from)?; ast.process(&mut state, None, (0, 0).into())?; @@ -45,11 +49,19 @@ pub fn build_boot(src: &str) -> Result, CompileError> { asm.push("hlt".into()); // Now start dumping compiled objects into there. First functions: - for (_, val) in state.functions.iter_mut() { + for (name, val) in state.functions.iter_mut() { + // If we've asked for comments, then put in a header comment + if include_comments { + asm.push(format!(";;; Begin {:=<70}", format!("{} ", name))) + } asm.push(format!("{}:", val.label)); for line in val.text() { asm.push(String::from(line)); } + // If we've asked for comments, then put in a footer also + if include_comments { + asm.push(format!(";;; End {:=<72}", format!("{} ", name))) + } } // Strings: @@ -64,11 +76,15 @@ pub fn build_boot(src: &str) -> Result, CompileError> { } // Global vars: - for (_, val) in state.global_scope.iter() { + for (name, val) in state.global_scope.iter() { // All global variables are indirect labels; anything in scope that's not that is a const, // fn, or string, which we'll emit elsewhere. if let Variable::IndirectLabel(label) = val { - asm.push(format!("{}: .db 0", label)) + if include_comments { + asm.push(format!("{}: .db 0 ;;; Global {}", label, name)) + } else { + asm.push(format!("{}: .db 0", label)) + } } } @@ -96,7 +112,7 @@ mod test { #[test] fn test_build_boot() { // Very basic test of one main() - let asm = build_boot("fn main() { return 5; }".into()).unwrap(); + let asm = build_boot("fn main() { return 5; }".into(), false).unwrap(); assert_eq!(asm.join("\n"), vec![ ".org 0x400", "push stack", @@ -119,7 +135,7 @@ mod test { ].join("\n")); // Slightly more complicated, with a global str - let asm = build_boot("const str = \"blah\"; fn main() { return str; }".into()).unwrap(); + let asm = build_boot("const str = \"blah\"; fn main() { return str; }".into(), false).unwrap(); assert_eq!(asm.join("\n"), vec![ ".org 0x400", "push stack", @@ -144,9 +160,38 @@ mod test { } #[test] + fn test_comments() { + // Very basic test of one main() + let asm = build_boot("global foo; fn main() { return 5; }".into(), true).unwrap(); + assert_eq!(asm.join("\n"), vec![ + ".org 0x400", + "push stack", + "call _forge_gensym_2", + "hlt", + ";;; Begin main =================================================================", + "_forge_gensym_2:", + "dup", + "pushr", + "pushr", + ";; 1:25 :: return", + "push 5", + "jmpr @_forge_gensym_3", + "push 0", + "_forge_gensym_3:", + "popr", + "pop", + "popr", + "pop", + "ret", + ";;; End main ===================================================================", + "_forge_gensym_1: .db 0 ;;; Global foo", + "stack: .db 0", + ].join("\n")); + } + #[test] fn test_global_vars() { // Trying a non-str global var - let asm = build_boot("global foo; fn main() { foo = 3; }".into()).unwrap(); + let asm = build_boot("global foo; fn main() { foo = 3; }".into(), false).unwrap(); assert_eq!(asm.join("\n"), vec![ ".org 0x400", "push stack", @@ -167,7 +212,7 @@ mod test { #[test] fn test_no_return() { - let asm = build_boot("fn foo() { } fn main() { foo(); }".into()).unwrap(); + let asm = build_boot("fn foo() { } fn main() { foo(); }".into(), false).unwrap(); assert_eq!(asm.join("\n"), vec![ ".org 0x400", "push stack", @@ -201,7 +246,7 @@ mod test { #[test] fn test_global_static() { - let asm = build_boot("global a = static(10); fn main() { a[2] = 5; }".into()).unwrap(); + let asm = build_boot("global a = static(10); fn main() { a[2] = 5; }".into(), false).unwrap(); assert_eq!(asm.join("\n"), vec![ ".org 0x400", "push _forge_gensym_2", // the addr of the static buffer @@ -226,7 +271,7 @@ mod test { #[test] fn test_string_escapes() { - let asm = build_boot("global a = \"\\\\foo\"; fn main() { }".into()).unwrap(); + let asm = build_boot("global a = \"\\\\foo\"; fn main() { }".into(), false).unwrap(); assert_eq!(asm.join("\n"), vec![ ".org 0x400", "push _forge_gensym_2", diff --git a/forge_core/src/compiler/state.rs b/forge_core/src/compiler/state.rs index 6219d38..855a0e5 100644 --- a/forge_core/src/compiler/state.rs +++ b/forge_core/src/compiler/state.rs @@ -8,6 +8,8 @@ use crate::compiler::utils::{Label, Scope, Variable}; /// The compiler state: #[derive(Clone, PartialEq, Debug, Default)] pub(crate) struct State { + /// Whether to emit documentation comments in the asm listing + pub comments: bool, /// Used by gensym to generate unique symbols pub gensym_index: usize, /// The globally-defined names @@ -25,6 +27,13 @@ pub(crate) struct State { } impl State { + pub(crate) fn with_comments() -> Self { + Self { + comments: true, + ..Self::default() + } + } + /// Generate a guaranteed-unique symbolic name pub(crate) fn gensym(&mut self) -> Label { self.gensym_index += 1; diff --git a/vtest/src/forge_tests.rs b/vtest/src/forge_tests.rs index 0528254..c742116 100644 --- a/vtest/src/forge_tests.rs +++ b/vtest/src/forge_tests.rs @@ -3,7 +3,7 @@ use vcore::memory::Memory; use vcore::CPU; fn run_forge(src: &str) -> CPU { - let asm = forge_core::compiler::build_boot(src).unwrap(); + let asm = forge_core::compiler::build_boot(src, true).unwrap(); let bin = assemble_snippet(asm).unwrap(); let mut cpu = CPU::new(Memory::with_program(bin)); cpu.run_to_halt(); @@ -12,7 +12,7 @@ fn run_forge(src: &str) -> CPU { #[allow(dead_code)] fn compiler_output(src: &str) -> Vec { - forge_core::compiler::build_boot(src).unwrap() + forge_core::compiler::build_boot(src, true).unwrap() } #[allow(dead_code)] @@ -27,7 +27,7 @@ fn main_return(src: &str) -> i32 { } fn error_message(src: &str) -> Option { - match forge_core::compiler::build_boot(src) { + match forge_core::compiler::build_boot(src, true) { Ok(_) => None, Err(forge_core::compiler::CompileError(_, _, s)) => Some(s) } diff --git a/vweb/src/lib.rs b/vweb/src/lib.rs index 233b756..14e1f2c 100644 --- a/vweb/src/lib.rs +++ b/vweb/src/lib.rs @@ -20,5 +20,5 @@ pub fn source_map(snippet: String) -> JsValue { #[wasm_bindgen] pub fn compile_forge(src: String) -> Result { - build_boot(src.as_str()).map(|s| s.join("\n")).map_err(|e| format!("{}", e)) + build_boot(src.as_str(), true).map(|s| s.join("\n")).map_err(|e| format!("{}", e)) } \ No newline at end of file