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))
|
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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,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
@@ -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))
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user