Skip to content

Commit

Permalink
fileset, templater: extract basic AST node types
Browse files Browse the repository at this point in the history
I'm going to extract generic alias substitution functions, and these AST types
will be accessed there. Revset parsing will also be migrated to the generic
functions.
  • Loading branch information
yuja committed May 23, 2024
1 parent 0c05c54 commit 97023b8
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 114 deletions.
77 changes: 3 additions & 74 deletions cli/src/template_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::{error, mem};

use itertools::Itertools as _;
use jj_lib::dsl_util::{
collect_similar, AliasDeclaration, AliasDeclarationParser, AliasId, AliasesMap,
self, collect_similar, AliasDeclaration, AliasDeclarationParser, AliasId, AliasesMap,
InvalidArguments, StringLiteralParser,
};
use once_cell::sync::Lazy;
Expand Down Expand Up @@ -246,19 +246,6 @@ fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Err
})
}

/// AST node without type or name checking.
#[derive(Clone, Debug, PartialEq)]
pub struct ExpressionNode<'i> {
pub kind: ExpressionKind<'i>,
pub span: pest::Span<'i>,
}

impl<'i> ExpressionNode<'i> {
fn new(kind: ExpressionKind<'i>, span: pest::Span<'i>) -> Self {
ExpressionNode { kind, span }
}
}

#[derive(Clone, Debug, PartialEq)]
pub enum ExpressionKind<'i> {
Identifier(&'i str),
Expand Down Expand Up @@ -291,13 +278,8 @@ pub enum BinaryOp {
LogicalAnd,
}

#[derive(Clone, Debug, PartialEq)]
pub struct FunctionCallNode<'i> {
pub name: &'i str,
pub name_span: pest::Span<'i>,
pub args: Vec<ExpressionNode<'i>>,
pub args_span: pest::Span<'i>,
}
pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;

#[derive(Clone, Debug, PartialEq)]
pub struct MethodCallNode<'i> {
Expand Down Expand Up @@ -683,59 +665,6 @@ pub fn parse<'i>(
expand_aliases(node, aliases_map)
}

impl<'i> FunctionCallNode<'i> {
pub fn expect_no_arguments(&self) -> TemplateParseResult<()> {
let [] = self.expect_exact_arguments()?;
Ok(())
}

/// Extracts exactly N required arguments.
pub fn expect_exact_arguments<const N: usize>(
&self,
) -> TemplateParseResult<&[ExpressionNode<'i>; N]> {
self.args
.as_slice()
.try_into()
.map_err(|_| self.invalid_arguments(format!("Expected {N} arguments")))
}

/// Extracts N required arguments and remainders.
pub fn expect_some_arguments<const N: usize>(
&self,
) -> TemplateParseResult<(&[ExpressionNode<'i>; N], &[ExpressionNode<'i>])> {
if self.args.len() >= N {
let (required, rest) = self.args.split_at(N);
Ok((required.try_into().unwrap(), rest))
} else {
Err(self.invalid_arguments(format!("Expected at least {N} arguments")))
}
}

/// Extracts N required arguments and M optional arguments.
pub fn expect_arguments<const N: usize, const M: usize>(
&self,
) -> TemplateParseResult<(&[ExpressionNode<'i>; N], [Option<&ExpressionNode<'i>>; M])> {
let count_range = N..=(N + M);
if count_range.contains(&self.args.len()) {
let (required, rest) = self.args.split_at(N);
let mut optional = rest.iter().map(Some).collect_vec();
optional.resize(M, None);
Ok((required.try_into().unwrap(), optional.try_into().unwrap()))
} else {
let (min, max) = count_range.into_inner();
Err(self.invalid_arguments(format!("Expected {min} to {max} arguments")))
}
}

fn invalid_arguments(&self, message: String) -> TemplateParseError {
InvalidArguments {
name: self.name,
message,
span: self.args_span,
}.into()
}
}

/// Applies the given function if the `node` is a string literal.
pub fn expect_string_literal_with<'a, 'i, T>(
node: &'a ExpressionNode<'i>,
Expand Down
95 changes: 95 additions & 0 deletions lib/src/dsl_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,101 @@ use itertools::Itertools as _;
use pest::iterators::Pairs;
use pest::RuleType;

/// AST node without type or name checking.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExpressionNode<'i, T> {
/// Expression item such as identifier, literal, function call, etc.
pub kind: T,
/// Span of the node.
pub span: pest::Span<'i>,
}

impl<'i, T> ExpressionNode<'i, T> {
/// Wraps the given expression and span.
pub fn new(kind: T, span: pest::Span<'i>) -> Self {
ExpressionNode { kind, span }
}
}

