From 4fed24c283fd5d2396e9090efe0815502628c470 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 26 May 2024 01:55:25 -0700 Subject: [PATCH] Add user-defined functions --- src/analyzer.rs | 5 ++++ src/function_definition.rs | 44 +++++++++++++++++++++++++++++++ src/item.rs | 2 ++ src/lib.rs | 12 +++++---- src/parser.rs | 53 ++++++++++++++++++++++++++++++-------- 5 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 src/function_definition.rs diff --git a/src/analyzer.rs b/src/analyzer.rs index 822b8128..47ee5618 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -39,6 +39,8 @@ impl<'src> Analyzer<'src> { let mut definitions: HashMap<&str, (&'static str, Name)> = HashMap::new(); + let mut function_definitions = Table::::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()); diff --git a/src/function_definition.rs b/src/function_definition.rs new file mode 100644 index 00000000..332c3d5d --- /dev/null +++ b/src/function_definition.rs @@ -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>, + 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(()) + } +} diff --git a/src/item.rs b/src/item.rs index a39b5d2d..d78f76c4 100644 --- a/src/item.rs +++ b/src/item.rs @@ -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, 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, .. } => { diff --git a/src/lib.rs b/src/lib.rs index 115437c9..43299a79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/parser.rs b/src/parser.rs index 3a05fea5..c27e3935 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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>> { 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>, ) -> 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>, ) -> 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()?;