From 5d8d8a13a669f98996614f92dfcf9eaaad5b7cf6 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Fri, 14 Jul 2023 01:53:10 -0500 Subject: [PATCH] String support, more refactoring --- forge_core/src/compiler.rs | 115 ++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 34 deletions(-) diff --git a/forge_core/src/compiler.rs b/forge_core/src/compiler.rs index 3358291..0d4ab4f 100644 --- a/forge_core/src/compiler.rs +++ b/forge_core/src/compiler.rs @@ -43,7 +43,7 @@ pub enum Variable { /// at your stack frame. Locals can be found by adding some offset from the frame pointer. #[derive(Clone, PartialEq, Debug, Default)] pub struct Signature { - pub label: String, + pub label: Label, pub frame_size: usize, pub local_scope: Scope, pub body: Vec, @@ -80,6 +80,9 @@ impl Signature { /// Maps from names to the variables they represent pub type Scope = BTreeMap; +/// So we don't get confused between string-strings and assembly-label strings +pub type Label = String; + /// The compiler state: #[derive(Clone, PartialEq, Debug, Default)] struct State { @@ -89,11 +92,13 @@ struct State { pub global_scope: Scope, /// The functions pub functions: BTreeMap, + /// The string table + pub strings: Vec<(Label, String)>, } impl State { /// Generate a guaranteed-unique symbolic name - fn gensym(&mut self) -> String { + fn gensym(&mut self) -> Label { self.gensym_index += 1; format!("_gensym_{}", self.gensym_index) } @@ -103,6 +108,7 @@ impl State { self.global_scope.contains_key(name) } + /// Add a symbol to the global namespace, catching name collisions fn add_global Variable>( &mut self, name: &str, @@ -116,6 +122,12 @@ impl State { Ok(()) } } + + fn add_string(&mut self, string: &str) -> Label { + let sym = self.gensym(); + self.strings.push((sym.clone(), string.into())); + sym + } } trait Compilable { @@ -178,30 +190,13 @@ impl Compilable for Function { sig.add_local(&arg.name)? } - // Compile each statement: todo this should be broken into several different Compilables + // Compile each statement: for stmt in self.body.0 { match stmt { Statement::Return(_) => {} Statement::Assignment(assign) => assign.process(state, Some(&mut sig))?, Statement::Call(_) => todo!(), - Statement::VarDecl(v) => { - if v.typename.is_some() || v.size.is_some() { - todo!("Structs and arrays are not yet supported") - } - if let Some(initial) = v.initial { - // If it's got an initial value, we have to compile that before we add - // the name to scope, or else UB will ensue if it refers to itself: - initial.process(state, Some(&mut sig))?; - // But then add it to scope and assign: - sig.add_local(&v.name)?; - // We'll just whip up an lvalue real quick... - Lvalue::Name(v.name).process(state, Some(&mut sig))?; - sig.emit("storew"); // And store the initial value there - } else { - // Otherwise, just add it to scope and leave garbage in there: - sig.add_local(&v.name)?; - } - } + Statement::VarDecl(vardecl) => vardecl.process(state, Some(&mut sig))?, Statement::Conditional(_) | Statement::WhileLoop(_) | Statement::RepeatLoop(_) => { todo!() } @@ -233,13 +228,43 @@ impl Compilable for Assignment { let Assignment { lvalue, rvalue } = self; let sig = sig.expect("Assignment outside function"); + // For a normal expr, eval and leave on the stack; for a string literal, add it to + // the str table and push the label's address match rvalue { - Rvalue::Expr(rvalue) => { - rvalue.process(state, Some(sig))?; - lvalue.process(state, Some(sig))?; - sig.emit("storew"); + Rvalue::Expr(rvalue) => rvalue.process(state, Some(sig))?, + Rvalue::String(string) => { + let label = state.add_string(&string); + sig.emit_arg("push", label); } - Rvalue::String(_) => todo!(), + } + + // Then process the lvalue and storew + lvalue.process(state, Some(sig))?; + sig.emit("storew"); + Ok(()) + } +} + +/////////////////////////////////////////////////////////// + +impl Compilable for VarDecl { + fn process(self, state: &mut State, sig: Option<&mut Signature>) -> Result<(), CompileError> { + let mut sig = sig.expect("Var declaration outside function"); + if self.typename.is_some() || self.size.is_some() { + todo!("Structs and arrays are not yet supported") + } + if let Some(initial) = self.initial { + // If it's got an initial value, we have to compile that before we add + // the name to scope, or else UB will ensue if it refers to itself: + initial.process(state, Some(sig))?; + // But then add it to scope and assign: + sig.add_local(&self.name)?; + // We'll just whip up an lvalue real quick... + Lvalue::Name(self.name).process(state, Some(sig))?; + sig.emit("storew"); // And store the initial value there + } else { + // Otherwise, just add it to scope and leave garbage in there: + sig.add_local(&self.name)?; } Ok(()) } @@ -410,16 +435,17 @@ impl Compilable for Global { impl Compilable for Const { fn process(self, state: &mut State, _: Option<&mut Signature>) -> Result<(), CompileError> { - if self.string.is_some() { - todo!("Strings are not yet supported") - } - - if let Some(expr) = self.value { - let val = eval_const(expr, &state.global_scope)?; - state.add_global(&self.name, |_| Variable::Literal(val)) + let var = if self.string.is_some() { + // If it's a string, add it to the string table + Variable::Label(state.add_string(&self.string.unwrap())) + } else if let Some(expr) = self.value { + // Otherwise eval_const it + Variable::Literal(eval_const(expr, &state.global_scope)?) } else { unreachable!() - } + }; + // Add it to the global namespace + state.add_global(&self.name, |_| var.clone()) } } @@ -609,4 +635,25 @@ mod test { .join("\n") ) } + + #[test] + fn test_literal_strings() { + let mut state = State::default(); + parse("const s1 = \"foo\"; fn blah() { var x; x = \"bar\"; }") + .unwrap() + .process(&mut state, None) + .expect("Failed to compile"); + let body = body_as_string(state.functions.get("blah").unwrap()); + assert_eq!( + state.strings, + vec![ + ("_gensym_1".into(), "foo".into()), + ("_gensym_3".into(), "bar".into()) // gensym 2 is the entrypoint of blah() + ] + ); + assert_eq!( + body, + vec!["push _gensym_3", "loadw frame", "storew"].join("\n") + ) // todo we want to allow strings in initializers also + } }