From a7c76101051f07dfb4c88466fd5d4342ed99c3b1 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sat, 26 Aug 2023 00:42:41 -0500 Subject: [PATCH] Function prototypes --- forge_core/src/ast.rs | 7 ++++ forge_core/src/compiler.rs | 58 ++++++++++++++++++++++++++++++++-- forge_core/src/forge_parser.rs | 30 ++++++++++++++++++ forge_core/src/new.pest | 4 ++- 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/forge_core/src/ast.rs b/forge_core/src/ast.rs index 45947ef..92f0a9e 100644 --- a/forge_core/src/ast.rs +++ b/forge_core/src/ast.rs @@ -24,6 +24,7 @@ pub enum Declaration { Function(Function), Global(Global), Const(Const), + Prototype(FunctionPrototype), } #[derive(PartialEq, Clone, Debug)] @@ -49,6 +50,12 @@ pub struct Function { pub body: Block, } +#[derive(PartialEq, Clone, Debug)] +pub struct FunctionPrototype { + pub name: String, + pub args: Vec, +} + #[derive(PartialEq, Clone, Debug)] pub enum Statement { Return(Return), diff --git a/forge_core/src/compiler.rs b/forge_core/src/compiler.rs index 7718a46..4f4ad74 100644 --- a/forge_core/src/compiler.rs +++ b/forge_core/src/compiler.rs @@ -118,6 +118,8 @@ struct State { pub functions: BTreeMap, /// The string table pub strings: Vec<(Label, String)>, + /// The functions that have been prototyped but not yet defined + pub prototypes: Scope, } impl State { @@ -152,6 +154,31 @@ impl State { self.strings.push((sym.clone(), string.into())); sym } + + fn declare_function(&mut self, name: &str, _args: Vec) -> Result<(), CompileError> { + // If it's not already prototyped, gensym a label and put it in the list. If it is, just + // ignore this (we don't check arity so it's not like the arglist being different matters) + // todo: check arity, store it here + if !self.prototypes.contains_key(name) { + let label = self.gensym(); + self.add_global(name, |_| Variable::DirectLabel(label.clone()))?; + self.prototypes.insert(name.into(), Variable::DirectLabel(label)); + } + Ok(()) + } + + fn find_or_declare_function(&mut self, name: &str, loc: Location) -> Result { + // If it's in here, remove it and return the label: + // (this will only ever match this way; only thing that puts stuff in prototypes adds directlabels) + if let Some(Variable::DirectLabel(label)) = self.prototypes.remove(name) { + Ok(label) + } else { + // Otherwise, make a new label, add it to globals, and return it + let label = self.gensym(); + self.add_global(name, |_| Variable::DirectLabel(label.clone()))?; + Ok(label) + } + } } trait Compilable { @@ -187,6 +214,7 @@ impl Compilable for Declaration { Declaration::Function(f) => f.process(state, None, loc), Declaration::Global(g) => g.process(state, None, loc), Declaration::Const(c) => c.process(state, None, loc), + Declaration::Prototype(p) => p.process(state, None, loc), } } } @@ -250,9 +278,11 @@ impl Compilable for Block { impl Compilable for Function { fn process(self, state: &mut State, _: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> { - // The signature for this function, which will eventually get added to the state + let label = state.find_or_declare_function(self.name.as_str(), loc)?; + + // The CompiledFn for this function, which will eventually get stuff populated into it: let mut sig = CompiledFn { - label: state.gensym(), + label, ..Default::default() }; @@ -267,17 +297,25 @@ impl Compilable for Function { // at the end // Add it to the global namespace so we can make recursive calls - state.add_global(&self.name, |_| Variable::DirectLabel(sig.label.clone()))?; + // state.add_global(&self.name, |_| Variable::DirectLabel(sig.label.clone()))?; + // Compile the body, storing all of it in the CompiledFn we just created / added self.body.process(state, Some(&mut sig), loc)?; // This can't fail because if it were a dupe name, adding the global would have failed state.functions.insert(self.name.clone(), sig); + // todo actually emit the function header / body Ok(()) } } +impl Compilable for FunctionPrototype { + fn process(self, state: &mut State, _: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> { + state.declare_function(self.name.as_str(), self.args) + } +} + /// Look up a name first in the local scope, and failing that in the global scope. fn lookup<'a>(name: &str, global_scope: &'a Scope, local_scope: &'a Scope) -> Option<&'a Variable> { if let Some(var) = local_scope.get(name) { @@ -924,4 +962,18 @@ mod test { .join("\n") ); } + + #[test] + fn test_function_prototypes() { + let mut state = state_for("fn blah(a, b);"); + assert_eq!(state.functions.get("blah"), None); + assert_eq!(state.prototypes.remove("blah"), Some(Variable::DirectLabel(String::from("_forge_gensym_1")))); + } + + #[test] + fn test_functions_with_prototypes() { + let mut state = state_for("fn blah(a, b); const foo = 3; fn blah(a, b) { return 7; }"); + assert_eq!(state.functions.remove("blah").unwrap().label, String::from("_forge_gensym_1")); + assert_eq!(state.prototypes.remove("blah"), None); + } } diff --git a/forge_core/src/forge_parser.rs b/forge_core/src/forge_parser.rs index 2953ba8..bf97984 100644 --- a/forge_core/src/forge_parser.rs +++ b/forge_core/src/forge_parser.rs @@ -163,6 +163,7 @@ impl AstNode for Declaration { Rule::function => Self::Function(Function::from_pair(pair)), Rule::global => Self::Global(Global::from_pair(pair)), Rule::const_decl => Self::Const(Const::from_pair(pair)), + Rule::function_prototype => Self::Prototype(FunctionPrototype::from_pair(pair)), _ => unreachable!(), } } @@ -227,6 +228,24 @@ impl AstNode for Function { /////////////////////////////////////////////////////////////////////////////////////////// +impl AstNode for FunctionPrototype { + const RULE: Rule = Rule::function_prototype; + fn from_pair(pair: Pair<'_>) -> Self { + let mut inner = pair.into_inner().peekable(); + let name = String::from(inner.next().unwrap().as_str()); + let args: Vec<_> = inner + .next() + .unwrap() + .into_inner() + .map(|p| String::from(p.as_str())) + .collect(); + + Self { name, args } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////// + impl AstNode for Statement { const RULE: Rule = Rule::statement; fn from_pair(pair: Pair) -> Self { @@ -865,6 +884,17 @@ mod test { ); } + #[test] + fn parse_fn_prototype() { + assert_eq!( + FunctionPrototype::from_str("fn foo();"), + Ok(FunctionPrototype { + name: "foo".into(), + args: vec![], + }) + ); + } + #[test] fn parse_program() { let prog = Program::from_str("global foo; const blah = 3;").unwrap(); diff --git a/forge_core/src/new.pest b/forge_core/src/new.pest index 1f91654..5e5bcc3 100644 --- a/forge_core/src/new.pest +++ b/forge_core/src/new.pest @@ -80,7 +80,9 @@ block = { "{" ~ statement* ~ "}" } function = { "fn" ~ name ~ argnames ~ block } argnames = { "(" ~ (name ~ ("," ~ name)*)? ~ ")" } -declaration = { function | global | const_decl } +function_prototype = { "fn" ~ name ~ argnames ~ ";" } + +declaration = { function | function_prototype | global | const_decl } program = { (COMMENT | WHITESPACE)? ~ declaration* ~ EOI } return_stmt = { "return" ~ expr? }