/// Function call in AST.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FunctionCallNode<'i, T> {
/// Function name.
pub name: &'i str,
/// Span of the function name.
pub name_span: pest::Span<'i>,
/// List of positional arguments.
pub args: Vec<ExpressionNode<'i, T>>,
// TODO: revset supports keyword args
/// Span of the arguments list.
pub args_span: pest::Span<'i>,
}

impl<'i, T> FunctionCallNode<'i, T> {
/// Ensures that no arguments passed.
pub fn expect_no_arguments(&self) -> Result<(), InvalidArguments<'i>> {
let [] = self.expect_exact_arguments()?;
Ok(())
}

/// Extracts exactly N required arguments.
pub fn expect_exact_arguments<const N: usize>(
&self,
) -> Result<&[ExpressionNode<'i, T>; N], InvalidArguments<'i>> {
self.args
.as_slice()
.try_into()
.map_err(|_| self.invalid_arguments(format!("Expected {N} arguments")))
}

/// Extracts N required arguments and remainders.
#[allow(clippy::type_complexity)]
pub fn expect_some_arguments<const N: usize>(
&self,
) -> Result<(&[ExpressionNode<'i, T>; N], &[ExpressionNode<'i, T>]), InvalidArguments<'i>> {
if self.args.len() >= N {
let (required, rest) = self.args.split_at(N);
Ok((required.try_into().unwrap(), rest))
} else {
Err(self.invalid_arguments(format!("Expected at least {N} arguments")))
}
}

/// Extracts N required arguments and M optional arguments.
#[allow(clippy::type_complexity)]
pub fn expect_arguments<const N: usize, const M: usize>(
&self,
) -> Result<
(
&[ExpressionNode<'i, T>; N],
[Option<&ExpressionNode<'i, T>>; M],
),
InvalidArguments<'i>,
> {
let count_range = N..=(N + M);
if count_range.contains(&self.args.len()) {
let (required, rest) = self.args.split_at(N);
let mut optional = rest.iter().map(Some).collect_vec();
optional.resize(M, None);
Ok((
required.try_into().unwrap(),
optional.try_into().map_err(|_: Vec<_>| ()).unwrap(),
))
} else {
let (min, max) = count_range.into_inner();
Err(self.invalid_arguments(format!("Expected {min} to {max} arguments")))
}
}

fn invalid_arguments(&self, message: String) -> InvalidArguments<'i> {
InvalidArguments {
name: self.name,
message,
span: self.args_span,
}
}
}

/// Unexpected number of arguments, or invalid combination of arguments.
///
/// This error is supposed to be converted to language-specific parse error
Expand Down
43 changes: 3 additions & 40 deletions lib/src/fileset_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use pest::Parser;
use pest_derive::Parser;
use thiserror::Error;

use crate::dsl_util::{InvalidArguments, StringLiteralParser};
use crate::dsl_util::{self, InvalidArguments, StringLiteralParser};

#[derive(Parser)]
#[grammar = "fileset.pest"]
Expand Down Expand Up @@ -159,19 +159,6 @@ fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Err
})
}

/// Parsed node without name resolution.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExpressionNode<'i> {
pub kind: ExpressionKind<'i>,
pub span: pest::Span<'i>,
}

impl<'i> ExpressionNode<'i> {
fn new(kind: ExpressionKind<'i>, span: pest::Span<'i>) -> Self {
ExpressionNode { kind, span }
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExpressionKind<'i> {
Identifier(&'i str),
Expand All @@ -198,13 +185,8 @@ pub enum BinaryOp {
Difference,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FunctionCallNode<'i> {
pub name: &'i str,
pub name_span: pest::Span<'i>,
pub args: Vec<ExpressionNode<'i>>,
pub args_span: pest::Span<'i>,
}
pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;

fn parse_function_call_node(pair: Pair<Rule>) -> FilesetParseResult<FunctionCallNode> {
assert_eq!(pair.as_rule(), Rule::function);
Expand Down Expand Up @@ -337,25 +319,6 @@ pub fn parse_program_or_bare_string(text: &str) -> FilesetParseResult<Expression
Ok(ExpressionNode::new(expr, span))
}

impl<'i> FunctionCallNode<'i> {
pub fn expect_no_arguments(&self) -> FilesetParseResult<()> {
if self.args.is_empty() {
Ok(())
} else {
Err(self.invalid_arguments("Expected 0 arguments".to_owned()))
}
}

fn invalid_arguments(&self, message: String) -> FilesetParseError {
InvalidArguments {
name: self.name,
message,
span: self.args_span,
}
.into()
}
}

#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
Expand Down

0 comments on commit 97023b8

Please sign in to comment.