use-go-keyword 0.0.1

Go keyword and predeclared identifier primitives for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

use core::{fmt, str::FromStr};
use std::error::Error;

/// Error returned when parsing Go vocabulary fails.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GoKeywordParseError {
    Empty,
    Unknown,
}

impl fmt::Display for GoKeywordParseError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Empty => formatter.write_str("Go word cannot be empty"),
            Self::Unknown => formatter.write_str("unknown Go keyword or predeclared identifier"),
        }
    }
}

impl Error for GoKeywordParseError {}

/// Go source keywords.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum GoKeyword {
    Break,
    Default,
    Func,
    Interface,
    Select,
    Case,
    Defer,
    Go,
    Map,
    Struct,
    Chan,
    Else,
    Goto,
    Package,
    Switch,
    Const,
    Fallthrough,
    If,
    Range,
    Type,
    Continue,
    For,
    Import,
    Return,
    Var,
}

impl GoKeyword {
    /// Returns the keyword label as it appears in Go source.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Break => "break",
            Self::Default => "default",
            Self::Func => "func",
            Self::Interface => "interface",
            Self::Select => "select",
            Self::Case => "case",
            Self::Defer => "defer",
            Self::Go => "go",
            Self::Map => "map",
            Self::Struct => "struct",
            Self::Chan => "chan",
            Self::Else => "else",
            Self::Goto => "goto",
            Self::Package => "package",
            Self::Switch => "switch",
            Self::Const => "const",
            Self::Fallthrough => "fallthrough",
            Self::If => "if",
            Self::Range => "range",
            Self::Type => "type",
            Self::Continue => "continue",
            Self::For => "for",
            Self::Import => "import",
            Self::Return => "return",
            Self::Var => "var",
        }
    }
}

impl fmt::Display for GoKeyword {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

impl FromStr for GoKeyword {
    type Err = GoKeywordParseError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        match normalized_word(input)?.as_str() {
            "break" => Ok(Self::Break),
            "default" => Ok(Self::Default),
            "func" => Ok(Self::Func),
            "interface" => Ok(Self::Interface),
            "select" => Ok(Self::Select),
            "case" => Ok(Self::Case),
            "defer" => Ok(Self::Defer),
            "go" => Ok(Self::Go),
            "map" => Ok(Self::Map),
            "struct" => Ok(Self::Struct),
            "chan" => Ok(Self::Chan),
            "else" => Ok(Self::Else),
            "goto" => Ok(Self::Goto),
            "package" => Ok(Self::Package),
            "switch" => Ok(Self::Switch),
            "const" => Ok(Self::Const),
            "fallthrough" => Ok(Self::Fallthrough),
            "if" => Ok(Self::If),
            "range" => Ok(Self::Range),
            "type" => Ok(Self::Type),
            "continue" => Ok(Self::Continue),
            "for" => Ok(Self::For),
            "import" => Ok(Self::Import),
            "return" => Ok(Self::Return),
            "var" => Ok(Self::Var),
            _ => Err(GoKeywordParseError::Unknown),
        }
    }
}

/// Common Go predeclared identifiers.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum GoPredeclaredIdentifier {
    Any,
    Bool,
    Byte,
    Comparable,
    Complex64,
    Complex128,
    Error,
    Float32,
    Float64,
    Int,
    Int8,
    Int16,
    Int32,
    Int64,
    Rune,
    String,
    Uint,
    Uint8,
    Uint16,
    Uint32,
    Uint64,
    Uintptr,
    True,
    False,
    Iota,
    Nil,
    Append,
    Cap,
    Clear,
    Close,
    Complex,
    Copy,
    Delete,
    Imag,
    Len,
    Make,
    Max,
    Min,
    New,
    Panic,
    Print,
    Println,
    Real,
    Recover,
}

impl GoPredeclaredIdentifier {
    /// Returns the identifier label as it appears in Go source.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Any => "any",
            Self::Bool => "bool",
            Self::Byte => "byte",
            Self::Comparable => "comparable",
            Self::Complex64 => "complex64",
            Self::Complex128 => "complex128",
            Self::Error => "error",
            Self::Float32 => "float32",
            Self::Float64 => "float64",
            Self::Int => "int",
            Self::Int8 => "int8",
            Self::Int16 => "int16",
            Self::Int32 => "int32",
            Self::Int64 => "int64",
            Self::Rune => "rune",
            Self::String => "string",
            Self::Uint => "uint",
            Self::Uint8 => "uint8",
            Self::Uint16 => "uint16",
            Self::Uint32 => "uint32",
            Self::Uint64 => "uint64",
            Self::Uintptr => "uintptr",
            Self::True => "true",
            Self::False => "false",
            Self::Iota => "iota",
            Self::Nil => "nil",
            Self::Append => "append",
            Self::Cap => "cap",
            Self::Clear => "clear",
            Self::Close => "close",
            Self::Complex => "complex",
            Self::Copy => "copy",
            Self::Delete => "delete",
            Self::Imag => "imag",
            Self::Len => "len",
            Self::Make => "make",
            Self::Max => "max",
            Self::Min => "min",
            Self::New => "new",
            Self::Panic => "panic",
            Self::Print => "print",
            Self::Println => "println",
            Self::Real => "real",
            Self::Recover => "recover",
        }
    }
}

