Added comment-emission to the compiler

This commit is contained in:
2024-05-03 21:04:57 -05:00
parent 72e980a2db
commit bde91901e2
8 changed files with 96 additions and 16 deletions
+17
View File
@@ -224,3 +224,20 @@ impl From<&str> for Expr {
Self::Name(String::from(value)) 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"
}
)
}
}
@@ -13,6 +13,7 @@ impl Compilable for Block {
// Compile each statement: // Compile each statement:
for stmt in self.0 { for stmt in self.0 {
let loc = stmt.location; let loc = stmt.location;
sig.emit_comment(format!(";; {}:{} :: {}", loc.line, loc.col, stmt.ast.description()));
match stmt.ast { match stmt.ast {
Statement::Return(ret) => { Statement::Return(ret) => {
ret.process(state, Some(sig), loc)?; ret.process(state, Some(sig), loc)?;
@@ -1,6 +1,6 @@
use crate::ast::{Function, Location}; use crate::ast::{Function, Location};
use crate::compiler::compilable::Compilable; use crate::compiler::compilable::Compilable;
use crate::compiler::compiled_fn::CompiledFn; use crate::compiler::compiled_fn::{CompiledFn};
use crate::compiler::CompileError; use crate::compiler::CompileError;
use crate::compiler::state::State; 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: // The CompiledFn for this function, which will eventually get stuff populated into it:
let mut sig = CompiledFn { let mut sig = CompiledFn {
comments: state.comments,
label, label,
end_label, end_label,
..Default::default() ..Default::default()
+7
View File
@@ -50,6 +50,7 @@ use crate::compiler::utils::{Label, Scope, Variable};
/// with an interface in the zero page. /// with an interface in the zero page.
#[derive(Clone, PartialEq, Debug, Default)] #[derive(Clone, PartialEq, Debug, Default)]
pub struct CompiledFn { pub struct CompiledFn {
pub comments: bool,
pub label: Label, pub label: Label,
pub end_label: Label, pub end_label: Label,
pub max_frame_size: usize, pub max_frame_size: usize,
@@ -96,6 +97,12 @@ impl CompiledFn {
self.body.emit_arg(opcode, arg) self.body.emit_arg(opcode, arg)
} }
pub(crate) fn emit_comment<T: Display>(&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, /// The (current) size of the local scope in bytes. This increases as variables are declared,
/// decreases as they leave scope /// decreases as they leave scope
pub(crate) fn frame_size(&self) -> usize { pub(crate) fn frame_size(&self) -> usize {
+56 -11
View File
@@ -20,8 +20,12 @@ mod text;
/// Turn a `Program` into a list of assembly lines that can be assembled to run it. /// 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 /// 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. /// 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<Vec<String>, CompileError> { pub fn build_boot(src: &str, include_comments: bool) -> Result<Vec<String>, CompileError> {
let mut state = State::default(); let mut state = if include_comments {
State::with_comments()
} else {
State::default()
};
let ast = parse(src).map_err(CompileError::from)?; let ast = parse(src).map_err(CompileError::from)?;
ast.process(&mut state, None, (0, 0).into())?; ast.process(&mut state, None, (0, 0).into())?;
@@ -45,11 +49,19 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, CompileError> {
asm.push("hlt".into()); asm.push("hlt".into());
// Now start dumping compiled objects into there. First functions: // 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)); asm.push(format!("{}:", val.label));
for line in val.text() { for line in val.text() {
asm.push(String::from(line)); 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: // Strings:
@@ -64,11 +76,15 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, CompileError> {
} }
// Global vars: // 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, // All global variables are indirect labels; anything in scope that's not that is a const,
// fn, or string, which we'll emit elsewhere. // fn, or string, which we'll emit elsewhere.
if let Variable::IndirectLabel(label) = val { 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] #[test]
fn test_build_boot() { fn test_build_boot() {
// Very basic test of one main() // 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![ assert_eq!(asm.join("\n"), vec![
".org 0x400", ".org 0x400",
"push stack", "push stack",
@@ -119,7 +135,7 @@ mod test {
].join("\n")); ].join("\n"));
// Slightly more complicated, with a global str // 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![ assert_eq!(asm.join("\n"), vec![
".org 0x400", ".org 0x400",
"push stack", "push stack",
@@ -144,9 +160,38 @@ mod test {
} }
#[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() { fn test_global_vars() {
// Trying a non-str global var // 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![ assert_eq!(asm.join("\n"), vec![
".org 0x400", ".org 0x400",
"push stack", "push stack",
@@ -167,7 +212,7 @@ mod test {
#[test] #[test]
fn test_no_return() { 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![ assert_eq!(asm.join("\n"), vec![
".org 0x400", ".org 0x400",
"push stack", "push stack",
@@ -201,7 +246,7 @@ mod test {
#[test] #[test]
fn test_global_static() { 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![ assert_eq!(asm.join("\n"), vec![
".org 0x400", ".org 0x400",
"push _forge_gensym_2", // the addr of the static buffer "push _forge_gensym_2", // the addr of the static buffer
@@ -226,7 +271,7 @@ mod test {
#[test] #[test]
fn test_string_escapes() { 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![ assert_eq!(asm.join("\n"), vec![
".org 0x400", ".org 0x400",
"push _forge_gensym_2", "push _forge_gensym_2",
+9
View File
@@ -8,6 +8,8 @@ use crate::compiler::utils::{Label, Scope, Variable};
/// The compiler state: /// The compiler state:
#[derive(Clone, PartialEq, Debug, Default)] #[derive(Clone, PartialEq, Debug, Default)]
pub(crate) struct State { pub(crate) struct State {
/// Whether to emit documentation comments in the asm listing
pub comments: bool,
/// Used by gensym to generate unique symbols /// Used by gensym to generate unique symbols
pub gensym_index: usize, pub gensym_index: usize,
/// The globally-defined names /// The globally-defined names
@@ -25,6 +27,13 @@ pub(crate) struct State {
} }
impl State { impl State {
pub(crate) fn with_comments() -> Self {
Self {
comments: true,
..Self::default()
}
}
/// Generate a guaranteed-unique symbolic name /// Generate a guaranteed-unique symbolic name
pub(crate) fn gensym(&mut self) -> Label { pub(crate) fn gensym(&mut self) -> Label {
self.gensym_index += 1; self.gensym_index += 1;
+3 -3
View File
@@ -3,7 +3,7 @@ use vcore::memory::Memory;
use vcore::CPU; use vcore::CPU;
fn run_forge(src: &str) -> 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 bin = assemble_snippet(asm).unwrap();
let mut cpu = CPU::new(Memory::with_program(bin)); let mut cpu = CPU::new(Memory::with_program(bin));
cpu.run_to_halt(); cpu.run_to_halt();
@@ -12,7 +12,7 @@ fn run_forge(src: &str) -> CPU {
#[allow(dead_code)] #[allow(dead_code)]
fn compiler_output(src: &str) -> Vec<String> { fn compiler_output(src: &str) -> Vec<String> {
forge_core::compiler::build_boot(src).unwrap() forge_core::compiler::build_boot(src, true).unwrap()
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -27,7 +27,7 @@ fn main_return(src: &str) -> i32 {
} }
fn error_message(src: &str) -> Option<String> { fn error_message(src: &str) -> Option<String> {
match forge_core::compiler::build_boot(src) { match forge_core::compiler::build_boot(src, true) {
Ok(_) => None, Ok(_) => None,
Err(forge_core::compiler::CompileError(_, _, s)) => Some(s) Err(forge_core::compiler::CompileError(_, _, s)) => Some(s)
} }
+1 -1
View File
@@ -20,5 +20,5 @@ pub fn source_map(snippet: String) -> JsValue {
#[wasm_bindgen] #[wasm_bindgen]
pub fn compile_forge(src: String) -> Result<String, String> { pub fn compile_forge(src: String) -> Result<String, String> {
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))
} }