// This file defines all the identifier enums and target-aware logic.

use crate::triple::{Endianness, PointerWidth, Triple};
use alloc::boxed::Box;
use alloc::string::String;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::str::FromStr;

/// The "architecture" field, which in some cases also specifies a specific
/// subarchitecture.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum Architecture {
    Unknown,
    Arm(ArmArchitecture),
    AmdGcn,
    Aarch64(Aarch64Architecture),
    Asmjs,
    Hexagon,
    I386,
    I586,
    I686,
    Mips,
    Mips64,
    Mips64el,
    Mipsel,
    Mipsisa32r6,
    Mipsisa32r6el,
    Mipsisa64r6,
    Mipsisa64r6el,
    Msp430,
    Nvptx64,
    Powerpc,
    Powerpc64,
    Powerpc64le,
    Riscv32,
    Riscv32i,
    Riscv32imac,
    Riscv32imc,
    Riscv64,
    Riscv64gc,
    Riscv64imac,
    S390x,
    Sparc,
    Sparc64,
    Sparcv9,
    Wasm32,
    X86_64,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum ArmArchitecture {
    Arm, // Generic arm
    Armeb,
    Armv4,
    Armv4t,
    Armv5t,
    Armv5te,
    Armv5tej,
    Armv6,
    Armv6j,
    Armv6k,
    Armv6z,
    Armv6kz,
    Armv6t2,
    Armv6m,
    Armv7,
    Armv7a,
    Armv7ve,
    Armv7m,
    Armv7r,
    Armv7s,
    Armv8,
    Armv8a,
    Armv8_1a,
    Armv8_2a,
    Armv8_3a,
    Armv8_4a,
    Armv8_5a,
    Armv8mBase,
    Armv8mMain,
    Armv8r,

    Armebv7r,

    Thumbeb,
    Thumbv6m,
    Thumbv7a,
    Thumbv7em,
    Thumbv7m,
    Thumbv7neon,
    Thumbv8mBase,
    Thumbv8mMain,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum Aarch64Architecture {
    Aarch64,
    Aarch64be,
}

// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
// #[allow(missing_docs)]
// pub enum ArmFpu {
//     Vfp,
//     Vfpv2,
//     Vfpv3,
//     Vfpv3Fp16,
//     Vfpv3Xd,
//     Vfpv3XdFp16,
//     Neon,
//     NeonVfpv3,
//     NeonVfpv4,
//     Vfpv4,
//     Vfpv4D16,
//     Fpv4SpD16,
//     Fpv5SpD16,
//     Fpv5D16,
//     FpArmv8,
//     NeonFpArmv8,
//     CryptoNeonFpArmv8,
// }

impl ArmArchitecture {
    /// Test if this architecture uses the Thumb instruction set.
    pub fn is_thumb(self) -> bool {
        match self {
            ArmArchitecture::Arm
            | ArmArchitecture::Armeb
            | ArmArchitecture::Armv4
            | ArmArchitecture::Armv4t
            | ArmArchitecture::Armv5t
            | ArmArchitecture::Armv5te
            | ArmArchitecture::Armv5tej
            | ArmArchitecture::Armv6
            | ArmArchitecture::Armv6j
            | ArmArchitecture::Armv6k
            | ArmArchitecture::Armv6z
            | ArmArchitecture::Armv6kz
            | ArmArchitecture::Armv6t2
            | ArmArchitecture::Armv6m
            | ArmArchitecture::Armv7
            | ArmArchitecture::Armv7a
            | ArmArchitecture::Armv7ve
            | ArmArchitecture::Armv7m
            | ArmArchitecture::Armv7r
            | ArmArchitecture::Armv7s
            | ArmArchitecture::Armv8
            | ArmArchitecture::Armv8a
            | ArmArchitecture::Armv8_1a
            | ArmArchitecture::Armv8_2a
            | ArmArchitecture::Armv8_3a
            | ArmArchitecture::Armv8_4a
            | ArmArchitecture::Armv8_5a
            | ArmArchitecture::Armv8mBase
            | ArmArchitecture::Armv8mMain
            | ArmArchitecture::Armv8r
            | ArmArchitecture::Armebv7r => false,
            ArmArchitecture::Thumbeb
            | ArmArchitecture::Thumbv6m
            | ArmArchitecture::Thumbv7a
            | ArmArchitecture::Thumbv7em
            | ArmArchitecture::Thumbv7m
            | ArmArchitecture::Thumbv7neon
            | ArmArchitecture::Thumbv8mBase
            | ArmArchitecture::Thumbv8mMain => true,
        }
    }

    // pub fn has_fpu(self) -> Result<&'static [ArmFpu], ()> {

    // }

    /// Return the pointer bit width of this target's architecture.
    pub fn pointer_width(self) -> PointerWidth {
        match self {
            ArmArchitecture::Arm
            | ArmArchitecture::Armeb
            | ArmArchitecture::Armv4
            | ArmArchitecture::Armv4t
            | ArmArchitecture::Armv5t
            | ArmArchitecture::Armv5te
            | ArmArchitecture::Armv5tej
            | ArmArchitecture::Armv6
            | ArmArchitecture::Armv6j
            | ArmArchitecture::Armv6k
            | ArmArchitecture::Armv6z
            | ArmArchitecture::Armv6kz
            | ArmArchitecture::Armv6t2
            | ArmArchitecture::Armv6m
            | ArmArchitecture::Armv7
            | ArmArchitecture::Armv7a
            | ArmArchitecture::Armv7ve
            | ArmArchitecture::Armv7m
            | ArmArchitecture::Armv7r
            | ArmArchitecture::Armv7s
            | ArmArchitecture::Armv8
            | ArmArchitecture::Armv8a
            | ArmArchitecture::Armv8_1a
            | ArmArchitecture::Armv8_2a
            | ArmArchitecture::Armv8_3a
            | ArmArchitecture::Armv8_4a
            | ArmArchitecture::Armv8_5a
            | ArmArchitecture::Armv8mBase
            | ArmArchitecture::Armv8mMain
            | ArmArchitecture::Armv8r
            | ArmArchitecture::Armebv7r
            | ArmArchitecture::Thumbeb
            | ArmArchitecture::Thumbv6m
            | ArmArchitecture::Thumbv7a
            | ArmArchitecture::Thumbv7em
            | ArmArchitecture::Thumbv7m
            | ArmArchitecture::Thumbv7neon
            | ArmArchitecture::Thumbv8mBase
            | ArmArchitecture::Thumbv8mMain => PointerWidth::U32,
        }
    }

    /// Return the endianness of this architecture.
    pub fn endianness(self) -> Endianness {
        match self {
            ArmArchitecture::Arm
            | ArmArchitecture::Armv4
            | ArmArchitecture::Armv4t
            | ArmArchitecture::Armv5t
            | ArmArchitecture::Armv5te
            | ArmArchitecture::Armv5tej
            | ArmArchitecture::Armv6
            | ArmArchitecture::Armv6j
            | ArmArchitecture::Armv6k
            | ArmArchitecture::Armv6z
            | ArmArchitecture::Armv6kz
            | ArmArchitecture::Armv6t2
            | ArmArchitecture::Armv6m
            | ArmArchitecture::Armv7
            | ArmArchitecture::Armv7a
            | ArmArchitecture::Armv7ve
            | ArmArchitecture::Armv7m
            | ArmArchitecture::Armv7r
            | ArmArchitecture::Armv7s
            | ArmArchitecture::Armv8
            | ArmArchitecture::Armv8a
            | ArmArchitecture::Armv8_1a
            | ArmArchitecture::Armv8_2a
            | ArmArchitecture::Armv8_3a
            | ArmArchitecture::Armv8_4a
            | ArmArchitecture::Armv8_5a
            | ArmArchitecture::Armv8mBase
            | ArmArchitecture::Armv8mMain
            | ArmArchitecture::Armv8r
            | ArmArchitecture::Thumbv6m
            | ArmArchitecture::Thumbv7a
            | ArmArchitecture::Thumbv7em
            | ArmArchitecture::Thumbv7m
            | ArmArchitecture::Thumbv7neon
            | ArmArchitecture::Thumbv8mBase
            | ArmArchitecture::Thumbv8mMain => Endianness::Little,
            ArmArchitecture::Armeb | ArmArchitecture::Armebv7r | ArmArchitecture::Thumbeb => {
                Endianness::Big
            }
        }
    }
}

impl Aarch64Architecture {
    /// Test if this architecture uses the Thumb instruction set.
    pub fn is_thumb(self) -> bool {
        match self {
            Aarch64Architecture::Aarch64 | Aarch64Architecture::Aarch64be => false,
        }
    }

    // pub fn has_fpu(self) -> Result<&'static [ArmFpu], ()> {

    // }

    /// Return the pointer bit width of this target's architecture.
    pub fn pointer_width(self) -> PointerWidth {
        match self {
            Aarch64Architecture::Aarch64 | Aarch64Architecture::Aarch64be => PointerWidth::U64,
        }
    }

    /// Return the endianness of this architecture.
    pub fn endianness(self) -> Endianness {
        match self {
            Aarch64Architecture::Aarch64 => Endianness::Little,
            Aarch64Architecture::Aarch64be => Endianness::Big,
        }
    }
}

/// A string for a `Vendor::Custom` that can either be used in `const`
/// contexts or hold dynamic strings.
#[derive(Clone, Debug, Eq)]
pub enum CustomVendor {
    /// An owned `String`. This supports the general case.
    Owned(Box<String>),
    /// A static `str`, so that `CustomVendor` can be constructed in `const`
    /// contexts.
    Static(&'static str),
}

impl CustomVendor {
    /// Extracts a string slice.
    pub fn as_str(&self) -> &str {
        match self {
            CustomVendor::Owned(s) => s,
            CustomVendor::Static(s) => s,
        }
    }
}

impl PartialEq for CustomVendor {
    fn eq(&self, other: &Self) -> bool {
        self.as_str() == other.as_str()
    }
}

impl Hash for CustomVendor {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_str().hash(state)
    }
}

/// The "vendor" field, which in practice is little more than an arbitrary
/// modifier.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum Vendor {
    Unknown,
    Amd,
    Apple,
    Experimental,
    Fortanix,
    Nvidia,
    Pc,
    Rumprun,
    Sun,
    Uwp,
    Wrs,

    /// A custom vendor. "Custom" in this context means that the vendor is
    /// not specifically recognized by upstream Autotools, LLVM, Rust, or other
    /// relevant authorities on triple naming. It's useful for people building
    /// and using locally patched toolchains.
    ///
    /// Outside of such patched environments, users of `target-lexicon` should
    /// treat `Custom` the same as `Unknown` and ignore the string.
    Custom(CustomVendor),
}

