Added comment-emission to the compiler
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String> {
|
||||
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<String> {
|
||||
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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,5 +20,5 @@ pub fn source_map(snippet: String) -> JsValue {
|
||||
|
||||
#[wasm_bindgen]
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user