#![doc(html_root_url = "https://docs.rs/rucc-gnu/0.2.13")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Kind {
Attribute,
CAttribute,
Builtin,
Feature,
Extension,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Status {
Unimplemented,
Partial,
Implemented,
Rejected,
}
impl Status {
pub const fn is_available(self) -> bool {
matches!(self, Status::Implemented)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Answer {
Warn,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Feature {
pub name: &'static str,
pub kind: Kind,
pub gcc_version: &'static str,
pub status: Status,
pub answer: Answer,
pub value: u32,
pub used_by: &'static [&'static str],
pub tests: &'static [&'static str],
pub notes: &'static str,
}
include!(concat!(env!("OUT_DIR"), "/features.rs"));
pub fn features() -> &'static [Feature] {
FEATURES
}
pub fn lookup(kind: Kind, name: &str) -> Option<&'static Feature> {
let bare = unarmour(name);
let at = FEATURES.binary_search_by(|f| f.kind.cmp(&kind).then_with(|| f.name.cmp(bare)));
at.ok().map(|at| &FEATURES[at])
}
pub fn has_attribute(name: &str) -> u32 {
answer(Kind::Attribute, name)
}
pub fn has_c_attribute(name: &str) -> u32 {
answer(Kind::CAttribute, name)
}
pub fn has_builtin(name: &str) -> u32 {
answer(Kind::Builtin, name)
}
pub fn has_feature(name: &str) -> u32 {
answer(Kind::Feature, name)
}
pub fn has_extension(name: &str) -> u32 {
let extension = answer(Kind::Extension, name);
if extension == 0 { answer(Kind::Feature, name) } else { extension }
}
fn answer(kind: Kind, name: &str) -> u32 {
match lookup(kind, name) {
Some(feature) if feature.status.is_available() => feature.value,
_ => 0,
}
}
fn unarmour(name: &str) -> &str {
let bare = name.strip_prefix("__").and_then(|n| n.strip_suffix("__"));
match bare {
Some(bare) if !bare.is_empty() && !name.starts_with("__builtin") => bare,
_ => name,
}
}
pub const MILESTONE: &str = "M1";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_table_is_sorted_so_the_lookup_can_be_a_search() {
let keys: Vec<(Kind, &str)> = FEATURES.iter().map(|f| (f.kind, f.name)).collect();
let mut sorted = keys.clone();
sorted.sort_unstable();
assert_eq!(keys, sorted);
}
#[test]
fn every_row_is_findable_by_its_own_name() {
for feature in FEATURES {
assert_eq!(lookup(feature.kind, feature.name), Some(feature));
}
}
#[test]
fn a_name_that_is_not_in_the_matrix_answers_no() {
assert_eq!(has_attribute("nonesuch"), 0);
assert_eq!(has_builtin("__builtin_nonesuch"), 0);
assert_eq!(has_feature("nonesuch"), 0);
assert_eq!(lookup(Kind::Attribute, "nonesuch"), None);
}
#[test]
fn the_armoured_spelling_is_the_same_question() {
assert_eq!(lookup(Kind::Attribute, "__packed__").map(|f| f.name), Some("packed"));
assert_eq!(lookup(Kind::Attribute, "packed").map(|f| f.name), Some("packed"));
assert_eq!(lookup(Kind::Attribute, "__packed"), None, "half the armour is not a name");
}
#[test]
fn a_builtin_keeps_the_prefix_that_is_part_of_its_name() {
assert!(lookup(Kind::Builtin, "__builtin_expect").is_some());
assert_eq!(lookup(Kind::Builtin, "expect"), None);
}
#[test]
fn only_an_implemented_row_answers_yes() {
for feature in FEATURES {
let answered = answer(feature.kind, feature.name);
assert_eq!(
answered != 0,
feature.status == Status::Implemented,
"{} answered {answered} at status {:?}",
feature.name,
feature.status
);
}
}
#[test]
fn an_implemented_row_names_a_test() {
for feature in FEATURES {
if feature.status == Status::Implemented {
assert!(!feature.tests.is_empty(), "{} claims to be implemented", feature.name);
}
}
}
#[test]
fn a_c_attribute_answers_with_the_number_the_standard_gives_it() {
let deprecated = lookup(Kind::CAttribute, "deprecated").expect("C23 has it");
assert_eq!(deprecated.value, 201904);
let gnu = lookup(Kind::Attribute, "deprecated").expect("GCC has it too");
assert_eq!(gnu.value, 1);
}
#[test]
fn ignoring_an_attribute_silently_is_a_decision_the_table_records() {
let packed = lookup(Kind::Attribute, "packed").expect("in the table");
assert_eq!(packed.answer, Answer::Error, "ignoring it would produce wrong code");
let cold = lookup(Kind::Attribute, "cold").expect("in the table");
assert_eq!(cold.answer, Answer::Warn, "ignoring it would only produce slow code");
}
#[test]
fn nested_functions_are_rejected_rather_than_pending() {
let nested = lookup(Kind::Extension, "nested_functions").expect("in the table");
assert_eq!(nested.status, Status::Rejected);
assert!(!nested.notes.is_empty(), "a rejection has to say why");
}
#[test]
fn milestone_is_recorded() {
assert!(MILESTONE.starts_with('M'));
}
}