/// The "operating system" field, which sometimes implies an environment, and
/// sometimes isn't an actual operating system.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum OperatingSystem {
    Unknown,
    AmdHsa,
    Bitrig,
    Cloudabi,
    Cuda,
    Darwin,
    Dragonfly,
    Emscripten,
    Freebsd,
    Fuchsia,
    Haiku,
    Hermit,
    Ios,
    L4re,
    Linux,
    MacOSX { major: u16, minor: u16, patch: u16 },
    Nebulet,
    Netbsd,
    None_,
    Openbsd,
    Redox,
    Solaris,
    Uefi,
    VxWorks,
    Wasi,
    Windows,
}

/// The "environment" field, which specifies an ABI environment on top of the
/// operating system. In many configurations, this field is omitted, and the
/// environment is implied by the operating system.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum Environment {
    Unknown,
    AmdGiz,
    Android,
    Androideabi,
    Eabi,
    Eabihf,
    Gnu,
    Gnuabi64,
    Gnueabi,
    Gnueabihf,
    Gnuspe,
    Gnux32,
    Musl,
    Musleabi,
    Musleabihf,
    Muslabi64,
    Msvc,
    Kernel,
    Uclibc,
    Sgx,
    Softfloat,
    Spe,
}

/// The "binary format" field, which is usually omitted, and the binary format
/// is implied by the other fields.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum BinaryFormat {
    Unknown,
    Elf,
    Coff,
    Macho,
    Wasm,
}

