// Copyright 2024 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. //! Parser for the fileset language. use std::error;use std::sync::LazyLock; use itertools::Itertools as _;use pest::Parser as _;use pest::iterators::Pair;use pest::pratt_parser::Assoc;use pest::pratt_parser::Op;use pest::pratt_parser::PrattParser;use pest_derive::Parser;use thiserror::Error; use crate::dsl_util;use crate::dsl_util::AliasDeclaration;use crate::dsl_util::AliasDeclarationParser;use crate::dsl_util::AliasDefinitionParser;use crate::dsl_util::AliasExpandError;use crate::dsl_util::AliasExpandableExpression;use crate::dsl_util::AliasId;use crate::dsl_util::AliasesMap;use crate::dsl_util::Diagnostics;use crate::dsl_util::ExpressionFolder;use crate::dsl_util::FoldableExpression;use crate::dsl_util::InvalidArguments;use crate::dsl_util::StringLiteralParser; #[derive(Parser)]#[grammar = "fileset.pest"]struct FilesetParser; const STRING_LITERAL_PARSER: StringLiteralParser<Rule> = StringLiteralParser { content_rule: Rule::string_content, escape_rule: Rule::string_escape,}; impl Rule { fn to_symbol(self) -> Option<&'static str> { match self { Self::EOI => None, Self::whitespace => None, Self::identifier => None, Self::strict_identifier_part => None, Self::strict_identifier => None, Self::bare_string => None, Self::string_escape => None, Self::string_content_char => None, Self::string_content => None, Self::string_literal => None, Self::raw_string_content => None, Self::raw_string_literal => None, Self::pattern_kind_op => Some(":"), Self::negate_op => Some("~"), Self::union_op => Some("|"), Self::intersection_op => Some("&"), Self::difference_op => Some("~"), Self::prefix_ops => None, Self::infix_ops => None, Self::function => None, Self::function_name => None, Self::function_arguments => None, Self::formal_parameters => None, Self::pattern => None, Self::bare_string_pattern => None, Self::primary => None, Self::expression => None, Self::program => None, Self::program_or_bare_string => None, Self::function_alias_declaration => None, Self::pattern_alias_declaration => None, Self::alias_declaration => None, } }} /// Manages diagnostic messages emitted during fileset parsing and name/// resolution.pub type FilesetDiagnostics = Diagnostics<FilesetParseError>; /// Result of fileset parsing and name resolution.pub type FilesetParseResult<T> = Result<T, FilesetParseError>; /// Error occurred during fileset parsing and name resolution.#[derive(Debug, Error)]#[error("{pest_error}")]pub struct FilesetParseError { kind: FilesetParseErrorKind, pest_error: Box<pest::error::Error<Rule>>, source: Option<Box<dyn error::Error + Send + Sync>>,} /// Categories of fileset parsing and name resolution error.#[expect(missing_docs)]#[derive(Clone, Debug, Eq, Error, PartialEq)]pub enum FilesetParseErrorKind { #[error("Syntax error")] SyntaxError, #[error("Function `{name}` doesn't exist")] NoSuchFunction { name: String, candidates: Vec<String>, }, #[error("Function `{name}`: {message}")] InvalidArguments { name: String, message: String }, #[error("Redefinition of function parameter")] RedefinedFunctionParameter, #[error("{0}")] Expression(String), #[error("In alias `{0}`")] InAliasExpansion(String), #[error("In function parameter `{0}`")] InParameterExpansion(String), #[error("Alias `{0}` expanded recursively")] RecursiveAlias(String),} impl FilesetParseError { pub(super) fn new(kind: FilesetParseErrorKind, span: pest::Span<'_>) -> Self { let message = kind.to_string(); let pest_error = Box::new(pest::error::Error::new_from_span( pest::error::ErrorVariant::CustomError { message }, span, )); Self { kind, pest_error, source: None, } } pub(super) fn with_source( mut self, source: impl Into<Box<dyn error::Error + Send + Sync>>, ) -> Self { self.source = Some(source.into()); self } /// Some other expression error. pub(super) fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self { Self::new(FilesetParseErrorKind::Expression(message.into()), span) } /// Category of the underlying error. pub fn kind(&self) -> &FilesetParseErrorKind { &self.kind }} impl AliasExpandError for FilesetParseError { fn invalid_arguments(err: InvalidArguments<'_>) -> Self { err.into() } fn recursive_expansion(id: AliasId<'_>, span: pest::Span<'_>) -> Self { Self::new(FilesetParseErrorKind::RecursiveAlias(id.to_string()), span) } fn within_alias_expansion(self, id: AliasId<'_>, span: pest::Span<'_>) -> Self { let kind = match id { AliasId::Symbol(_) | AliasId::Pattern(..) | AliasId::Function(..) => { FilesetParseErrorKind::InAliasExpansion(id.to_string()) } AliasId::Parameter(_) => FilesetParseErrorKind::InParameterExpansion(id.to_string()), }; Self::new(kind, span).with_source(self) }} impl From<pest::error::Error<Rule>> for FilesetParseError { fn from(err: pest::error::Error<Rule>) -> Self { Self { kind: FilesetParseErrorKind::SyntaxError, pest_error: Box::new(rename_rules_in_pest_error(err)), source: None, } }} impl From<InvalidArguments<'_>> for FilesetParseError { fn from(err: InvalidArguments<'_>) -> Self { let kind = FilesetParseErrorKind::InvalidArguments { name: err.name.to_owned(), message: err.message, }; Self::new(kind, err.span) }} fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Error<Rule> { err.renamed_rules(|rule| { rule.to_symbol() .map(|sym| format!("`{sym}`")) .unwrap_or_else(|| format!("<{rule:?}>")) })} #[derive(Clone, Debug, Eq, PartialEq)]pub enum ExpressionKind<'i> { Identifier(&'i str), String(String), /// `<name>:<value>` where `<value>` is usually `Identifier` or `String`. Pattern(Box<PatternNode<'i>>), Unary(UnaryOp, Box<ExpressionNode<'i>>), Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>), /// `x | y | ..` UnionAll(Vec<ExpressionNode<'i>>), FunctionCall(Box<FunctionCallNode<'i>>), /// Identity node to preserve the span in the source text. AliasExpanded(AliasId<'i>, Box<ExpressionNode<'i>>),} impl<'i> FoldableExpression<'i> for ExpressionKind<'i> { fn fold<F>(self, folder: &mut F, span: pest::Span<'i>) -> Result<Self, F::Error> where F: ExpressionFolder<'i, Self> + ?Sized, { match self { Self::Identifier(name) => folder.fold_identifier(name, span), Self::String(_) => Ok(self), Self::Pattern(pattern) => folder.fold_pattern(pattern, span), Self::Unary(op, arg) => { let arg = Box::new(folder.fold_expression(*arg)?); Ok(Self::Unary(op, arg)) } Self::Binary(op, lhs, rhs) => { let lhs = Box::new(folder.fold_expression(*lhs)?); let rhs = Box::new(folder.fold_expression(*rhs)?); Ok(Self::Binary(op, lhs, rhs)) } Self::UnionAll(nodes) => { let nodes = dsl_util::fold_expression_nodes(folder, nodes)?; Ok(Self::UnionAll(nodes)) } Self::FunctionCall(function) => folder.fold_function_call(function, span), Self::AliasExpanded(id, subst) => { let subst = Box::new(folder.fold_expression(*subst)?); Ok(Self::AliasExpanded(id, subst)) } } }} impl<'i> AliasExpandableExpression<'i> for ExpressionKind<'i> { fn identifier(name: &'i str) -> Self { Self::Identifier(name) } fn pattern(pattern: Box<PatternNode<'i>>) -> Self { Self::Pattern(pattern) } fn function_call(function: Box<FunctionCallNode<'i>>) -> Self { Self::FunctionCall(function) } fn alias_expanded(id: AliasId<'i>, subst: Box<ExpressionNode<'i>>) -> Self { Self::AliasExpanded(id, subst) }} #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]pub enum UnaryOp { /// `~` Negate,} #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]pub enum BinaryOp { /// `&` Intersection, /// `~` Difference,} pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;pub type PatternNode<'i> = dsl_util::PatternNode<'i, ExpressionKind<'i>>; fn union_nodes<'i>(lhs: ExpressionNode<'i>, rhs: ExpressionNode<'i>) -> ExpressionNode<'i> { let span = lhs.span.start_pos().span(&rhs.span.end_pos()); let expr = match lhs.kind { // Flatten "x | y | z" to save recursion stack. Machine-generated query // might have long chain of unions. ExpressionKind::UnionAll(mut nodes) => { nodes.push(rhs); ExpressionKind::UnionAll(nodes) } _ => ExpressionKind::UnionAll(vec![lhs, rhs]), }; ExpressionNode::new(expr, span)} fn parse_function_call_node(pair: Pair<Rule>) -> FilesetParseResult<FunctionCallNode> { assert_eq!(pair.as_rule(), Rule::function); let [name_pair, args_pair] = pair.into_inner().collect_array().unwrap(); assert_eq!(name_pair.as_rule(), Rule::function_name); assert_eq!(args_pair.as_rule(), Rule::function_arguments); let name_span = name_pair.as_span(); let args_span = args_pair.as_span(); let name = name_pair.as_str(); let args = args_pair .into_inner() .map(parse_expression_node) .try_collect()?; Ok(FunctionCallNode { name, name_span, args, keyword_args: vec![], // unsupported args_span, })} fn parse_as_string_literal(pair: Pair<Rule>) -> String { match pair.as_rule() { Rule::identifier => pair.as_str().to_owned(), Rule::string_literal => STRING_LITERAL_PARSER.parse(pair.into_inner()), Rule::raw_string_literal => { let [content] = pair.into_inner().collect_array().unwrap(); assert_eq!(content.as_rule(), Rule::raw_string_content); content.as_str().to_owned() } r => panic!("unexpected string literal rule: {r:?}"), }} fn parse_primary_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> { assert_eq!(pair.as_rule(), Rule::primary); let span = pair.as_span(); let first = pair.into_inner().next().unwrap(); let expr = match first.as_rule() { // Ignore inner span to preserve parenthesized expression as such. Rule::expression => parse_expression_node(first)?.kind, Rule::function => { let function = Box::new(parse_function_call_node(first)?); ExpressionKind::FunctionCall(function) } Rule::pattern => { let [lhs, op, rhs] = first.into_inner().collect_array().unwrap(); assert_eq!(lhs.as_rule(), Rule::strict_identifier); assert_eq!(op.as_rule(), Rule::pattern_kind_op); let pattern = Box::new(PatternNode { name: lhs.as_str(), name_span: lhs.as_span(), value: parse_primary_node(rhs)?, }); ExpressionKind::Pattern(pattern) } Rule::identifier => ExpressionKind::Identifier(first.as_str()), Rule::string_literal | Rule::raw_string_literal => { ExpressionKind::String(parse_as_string_literal(first)) } r => panic!("unexpected primary rule: {r:?}"), }; Ok(ExpressionNode::new(expr, span))} fn parse_expression_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> { assert_eq!(pair.as_rule(), Rule::expression); static PRATT: LazyLock<PrattParser<Rule>> = LazyLock::new(|| { PrattParser::new() .op(Op::infix(Rule::union_op, Assoc::Left)) .op(Op::infix(Rule::intersection_op, Assoc::Left) | Op::infix(Rule::difference_op, Assoc::Left)) .op(Op::prefix(Rule::negate_op)) }); PRATT .map_primary(parse_primary_node) .map_prefix(|op, rhs| { let op_kind = match op.as_rule() { Rule::negate_op => UnaryOp::Negate, r => panic!("unexpected prefix operator rule {r:?}"), }; let rhs = Box::new(rhs?); let span = op.as_span().start_pos().span(&rhs.span.end_pos()); let expr = ExpressionKind::Unary(op_kind, rhs); Ok(ExpressionNode::new(expr, span)) }) .map_infix(|lhs, op, rhs| { let op_kind = match op.as_rule() { Rule::union_op => return Ok(union_nodes(lhs?, rhs?)), Rule::intersection_op => BinaryOp::Intersection, Rule::difference_op => BinaryOp::Difference, r => panic!("unexpected infix operator rule {r:?}"), }; let lhs = Box::new(lhs?); let rhs = Box::new(rhs?); let span = lhs.span.start_pos().span(&rhs.span.end_pos()); let expr = ExpressionKind::Binary(op_kind, lhs, rhs); Ok(ExpressionNode::new(expr, span)) }) .parse(pair.into_inner())} /// Parses text into expression tree. No name resolution is made at this stage.pub fn parse_program(text: &str) -> FilesetParseResult<ExpressionNode<'_>> { let mut pairs = FilesetParser::parse(Rule::program, text)?; let first = pairs.next().unwrap(); parse_expression_node(first)} /// Parses text into expression tree with bare string fallback. No name/// resolution is made at this stage.////// If the text can't be parsed as a fileset expression, and if it doesn't/// contain any operator-like characters, it will be parsed as a file path.pub fn parse_program_or_bare_string(text: &str) -> FilesetParseResult<ExpressionNode<'_>> { let mut pairs = FilesetParser::parse(Rule::program_or_bare_string, text)?; let first = pairs.next().unwrap(); let span = first.as_span(); let expr = match first.as_rule() { Rule::expression => return parse_expression_node(first), Rule::bare_string_pattern => { let [lhs, op, rhs] = first.into_inner().collect_array().unwrap(); assert_eq!(lhs.as_rule(), Rule::strict_identifier); assert_eq!(op.as_rule(), Rule::pattern_kind_op); assert_eq!(rhs.as_rule(), Rule::bare_string); let name_span = lhs.as_span(); let value_span = rhs.as_span(); let name = lhs.as_str(); let value_expr = ExpressionKind::String(rhs.as_str().to_owned()); let pattern = Box::new(PatternNode { name, name_span, value: ExpressionNode::new(value_expr, value_span), }); ExpressionKind::Pattern(pattern) } Rule::bare_string => ExpressionKind::String(first.as_str().to_owned()), r => panic!("unexpected program or bare string rule: {r:?}"), }; Ok(ExpressionNode::new(expr, span))} /// Map of fileset aliases.pub type FilesetAliasesMap = AliasesMap<FilesetAliasParser, String>; #[derive(Clone, Debug, Default)]pub struct FilesetAliasParser; impl AliasDeclarationParser for FilesetAliasParser { type Error = FilesetParseError; fn parse_declaration(&self, source: &str) -> Result<AliasDeclaration, Self::Error> { let mut pairs = FilesetParser::parse(Rule::alias_declaration, source)?; let first = pairs.next().unwrap(); match first.as_rule() { Rule::strict_identifier => Ok(AliasDeclaration::Symbol(first.as_str().to_owned())), Rule::pattern_alias_declaration => { let [name_pair, op, param_pair] = first.into_inner().collect_array().unwrap(); assert_eq!(name_pair.as_rule(), Rule::strict_identifier); assert_eq!(op.as_rule(), Rule::pattern_kind_op); assert_eq!(param_pair.as_rule(), Rule::strict_identifier); let name = name_pair.as_str().to_owned(); let param = param_pair.as_str().to_owned(); Ok(AliasDeclaration::Pattern(name, param)) } Rule::function_alias_declaration => { let [name_pair, params_pair] = first.into_inner().collect_array().unwrap(); assert_eq!(name_pair.as_rule(), Rule::function_name); assert_eq!(params_pair.as_rule(), Rule::formal_parameters); let name = name_pair.as_str().to_owned(); let params_span = params_pair.as_span(); let params = params_pair .into_inner() .map(|pair| match pair.as_rule() { Rule::strict_identifier => pair.as_str().to_owned(), r => panic!("unexpected formal parameter rule {r:?}"), }) .collect_vec(); if params.iter().all_unique() { Ok(AliasDeclaration::Function(name, params)) } else { Err(FilesetParseError::new( FilesetParseErrorKind::RedefinedFunctionParameter, params_span, )) } } r => panic!("unexpected alias declaration rule {r:?}"), } }} Lines 1–500 of 1364. Load more
// Copyright 2024 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Parser for the fileset language. use std::error; use std::sync::LazyLock; use itertools::Itertools as _; use pest::Parser as _; use pest::iterators::Pair; use pest::pratt_parser::Assoc; use pest::pratt_parser::Op; use pest::pratt_parser::PrattParser; use pest_derive::Parser; use thiserror::Error; use crate::dsl_util; use crate::dsl_util::AliasDeclaration; use crate::dsl_util::AliasDeclarationParser; use crate::dsl_util::AliasDefinitionParser; use crate::dsl_util::AliasExpandError; use crate::dsl_util::AliasExpandableExpression; use crate::dsl_util::AliasId; use crate::dsl_util::AliasesMap; use crate::dsl_util::Diagnostics; use crate::dsl_util::ExpressionFolder; use crate::dsl_util::FoldableExpression; use crate::dsl_util::InvalidArguments; use crate::dsl_util::StringLiteralParser; #[derive(Parser)] #[grammar = "fileset.pest"] struct FilesetParser; const STRING_LITERAL_PARSER: StringLiteralParser<Rule> = StringLiteralParser { content_rule: Rule::string_content, escape_rule: Rule::string_escape, }; impl Rule { fn to_symbol(self) -> Option<&'static str> { match self { Self::EOI => None, Self::whitespace => None, Self::identifier => None, Self::strict_identifier_part => None, Self::strict_identifier => None, Self::bare_string => None, Self::string_escape => None, Self::string_content_char => None, Self::string_content => None, Self::string_literal => None, Self::raw_string_content => None, Self::raw_string_literal => None, Self::pattern_kind_op => Some(":"), Self::negate_op => Some("~"), Self::union_op => Some("|"), Self::intersection_op => Some("&"), Self::difference_op => Some("~"), Self::prefix_ops => None, Self::infix_ops => None, Self::function => None, Self::function_name => None, Self::function_arguments => None, Self::formal_parameters => None, Self::pattern => None, Self::bare_string_pattern => None, Self::primary => None, Self::expression => None, Self::program => None, Self::program_or_bare_string => None, Self::function_alias_declaration => None, Self::pattern_alias_declaration => None, Self::alias_declaration => None, } } } /// Manages diagnostic messages emitted during fileset parsing and name /// resolution. pub type FilesetDiagnostics = Diagnostics<FilesetParseError>; /// Result of fileset parsing and name resolution. pub type FilesetParseResult<T> = Result<T, FilesetParseError>; /// Error occurred during fileset parsing and name resolution. #[derive(Debug, Error)] #[error("{pest_error}")] pub struct FilesetParseError { kind: FilesetParseErrorKind, pest_error: Box<pest::error::Error<Rule>>, source: Option<Box<dyn error::Error + Send + Sync>>, } /// Categories of fileset parsing and name resolution error. #[expect(missing_docs)] #[derive(Clone, Debug, Eq, Error, PartialEq)] pub enum FilesetParseErrorKind { #[error("Syntax error")] SyntaxError, #[error("Function `{name}` doesn't exist")] NoSuchFunction { name: String, candidates: Vec<String>, }, #[error("Function `{name}`: {message}")] InvalidArguments { name: String, message: String }, #[error("Redefinition of function parameter")] RedefinedFunctionParameter, #[error("{0}")] Expression(String), #[error("In alias `{0}`")] InAliasExpansion(String), #[error("In function parameter `{0}`")] InParameterExpansion(String), #[error("Alias `{0}` expanded recursively")] RecursiveAlias(String), } impl FilesetParseError { pub(super) fn new(kind: FilesetParseErrorKind, span: pest::Span<'_>) -> Self { let message = kind.to_string(); let pest_error = Box::new(pest::error::Error::new_from_span( pest::error::ErrorVariant::CustomError { message }, span, )); Self { kind, pest_error, source: None, } } pub(super) fn with_source( mut self, source: impl Into<Box<dyn error::Error + Send + Sync>>, ) -> Self { self.source = Some(source.into()); self } /// Some other expression error. pub(super) fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self { Self::new(FilesetParseErrorKind::Expression(message.into()), span) } /// Category of the underlying error. pub fn kind(&self) -> &FilesetParseErrorKind { &self.kind } } impl AliasExpandError for FilesetParseError { fn invalid_arguments(err: InvalidArguments<'_>) -> Self { err.into() } fn recursive_expansion(id: AliasId<'_>, span: pest::Span<'_>) -> Self { Self::new(FilesetParseErrorKind::RecursiveAlias(id.to_string()), span) } fn within_alias_expansion(self, id: AliasId<'_>, span: pest::Span<'_>) -> Self { let kind = match id { AliasId::Symbol(_) | AliasId::Pattern(..) | AliasId::Function(..) => { FilesetParseErrorKind::InAliasExpansion(id.to_string()) } AliasId::Parameter(_) => FilesetParseErrorKind::InParameterExpansion(id.to_string()), }; Self::new(kind, span).with_source(self) } } impl From<pest::error::Error<Rule>> for FilesetParseError { fn from(err: pest::error::Error<Rule>) -> Self { Self { kind: FilesetParseErrorKind::SyntaxError, pest_error: Box::new(rename_rules_in_pest_error(err)), source: None, } } } impl From<InvalidArguments<'_>> for FilesetParseError { fn from(err: InvalidArguments<'_>) -> Self { let kind = FilesetParseErrorKind::InvalidArguments { name: err.name.to_owned(), message: err.message, }; Self::new(kind, err.span) } } fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Error<Rule> { err.renamed_rules(|rule| { rule.to_symbol() .map(|sym| format!("`{sym}`")) .unwrap_or_else(|| format!("<{rule:?}>")) }) } #[derive(Clone, Debug, Eq, PartialEq)] pub enum ExpressionKind<'i> { Identifier(&'i str), String(String), /// `<name>:<value>` where `<value>` is usually `Identifier` or `String`. Pattern(Box<PatternNode<'i>>), Unary(UnaryOp, Box<ExpressionNode<'i>>), Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>), /// `x | y | ..` UnionAll(Vec<ExpressionNode<'i>>), FunctionCall(Box<FunctionCallNode<'i>>), /// Identity node to preserve the span in the source text. AliasExpanded(AliasId<'i>, Box<ExpressionNode<'i>>), } impl<'i> FoldableExpression<'i> for ExpressionKind<'i> { fn fold<F>(self, folder: &mut F, span: pest::Span<'i>) -> Result<Self, F::Error> where F: ExpressionFolder<'i, Self> + ?Sized, { match self { Self::Identifier(name) => folder.fold_identifier(name, span), Self::String(_) => Ok(self), Self::Pattern(pattern) => folder.fold_pattern(pattern, span), Self::Unary(op, arg) => { let arg = Box::new(folder.fold_expression(*arg)?); Ok(Self::Unary(op, arg)) } Self::Binary(op, lhs, rhs) => { let lhs = Box::new(folder.fold_expression(*lhs)?); let rhs = Box::new(folder.fold_expression(*rhs)?); Ok(Self::Binary(op, lhs, rhs)) } Self::UnionAll(nodes) => { let nodes = dsl_util::fold_expression_nodes(folder, nodes)?; Ok(Self::UnionAll(nodes)) } Self::FunctionCall(function) => folder.fold_function_call(function, span), Self::AliasExpanded(id, subst) => { let subst = Box::new(folder.fold_expression(*subst)?); Ok(Self::AliasExpanded(id, subst)) } } } } impl<'i> AliasExpandableExpression<'i> for ExpressionKind<'i> { fn identifier(name: &'i str) -> Self { Self::Identifier(name) } fn pattern(pattern: Box<PatternNode<'i>>) -> Self { Self::Pattern(pattern) } fn function_call(function: Box<FunctionCallNode<'i>>) -> Self { Self::FunctionCall(function) } fn alias_expanded(id: AliasId<'i>, subst: Box<ExpressionNode<'i>>) -> Self { Self::AliasExpanded(id, subst) } } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum UnaryOp { /// `~` Negate, } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum BinaryOp { /// `&` Intersection, /// `~` Difference, } pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>; pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>; pub type PatternNode<'i> = dsl_util::PatternNode<'i, ExpressionKind<'i>>; fn union_nodes<'i>(lhs: ExpressionNode<'i>, rhs: ExpressionNode<'i>) -> ExpressionNode<'i> { let span = lhs.span.start_pos().span(&rhs.span.end_pos()); let expr = match lhs.kind { // Flatten "x | y | z" to save recursion stack. Machine-generated query // might have long chain of unions. ExpressionKind::UnionAll(mut nodes) => { nodes.push(rhs); ExpressionKind::UnionAll(nodes) } _ => ExpressionKind::UnionAll(vec![lhs, rhs]), }; ExpressionNode::new(expr, span) } fn parse_function_call_node(pair: Pair<Rule>) -> FilesetParseResult<FunctionCallNode> { assert_eq!(pair.as_rule(), Rule::function); let [name_pair, args_pair] = pair.into_inner().collect_array().unwrap(); assert_eq!(name_pair.as_rule(), Rule::function_name); assert_eq!(args_pair.as_rule(), Rule::function_arguments); let name_span = name_pair.as_span(); let args_span = args_pair.as_span(); let name = name_pair.as_str(); let args = args_pair .into_inner() .map(parse_expression_node) .try_collect()?; Ok(FunctionCallNode { name, name_span, args, keyword_args: vec![], // unsupported args_span, }) } fn parse_as_string_literal(pair: Pair<Rule>) -> String { match pair.as_rule() { Rule::identifier => pair.as_str().to_owned(), Rule::string_literal => STRING_LITERAL_PARSER.parse(pair.into_inner()), Rule::raw_string_literal => { let [content] = pair.into_inner().collect_array().unwrap(); assert_eq!(content.as_rule(), Rule::raw_string_content); content.as_str().to_owned() } r => panic!("unexpected string literal rule: {r:?}"), } } fn parse_primary_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> { assert_eq!(pair.as_rule(), Rule::primary); let span = pair.as_span(); let first = pair.into_inner().next().unwrap(); let expr = match first.as_rule() { // Ignore inner span to preserve parenthesized expression as such. Rule::expression => parse_expression_node(first)?.kind, Rule::function => { let function = Box::new(parse_function_call_node(first)?); ExpressionKind::FunctionCall(function) } Rule::pattern => { let [lhs, op, rhs] = first.into_inner().collect_array().unwrap(); assert_eq!(lhs.as_rule(), Rule::strict_identifier); assert_eq!(op.as_rule(), Rule::pattern_kind_op); let pattern = Box::new(PatternNode { name: lhs.as_str(), name_span: lhs.as_span(), value: parse_primary_node(rhs)?, }); ExpressionKind::Pattern(pattern) } Rule::identifier => ExpressionKind::Identifier(first.as_str()), Rule::string_literal | Rule::raw_string_literal => { ExpressionKind::String(parse_as_string_literal(first)) } r => panic!("unexpected primary rule: {r:?}"), }; Ok(ExpressionNode::new(expr, span)) } fn parse_expression_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> { assert_eq!(pair.as_rule(), Rule::expression); static PRATT: LazyLock<PrattParser<Rule>> = LazyLock::new(|| { PrattParser::new() .op(Op::infix(Rule::union_op, Assoc::Left)) .op(Op::infix(Rule::intersection_op, Assoc::Left) | Op::infix(Rule::difference_op, Assoc::Left)) .op(Op::prefix(Rule::negate_op)) }); PRATT .map_primary(parse_primary_node) .map_prefix(|op, rhs| { let op_kind = match op.as_rule() { Rule::negate_op => UnaryOp::Negate, r => panic!("unexpected prefix operator rule {r:?}"), }; let rhs = Box::new(rhs?); let span = op.as_span().start_pos().span(&rhs.span.end_pos()); let expr = ExpressionKind::Unary(op_kind, rhs); Ok(ExpressionNode::new(expr, span)) }) .map_infix(|lhs, op, rhs| { let op_kind = match op.as_rule() { Rule::union_op => return Ok(union_nodes(lhs?, rhs?)), Rule::intersection_op => BinaryOp::Intersection, Rule::difference_op => BinaryOp::Difference, r => panic!("unexpected infix operator rule {r:?}"), }; let lhs = Box::new(lhs?); let rhs = Box::new(rhs?); let span = lhs.span.start_pos().span(&rhs.span.end_pos()); let expr = ExpressionKind::Binary(op_kind, lhs, rhs); Ok(ExpressionNode::new(expr, span)) }) .parse(pair.into_inner()) } /// Parses text into expression tree. No name resolution is made at this stage. pub fn parse_program(text: &str) -> FilesetParseResult<ExpressionNode<'_>> { let mut pairs = FilesetParser::parse(Rule::program, text)?; let first = pairs.next().unwrap(); parse_expression_node(first) } /// Parses text into expression tree with bare string fallback. No name /// resolution is made at this stage. /// /// If the text can't be parsed as a fileset expression, and if it doesn't /// contain any operator-like characters, it will be parsed as a file path. pub fn parse_program_or_bare_string(text: &str) -> FilesetParseResult<ExpressionNode<'_>> { let mut pairs = FilesetParser::parse(Rule::program_or_bare_string, text)?; let first = pairs.next().unwrap(); let span = first.as_span(); let expr = match first.as_rule() { Rule::expression => return parse_expression_node(first), Rule::bare_string_pattern => { let [lhs, op, rhs] = first.into_inner().collect_array().unwrap(); assert_eq!(lhs.as_rule(), Rule::strict_identifier); assert_eq!(op.as_rule(), Rule::pattern_kind_op); assert_eq!(rhs.as_rule(), Rule::bare_string); let name_span = lhs.as_span(); let value_span = rhs.as_span(); let name = lhs.as_str(); let value_expr = ExpressionKind::String(rhs.as_str().to_owned()); let pattern = Box::new(PatternNode { name, name_span, value: ExpressionNode::new(value_expr, value_span), }); ExpressionKind::Pattern(pattern) } Rule::bare_string => ExpressionKind::String(first.as_str().to_owned()), r => panic!("unexpected program or bare string rule: {r:?}"), }; Ok(ExpressionNode::new(expr, span)) } /// Map of fileset aliases. pub type FilesetAliasesMap = AliasesMap<FilesetAliasParser, String>; #[derive(Clone, Debug, Default)] pub struct FilesetAliasParser; impl AliasDeclarationParser for FilesetAliasParser { type Error = FilesetParseError; fn parse_declaration(&self, source: &str) -> Result<AliasDeclaration, Self::Error> { let mut pairs = FilesetParser::parse(Rule::alias_declaration, source)?; let first = pairs.next().unwrap(); match first.as_rule() { Rule::strict_identifier => Ok(AliasDeclaration::Symbol(first.as_str().to_owned())), Rule::pattern_alias_declaration => { let [name_pair, op, param_pair] = first.into_inner().collect_array().unwrap(); assert_eq!(name_pair.as_rule(), Rule::strict_identifier); assert_eq!(op.as_rule(), Rule::pattern_kind_op); assert_eq!(param_pair.as_rule(), Rule::strict_identifier); let name = name_pair.as_str().to_owned(); let param = param_pair.as_str().to_owned(); Ok(AliasDeclaration::Pattern(name, param)) } Rule::function_alias_declaration => { let [name_pair, params_pair] = first.into_inner().collect_array().unwrap(); assert_eq!(name_pair.as_rule(), Rule::function_name); assert_eq!(params_pair.as_rule(), Rule::formal_parameters); let name = name_pair.as_str().to_owned(); let params_span = params_pair.as_span(); let params = params_pair .into_inner() .map(|pair| match pair.as_rule() { Rule::strict_identifier => pair.as_str().to_owned(), r => panic!("unexpected formal parameter rule {r:?}"), }) .collect_vec(); if params.iter().all_unique() { Ok(AliasDeclaration::Function(name, params)) } else { Err(FilesetParseError::new( FilesetParseErrorKind::RedefinedFunctionParameter, params_span, )) } } r => panic!("unexpected alias declaration rule {r:?}"), } } }