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
+7
View File
@@ -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<String>,
}
#[derive(PartialEq, Clone, Debug)]
pub enum Statement {
Return(Return),
+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);
}
}
+30
View File
@@ -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();
+3 -1
View File
@@ -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? }