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))
}
}
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:
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)?;
@@ -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()
+7
View File
@@ -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<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,
/// decreases as they leave scope
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.
/// 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<Vec<String>, CompileError> {
let mut state = State::default();
pub fn build_boot(src: &str, include_comments: bool) -> Result<Vec<String>, 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<Vec<String>, 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<Vec<String>, 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",
+9
View File
@@ -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;