use std::sync::Arc;
use nom::{bytes::complete::tag, branch::alt, character::complete::{char, multispace0}, combinator::opt, multi::separated_list0, sequence::{delimited, preceded, terminated}, IResult, Parser};
use crate::{model::{Func, FuncDoc, Param, SId, ASYNC_FUNC_ATTR}, parser::{context::ParseContext, doc::{err_fail, StofParseError}, expr::expr, ident::ident, parse_attributes, statement::block, types::parse_type, whitespace::{doc_comment, whitespace}}, runtime::{instruction::Instruction, instructions::Base, Val}};
pub fn parse_function<'a>(input: &'a str, context: &mut ParseContext) -> IResult<&'a str, (), StofParseError> {
let mut func = Func::default();
let (input, mut comments) = doc_comment(input)?;
let mut do_create_func;
let (input, (attrs, do_insert)) = parse_attributes(input, context)?;
for (k, v) in attrs { func.attributes.insert(k, v); }
do_create_func = do_insert;
let (input, more_comments) = doc_comment(input)?;
if more_comments.len() > 0 { if comments.len() > 0 { comments.push('\n'); } comments.push_str(&more_comments); }
let (input, (attrs, do_insert)) = parse_attributes(input, context)?;
for (k, v) in attrs { func.attributes.insert(k, v); }
do_create_func = do_create_func && do_insert;
let (input, _) = whitespace(input)?;
let (input, async_fn) = opt(terminated(tag("async"), multispace0)).parse(input)?;
if async_fn.is_some() && !func.attributes.contains_key(ASYNC_FUNC_ATTR.as_str()) {
func.attributes.insert(ASYNC_FUNC_ATTR.to_string(), Val::Null);
}
let (input, _) = tag("fn").parse(input)?;
let (input, name) = preceded(multispace0, ident).parse(input).map_err(err_fail)?;
let (input, params) = delimited(char('('), separated_list0(char(','), alt((parameter, opt_parameter))), char(')')).parse(input).map_err(err_fail)?;
let (input, return_type) = opt(preceded(delimited(multispace0, tag("->"), multispace0), parse_type)).parse(input).map_err(err_fail)?;
let (input, instructions) = block(input).map_err(err_fail)?;
if !do_create_func {
return Ok((input, ()));
}
for param in params { func.params.push_back(param); }
func.return_type = return_type.unwrap_or_default(); func.instructions = instructions;
let mut init_func = false;
if func.attributes.contains_key("init") { init_func = true; }
let self_ptr = context.self_ptr();
let func_ref = context.graph.insert_stof_data(&self_ptr, name, Box::new(func), None).expect("failed to insert a parsed function into this context");
if init_func {
context.init_funcs.push(func_ref.clone());
}
if context.profile.docs && comments.len() > 0 {
context.graph.insert_stof_data(&self_ptr, &format!("{name}_docs"), Box::new(FuncDoc {
docs: comments,
func: func_ref,
}), None);
}
Ok((input, ()))
}
pub fn parameter(input: &str) -> IResult<&str, Param, StofParseError> {
let (input, _) = multispace0(input)?;
let (input, name) = ident(input)?;
let (input, param_type) = preceded(preceded(multispace0, char(':')), preceded(multispace0, parse_type)).parse(input)?;
let (input, default) = opt(
preceded(delimited(multispace0, char('='), multispace0), expr)
).parse(input)?;
let (input, _) = multispace0(input)?;
let param = Param {
name: SId::from(name),
param_type,
default
};
Ok((input, param))
}
pub fn opt_parameter(input: &str) -> IResult<&str, Param, StofParseError> {
let (input, _) = multispace0(input)?;
let (input, name) = ident(input)?;
let (input, param_type) = preceded(preceded(multispace0, tag("?:")), preceded(multispace0, parse_type)).parse(input)?;
let (input, default) = opt(
preceded(delimited(multispace0, char('='), multispace0), expr)
).parse(input)?;
let (input, _) = multispace0(input)?;
let mut defalt_expr = Arc::new(Base::Literal(Val::Null)) as Arc<dyn Instruction>;
if let Some(def) = default { defalt_expr = def; }
let param = Param {
name: SId::from(name),
param_type,
default: Some(defalt_expr)
};
Ok((input, param))
}
#[cfg(test)]
mod tests {
use crate::{model::{Graph, Profile}, parser::{context::ParseContext, func::parse_function}, runtime::{Runtime, Val}};
#[test]
fn basic_func() {
let mut graph = Graph::default();
{
let mut context = ParseContext::new(&mut graph, Profile::docs(true));
let (_input, ()) = parse_function(r#"
// This is an ignored comment
#[test('hello')]
/**
* # This is a test function.
* This function represents the first ever function in Stof v2.
*/
#[another] // heres another ignored comment.
fn main(x: float = 5, optional?: str) -> float { x }
"#, &mut context).unwrap();
}
let res = Runtime::call(&mut graph, "root.main", vec![Val::from(10)]).unwrap();
assert_eq!(res, 10.into());
}
}