impl Architecture {
    /// Return the endianness of this architecture.
    pub fn endianness(self) -> Result<Endianness, ()> {
        match self {
            Architecture::Unknown => Err(()),
            Architecture::Arm(arm) => Ok(arm.endianness()),
            Architecture::Aarch64(aarch) => Ok(aarch.endianness()),
            Architecture::AmdGcn
            | Architecture::Asmjs
            | Architecture::Hexagon
            | Architecture::I386
            | Architecture::I586
            | Architecture::I686
            | Architecture::Mips64el
            | Architecture::Mipsel
            | Architecture::Mipsisa32r6el
            | Architecture::Mipsisa64r6el
            | Architecture::Msp430
            | Architecture::Nvptx64
            | Architecture::Powerpc64le
            | Architecture::Riscv32
            | Architecture::Riscv32i
            | Architecture::Riscv32imac
            | Architecture::Riscv32imc
            | Architecture::Riscv64
            | Architecture::Riscv64gc
            | Architecture::Riscv64imac
            | Architecture::Wasm32
            | Architecture::X86_64 => Ok(Endianness::Little),
            Architecture::Mips
            | Architecture::Mips64
            | Architecture::Mipsisa32r6
            | Architecture::Mipsisa64r6
            | Architecture::Powerpc
            | Architecture::Powerpc64
            | Architecture::S390x
            | Architecture::Sparc
            | Architecture::Sparc64
            | Architecture::Sparcv9 => Ok(Endianness::Big),
        }
    }

    /// Return the pointer bit width of this target's architecture.
    pub fn pointer_width(self) -> Result<PointerWidth, ()> {
        match self {
            Architecture::Unknown => Err(()),
            Architecture::Msp430 => Ok(PointerWidth::U16),
            Architecture::Arm(arm) => Ok(arm.pointer_width()),
            Architecture::Aarch64(aarch) => Ok(aarch.pointer_width()),
            Architecture::Asmjs
            | Architecture::Hexagon
            | Architecture::I386
            | Architecture::I586
            | Architecture::I686
            | Architecture::Mipsel
            | Architecture::Mipsisa32r6
            | Architecture::Mipsisa32r6el
            | Architecture::Riscv32
            | Architecture::Riscv32i
            | Architecture::Riscv32imac
            | Architecture::Riscv32imc
            | Architecture::Sparc
            | Architecture::Wasm32
            | Architecture::Mips
            | Architecture::Powerpc => Ok(PointerWidth::U32),
            Architecture::AmdGcn
            | Architecture::Mips64el
            | Architecture::Mipsisa64r6
            | Architecture::Mipsisa64r6el
            | Architecture::Powerpc64le
            | Architecture::Riscv64
            | Architecture::Riscv64gc
            | Architecture::Riscv64imac
            | Architecture::X86_64
            | Architecture::Mips64
            | Architecture::Nvptx64
            | Architecture::Powerpc64
            | Architecture::S390x
            | Architecture::Sparc64
            | Architecture::Sparcv9 => Ok(PointerWidth::U64),
        }
    }
}

/// Return the binary format implied by this target triple, ignoring its
/// `binary_format` field.
pub(crate) fn default_binary_format(triple: &Triple) -> BinaryFormat {
    match triple.operating_system {
        OperatingSystem::None_ => match triple.environment {
            Environment::Eabi | Environment::Eabihf => BinaryFormat::Elf,
            _ => BinaryFormat::Unknown,
        },
        OperatingSystem::Darwin | OperatingSystem::Ios | OperatingSystem::MacOSX { .. } => {
            BinaryFormat::Macho
        }
        OperatingSystem::Windows => BinaryFormat::Coff,
        OperatingSystem::Nebulet
        | OperatingSystem::Emscripten
        | OperatingSystem::VxWorks
        | OperatingSystem::Wasi
        | OperatingSystem::Unknown => match triple.architecture {
            Architecture::Wasm32 => BinaryFormat::Wasm,
            _ => BinaryFormat::Unknown,
        },
        _ => BinaryFormat::Elf,
    }
}

