Function prototypes
This commit is contained in:
@@ -24,6 +24,7 @@ pub enum Declaration {
|
|||||||
Function(Function),
|
Function(Function),
|
||||||
Global(Global),
|
Global(Global),
|
||||||
Const(Const),
|
Const(Const),
|
||||||
|
Prototype(FunctionPrototype),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
@@ -49,6 +50,12 @@ pub struct Function {
|
|||||||
pub body: Block,
|
pub body: Block,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub struct FunctionPrototype {
|
||||||
|
pub name: String,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub enum Statement {
|
pub enum Statement {
|
||||||
Return(Return),
|
Return(Return),
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ struct State {
|
|||||||
pub functions: BTreeMap<String, CompiledFn>,
|
pub functions: BTreeMap<String, CompiledFn>,
|
||||||
/// The string table
|
/// The string table
|
||||||
pub strings: Vec<(Label, String)>,
|
pub strings: Vec<(Label, String)>,
|
||||||
|
/// The functions that have been prototyped but not yet defined
|
||||||
|
pub prototypes: Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@@ -152,6 +154,31 @@ impl State {
|
|||||||
self.strings.push((sym.clone(), string.into()));
|
self.strings.push((sym.clone(), string.into()));
|
||||||
sym
|
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 {
|
trait Compilable {
|
||||||
@@ -187,6 +214,7 @@ impl Compilable for Declaration {
|
|||||||
Declaration::Function(f) => f.process(state, None, loc),
|
Declaration::Function(f) => f.process(state, None, loc),
|
||||||
Declaration::Global(g) => g.process(state, None, loc),
|
Declaration::Global(g) => g.process(state, None, loc),
|
||||||
Declaration::Const(c) => c.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 {
|
impl Compilable for Function {
|
||||||
fn process(self, state: &mut State, _: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> {
|
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 {
|
let mut sig = CompiledFn {
|
||||||
label: state.gensym(),
|
label,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,17 +297,25 @@ impl Compilable for Function {
|
|||||||
// at the end
|
// at the end
|
||||||
|
|
||||||
// Add it to the global namespace so we can make recursive calls
|
// 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)?;
|
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
|
// This can't fail because if it were a dupe name, adding the global would have failed
|
||||||
state.functions.insert(self.name.clone(), sig);
|
state.functions.insert(self.name.clone(), sig);
|
||||||
|
|
||||||
|
// todo actually emit the function header / body
|
||||||
Ok(())
|
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.
|
/// 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> {
|
fn lookup<'a>(name: &str, global_scope: &'a Scope, local_scope: &'a Scope) -> Option<&'a Variable> {
|
||||||
if let Some(var) = local_scope.get(name) {
|
if let Some(var) = local_scope.get(name) {
|
||||||
@@ -924,4 +962,18 @@ mod test {
|
|||||||
.join("\n")
|
.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::function => Self::Function(Function::from_pair(pair)),
|
||||||
Rule::global => Self::Global(Global::from_pair(pair)),
|
Rule::global => Self::Global(Global::from_pair(pair)),
|
||||||
Rule::const_decl => Self::Const(Const::from_pair(pair)),
|
Rule::const_decl => Self::Const(Const::from_pair(pair)),
|
||||||
|
Rule::function_prototype => Self::Prototype(FunctionPrototype::from_pair(pair)),
|
||||||
_ => unreachable!(),
|
_ => 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 {
|
impl AstNode for Statement {
|
||||||
const RULE: Rule = Rule::statement;
|
const RULE: Rule = Rule::statement;
|
||||||
fn from_pair(pair: Pair) -> Self {
|
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]
|
#[test]
|
||||||
fn parse_program() {
|
fn parse_program() {
|
||||||
let prog = Program::from_str("global foo; const blah = 3;").unwrap();
|
let prog = Program::from_str("global foo; const blah = 3;").unwrap();
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ block = { "{" ~ statement* ~ "}" }
|
|||||||
function = { "fn" ~ name ~ argnames ~ block }
|
function = { "fn" ~ name ~ argnames ~ block }
|
||||||
argnames = { "(" ~ (name ~ ("," ~ name)*)? ~ ")" }
|
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 }
|
program = { (COMMENT | WHITESPACE)? ~ declaration* ~ EOI }
|
||||||
|
|
||||||
return_stmt = { "return" ~ expr? }
|
return_stmt = { "return" ~ expr? }
|
||||||
|
|||||||
Reference in New Issue
Block a user