Function prototypes

This commit is contained in:
2023-08-26 00:42:41 -05:00
parent 5bf5b7a78f
commit a7c7610105
4 changed files with 95 additions and 4 deletions
+55 -3
View File
@@ -118,6 +118,8 @@ struct State {
pub functions: BTreeMap<String, CompiledFn>,
/// 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<String>) -> 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<String, CompileError> {
// 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);
}
}