impl fmt::Display for ArmArchitecture {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match *self {
            ArmArchitecture::Arm => "arm",
            ArmArchitecture::Armeb => "armeb",
            ArmArchitecture::Armv4 => "armv4",
            ArmArchitecture::Armv4t => "armv4t",
            ArmArchitecture::Armv5t => "armv5t",
            ArmArchitecture::Armv5te => "armv5te",
            ArmArchitecture::Armv5tej => "armv5tej",
            ArmArchitecture::Armv6 => "armv6",
            ArmArchitecture::Armv6j => "armv6j",
            ArmArchitecture::Armv6k => "armv6k",
            ArmArchitecture::Armv6z => "armv6z",
            ArmArchitecture::Armv6kz => "armv6kz",
            ArmArchitecture::Armv6t2 => "armv6t2",
            ArmArchitecture::Armv6m => "armv6m",
            ArmArchitecture::Armv7 => "armv7",
            ArmArchitecture::Armv7a => "armv7a",
            ArmArchitecture::Armv7ve => "armv7ve",
            ArmArchitecture::Armv7m => "armv7m",
            ArmArchitecture::Armv7r => "armv7r",
            ArmArchitecture::Armv7s => "armv7s",
            ArmArchitecture::Armv8 => "armv8",
            ArmArchitecture::Armv8a => "armv8a",
            ArmArchitecture::Armv8_1a => "armv8.1a",
            ArmArchitecture::Armv8_2a => "armv8.2a",
            ArmArchitecture::Armv8_3a => "armv8.3a",
            ArmArchitecture::Armv8_4a => "armv8.4a",
            ArmArchitecture::Armv8_5a => "armv8.5a",
            ArmArchitecture::Armv8mBase => "armv8m.base",
            ArmArchitecture::Armv8mMain => "armv8m.main",
            ArmArchitecture::Armv8r => "armv8r",
            ArmArchitecture::Thumbeb => "thumbeb",
            ArmArchitecture::Thumbv6m => "thumbv6m",
            ArmArchitecture::Thumbv7a => "thumbv7a",
            ArmArchitecture::Thumbv7em => "thumbv7em",
            ArmArchitecture::Thumbv7m => "thumbv7m",
            ArmArchitecture::Thumbv7neon => "thumbv7neon",
            ArmArchitecture::Thumbv8mBase => "thumbv8m.base",
            ArmArchitecture::Thumbv8mMain => "thumbv8m.main",
            ArmArchitecture::Armebv7r => "armebv7r",
        };
        f.write_str(s)
    }
}

impl fmt::Display for Aarch64Architecture {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match *self {
            Aarch64Architecture::Aarch64 => "aarch64",
            Aarch64Architecture::Aarch64be => "aarch64be",
        };
        f.write_str(s)
    }
}

impl fmt::Display for Architecture {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Architecture::Arm(arm) => arm.fmt(f),
            Architecture::Aarch64(aarch) => aarch.fmt(f),
            Architecture::Unknown => f.write_str("unknown"),
            Architecture::AmdGcn => f.write_str("amdgcn"),
            Architecture::Asmjs => f.write_str("asmjs"),
            Architecture::Hexagon => f.write_str("hexagon"),
            Architecture::I386 => f.write_str("i386"),
            Architecture::I586 => f.write_str("i586"),
            Architecture::I686 => f.write_str("i686"),
            Architecture::Mips => f.write_str("mips"),
            Architecture::Mips64 => f.write_str("mips64"),
            Architecture::Mips64el => f.write_str("mips64el"),
            Architecture::Mipsel => f.write_str("mipsel"),
            Architecture::Mipsisa32r6 => f.write_str("mipsisa32r6"),
            Architecture::Mipsisa32r6el => f.write_str("mipsisa32r6el"),
            Architecture::Mipsisa64r6 => f.write_str("mipsisa64r6"),
            Architecture::Mipsisa64r6el => f.write_str("mipsisa64r6el"),
            Architecture::Msp430 => f.write_str("msp430"),
            Architecture::Nvptx64 => f.write_str("nvptx64"),
            Architecture::Powerpc => f.write_str("powerpc"),
            Architecture::Powerpc64 => f.write_str("powerpc64"),
            Architecture::Powerpc64le => f.write_str("powerpc64le"),
            Architecture::Riscv32 => f.write_str("riscv32"),
            Architecture::Riscv32i => f.write_str("riscv32i"),
            Architecture::Riscv32imac => f.write_str("riscv32imac"),
            Architecture::Riscv32imc => f.write_str("riscv32imc"),
            Architecture::Riscv64 => f.write_str("riscv64"),
            Architecture::Riscv64gc => f.write_str("riscv64gc"),
            Architecture::Riscv64imac => f.write_str("riscv64imac"),
            Architecture::S390x => f.write_str("s390x"),
            Architecture::Sparc => f.write_str("sparc"),
            Architecture::Sparc64 => f.write_str("sparc64"),
            Architecture::Sparcv9 => f.write_str("sparcv9"),
            Architecture::Wasm32 => f.write_str("wasm32"),
            Architecture::X86_64 => f.write_str("x86_64"),
        }
    }
}

impl FromStr for ArmArchitecture {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        Ok(match s {
            "arm" => ArmArchitecture::Arm,
            "armeb" => ArmArchitecture::Armeb,
            "armv4" => ArmArchitecture::Armv4,
            "armv4t" => ArmArchitecture::Armv4t,
            "armv5t" => ArmArchitecture::Armv5t,
            "armv5te" => ArmArchitecture::Armv5te,
            "armv5tej" => ArmArchitecture::Armv5tej,
            "armv6" => ArmArchitecture::Armv6,
            "armv6j" => ArmArchitecture::Armv6j,
            "armv6k" => ArmArchitecture::Armv6k,
            "armv6z" => ArmArchitecture::Armv6z,
            "armv6kz" => ArmArchitecture::Armv6kz,
            "armv6t2" => ArmArchitecture::Armv6t2,
            "armv6m" => ArmArchitecture::Armv6m,
            "armv7" => ArmArchitecture::Armv7,
            "armv7a" => ArmArchitecture::Armv7a,
            "armv7ve" => ArmArchitecture::Armv7ve,
            "armv7m" => ArmArchitecture::Armv7m,
            "armv7r" => ArmArchitecture::Armv7r,
            "armv7s" => ArmArchitecture::Armv7s,
            "armv8" => ArmArchitecture::Armv8,
            "armv8a" => ArmArchitecture::Armv8a,
            "armv8.1a" => ArmArchitecture::Armv8_1a,
            "armv8.2a" => ArmArchitecture::Armv8_2a,
            "armv8.3a" => ArmArchitecture::Armv8_3a,
            "armv8.4a" => ArmArchitecture::Armv8_4a,
            "armv8.5a" => ArmArchitecture::Armv8_5a,
            "armv8m.base" => ArmArchitecture::Armv8mBase,
            "armv8m.main" => ArmArchitecture::Armv8mMain,
            "armv8r" => ArmArchitecture::Armv8r,
            "thumbeb" => ArmArchitecture::Thumbeb,
            "thumbv6m" => ArmArchitecture::Thumbv6m,
            "thumbv7a" => ArmArchitecture::Thumbv7a,
            "thumbv7em" => ArmArchitecture::Thumbv7em,
            "thumbv7m" => ArmArchitecture::Thumbv7m,
            "thumbv7neon" => ArmArchitecture::Thumbv7neon,
            "thumbv8m.base" => ArmArchitecture::Thumbv8mBase,
            "thumbv8m.main" => ArmArchitecture::Thumbv8mMain,
            "armebv7r" => ArmArchitecture::Armebv7r,
            _ => return Err(()),
        })
    }
}

