diff --git a/CHANGELOG.md b/CHANGELOG.md index 8931d7344..34fd55628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed bugs +* Revsets now support `\`-escapes in string literal. + + ## [0.16.0] - 2024-04-03 ### Deprecations diff --git a/lib/src/revset.pest b/lib/src/revset.pest index 2fe371aac..b929eeba2 100644 --- a/lib/src/revset.pest +++ b/lib/src/revset.pest @@ -21,7 +21,12 @@ symbol = { identifier | string_literal } -string_literal = { "\"" ~ (!"\"" ~ ANY)* ~ "\"" } + +string_escape = @{ "\\" ~ ("t" | "r" | "n" | "0" | "\"" | "\\") } +string_content_char = @{ !("\"" | "\\") ~ ANY } +string_content = @{ string_content_char+ } +string_literal = ${ "\"" ~ (string_content | string_escape)* ~ "\"" } + whitespace = _{ " " | "\t" | "\r" | "\n" | "\x0c" } at_op = { "@" } diff --git a/lib/src/revset.rs b/lib/src/revset.rs index 77583ebed..500661e67 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -33,6 +33,7 @@ use thiserror::Error; use crate::backend::{BackendError, BackendResult, ChangeId, CommitId}; use crate::commit::Commit; +use crate::dsl_util::StringLiteralParser; use crate::fileset::{FilePattern, FilesetExpression, FilesetParseContext}; use crate::git; use crate::hex_util::to_forward_hex; @@ -76,6 +77,11 @@ pub enum RevsetEvaluationError { #[grammar = "revset.pest"] pub struct RevsetParser; +const STRING_LITERAL_PARSER: StringLiteralParser = StringLiteralParser { + content_rule: Rule::string_content, + escape_rule: Rule::string_escape, +}; + impl Rule { /// Whether this is a placeholder rule for compatibility with the other /// systems. @@ -97,6 +103,9 @@ impl Rule { Rule::identifier_part => None, Rule::identifier => None, Rule::symbol => None, + Rule::string_escape => None, + Rule::string_content_char => None, + Rule::string_content => None, Rule::string_literal => None, Rule::whitespace => None, Rule::at_op => Some("@"), @@ -1090,7 +1099,9 @@ fn parse_symbol_rule( Ok(RevsetExpression::symbol(name.to_owned())) } } - Rule::string_literal => parse_string_literal(first).map(RevsetExpression::symbol), + Rule::string_literal => Ok(RevsetExpression::symbol( + STRING_LITERAL_PARSER.parse(first.into_inner()), + )), _ => { panic!("unexpected symbol parse rule: {:?}", first.as_str()); } @@ -1102,25 +1113,13 @@ fn parse_symbol_rule_as_literal(mut pairs: Pairs) -> Result Ok(first.as_str().to_owned()), - Rule::string_literal => parse_string_literal(first), + Rule::string_literal => Ok(STRING_LITERAL_PARSER.parse(first.into_inner())), _ => { panic!("unexpected symbol parse rule: {:?}", first.as_str()); } } } -// TODO: Add support for \-escape syntax -fn parse_string_literal(pair: Pair) -> Result { - assert_eq!(pair.as_rule(), Rule::string_literal); - Ok(pair - .as_str() - .strip_prefix('"') - .unwrap() - .strip_suffix('"') - .unwrap() - .to_owned()) -} - fn parse_function_expression( name_pair: Pair, arguments_pair: Pair, @@ -3084,6 +3083,23 @@ mod tests { ); } + #[test] + fn test_parse_string_literal() { + // "\" escapes + assert_eq!( + parse(r#"branches("\t\r\n\"\\\0")"#), + Ok(RevsetExpression::branches(StringPattern::Substring( + "\t\r\n\"\\\0".to_owned() + ))) + ); + + // Invalid "\" escape + assert_eq!( + parse(r#"branches("\y")"#), + Err(RevsetParseErrorKind::SyntaxError) + ); + } + #[test] fn test_parse_string_pattern() { assert_eq!(