#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Descriptor {
Namespace(String),
Type(String),
Term(String),
Method { name: String, disambiguator: String },
TypeParameter(String),
Parameter(String),
Meta(String),
Macro(String),
}
impl Descriptor {
pub fn name(&self) -> &str {
match self {
Descriptor::Namespace(n)
| Descriptor::Type(n)
| Descriptor::Term(n)
| Descriptor::TypeParameter(n)
| Descriptor::Parameter(n)
| Descriptor::Meta(n)
| Descriptor::Macro(n) => n,
Descriptor::Method { name, .. } => name,
}
}
pub fn render<W: core::fmt::Write>(&self, out: &mut W) -> core::fmt::Result {
match self {
Descriptor::Namespace(n) => {
push_ident(out, n)?;
out.write_char('/')
}
Descriptor::Type(n) => {
push_ident(out, n)?;
out.write_char('#')
}
Descriptor::Term(n) => {
push_ident(out, n)?;
out.write_char('.')
}
Descriptor::Method {
name,
disambiguator,
} => {
push_ident(out, name)?;
out.write_char('(')?;
debug_assert!(
disambiguator.chars().all(is_simple_ident_char),
"SCIP method disambiguator must be a simple identifier, got {disambiguator:?}"
);
out.write_str(disambiguator)?;
out.write_str(").")
}
Descriptor::TypeParameter(n) => {
out.write_char('[')?;
push_ident(out, n)?;
out.write_char(']')
}
Descriptor::Parameter(n) => {
out.write_char('(')?;
push_ident(out, n)?;
out.write_char(')')
}
Descriptor::Meta(n) => {
push_ident(out, n)?;
out.write_char(':')
}
Descriptor::Macro(n) => {
push_ident(out, n)?;
out.write_char('!')
}
}
}
}
fn push_ident<W: core::fmt::Write>(out: &mut W, ident: &str) -> core::fmt::Result {
let simple = !ident.is_empty() && ident.chars().all(is_simple_ident_char);
if simple {
out.write_str(ident)
} else {
out.write_char('`')?;
for c in ident.chars() {
if c == '`' {
out.write_char('`')?;
}
out.write_char(c)?;
}
out.write_char('`')
}
}
fn is_simple_ident_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '_' || c == '+' || c == '-' || c == '$'
}
pub(crate) fn parse_ident(s: &str) -> Result<(String, &str), super::id::SymbolParseError> {
use super::id::SymbolParseError;
if let Some(quoted) = s.strip_prefix('`') {
let mut name = String::new();
let mut rest = quoted;
loop {
let mut chars = rest.char_indices();
match chars.next() {
None => return Err(SymbolParseError::UnterminatedQuote),
Some((_, '`')) => {
let after = chars.as_str();
if let Some(next) = after.strip_prefix('`') {
name.push('`');
rest = next;
} else {
return Ok((name, after));
}
}
Some((_, c)) => {
name.push(c);
rest = chars.as_str();
}
}
}
} else {
let end = s
.char_indices()
.find(|&(_, c)| !is_simple_ident_char(c))
.map(|(i, _)| i)
.unwrap_or(s.len());
if end == 0 {
return Err(SymbolParseError::ExpectedIdent);
}
let (name, rest) = s.split_at(end);
Ok((name.to_owned(), rest))
}
}
pub(crate) fn parse_descriptor(s: &str) -> Result<(Descriptor, &str), super::id::SymbolParseError> {
use super::id::SymbolParseError;
if let Some(rest) = s.strip_prefix('[') {
let (name, rest) = parse_ident(rest)?;
let rest = rest
.strip_prefix(']')
.ok_or(SymbolParseError::UnknownDescriptor)?;
return Ok((Descriptor::TypeParameter(name), rest));
}
if let Some(rest) = s.strip_prefix('(') {
let (name, rest) = parse_ident(rest)?;
let rest = rest
.strip_prefix(')')
.ok_or(SymbolParseError::UnknownDescriptor)?;
return Ok((Descriptor::Parameter(name), rest));
}
let (name, rest) = parse_ident(s)?;
let mut chars = rest.chars();
match chars.next() {
Some('(') => {
let (disambiguator, after_close) = chars
.as_str()
.split_once(')')
.ok_or(SymbolParseError::UnknownDescriptor)?;
let disambiguator = disambiguator.to_owned();
let rest = after_close
.strip_prefix('.')
.ok_or(SymbolParseError::UnknownDescriptor)?;
Ok((
Descriptor::Method {
name,
disambiguator,
},
rest,
))
}
Some('/') => Ok((Descriptor::Namespace(name), chars.as_str())),
Some('#') => Ok((Descriptor::Type(name), chars.as_str())),
Some('.') => Ok((Descriptor::Term(name), chars.as_str())),
Some(':') => Ok((Descriptor::Meta(name), chars.as_str())),
Some('!') => Ok((Descriptor::Macro(name), chars.as_str())),
_ => Err(SymbolParseError::UnknownDescriptor),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn renders_scip_suffixes() {
let mut s = String::new();
Descriptor::Namespace("auth".into()).render(&mut s).unwrap();
Descriptor::Method {
name: "validate_token".into(),
disambiguator: String::new(),
}
.render(&mut s)
.unwrap();
assert_eq!(s, "auth/validate_token().");
}
#[test]
fn escapes_non_simple_idents() {
let mut s = String::new();
Descriptor::Type("Foo Bar".into()).render(&mut s).unwrap();
assert_eq!(s, "`Foo Bar`#");
}
#[test]
fn method_with_nonempty_disambiguator_round_trips() {
let desc = Descriptor::Method {
name: "to_string".into(),
disambiguator: "1".into(),
};
let mut s = String::new();
desc.render(&mut s).unwrap();
assert_eq!(s, "to_string(1).");
let (parsed, rest) = parse_descriptor(&s).unwrap();
assert_eq!(parsed, desc);
assert!(rest.is_empty(), "no trailing input, got {rest:?}");
}
}