mirror of
https://github.com/martinvonz/jj.git
synced 2025-02-10 06:20:18 +00:00
fileset, templater: extract basic AST node types
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.
This commit is contained in:
parent
0c05c541a1
commit
97023b8da6
3 changed files with 101 additions and 114 deletions
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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> {
|
||||
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue