use alloc::string::String;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ami4CcmPragma {
Interface {
name: String,
},
Receptacle {
name: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsePragmaError {
NotAmi4ccmPragma,
UnknownTag(String),
MalformedQuotedName,
EmptyName,
}
impl fmt::Display for ParsePragmaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotAmi4ccmPragma => f.write_str("not a `#pragma ami4ccm` line"),
Self::UnknownTag(tag) => write!(f, "unknown ami4ccm tag `{tag}`"),
Self::MalformedQuotedName => f.write_str("malformed quoted name"),
Self::EmptyName => f.write_str("empty name in quotes"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParsePragmaError {}
pub fn parse_pragma(line: &str) -> Result<Ami4CcmPragma, ParsePragmaError> {
let trimmed = line.trim();
let after_hash = trimmed
.strip_prefix('#')
.ok_or(ParsePragmaError::NotAmi4ccmPragma)?
.trim_start();
let after_pragma = after_hash
.strip_prefix("pragma")
.ok_or(ParsePragmaError::NotAmi4ccmPragma)?
.trim_start();
let after_ami = after_pragma
.strip_prefix("ami4ccm")
.ok_or(ParsePragmaError::NotAmi4ccmPragma)?
.trim_start();
let tag_end = after_ami
.find(char::is_whitespace)
.unwrap_or(after_ami.len());
let tag = &after_ami[..tag_end];
let rest = after_ami[tag_end..].trim_start();
let name = parse_quoted(rest)?;
match tag {
"interface" => Ok(Ami4CcmPragma::Interface {
name: String::from(name),
}),
"receptacle" => Ok(Ami4CcmPragma::Receptacle {
name: String::from(name),
}),
other => Err(ParsePragmaError::UnknownTag(String::from(other))),
}
}
fn parse_quoted(s: &str) -> Result<&str, ParsePragmaError> {
let s = s.trim_end();
let inner = s
.strip_prefix('"')
.ok_or(ParsePragmaError::MalformedQuotedName)?
.strip_suffix('"')
.ok_or(ParsePragmaError::MalformedQuotedName)?;
if inner.is_empty() {
return Err(ParsePragmaError::EmptyName);
}
Ok(inner)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn parses_interface_pragma() {
let p = parse_pragma(r#"#pragma ami4ccm interface "StockManager""#).expect("valid pragma");
assert_eq!(
p,
Ami4CcmPragma::Interface {
name: String::from("StockManager")
}
);
}
#[test]
fn parses_receptacle_pragma() {
let p =
parse_pragma(r#"#pragma ami4ccm receptacle "Client::manager""#).expect("valid pragma");
assert_eq!(
p,
Ami4CcmPragma::Receptacle {
name: String::from("Client::manager")
}
);
}
#[test]
fn parses_pragma_with_extra_whitespace() {
let p = parse_pragma(" # pragma ami4ccm interface \"Foo::Bar\" ").expect("valid");
assert_eq!(
p,
Ami4CcmPragma::Interface {
name: String::from("Foo::Bar")
}
);
}
#[test]
fn rejects_non_ami_pragma() {
let err = parse_pragma("#pragma prefix Foo").unwrap_err();
assert_eq!(err, ParsePragmaError::NotAmi4ccmPragma);
}
#[test]
fn rejects_non_pragma_line() {
let err = parse_pragma("interface Foo {};").unwrap_err();
assert_eq!(err, ParsePragmaError::NotAmi4ccmPragma);
}
#[test]
fn rejects_unknown_tag() {
let err = parse_pragma(r#"#pragma ami4ccm component "Foo""#).unwrap_err();
assert_eq!(err, ParsePragmaError::UnknownTag(String::from("component")));
}
#[test]
fn rejects_unquoted_name() {
let err = parse_pragma("#pragma ami4ccm interface Foo").unwrap_err();
assert_eq!(err, ParsePragmaError::MalformedQuotedName);
}
#[test]
fn rejects_empty_quoted_name() {
let err = parse_pragma(r#"#pragma ami4ccm interface """#).unwrap_err();
assert_eq!(err, ParsePragmaError::EmptyName);
}
#[test]
fn display_error_describes_each_variant() {
assert!(alloc::format!("{}", ParsePragmaError::NotAmi4ccmPragma).contains("ami4ccm"));
assert!(
alloc::format!("{}", ParsePragmaError::UnknownTag(String::from("x"))).contains('x')
);
assert!(alloc::format!("{}", ParsePragmaError::MalformedQuotedName).contains("malformed"));
assert!(alloc::format!("{}", ParsePragmaError::EmptyName).contains("empty"));
}
}