Function prototypes
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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? }
|
||||
|
||||
Reference in New Issue
Block a user