mirror of
https://github.com/casey/just
synced 2024-09-30 05:03:46 +00:00
Add user-defined functions
This commit is contained in:
parent
2bacbddadb
commit
4fed24c283
|
@ -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());
|
||||
|
|
44
src/function_definition.rs
Normal file
44
src/function_definition.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -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, ..
|
||||
} => {
|
||||
|
|
12
src/lib.rs
12
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;
|
||||
|
|
|
@ -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()?;
|
||||
|
|
Loading…
Reference in a new issue