impl FromStr for Aarch64Architecture {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        Ok(match s {
            "aarch64" => Aarch64Architecture::Aarch64,
            "arm64" => Aarch64Architecture::Aarch64,
            "aarch64be" => Aarch64Architecture::Aarch64be,
            _ => return Err(()),
        })
    }
}

impl FromStr for Architecture {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        Ok(match s {
            "unknown" => Architecture::Unknown,
            "amdgcn" => Architecture::AmdGcn,
            "asmjs" => Architecture::Asmjs,
            "hexagon" => Architecture::Hexagon,
            "i386" => Architecture::I386,
            "i586" => Architecture::I586,
            "i686" => Architecture::I686,
            "mips" => Architecture::Mips,
            "mips64" => Architecture::Mips64,
            "mips64el" => Architecture::Mips64el,
            "mipsel" => Architecture::Mipsel,
            "mipsisa32r6" => Architecture::Mipsisa32r6,
            "mipsisa32r6el" => Architecture::Mipsisa32r6el,
            "mipsisa64r6" => Architecture::Mipsisa64r6,
            "mipsisa64r6el" => Architecture::Mipsisa64r6el,
            "msp430" => Architecture::Msp430,
            "nvptx64" => Architecture::Nvptx64,
            "powerpc" => Architecture::Powerpc,
            "powerpc64" => Architecture::Powerpc64,
            "powerpc64le" => Architecture::Powerpc64le,
            "riscv32" => Architecture::Riscv32,
            "riscv32i" => Architecture::Riscv32i,
            "riscv32imac" => Architecture::Riscv32imac,
            "riscv32imc" => Architecture::Riscv32imc,
            "riscv64" => Architecture::Riscv64,
            "riscv64gc" => Architecture::Riscv64gc,
            "riscv64imac" => Architecture::Riscv64imac,
            "s390x" => Architecture::S390x,
            "sparc" => Architecture::Sparc,
            "sparc64" => Architecture::Sparc64,
            "sparcv9" => Architecture::Sparcv9,
            "wasm32" => Architecture::Wasm32,
            "x86_64" => Architecture::X86_64,
            _ => {
                if let Ok(arm) = ArmArchitecture::from_str(s) {
                    Architecture::Arm(arm)
                } else if let Ok(aarch64) = Aarch64Architecture::from_str(s) {
                    Architecture::Aarch64(aarch64)
                } else {
                    return Err(());
                }
            }
        })
    }
}

impl fmt::Display for Vendor {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match *self {
            Vendor::Unknown => "unknown",
            Vendor::Amd => "amd",
            Vendor::Apple => "apple",
            Vendor::Experimental => "experimental",
            Vendor::Fortanix => "fortanix",
            Vendor::Nvidia => "nvidia",
            Vendor::Pc => "pc",
            Vendor::Rumprun => "rumprun",
            Vendor::Sun => "sun",
            Vendor::Uwp => "uwp",
            Vendor::Wrs => "wrs",
            Vendor::Custom(ref name) => name.as_str(),
        };
        f.write_str(s)
    }
}

impl FromStr for Vendor {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        Ok(match s {
            "unknown" => Vendor::Unknown,
            "amd" => Vendor::Amd,
            "apple" => Vendor::Apple,
            "experimental" => Vendor::Experimental,
            "fortanix" => Vendor::Fortanix,
            "nvidia" => Vendor::Nvidia,
            "pc" => Vendor::Pc,
            "rumprun" => Vendor::Rumprun,
            "sun" => Vendor::Sun,
            "uwp" => Vendor::Uwp,
            "wrs" => Vendor::Wrs,
            custom => {
                use alloc::borrow::ToOwned;

                // A custom vendor. Since triple syntax is so loosely defined,
                // be as conservative as we can to avoid potential ambiguities.
                // We err on the side of being too strict here, as we can
                // always relax it if needed.

                // Don't allow empty string names.
                if custom.is_empty() {
                    return Err(());
                }

                // Don't allow any other recognized name as a custom vendor,
                // since vendors can be omitted in some contexts.
                if Architecture::from_str(custom).is_ok()
                    || OperatingSystem::from_str(custom).is_ok()
                    || Environment::from_str(custom).is_ok()
                    || BinaryFormat::from_str(custom).is_ok()
                {
                    return Err(());
                }

                // Require the first character to be an ascii lowercase.
                if !custom.chars().nth(0).unwrap().is_ascii_lowercase() {
                    return Err(());
                }

                // Restrict the set of characters permitted in a custom vendor.
                if custom.chars().any(|c: char| {
                    !(c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '.')
                }) {
                    return Err(());
                }

                Vendor::Custom(CustomVendor::Owned(Box::new(custom.to_owned())))
            }
        })
    }
}

impl fmt::Display for OperatingSystem {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match *self {
            OperatingSystem::Unknown => "unknown",
            OperatingSystem::AmdHsa => "amdhsa",
            OperatingSystem::Bitrig => "bitrig",
            OperatingSystem::Cloudabi => "cloudabi",
            OperatingSystem::Cuda => "cuda",
            OperatingSystem::Darwin => "darwin",
            OperatingSystem::Dragonfly => "dragonfly",
            OperatingSystem::Emscripten => "emscripten",
            OperatingSystem::Freebsd => "freebsd",
            OperatingSystem::Fuchsia => "fuchsia",
            OperatingSystem::Haiku => "haiku",
            OperatingSystem::Hermit => "hermit",
            OperatingSystem::Ios => "ios",
            OperatingSystem::L4re => "l4re",
            OperatingSystem::Linux => "linux",
            OperatingSystem::MacOSX {
                major,
                minor,
                patch,
            } => {
                return write!(f, "macosx{}.{}.{}", major, minor, patch);
            }
            OperatingSystem::Nebulet => "nebulet",
            OperatingSystem::Netbsd => "netbsd",
            OperatingSystem::None_ => "none",
            OperatingSystem::Openbsd => "openbsd",
            OperatingSystem::Redox => "redox",
            OperatingSystem::Solaris => "solaris",
            OperatingSystem::Uefi => "uefi",
            OperatingSystem::VxWorks => "vxworks",
            OperatingSystem::Wasi => "wasi",
            OperatingSystem::Windows => "windows",
        };
        f.write_str(s)
    }
}

impl FromStr for OperatingSystem {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        // TODO also parse version number for darwin and ios OSes
        if s.starts_with("macosx") {
            // Parse operating system names like `macosx10.7.0`.
            let s = &s["macosx".len()..];
            let mut parts = s.split('.').map(|num| num.parse::<u16>());

            macro_rules! get_part {
                () => {
                    if let Some(Ok(part)) = parts.next() {
                        part
                    } else {
                        return Err(());
                    }
                };
            }

            let major = get_part!();
            let minor = get_part!();
            let patch = get_part!();

            if parts.next().is_some() {
                return Err(());
            }

            return Ok(OperatingSystem::MacOSX {
                major,
                minor,
                patch,
            });
        }

        Ok(match s {
            "unknown" => OperatingSystem::Unknown,
            "amdhsa" => OperatingSystem::AmdHsa,
            "bitrig" => OperatingSystem::Bitrig,
            "cloudabi" => OperatingSystem::Cloudabi,
            "cuda" => OperatingSystem::Cuda,
            "darwin" => OperatingSystem::Darwin,
            "dragonfly" => OperatingSystem::Dragonfly,
            "emscripten" => OperatingSystem::Emscripten,
            "freebsd" => OperatingSystem::Freebsd,
            "fuchsia" => OperatingSystem::Fuchsia,
            "haiku" => OperatingSystem::Haiku,
            "hermit" => OperatingSystem::Hermit,
            "ios" => OperatingSystem::Ios,
            "l4re" => OperatingSystem::L4re,
            "linux" => OperatingSystem::Linux,
            "nebulet" => OperatingSystem::Nebulet,
            "netbsd" => OperatingSystem::Netbsd,
            "none" => OperatingSystem::None_,
            "openbsd" => OperatingSystem::Openbsd,
            "redox" => OperatingSystem::Redox,
            "solaris" => OperatingSystem::Solaris,
            "uefi" => OperatingSystem::Uefi,
            "vxworks" => OperatingSystem::VxWorks,
            "wasi" => OperatingSystem::Wasi,
            "windows" => OperatingSystem::Windows,
            _ => return Err(()),
        })
    }
}

impl fmt::Display for Environment {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match *self {
            Environment::Unknown => "unknown",
            Environment::AmdGiz => "amdgiz",
            Environment::Android => "android",
            Environment::Androideabi => "androideabi",
            Environment::Eabi => "eabi",
            Environment::Eabihf => "eabihf",
            Environment::Gnu => "gnu",
            Environment::Gnuabi64 => "gnuabi64",
            Environment::Gnueabi => "gnueabi",
            Environment::Gnueabihf => "gnueabihf",
            Environment::Gnuspe => "gnuspe",
            Environment::Gnux32 => "gnux32",
            Environment::Musl => "musl",
            Environment::Musleabi => "musleabi",
            Environment::Musleabihf => "musleabihf",
            Environment::Muslabi64 => "muslabi64",
            Environment::Msvc => "msvc",
            Environment::Kernel => "kernel",
            Environment::Uclibc => "uclibc",
            Environment::Sgx => "sgx",
            Environment::Softfloat => "softfloat",
            Environment::Spe => "spe",
        };
        f.write_str(s)
    }
}

impl FromStr for Environment {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        Ok(match s {
            "unknown" => Environment::Unknown,
            "amdgiz" => Environment::AmdGiz,
            "android" => Environment::Android,
            "androideabi" => Environment::Androideabi,
            "eabi" => Environment::Eabi,
            "eabihf" => Environment::Eabihf,
            "gnu" => Environment::Gnu,
            "gnuabi64" => Environment::Gnuabi64,
            "gnueabi" => Environment::Gnueabi,
            "gnueabihf" => Environment::Gnueabihf,
            "gnuspe" => Environment::Gnuspe,
            "gnux32" => Environment::Gnux32,
            "musl" => Environment::Musl,
            "musleabi" => Environment::Musleabi,
            "musleabihf" => Environment::Musleabihf,
            "muslabi64" => Environment::Muslabi64,
            "msvc" => Environment::Msvc,
            "kernel" => Environment::Kernel,
            "uclibc" => Environment::Uclibc,
            "sgx" => Environment::Sgx,
            "softfloat" => Environment::Softfloat,
            "spe" => Environment::Spe,
            _ => return Err(()),
        })
    }
}

impl fmt::Display for BinaryFormat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match *self {
            BinaryFormat::Unknown => "unknown",
            BinaryFormat::Elf => "elf",
            BinaryFormat::Coff => "coff",
            BinaryFormat::Macho => "macho",
            BinaryFormat::Wasm => "wasm",
        };
        f.write_str(s)
    }
}