impl fmt::Display for GoPredeclaredIdentifier {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

impl FromStr for GoPredeclaredIdentifier {
    type Err = GoKeywordParseError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        match normalized_word(input)?.as_str() {
            "any" => Ok(Self::Any),
            "bool" => Ok(Self::Bool),
            "byte" => Ok(Self::Byte),
            "comparable" => Ok(Self::Comparable),
            "complex64" => Ok(Self::Complex64),
            "complex128" => Ok(Self::Complex128),
            "error" => Ok(Self::Error),
            "float32" => Ok(Self::Float32),
            "float64" => Ok(Self::Float64),
            "int" => Ok(Self::Int),
            "int8" => Ok(Self::Int8),
            "int16" => Ok(Self::Int16),
            "int32" => Ok(Self::Int32),
            "int64" => Ok(Self::Int64),
            "rune" => Ok(Self::Rune),
            "string" => Ok(Self::String),
            "uint" => Ok(Self::Uint),
            "uint8" => Ok(Self::Uint8),
            "uint16" => Ok(Self::Uint16),
            "uint32" => Ok(Self::Uint32),
            "uint64" => Ok(Self::Uint64),
            "uintptr" => Ok(Self::Uintptr),
            "true" => Ok(Self::True),
            "false" => Ok(Self::False),
            "iota" => Ok(Self::Iota),
            "nil" => Ok(Self::Nil),
            "append" => Ok(Self::Append),
            "cap" => Ok(Self::Cap),
            "clear" => Ok(Self::Clear),
            "close" => Ok(Self::Close),
            "complex" => Ok(Self::Complex),
            "copy" => Ok(Self::Copy),
            "delete" => Ok(Self::Delete),
            "imag" => Ok(Self::Imag),
            "len" => Ok(Self::Len),
            "make" => Ok(Self::Make),
            "max" => Ok(Self::Max),
            "min" => Ok(Self::Min),
            "new" => Ok(Self::New),
            "panic" => Ok(Self::Panic),
            "print" => Ok(Self::Print),
            "println" => Ok(Self::Println),
            "real" => Ok(Self::Real),
            "recover" => Ok(Self::Recover),
            _ => Err(GoKeywordParseError::Unknown),
        }
    }
}

/// Go reserved vocabulary: keywords plus common predeclared identifiers.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum GoReservedWord {
    Keyword(GoKeyword),
    PredeclaredIdentifier(GoPredeclaredIdentifier),
}

impl GoReservedWord {
    /// Returns the reserved word label as it appears in Go source.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Keyword(keyword) => keyword.as_str(),
            Self::PredeclaredIdentifier(identifier) => identifier.as_str(),
        }
    }
}

impl fmt::Display for GoReservedWord {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

impl FromStr for GoReservedWord {
    type Err = GoKeywordParseError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        if let Ok(keyword) = input.parse::<GoKeyword>() {
            return Ok(Self::Keyword(keyword));
        }
        input
            .parse::<GoPredeclaredIdentifier>()
            .map(Self::PredeclaredIdentifier)
    }
}

/// Returns whether `input` is a Go keyword.
#[must_use]
pub fn is_go_keyword(input: &str) -> bool {
    input.parse::<GoKeyword>().is_ok()
}

/// Returns whether `input` is a common Go predeclared identifier.
#[must_use]
pub fn is_go_predeclared_identifier(input: &str) -> bool {
    input.parse::<GoPredeclaredIdentifier>().is_ok()
}

/// Returns whether `input` is a Go keyword or common predeclared identifier.
#[must_use]
pub fn is_go_reserved_word(input: &str) -> bool {
    input.parse::<GoReservedWord>().is_ok()
}

fn normalized_word(input: &str) -> Result<String, GoKeywordParseError> {
    let trimmed = input.trim();
    if trimmed.is_empty() {
        Err(GoKeywordParseError::Empty)
    } else {
        Ok(trimmed.to_ascii_lowercase())
    }
}

#[cfg(test)]
mod tests {
    use super::{
        is_go_keyword, is_go_predeclared_identifier, is_go_reserved_word, GoKeyword,
        GoKeywordParseError, GoPredeclaredIdentifier, GoReservedWord,
    };

    #[test]
    fn parses_keywords() -> Result<(), GoKeywordParseError> {
        let keyword: GoKeyword = "Func".parse()?;
        assert_eq!(keyword, GoKeyword::Func);
        assert_eq!(keyword.to_string(), "func");
        assert!(is_go_keyword("package"));
        assert!(!is_go_keyword("nil"));
        Ok(())
    }

    #[test]
    fn parses_predeclared_identifiers() -> Result<(), GoKeywordParseError> {
        let identifier: GoPredeclaredIdentifier = "complex128".parse()?;
        assert_eq!(identifier, GoPredeclaredIdentifier::Complex128);
        assert_eq!(GoPredeclaredIdentifier::Uintptr.to_string(), "uintptr");
        assert!(is_go_predeclared_identifier("nil"));
        assert!(is_go_predeclared_identifier("println"));
        Ok(())
    }

    #[test]
    fn checks_reserved_words() -> Result<(), GoKeywordParseError> {
        let reserved: GoReservedWord = "interface".parse()?;
        assert_eq!(reserved.as_str(), "interface");
        assert!(is_go_reserved_word("true"));
        assert!(is_go_reserved_word("return"));
        assert!(!is_go_reserved_word("handler"));
        Ok(())
    }

    #[test]
    fn rejects_empty_and_unknown_words() {
        assert_eq!("".parse::<GoKeyword>(), Err(GoKeywordParseError::Empty));
        assert_eq!(
            "handler".parse::<GoKeyword>(),
            Err(GoKeywordParseError::Unknown)
        );
    }
}