Add user-defined functions

This commit is contained in:
Casey Rodarmor 2024-05-26 01:55:25 -07:00
parent 2bacbddadb
commit 4fed24c283
5 changed files with 100 additions and 16 deletions

View file

@ -39,6 +39,8 @@ impl<'src> Analyzer<'src> {
let mut definitions: HashMap<&str, (&'static str, Name)> = HashMap::new();
let mut function_definitions = Table::<FunctionDefinition>::new();
let mut define = |name: Name<'src>,
second_type: &'static str,
duplicates_allowed: bool|
@ -77,6 +79,9 @@ impl<'src> Analyzer<'src> {
assignments.push(assignment);
}
Item::Comment(_) => (),
Item::FunctionDefinition(function_definition) => {
function_definitions.insert(function_definition.clone())
}
Item::Import { absolute, .. } => {
if let Some(absolute) = absolute {
stack.push(asts.get(absolute).unwrap());

View file

@ -0,0 +1,44 @@
use super::*;
// todo:
// - test that functions parse:
// - 0, 1, 2 parameters
// - resolve undefined variables in function body
// - allow function parameters in function body
// - parse function calls
// - evaluate function calls, binding arguments to parameters
// - test that functions dump correctly
// - make functions unstable
// - catch function stack overflow
// - allow recursion in functions
#[derive(Debug, Clone)]
pub(crate) struct FunctionDefinition<'src> {
pub(crate) name: Name<'src>,
pub(crate) parameters: Vec<Name<'src>>,
pub(crate) body: Expression<'src>,
}
impl<'src> Keyed<'src> for FunctionDefinition<'src> {
fn key(&self) -> &'src str {
self.name.lexeme()
}
}
impl<'src> Display for FunctionDefinition<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "{}(", self.name)?;
for (i, parameter) in self.parameters.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{parameter}")?;
}
write!(f, ") := {}", self.body)?;
Ok(())
}
}

View file