impl FromStr for BinaryFormat {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        Ok(match s {
            "unknown" => BinaryFormat::Unknown,
            "elf" => BinaryFormat::Elf,
            "coff" => BinaryFormat::Coff,
            "macho" => BinaryFormat::Macho,
            "wasm" => BinaryFormat::Wasm,
            _ => return Err(()),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::string::ToString;

    #[test]
    fn roundtrip_known_triples() {
        // This list is constructed from:
        //  - targets emitted by "rustup target list"
        //  - targets emitted by "rustc +nightly --print target-list"
        //  - targets contributors have added
        let targets = [
            "aarch64-apple-ios",
            "aarch64-fuchsia",
            "aarch64-linux-android",
            "aarch64-pc-windows-msvc",
            "aarch64-unknown-cloudabi",
            "aarch64-unknown-freebsd",
            "aarch64-unknown-hermit",
            "aarch64-unknown-linux-gnu",
            "aarch64-unknown-linux-musl",
            "aarch64-unknown-netbsd",
            "aarch64-unknown-none",
            "aarch64-unknown-none-softfloat",
            "aarch64-unknown-openbsd",
            "aarch64-unknown-redox",
            "aarch64-uwp-windows-msvc",
            "aarch64-wrs-vxworks",
            "amdgcn-amd-amdhsa",
            "amdgcn-amd-amdhsa-amdgiz",
            "armebv7r-none-eabi",
            "armebv7r-none-eabihf",
            "arm-linux-androideabi",
            "arm-unknown-linux-gnueabi",
            "arm-unknown-linux-gnueabihf",
            "arm-unknown-linux-musleabi",
            "arm-unknown-linux-musleabihf",
            "armv4t-unknown-linux-gnueabi",
            "armv5te-unknown-linux-gnueabi",
            "armv5te-unknown-linux-musleabi",
            "armv6-unknown-freebsd",
            "armv6-unknown-netbsd-eabihf",
            "armv7-apple-ios",
            "armv7-linux-androideabi",
            "armv7r-none-eabi",
            "armv7r-none-eabihf",
            "armv7s-apple-ios",
            "armv7-unknown-cloudabi-eabihf",
            "armv7-unknown-freebsd",
            "armv7-unknown-linux-gnueabi",
            "armv7-unknown-linux-gnueabihf",
            "armv7-unknown-linux-musleabi",
            "armv7-unknown-linux-musleabihf",
            "armv7-unknown-netbsd-eabihf",
            "armv7-wrs-vxworks-eabihf",
            "asmjs-unknown-emscripten",
            "hexagon-unknown-linux-musl",
            "i386-apple-ios",
            "i586-pc-windows-msvc",
            "i586-unknown-linux-gnu",
            "i586-unknown-linux-musl",
            "i686-apple-darwin",
            "i686-linux-android",
            "i686-apple-macosx10.7.0",
            "i686-pc-windows-gnu",
            "i686-pc-windows-msvc",
            "i686-unknown-cloudabi",
            "i686-unknown-dragonfly",
            "i686-unknown-freebsd",
            "i686-unknown-haiku",
            "i686-unknown-linux-gnu",
            "i686-unknown-linux-musl",
            "i686-unknown-netbsd",
            "i686-unknown-openbsd",
            "i686-unknown-uefi",
            "i686-uwp-windows-gnu",
            "i686-uwp-windows-msvc",
            "i686-wrs-vxworks",
            "mips64el-unknown-linux-gnuabi64",
            "mips64el-unknown-linux-muslabi64",
            "mips64-unknown-linux-gnuabi64",
            "mips64-unknown-linux-muslabi64",
            "mipsel-unknown-linux-gnu",
            "mipsel-unknown-linux-musl",
            "mipsel-unknown-linux-uclibc",
            "mipsisa32r6el-unknown-linux-gnu",
            "mipsisa32r6-unknown-linux-gnu",
            "mipsisa64r6el-unknown-linux-gnuabi64",
            "mipsisa64r6-unknown-linux-gnuabi64",
            "mips-unknown-linux-gnu",
            "mips-unknown-linux-musl",
            "mips-unknown-linux-uclibc",
            "msp430-none-elf",
            "nvptx64-nvidia-cuda",
            "powerpc64le-unknown-linux-gnu",
            "powerpc64le-unknown-linux-musl",
            "powerpc64-unknown-freebsd",
            "powerpc64-unknown-linux-gnu",
            "powerpc64-unknown-linux-musl",
            "powerpc64-wrs-vxworks",
            "powerpc-unknown-linux-gnu",
            "powerpc-unknown-linux-gnuspe",
            "powerpc-unknown-linux-musl",
            "powerpc-unknown-netbsd",
            "powerpc-wrs-vxworks",
            "powerpc-wrs-vxworks-spe",
            "riscv32imac-unknown-none-elf",
            "riscv32imc-unknown-none-elf",
            "riscv32i-unknown-none-elf",
            "riscv64gc-unknown-none-elf",
            "riscv64imac-unknown-none-elf",
            "s390x-unknown-linux-gnu",
            "sparc64-unknown-linux-gnu",
            "sparc64-unknown-netbsd",
            "sparc64-unknown-openbsd",
            "sparc-unknown-linux-gnu",
            "sparcv9-sun-solaris",
            "thumbv6m-none-eabi",
            "thumbv7a-pc-windows-msvc",
            "thumbv7em-none-eabi",
            "thumbv7em-none-eabihf",
            "thumbv7m-none-eabi",
            "thumbv7neon-linux-androideabi",
            "thumbv7neon-unknown-linux-gnueabihf",
            "thumbv8m.base-none-eabi",
            "thumbv8m.main-none-eabi",
            "thumbv8m.main-none-eabihf",
            "wasm32-experimental-emscripten",
            "wasm32-unknown-emscripten",
            "wasm32-unknown-unknown",
            "wasm32-wasi",
            "x86_64-apple-darwin",
            "x86_64-apple-ios",
            "x86_64-fortanix-unknown-sgx",
            "x86_64-fuchsia",
            "x86_64-linux-android",
            "x86_64-linux-kernel",
            "x86_64-apple-macosx10.7.0",
            "x86_64-pc-solaris",
            "x86_64-pc-windows-gnu",
            "x86_64-pc-windows-msvc",
            "x86_64-rumprun-netbsd",
            "x86_64-sun-solaris",
            "x86_64-unknown-bitrig",
            "x86_64-unknown-cloudabi",
            "x86_64-unknown-dragonfly",
            "x86_64-unknown-freebsd",
            "x86_64-unknown-haiku",
            "x86_64-unknown-hermit",
            "x86_64-unknown-l4re-uclibc",
            "x86_64-unknown-linux-gnu",
            "x86_64-unknown-linux-gnux32",
            "x86_64-unknown-linux-musl",
            "x86_64-unknown-netbsd",
            "x86_64-unknown-openbsd",
            "x86_64-unknown-redox",
            "x86_64-unknown-uefi",
            "x86_64-uwp-windows-gnu",
            "x86_64-uwp-windows-msvc",
            "x86_64-wrs-vxworks",
        ];

        for target in targets.iter() {
            let t = Triple::from_str(target).expect("can't parse target");
            assert_ne!(t.architecture, Architecture::Unknown);
            assert_eq!(t.to_string(), *target);
        }
    }

    #[test]
    fn thumbv7em_none_eabihf() {
        let t = Triple::from_str("thumbv7em-none-eabihf").expect("can't parse target");
        assert_eq!(
            t.architecture,
            Architecture::Arm(ArmArchitecture::Thumbv7em)
        );
        assert_eq!(t.vendor, Vendor::Unknown);
        assert_eq!(t.operating_system, OperatingSystem::None_);
        assert_eq!(t.environment, Environment::Eabihf);
        assert_eq!(t.binary_format, BinaryFormat::Elf);
    }

    #[test]
    fn custom_vendors() {
        // Test various invalid cases.
        assert!(Triple::from_str("x86_64--linux").is_err());
        assert!(Triple::from_str("x86_64-42-linux").is_err());
        assert!(Triple::from_str("x86_64-__customvendor__-linux").is_err());
        assert!(Triple::from_str("x86_64-^-linux").is_err());
        assert!(Triple::from_str("x86_64- -linux").is_err());
        assert!(Triple::from_str("x86_64-CustomVendor-linux").is_err());
        assert!(Triple::from_str("x86_64-linux-linux").is_err());
        assert!(Triple::from_str("x86_64-x86_64-linux").is_err());
        assert!(Triple::from_str("x86_64-elf-linux").is_err());
        assert!(Triple::from_str("x86_64-gnu-linux").is_err());
        assert!(Triple::from_str("x86_64-linux-customvendor").is_err());
        assert!(Triple::from_str("customvendor").is_err());
        assert!(Triple::from_str("customvendor-x86_64").is_err());
        assert!(Triple::from_str("x86_64-").is_err());
        assert!(Triple::from_str("x86_64--").is_err());

        // Test various Unicode things.
        assert!(
            Triple::from_str("x86_64-𝓬𝓾𝓼𝓽𝓸𝓶𝓿𝓮𝓷𝓭𝓸𝓻-linux").is_err(),
            "unicode font hazard"
        );
        assert!(
            Triple::from_str("x86_64-ćúśtőḿvéńdőŕ-linux").is_err(),
            "diacritical mark stripping hazard"
        );
        assert!(
            Triple::from_str("x86_64-customvendοr-linux").is_err(),
            "homoglyph hazard"
        );
        assert!(Triple::from_str("x86_64-customvendor-linux").is_ok());
        assert!(
            Triple::from_str("x86_64-ﬃ-linux").is_err(),
            "normalization hazard"
        );
        assert!(Triple::from_str("x86_64-ffi-linux").is_ok());
        assert!(
            Triple::from_str("x86_64-custom‍vendor-linux").is_err(),
            "zero-width character hazard"
        );
        assert!(
            Triple::from_str("x86_64-﻿customvendor-linux").is_err(),
            "BOM hazard"
        );

        // Test some valid cases.
        let t = Triple::from_str("x86_64-customvendor-linux")
            .expect("can't parse target with custom vendor");
        assert_eq!(t.architecture, Architecture::X86_64);
        assert_eq!(
            t.vendor,
            Vendor::Custom(CustomVendor::Static("customvendor"))
        );
        assert_eq!(t.operating_system, OperatingSystem::Linux);
        assert_eq!(t.environment, Environment::Unknown);
        assert_eq!(t.binary_format, BinaryFormat::Elf);
        assert_eq!(t.to_string(), "x86_64-customvendor-linux");

        let t =
            Triple::from_str("x86_64-customvendor").expect("can't parse target with custom vendor");
        assert_eq!(t.architecture, Architecture::X86_64);
        assert_eq!(
            t.vendor,
            Vendor::Custom(CustomVendor::Static("customvendor"))
        );
        assert_eq!(t.operating_system, OperatingSystem::Unknown);
        assert_eq!(t.environment, Environment::Unknown);
        assert_eq!(t.binary_format, BinaryFormat::Unknown);

        assert_eq!(
            Triple::from_str("unknown-foo"),
            Ok(Triple {
                architecture: Architecture::Unknown,
                vendor: Vendor::Custom(CustomVendor::Static("foo")),
                operating_system: OperatingSystem::Unknown,
                environment: Environment::Unknown,
                binary_format: BinaryFormat::Unknown,
            })
        );
    }
}
