diff --git a/src/analyzer.rs b/src/analyzer.rs index e74615a6c8..3e5f3538b1 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -16,7 +16,7 @@ impl<'run, 'src> Analyzer<'run, 'src> { asts: &'run HashMap>, config: &Config, doc: Option, - groups: &[StringLiteral], + groups: &[StringLiteral<'src>], loaded: &[PathBuf], name: Option>, paths: &HashMap, @@ -30,7 +30,7 @@ impl<'run, 'src> Analyzer<'run, 'src> { asts: &'run HashMap>, config: &Config, doc: Option, - groups: &[StringLiteral], + groups: &[StringLiteral<'src>], loaded: &[PathBuf], name: Option>, paths: &HashMap, diff --git a/src/arg_attribute.rs b/src/arg_attribute.rs index 3c96d88169..942e67e1c0 100644 --- a/src/arg_attribute.rs +++ b/src/arg_attribute.rs @@ -4,7 +4,7 @@ pub(crate) struct ArgAttribute<'src> { pub(crate) help: Option, pub(crate) long: Option, pub(crate) name: Token<'src>, - pub(crate) pattern: Option, + pub(crate) pattern: Option>, pub(crate) short: Option, pub(crate) value: Option, } diff --git a/src/attribute.rs b/src/attribute.rs index 7fab5c2912..567f84ae24 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -11,31 +11,22 @@ use super::*; #[strum_discriminants(strum(serialize_all = "kebab-case"))] pub(crate) enum Attribute<'src> { Arg { - help: Option, - long: Option, - #[serde(skip)] - long_token: Option>, - name: StringLiteral, - #[serde(skip)] - name_token: Token<'src>, - #[serde(skip)] - pattern: Option, - #[serde(rename = "pattern")] - pattern_literal: Option, - short: Option, - #[serde(skip)] - short_token: Option>, - value: Option, + help: Option>, + long: Option>, + name: StringLiteral<'src>, + pattern: Option>, + short: Option>, + value: Option>, }, - Confirm(Option), + Confirm(Option>), Default, - Doc(Option), + Doc(Option>), ExitMessage, - Extension(StringLiteral), - Group(StringLiteral), + Extension(StringLiteral<'src>), + Group(StringLiteral<'src>), Linux, Macos, - Metadata(Vec), + Metadata(Vec>), NoCd, NoExitMessage, NoQuiet, @@ -43,10 +34,10 @@ pub(crate) enum Attribute<'src> { Parallel, PositionalArguments, Private, - Script(Option>), + Script(Option>>), Unix, Windows, - WorkingDirectory(StringLiteral), + WorkingDirectory(StringLiteral<'src>), } impl AttributeDiscriminant { @@ -74,16 +65,38 @@ impl AttributeDiscriminant { } impl<'src> Attribute<'src> { + fn check_option_name( + parameter: &StringLiteral<'src>, + literal: &StringLiteral<'src>, + ) -> CompileResult<'src> { + if literal.cooked.contains('=') { + return Err( + literal + .token + .error(CompileErrorKind::OptionNameContainsEqualSign { + parameter: parameter.cooked.clone(), + }), + ); + } + + if literal.cooked.is_empty() { + return Err(literal.token.error(CompileErrorKind::OptionNameEmpty { + parameter: parameter.cooked.clone(), + })); + } + + Ok(()) + } + pub(crate) fn new( name: Name<'src>, - arguments: Vec<(Token<'src>, StringLiteral)>, - mut keyword_arguments: BTreeMap<&'src str, (Name<'src>, Token<'src>, StringLiteral)>, + arguments: Vec>, + mut keyword_arguments: BTreeMap<&'src str, (Name<'src>, StringLiteral<'src>)>, ) -> CompileResult<'src, Self> { let discriminant = name .lexeme() .parse::() - .ok() - .ok_or_else(|| { + .map_err(|_| { name.error(CompileErrorKind::UnknownAttribute { attribute: name.lexeme(), }) @@ -94,7 +107,7 @@ impl<'src> Attribute<'src> { if !range.contains(&found) { return Err( name.error(CompileErrorKind::AttributeArgumentCountMismatch { - attribute: name.lexeme(), + attribute: name, found, min: *range.start(), max: *range.end(), @@ -102,91 +115,60 @@ impl<'src> Attribute<'src> { ); } - let (tokens, arguments): (Vec, Vec) = arguments.into_iter().unzip(); - let attribute = match discriminant { AttributeDiscriminant::Arg => { let name = arguments.into_iter().next().unwrap(); - let name_token = tokens.into_iter().next().unwrap(); - - let (long_token, long) = - if let Some((_name, token, literal)) = keyword_arguments.remove("long") { - if literal.cooked.contains('=') { - return Err(token.error(CompileErrorKind::OptionNameContainsEqualSign { - parameter: name.cooked, - })); - } - - if literal.cooked.is_empty() { - return Err(token.error(CompileErrorKind::OptionNameEmpty { - parameter: name.cooked, - })); - } - (Some(token), Some(literal)) - } else { - (None, None) - }; - - let (short_token, short) = - if let Some((_name, token, literal)) = keyword_arguments.remove("short") { - if literal.cooked.contains('=') { - return Err(token.error(CompileErrorKind::OptionNameContainsEqualSign { - parameter: name.cooked, - })); - } + let long = keyword_arguments + .remove("long") + .map(|(_name, literal)| { + Self::check_option_name(&name, &literal)?; + Ok(literal) + }) + .transpose()?; - if literal.cooked.is_empty() { - return Err(token.error(CompileErrorKind::OptionNameEmpty { - parameter: name.cooked, - })); - } + let short = keyword_arguments + .remove("short") + .map(|(_name, literal)| { + Self::check_option_name(&name, &literal)?; if literal.cooked.chars().count() != 1 { - return Err( - token.error(CompileErrorKind::ShortOptionWithMultipleCharacters { - parameter: name.cooked, - }), - ); + return Err(literal.token.error( + CompileErrorKind::ShortOptionWithMultipleCharacters { + parameter: name.cooked.clone(), + }, + )); } - (Some(token), Some(literal)) - } else { - (None, None) - }; + Ok(literal) + }) + .transpose()?; - let (pattern_literal, pattern) = keyword_arguments + let pattern = keyword_arguments .remove("pattern") - .map(|(_name, token, literal)| { - let pattern = Pattern::new(token, &literal)?; - Ok((Some(literal), Some(pattern))) + .map(|(_name, literal)| Pattern::new(&literal)) + .transpose()?; + + let value = keyword_arguments + .remove("value") + .map(|(name, literal)| { + if long.is_none() && short.is_none() { + return Err(name.error(CompileErrorKind::ArgAttributeValueRequiresOption)); + } + Ok(literal) }) - .transpose()? - .unwrap_or((None, None)); - - let value = if let Some((name, _token, literal)) = keyword_arguments.remove("value") { - if long.is_none() && short.is_none() { - return Err(name.error(CompileErrorKind::ArgAttributeValueRequiresOption)); - } - Some(literal) - } else { - None - }; + .transpose()?; let help = keyword_arguments .remove("help") - .map(|(_name, _token, literal)| literal); + .map(|(_name, literal)| literal); Self::Arg { help, long, - long_token, name, - name_token, pattern, - pattern_literal, short, - short_token, value, } } @@ -220,7 +202,7 @@ impl<'src> Attribute<'src> { } }; - if let Some((_name, (keyword_name, _token, _literal))) = keyword_arguments.into_iter().next() { + if let Some((_name, (keyword_name, _literal))) = keyword_arguments.into_iter().next() { return Err( keyword_name.error(CompileErrorKind::UnknownAttributeKeyword { attribute: name.lexeme(), @@ -256,13 +238,9 @@ impl Display for Attribute<'_> { Self::Arg { help, long, - long_token: _, name, - name_token: _, - pattern: _, - pattern_literal, + pattern, short, - short_token: _, value, } => { write!(f, "({name}")?; @@ -275,8 +253,8 @@ impl Display for Attribute<'_> { write!(f, ", short={short}")?; } - if let Some(pattern) = pattern_literal { - write!(f, ", pattern={pattern}")?; + if let Some(pattern) = pattern { + write!(f, ", pattern={}", pattern.token.lexeme())?; } if let Some(value) = value { diff --git a/src/compile_error_kind.rs b/src/compile_error_kind.rs index c819ac9cd5..4f85e9f344 100644 --- a/src/compile_error_kind.rs +++ b/src/compile_error_kind.rs @@ -7,7 +7,7 @@ pub(crate) enum CompileErrorKind<'src> { source: regex::Error, }, AttributeArgumentCountMismatch { - attribute: &'src str, + attribute: Name<'src>, found: usize, min: usize, max: usize, diff --git a/src/compiler.rs b/src/compiler.rs index 4d8779adba..2953702083 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -71,7 +71,6 @@ impl Compiler { relative, absolute, optional, - path, } => { let import = current .path @@ -88,9 +87,11 @@ impl Compiler { }); } *absolute = Some(import.clone()); - stack.push(current.import(import, path.offset)); + stack.push(current.import(import, relative.token.offset)); } else if !*optional { - return Err(Error::MissingImportFile { path: *path }); + return Err(Error::MissingImportFile { + path: relative.token, + }); } } _ => {} diff --git a/src/error.rs b/src/error.rs index b1cc21dd99..ffee01a35a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,7 +9,7 @@ pub(crate) enum Error<'src> { ArgumentPatternMismatch { argument: String, parameter: &'src str, - pattern: Pattern, + pattern: Box>, recipe: &'src str, }, Assert { @@ -363,7 +363,8 @@ impl ColorDisplay for Error<'_> { } => { write!( f, - "Argument `{argument}` passed to recipe `{recipe}` parameter `{parameter}` does not match pattern '{pattern}'", + "Argument `{argument}` passed to recipe `{recipe}` parameter `{parameter}` does not match pattern '{}'", + pattern.original(), )?; } Assert { message, .. } => { diff --git a/src/expression.rs b/src/expression.rs index 3db3202d35..ab4d21f275 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -39,8 +39,8 @@ pub(crate) enum Expression<'src> { }, // `f"format string"` FormatString { - start: StringLiteral, - expressions: Vec<(Expression<'src>, StringLiteral)>, + start: StringLiteral<'src>, + expressions: Vec<(Expression<'src>, StringLiteral<'src>)>, }, /// `(contents)` Group { contents: Box> }, @@ -55,7 +55,7 @@ pub(crate) enum Expression<'src> { rhs: Box>, }, /// `"string_literal"` or `'string_literal'` - StringLiteral { string_literal: StringLiteral }, + StringLiteral { string_literal: StringLiteral<'src> }, /// `variable` Variable { name: Name<'src> }, } diff --git a/src/item.rs b/src/item.rs index e6d8b665cd..fa82005b5a 100644 --- a/src/item.rs +++ b/src/item.rs @@ -9,16 +9,15 @@ pub(crate) enum Item<'src> { Import { absolute: Option, optional: bool, - path: Token<'src>, - relative: StringLiteral, + relative: StringLiteral<'src>, }, Module { absolute: Option, doc: Option, - groups: Vec, + groups: Vec>, name: Name<'src>, optional: bool, - relative: Option, + relative: Option>, }, Recipe(UnresolvedRecipe<'src>), Set(Set<'src>), diff --git a/src/justfile.rs b/src/justfile.rs index 769ea4dba8..95d2376513 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -7,7 +7,7 @@ pub(crate) struct Justfile<'src> { #[serde(rename = "first", serialize_with = "keyed::serialize_option")] pub(crate) default: Option>>, pub(crate) doc: Option, - pub(crate) groups: Vec, + pub(crate) groups: Vec>, #[serde(skip)] pub(crate) loaded: Vec, #[serde(skip)] diff --git a/src/parameter.rs b/src/parameter.rs index a994df4af4..6b250fcb39 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -8,7 +8,7 @@ pub(crate) struct Parameter<'src> { pub(crate) kind: ParameterKind, pub(crate) long: Option, pub(crate) name: Name<'src>, - pub(crate) pattern: Option, + pub(crate) pattern: Option>, pub(crate) short: Option, pub(crate) value: Option, } @@ -38,7 +38,7 @@ impl<'src> Parameter<'src> { Err(Error::ArgumentPatternMismatch { argument: value.into(), parameter: self.name.lexeme(), - pattern: pattern.clone(), + pattern: Box::new(pattern.clone()), recipe: recipe.name(), }) } diff --git a/src/parser.rs b/src/parser.rs index c1f2d3e513..a30a80b165 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -378,11 +378,10 @@ impl<'run, 'src> Parser<'run, 'src> { { self.presume_keyword(Keyword::Import)?; let optional = self.accepted(QuestionMark)?; - let (path, relative) = self.parse_string_literal_token()?; + let relative = self.parse_string_literal()?; items.push(Item::Import { absolute: None, optional, - path, relative, }); } @@ -668,11 +667,11 @@ impl<'run, 'src> Parser<'run, 'src> { fn parse_format_string(&mut self) -> CompileResult<'src, Expression<'src>> { self.expect_keyword(Keyword::F)?; - let (token, start) = self.parse_string_literal_token_in_state(StringState::FormatStart)?; + let start = self.parse_string_literal_in_state(StringState::FormatStart)?; - let kind = StringKind::from_string_or_backtick(token)?; + let kind = StringKind::from_string_or_backtick(start.token)?; - let mut more = token.kind == FormatStringStart; + let mut more = start.token.kind == FormatStringStart; let mut expressions = Vec::new(); @@ -789,16 +788,16 @@ impl<'run, 'src> Parser<'run, 'src> { } } - /// Parse a string literal, e.g. `"FOO"`, returning the string literal and the string token - fn parse_string_literal_token(&mut self) -> CompileResult<'src, (Token<'src>, StringLiteral)> { - self.parse_string_literal_token_in_state(StringState::Normal) + /// Parse a string literal, e.g. `"FOO"` + fn parse_string_literal(&mut self) -> CompileResult<'src, StringLiteral<'src>> { + self.parse_string_literal_in_state(StringState::Normal) } - /// Parse a string literal, e.g. `"FOO"`, returning the string literal and the string token - fn parse_string_literal_token_in_state( + /// Parse a string literal, e.g. `"FOO"` + fn parse_string_literal_in_state( &mut self, state: StringState, - ) -> CompileResult<'src, (Token<'src>, StringLiteral)> { + ) -> CompileResult<'src, StringLiteral<'src>> { let expand = if self.next_is(Identifier) { self.expect_keyword(Keyword::X)?; true @@ -859,32 +858,29 @@ impl<'run, 'src> Parser<'run, 'src> { cooked }; - Ok(( + Ok(StringLiteral { token, - StringLiteral { - cooked, - expand, - kind, - part: match token.kind { - FormatStringStart => Some(FormatStringPart::Start), - FormatStringContinue => Some(FormatStringPart::Continue), - FormatStringEnd => Some(FormatStringPart::End), - StringToken => { - if matches!(state, StringState::Normal) { - None - } else { - Some(FormatStringPart::Single) - } - } - _ => { - return Err(token.error(CompileErrorKind::Internal { - message: "unexpected token kind while parsing string literal".into(), - })); + cooked, + expand, + kind, + part: match token.kind { + FormatStringStart => Some(FormatStringPart::Start), + FormatStringContinue => Some(FormatStringPart::Continue), + FormatStringEnd => Some(FormatStringPart::End), + StringToken => { + if matches!(state, StringState::Normal) { + None + } else { + Some(FormatStringPart::Single) } - }, - raw: raw.into(), + } + _ => { + return Err(token.error(CompileErrorKind::Internal { + message: "unexpected token kind while parsing string literal".into(), + })); + } }, - )) + }) } // Transform escape sequences in from string literal `token` with content `text` @@ -969,21 +965,6 @@ impl<'run, 'src> Parser<'run, 'src> { Ok(cooked) } - /// Parse a string literal, e.g. `"FOO"` - fn parse_string_literal(&mut self) -> CompileResult<'src, StringLiteral> { - let (_token, string_literal) = self.parse_string_literal_token()?; - Ok(string_literal) - } - - // /// Parse a format string literal, e.g. `"foo{"`, `}bar{`, or `}baz"` - fn parse_string_literal_in_state( - &mut self, - string_state: StringState, - ) -> CompileResult<'src, StringLiteral> { - let (_token, string_literal) = self.parse_string_literal_token_in_state(string_state)?; - Ok(string_literal) - } - /// Parse a name from an identifier token fn parse_name(&mut self) -> CompileResult<'src, Name<'src>> { self.expect(Identifier).map(Name::from_identifier) @@ -1041,12 +1022,9 @@ impl<'run, 'src> Parser<'run, 'src> { let Attribute::Arg { help, long, - long_token, name: arg, - name_token, pattern, short, - short_token, value, .. } = attribute @@ -1056,27 +1034,19 @@ impl<'run, 'src> Parser<'run, 'src> { if let Some(option) = long { if !longs.insert(&option.cooked) { - return Err( - long_token - .unwrap() - .error(CompileErrorKind::DuplicateOption { - option: Switch::Long(option.cooked.clone()), - recipe: name.lexeme(), - }), - ); + return Err(option.token.error(CompileErrorKind::DuplicateOption { + option: Switch::Long(option.cooked.clone()), + recipe: name.lexeme(), + })); } } if let Some(option) = short { if !shorts.insert(&option.cooked) { - return Err( - short_token - .unwrap() - .error(CompileErrorKind::DuplicateOption { - option: Switch::Short(option.cooked.chars().next().unwrap()), - recipe: name.lexeme(), - }), - ); + return Err(option.token.error(CompileErrorKind::DuplicateOption { + option: Switch::Short(option.cooked.chars().next().unwrap()), + recipe: name.lexeme(), + })); } } @@ -1084,7 +1054,7 @@ impl<'run, 'src> Parser<'run, 'src> { arg.cooked.clone(), ArgAttribute { help: help.as_ref().map(|literal| literal.cooked.clone()), - name: *name_token, + name: arg.token, pattern: pattern.clone(), long: long.as_ref().map(|long| long.cooked.clone()), short: short @@ -1207,7 +1177,7 @@ impl<'run, 'src> Parser<'run, 'src> { /// Parse a recipe parameter fn parse_parameter( &mut self, - arg_attributes: &mut BTreeMap, + arg_attributes: &mut BTreeMap>, kind: ParameterKind, ) -> CompileResult<'src, Parameter<'src>> { let export = self.accepted(Dollar)?; @@ -1411,7 +1381,7 @@ impl<'run, 'src> Parser<'run, 'src> { let mut keyword_arguments = BTreeMap::new(); if self.accepted(Colon)? { - arguments.push(self.parse_string_literal_token()?); + arguments.push(self.parse_string_literal()?); } else if self.accepted(ParenL)? { loop { if self.next_is(Identifier) && !self.next_is_shell_expanded_string() { @@ -1419,17 +1389,21 @@ impl<'run, 'src> Parser<'run, 'src> { self.expect(Equals)?; - let (token, value) = self.parse_string_literal_token()?; + let value = self.parse_string_literal()?; - keyword_arguments.insert(name.lexeme(), (name, token, value)); + keyword_arguments.insert(name.lexeme(), (name, value)); } else { - let (token, literal) = self.parse_string_literal_token()?; + let literal = self.parse_string_literal()?; if !keyword_arguments.is_empty() { - return Err(token.error(CompileErrorKind::AttributePositionalFollowsKeyword)); + return Err( + literal + .token + .error(CompileErrorKind::AttributePositionalFollowsKeyword), + ); } - arguments.push((token, literal)); + arguments.push(literal); } if !self.accepted(Comma)? || self.next_is(ParenR) { diff --git a/src/pattern.rs b/src/pattern.rs index 00d13cdc49..d210210526 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -1,61 +1,61 @@ use super::*; #[derive(Debug, Clone)] -pub(crate) struct Pattern(pub(crate) Regex); +pub(crate) struct Pattern<'src> { + pub(crate) regex: Regex, + pub(crate) token: Token<'src>, +} -impl Pattern { +impl<'src> Pattern<'src> { pub(crate) fn is_match(&self, haystack: &str) -> bool { - self.0.is_match(haystack) + self.regex.is_match(haystack) } - pub(crate) fn new<'src>( - token: Token<'src>, - literal: &StringLiteral, - ) -> Result> { - literal - .cooked - .parse::() - .map_err(|source| token.error(CompileErrorKind::ArgumentPatternRegex { source }))?; + pub(crate) fn new(literal: &StringLiteral<'src>) -> Result> { + literal.cooked.parse::().map_err(|source| { + literal + .token + .error(CompileErrorKind::ArgumentPatternRegex { source }) + })?; - Ok(Self( - format!("^(?:{})$", literal.cooked) + Ok(Self { + regex: format!("^(?:{})$", literal.cooked) .parse::() - .map_err(|source| token.error(CompileErrorKind::ArgumentPatternRegex { source }))?, - )) - } - - fn original(&self) -> &str { - &self.0.as_str()[4..self.0.as_str().len() - 2] + .map_err(|source| { + literal + .token + .error(CompileErrorKind::ArgumentPatternRegex { source }) + })?, + token: literal.token, + }) } -} -impl Display for Pattern { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.original()) + pub(crate) fn original(&self) -> &str { + &self.regex.as_str()[4..self.regex.as_str().len() - 2] } } -impl Eq for Pattern {} +impl Eq for Pattern<'_> {} -impl Ord for Pattern { +impl Ord for Pattern<'_> { fn cmp(&self, other: &pattern::Pattern) -> Ordering { - self.0.as_str().cmp(other.0.as_str()) + self.regex.as_str().cmp(other.regex.as_str()) } } -impl PartialEq for Pattern { +impl PartialEq for Pattern<'_> { fn eq(&self, other: &pattern::Pattern) -> bool { - self.0.as_str() == other.0.as_str() + self.regex.as_str() == other.regex.as_str() } } -impl PartialOrd for Pattern { +impl PartialOrd for Pattern<'_> { fn partial_cmp(&self, other: &pattern::Pattern) -> Option { Some(self.cmp(other)) } } -impl Serialize for Pattern { +impl Serialize for Pattern<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/src/string_literal.rs b/src/string_literal.rs index 21d8b541fa..d28e913617 100644 --- a/src/string_literal.rs +++ b/src/string_literal.rs @@ -1,15 +1,15 @@ use super::*; #[derive(PartialEq, Debug, Clone, Ord, Eq, PartialOrd)] -pub(crate) struct StringLiteral { +pub(crate) struct StringLiteral<'src> { pub(crate) cooked: String, pub(crate) expand: bool, pub(crate) kind: StringKind, pub(crate) part: Option, - pub(crate) raw: String, + pub(crate) token: Token<'src>, } -impl Display for StringLiteral { +impl Display for StringLiteral<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if self.expand { write!(f, "x")?; @@ -19,29 +19,11 @@ impl Display for StringLiteral { write!(f, "f")?; } - let open = if matches!( - self.part, - Some(FormatStringPart::Continue | FormatStringPart::End) - ) { - Lexer::INTERPOLATION_END - } else { - self.kind.delimiter() - }; - - let close = if matches!( - self.part, - Some(FormatStringPart::Start | FormatStringPart::Continue) - ) { - Lexer::INTERPOLATION_START - } else { - self.kind.delimiter() - }; - - write!(f, "{open}{}{close}", self.raw) + write!(f, "{}", self.token.lexeme()) } } -impl Serialize for StringLiteral { +impl Serialize for StringLiteral<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/src/usage.rs b/src/usage.rs index 04a741c927..42f342b40c 100644 --- a/src/usage.rs +++ b/src/usage.rs @@ -181,7 +181,7 @@ impl ColorDisplay for UsageParameter<'_> { } if let Some(pattern) = &self.parameter.pattern { - write!(f, " [pattern: '{pattern}']")?; + write!(f, " [pattern: '{}']", pattern.original())?; } Ok(())