Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions lib/src/fileset_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ 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 self::private::FilesetParser;
use self::private::Rule;
use crate::dsl_util;
use crate::dsl_util::AliasDeclaration;
use crate::dsl_util::AliasDeclarationParser;
Expand All @@ -40,9 +41,14 @@ use crate::dsl_util::FoldableExpression;
use crate::dsl_util::InvalidArguments;
use crate::dsl_util::StringLiteralParser;

#[derive(Parser)]
#[grammar = "fileset.pest"]
struct FilesetParser;
mod private {
use pest_derive::Parser;

// This generates a `pub enum Rule` type.
#[derive(Parser)]
#[grammar = "fileset.pest"]
pub struct FilesetParser;
}

const STRING_LITERAL_PARSER: StringLiteralParser<Rule> = StringLiteralParser {
content_rule: Rule::string_content,
Expand Down Expand Up @@ -130,6 +136,7 @@ pub enum FilesetParseErrorKind {
}

impl FilesetParseError {
/// Creates a new error with the given `kind` and `span`.
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(
Expand All @@ -143,6 +150,7 @@ impl FilesetParseError {
}
}

/// Attaches the `source` error.
pub(super) fn with_source(
mut self,
source: impl Into<Box<dyn error::Error + Send + Sync>>,
Expand Down Expand Up @@ -210,16 +218,22 @@ fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Err
})
}

/// AST expression item.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExpressionKind<'i> {
/// Unquoted symbol.
Identifier(&'i str),
/// Quoted symbol or string.
String(String),
/// `<name>:<value>` where `<value>` is usually `Identifier` or `String`.
Pattern(Box<PatternNode<'i>>),
/// `<op> <arg>` or `<arg> <op>`.
Unary(UnaryOp, Box<ExpressionNode<'i>>),
/// `<lhs> <op> <rhs>`.
Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>),
/// `x | y | ..`
UnionAll(Vec<ExpressionNode<'i>>),
/// `<name>(<args>..)`
FunctionCall(Box<FunctionCallNode<'i>>),
/// Identity node to preserve the span in the source text.
AliasExpanded(AliasId<'i>, Box<ExpressionNode<'i>>),
Expand Down Expand Up @@ -274,12 +288,14 @@ impl<'i> AliasExpandableExpression<'i> for ExpressionKind<'i> {
}
}

/// Unary operator.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum UnaryOp {
/// `~`
Negate,
}

/// Binary operator.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum BinaryOp {
/// `&`
Expand All @@ -288,8 +304,11 @@ pub enum BinaryOp {
Difference,
}

/// AST node without type or name checking.
pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
/// Function call in AST.
pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;
/// `<name>:<value>` expression in AST.
pub type PatternNode<'i> = dsl_util::PatternNode<'i, ExpressionKind<'i>>;

fn union_nodes<'i>(lhs: ExpressionNode<'i>, rhs: ExpressionNode<'i>) -> ExpressionNode<'i> {
Expand Down Expand Up @@ -451,6 +470,7 @@ pub fn parse_program_or_bare_string(text: &str) -> FilesetParseResult<Expression
/// Map of fileset aliases.
pub type FilesetAliasesMap = AliasesMap<FilesetAliasParser, String>;

/// Parser for the fileset symbol and function alias declarations.
#[derive(Clone, Debug, Default)]
pub struct FilesetAliasParser;

Expand Down Expand Up @@ -507,13 +527,15 @@ impl AliasDefinitionParser for FilesetAliasParser {
}
}

/// Expands aliases recursively.
pub fn expand_aliases<'i>(
node: ExpressionNode<'i>,
aliases_map: &'i FilesetAliasesMap,
) -> FilesetParseResult<ExpressionNode<'i>> {
dsl_util::expand_aliases(node, aliases_map)
}

/// Unwraps the inner value if the given `node` is an identifier or string.
pub(super) fn expect_string_literal<'a>(
type_name: &str,
node: &'a ExpressionNode<'_>,
Expand Down
59 changes: 2 additions & 57 deletions lib/src/revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ pub use crate::revset_parser::RevsetParseError;
pub use crate::revset_parser::RevsetParseErrorKind;
pub use crate::revset_parser::UnaryOp;
pub use crate::revset_parser::expect_literal;
pub use crate::revset_parser::format_remote_symbol;
pub use crate::revset_parser::format_symbol;
pub use crate::revset_parser::parse_program;
pub use crate::revset_parser::parse_symbol;
use crate::store::Store;
use crate::str_util::StringExpression;
use crate::str_util::StringPattern;
use crate::symbol_util::format_string;
use crate::time_util::DatePattern;
use crate::time_util::DatePatternContext;
use crate::ui_path::RepoPathUiConverter;
Expand Down Expand Up @@ -3602,26 +3603,6 @@ pub struct RevsetWorkspaceContext<'a> {
pub workspace_name: &'a WorkspaceName,
}

/// Formats a string as symbol by quoting and escaping it if necessary.
///
/// Note that symbols may be substituted to user aliases. Use
/// [`format_string()`] to ensure that the provided string is resolved as a
/// tag/bookmark name, commit/change ID prefix, etc.
pub fn format_symbol(literal: &str) -> String {
if revset_parser::is_identifier(literal) {
literal.to_string()
} else {
format_string(literal)
}
}

/// Formats a `name@remote` symbol, applies quoting and escaping if necessary.
pub fn format_remote_symbol(name: &str, remote: &str) -> String {
let name = format_symbol(name);
let remote = format_symbol(remote);
format!("{name}@{remote}")
}

#[cfg(test)]
#[rustversion::attr(
since(1.89),
Expand Down Expand Up @@ -6463,40 +6444,4 @@ mod tests {
"#);
Ok(())
}

#[test]
fn test_escape_string_literal() {
// Valid identifiers don't need quoting
assert_eq!(format_symbol("foo"), "foo");
assert_eq!(format_symbol("foo.bar"), "foo.bar");

// Invalid identifiers need quoting
assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
assert_eq!(format_symbol(" foo "), r#"" foo ""#);
assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
assert_eq!(format_symbol("all:foo"), r#""all:foo""#);

// Some characters also need escaping
assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);

// Some characters don't technically need escaping, but we escape them for
// clarity
assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
}

#[test]
fn test_escape_remote_symbol() {
assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
assert_eq!(
format_remote_symbol(" foo ", "bar:baz"),
r#"" foo "@"bar:baz""#
);
}
}
Loading