pub fn is_conventional_invocant_name(name: &str) -> bool {
matches!(
name.strip_prefix('$').unwrap_or(name),
"self" | "class" | "this" | "proto"
)
}
pub fn is_constructor_name(name: &str) -> bool {
name == "new"
}
pub fn is_current_package_token(text: &str) -> bool {
text == "__PACKAGE__"
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct InvocantName(String);
impl InvocantName {
pub fn assume_canonical(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn classify(&self) -> InvocantText<'_> {
InvocantText::parse(&self.0)
}
}
impl std::ops::Deref for InvocantName {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl PartialEq<str> for InvocantName {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for InvocantName {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl std::fmt::Display for InvocantName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvocantText<'a> {
Scalar(&'a str),
CurrentPackage,
PositionalReceiver,
NonScalar(&'a str),
Bareword(&'a str),
}
impl<'a> InvocantText<'a> {
pub fn parse(text: &'a str) -> Self {
match text {
t if is_current_package_token(t) => Self::CurrentPackage,
"shift" | "$_[0]" | "@_[0]" => Self::PositionalReceiver,
t if t.starts_with('$') => Self::Scalar(&t[1..]),
t if t.starts_with('@') || t.starts_with('%') => Self::NonScalar(&t[1..]),
t => Self::Bareword(t),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MethodToken<'a> {
Bare(&'a str),
Super(&'a str),
Main(&'a str),
Qualified { package: &'a str, name: &'a str },
}
impl<'a> MethodToken<'a> {
pub fn parse(token: &'a str) -> Self {
match token.rsplit_once("::") {
None => Self::Bare(token),
Some(("SUPER", tail)) => Self::Super(tail),
Some(("", tail)) => Self::Main(tail),
Some((pkg, tail)) => Self::Qualified { package: pkg, name: tail },
}
}
pub fn name(&self) -> &'a str {
match self {
Self::Bare(n) | Self::Super(n) | Self::Main(n) => n,
Self::Qualified { name, .. } => name,
}
}
pub fn literal_package(&self) -> Option<&'a str> {
match self {
Self::Qualified { package, .. } => Some(package),
Self::Main(_) => Some("main"),
Self::Bare(_) | Self::Super(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::{InvocantText, MethodToken};
#[test]
fn invocant_text_variants() {
assert_eq!(InvocantText::parse("$obj"), InvocantText::Scalar("obj"));
assert_eq!(InvocantText::parse("@list"), InvocantText::NonScalar("list"));
assert_eq!(InvocantText::parse("%h"), InvocantText::NonScalar("h"));
assert_eq!(InvocantText::parse("__PACKAGE__"), InvocantText::CurrentPackage);
assert_eq!(InvocantText::parse("shift"), InvocantText::PositionalReceiver);
assert_eq!(InvocantText::parse("$_[0]"), InvocantText::PositionalReceiver);
assert_eq!(InvocantText::parse("@_[0]"), InvocantText::PositionalReceiver);
assert_eq!(InvocantText::parse("Foo::Bar"), InvocantText::Bareword("Foo::Bar"));
}
#[test]
fn method_token_variants() {
assert_eq!(MethodToken::parse("m"), MethodToken::Bare("m"));
assert_eq!(MethodToken::parse("SUPER::m"), MethodToken::Super("m"));
assert_eq!(MethodToken::parse("::m"), MethodToken::Main("m"));
assert_eq!(
MethodToken::parse("Foo::Bar::m"),
MethodToken::Qualified { package: "Foo::Bar", name: "m" }
);
assert_eq!(
MethodToken::parse("Foo::SUPER::m"),
MethodToken::Qualified { package: "Foo::SUPER", name: "m" }
);
}
#[test]
fn method_token_projections() {
assert_eq!(MethodToken::parse("SUPER::m").name(), "m");
assert_eq!(MethodToken::parse("Foo::Bar::m").literal_package(), Some("Foo::Bar"));
assert_eq!(MethodToken::parse("::m").literal_package(), Some("main"));
assert_eq!(MethodToken::parse("SUPER::m").literal_package(), None);
assert_eq!(MethodToken::parse("m").literal_package(), None);
}
}