@ -6,6 +6,7 @@ pub(crate) enum Item<'src> {
Alias(Alias<'src, Name<'src>>),
Assignment(Assignment<'src>),
Comment(&'src str),
FunctionDefinition(FunctionDefinition<'src>),
Import {
absolute: Option<PathBuf>,
optional: bool,
@ -28,6 +29,7 @@ impl<'src> Display for Item<'src> {
Self::Alias(alias) => write!(f, "{alias}"),
Self::Assignment(assignment) => write!(f, "{assignment}"),
Self::Comment(comment) => write!(f, "{comment}"),
Self::FunctionDefinition(function_definition) => write!(f, "{function_definition}"),
Self::Import {
relative, optional, ..
} => {

View file

@ -23,11 +23,12 @@ pub(crate) use {
config_error::ConfigError, constants::constants, count::Count, delimiter::Delimiter,
dependency::Dependency, dump_format::DumpFormat, enclosure::Enclosure, error::Error,
evaluator::Evaluator, expression::Expression, fragment::Fragment, function::Function,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal,
output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position,
function_definition::FunctionDefinition, interrupt_guard::InterruptGuard,
interrupt_handler::InterruptHandler, item::Item, justfile::Justfile, keyed::Keyed,
keyword::Keyword, lexer::Lexer, line::Line, list::List, load_dotenv::load_dotenv,
loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal, output::output,
output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, parser::Parser,
platform::Platform, platform_interface::PlatformInterface, position::Position,
positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe,
recipe_context::RecipeContext, recipe_resolver::RecipeResolver,
recipe_signature::RecipeSignature, scope::Scope, search::Search, search_config::SearchConfig,
@ -141,6 +142,7 @@ mod evaluator;
mod expression;
mod fragment;
mod function;
mod function_definition;
mod interrupt_guard;
mod interrupt_handler;
mod item;

View file

@ -253,7 +253,7 @@ impl<'run, 'src> Parser<'run, 'src> {
/// Accept a token of kind `Identifier` and parse into a `Name`
fn accept_name(&mut self) -> CompileResult<'src, Option<Name<'src>>> {
if self.next_is(Identifier) {
Ok(Some(self.parse_name()?))
Ok(Some(self.expect_name()?))
} else {
Ok(None)
}
@ -278,7 +278,7 @@ impl<'run, 'src> Parser<'run, 'src> {
recipe,
}))
} else if self.accepted(ParenL)? {
let recipe = self.parse_name()?;
let recipe = self.expect_name()?;
let mut arguments = Vec::new();
@ -331,6 +331,8 @@ impl<'run, 'src> Parser<'run, 'src> {
eol_since_last_comment = true;
} else if self.accepted(Eof)? {
break;
} else if self.next_are(&[Identifier, ParenL]) {
items.push(Item::FunctionDefinition(self.parse_function_definition()?));
} else if self.next_is(Identifier) {
match Keyword::from_lexeme(next.lexeme()) {
Some(Keyword::Alias) if self.next_are(&[Identifier, Identifier, ColonEquals]) => {
@ -366,7 +368,7 @@ impl<'run, 'src> Parser<'run, 'src> {
let optional = self.accepted(QuestionMark)?;
let name = self.parse_name()?;
let name = self.expect_name()?;
let relative = if self.next_is(StringToken) || self.next_are(&[Identifier, StringToken])
{
@ -447,9 +449,9 @@ impl<'run, 'src> Parser<'run, 'src> {
attributes: BTreeSet<Attribute<'src>>,
) -> CompileResult<'src, Alias<'src, Name<'src>>> {
self.presume_keyword(Keyword::Alias)?;
let name = self.parse_name()?;
let name = self.expect_name()?;
self.presume_any(&[Equals, ColonEquals])?;
let target = self.parse_name()?;
let target = self.expect_name()?;
self.expect_eol()?;
Ok(Alias {
attributes,
@ -460,7 +462,7 @@ impl<'run, 'src> Parser<'run, 'src> {
/// Parse an assignment, e.g. `foo := bar`
fn parse_assignment(&mut self, export: bool) -> CompileResult<'src, Assignment<'src>> {
let name = self.parse_name()?;
let name = self.expect_name()?;
self.presume_any(&[Equals, ColonEquals])?;
let value = self.parse_expression()?;
self.expect_eol()?;
@ -511,6 +513,35 @@ impl<'run, 'src> Parser<'run, 'src> {
Ok(expression)
}
// Parse a function definition, e.g., `foo(x) := x + x`
fn parse_function_definition(&mut self) -> CompileResult<'src, FunctionDefinition<'src>> {
let name = self.expect_name()?;
self.presume(ParenL)?;
let mut parameters = Vec::new();
while !self.next_is(ParenR) {
parameters.push(self.expect_name()?);
if !self.accepted(Comma)? {
break;
}
}
self.expect(ParenR)?;
self.expect(ColonEquals)?;
let body = self.parse_expression()?;
Ok(FunctionDefinition {
name,
parameters,
body,
})
}
/// Parse a conditional, e.g. `if a == b { "foo" } else { "bar" }`
fn parse_conditional(&mut self) -> CompileResult<'src, Expression<'src>> {
let condition = self.parse_condition()?;
@ -606,7 +637,7 @@ impl<'run, 'src> Parser<'run, 'src> {
self.expect(ParenR)?;
Ok(Expression::Assert { condition, error })
} else {
let name = self.parse_name()?;
let name = self.expect_name()?;
if self.next_is(ParenL) {
let arguments = self.parse_sequence()?;
@ -708,7 +739,7 @@ impl<'run, 'src> Parser<'run, 'src> {
}
/// Parse a name from an identifier token
fn parse_name(&mut self) -> CompileResult<'src, Name<'src>> {
fn expect_name(&mut self) -> CompileResult<'src, Name<'src>> {
self.expect(Identifier).map(Name::from_identifier)
}
@ -738,7 +769,7 @@ impl<'run, 'src> Parser<'run, 'src> {
quiet: bool,
attributes: BTreeSet<Attribute<'src>>,
) -> CompileResult<'src, UnresolvedRecipe<'src>> {
let name = self.parse_name()?;
let name = self.expect_name()?;
let mut positional = Vec::new();
@ -820,7 +851,7 @@ impl<'run, 'src> Parser<'run, 'src> {
fn parse_parameter(&mut self, kind: ParameterKind) -> CompileResult<'src, Parameter<'src>> {
let export = self.accepted(Dollar)?;
let name = self.parse_name()?;
let name = self.expect_name()?;
let default = if self.accepted(Equals)? {
Some(self.parse_value()?)
@ -979,7 +1010,7 @@ impl<'run, 'src> Parser<'run, 'src> {
while self.accepted(BracketL)? {
loop {
let name = self.parse_name()?;
let name = self.expect_name()?;
let argument = if self.accepted(ParenL)? {
let argument = self.parse_string_literal()?;