use std::{fmt, str::FromStr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Libc {
Glibc,
Musl,
Lamina,
}
impl Libc {
pub const fn triple_component(self) -> &'static str {
match self {
Self::Glibc => "gnu",
Self::Musl => "musl",
Self::Lamina => "lamina",
}
}
pub const fn is_implemented(self) -> bool {
match self {
Self::Glibc | Self::Musl => true,
Self::Lamina => false,
}
}
pub const fn unavailable_reason(self) -> Option<&'static str> {
match self {
Self::Glibc | Self::Musl => None,
Self::Lamina => Some(
"the Lamina C library is not implemented yet: there is no Lamina libc to link, no start-up objects and no dynamic loader for it",
),
}
}
}
impl fmt::Display for Libc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Glibc => "glibc",
Self::Musl => "musl",
Self::Lamina => "lamina",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CompilerRuntime {
LaminaBuiltins,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TargetEnvironment {
Gnu,
Musl,
Lamina,
Eabi,
Eabihf,
Freestanding,
}
impl TargetEnvironment {
pub const ALL: [Self; 6] = [
Self::Gnu,
Self::Musl,
Self::Lamina,
Self::Eabi,
Self::Eabihf,
Self::Freestanding,
];
pub const fn token(self) -> &'static str {
match self {
Self::Gnu => "gnu",
Self::Musl => "musl",
Self::Lamina => "lamina",
Self::Eabi => "eabi",
Self::Eabihf => "eabihf",
Self::Freestanding => "none",
}
}
pub const fn libc(self) -> Option<Libc> {
match self {
Self::Gnu | Self::Eabi | Self::Eabihf => Some(Libc::Glibc),
Self::Musl => Some(Libc::Musl),
Self::Lamina => Some(Libc::Lamina),
Self::Freestanding => None,
}
}
pub const fn compiler_runtime(self) -> CompilerRuntime {
match self {
Self::Gnu | Self::Musl | Self::Lamina | Self::Eabi | Self::Eabihf => {
CompilerRuntime::LaminaBuiltins
}
Self::Freestanding => CompilerRuntime::None,
}
}
pub const fn hardware_float(self) -> bool {
match self {
Self::Gnu | Self::Musl | Self::Lamina | Self::Eabihf => true,
Self::Eabi | Self::Freestanding => false,
}
}
pub const fn is_hosted(self) -> bool {
self.libc().is_some()
}
}
impl fmt::Display for TargetEnvironment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.token())
}
}
impl FromStr for TargetEnvironment {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"gnu" => Ok(Self::Gnu),
"musl" => Ok(Self::Musl),
"lamina" => Ok(Self::Lamina),
"eabi" => Ok(Self::Eabi),
"eabihf" => Ok(Self::Eabihf),
"none" => Ok(Self::Freestanding),
_ => Err("Unknown target environment"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tokens_round_trip() {
for env in TargetEnvironment::ALL {
assert_eq!(env.token().parse(), Ok(env));
assert_eq!(env.to_string(), env.token());
}
assert!("glibc".parse::<TargetEnvironment>().is_err());
}
#[test]
fn freestanding_links_neither_libc_nor_a_compiler_runtime() {
assert_eq!(TargetEnvironment::Freestanding.libc(), None);
assert_eq!(
TargetEnvironment::Freestanding.compiler_runtime(),
CompilerRuntime::None
);
assert!(!TargetEnvironment::Freestanding.is_hosted());
}
#[test]
fn musl_takes_the_same_compiler_runtime_as_gnu() {
assert_eq!(TargetEnvironment::Musl.libc(), Some(Libc::Musl));
assert_eq!(
TargetEnvironment::Musl.compiler_runtime(),
CompilerRuntime::LaminaBuiltins
);
assert_eq!(
TargetEnvironment::Gnu.compiler_runtime(),
CompilerRuntime::LaminaBuiltins
);
assert_eq!(Libc::Musl.triple_component(), "musl");
assert_eq!(Libc::Glibc.triple_component(), "gnu");
}
#[test]
fn every_hosted_environment_selects_our_own_compiler_runtime() {
for env in TargetEnvironment::ALL {
let expected = if env.is_hosted() {
CompilerRuntime::LaminaBuiltins
} else {
CompilerRuntime::None
};
assert_eq!(env.compiler_runtime(), expected, "{env}");
}
}
#[test]
fn lamina_is_a_libc_of_its_own_taking_our_own_compiler_runtime() {
let env = TargetEnvironment::Lamina;
assert_eq!(env.token(), "lamina");
assert_eq!(env.libc(), Some(Libc::Lamina));
assert_eq!(env.compiler_runtime(), CompilerRuntime::LaminaBuiltins);
assert!(env.is_hosted());
assert!(env.hardware_float());
assert_eq!(Libc::Lamina.triple_component(), "lamina");
assert_eq!(Libc::Lamina.to_string(), "lamina");
}
#[test]
fn the_lamina_libc_is_the_one_that_is_not_implemented_yet() {
assert!(Libc::Glibc.is_implemented());
assert!(Libc::Musl.is_implemented());
assert!(!Libc::Lamina.is_implemented());
assert_eq!(Libc::Glibc.unavailable_reason(), None);
assert_eq!(Libc::Musl.unavailable_reason(), None);
let reason = Libc::Lamina
.unavailable_reason()
.expect("an unimplemented libc states why");
assert!(reason.contains("Lamina C library"), "{reason}");
assert!(reason.contains("not implemented"), "{reason}");
}
#[test]
fn an_unimplemented_libc_is_still_a_hosted_environment() {
for env in TargetEnvironment::ALL {
let hosted = env.libc().is_some();
assert_eq!(env.is_hosted(), hosted, "{env}");
}
}
#[test]
fn only_eabi_and_freestanding_are_soft_float() {
assert!(!TargetEnvironment::Eabi.hardware_float());
assert!(!TargetEnvironment::Freestanding.hardware_float());
assert!(TargetEnvironment::Eabihf.hardware_float());
assert!(TargetEnvironment::Gnu.hardware_float());
assert!(TargetEnvironment::Musl.hardware_float());
}
}