use rucc_ast as ast;
use rucc_ast::BinaryOp;
use rucc_base::Symbol;
use rucc_diag::{Diagnostic, Span};
use rucc_types::{FloatKind, TypeId, is_real_floating};
use crate::check::Checker;
use crate::expr::{Category, Classify, Expr, ExprId, ExprKind};
#[derive(Debug, Clone, Copy)]
struct Question {
name: &'static str,
asks: Asks,
at: Option<FloatKind>,
}
#[derive(Debug, Clone, Copy)]
enum Asks {
Operator(BinaryOp),
Node(Classify),
}
impl Asks {
const fn is_pair(self) -> bool {
match self {
Asks::Operator(_) => true,
Asks::Node(op) => op.is_pair(),
}
}
}
const fn any(name: &'static str, asks: Asks) -> Question {
Question { name, asks, at: None }
}
const fn at(name: &'static str, asks: Asks, at: FloatKind) -> Question {
Question { name, asks, at: Some(at) }
}
const FAMILY: &[Question] = &[
any("__builtin_isgreater", Asks::Operator(BinaryOp::Gt)),
any("__builtin_isgreaterequal", Asks::Operator(BinaryOp::Ge)),
any("__builtin_isless", Asks::Operator(BinaryOp::Lt)),
any("__builtin_islessequal", Asks::Operator(BinaryOp::Le)),
any("__builtin_islessgreater", Asks::Node(Classify::LessGreater)),
any("__builtin_isunordered", Asks::Node(Classify::Unordered)),
any("__builtin_isnan", Asks::Node(Classify::Nan)),
at("__builtin_isnanf", Asks::Node(Classify::Nan), FloatKind::Float),
at("__builtin_isnanl", Asks::Node(Classify::Nan), FloatKind::LongDouble),
any("__builtin_isinf", Asks::Node(Classify::Infinite)),
at("__builtin_isinff", Asks::Node(Classify::Infinite), FloatKind::Float),
at("__builtin_isinfl", Asks::Node(Classify::Infinite), FloatKind::LongDouble),
any("__builtin_isfinite", Asks::Node(Classify::Finite)),
at("__builtin_finite", Asks::Node(Classify::Finite), FloatKind::Double),
at("__builtin_finitef", Asks::Node(Classify::Finite), FloatKind::Float),
at("__builtin_finitel", Asks::Node(Classify::Finite), FloatKind::LongDouble),
any("__builtin_signbit", Asks::Node(Classify::SignBit)),
at("__builtin_signbitf", Asks::Node(Classify::SignBit), FloatKind::Float),
at("__builtin_signbitl", Asks::Node(Classify::SignBit), FloatKind::LongDouble),
];
#[cfg(test)]
pub(super) fn is_family(name: &str) -> bool {
FAMILY.iter().any(|question| question.name == name)
}
impl Checker<'_> {
pub(super) fn classify_builtin_call(
&mut self,
name: Symbol,
args: ast::ExprList,
span: Span,
) -> Option<ExprId> {
let spelled = self.text(name);
let question = *FAMILY.iter().find(|question| question.name == spelled)?;
Some(self.classify_call(question, args, span))
}
fn classify_call(&mut self, question: Question, args: ast::ExprList, span: Span) -> ExprId {
let spelled = question.name;
let written: Vec<ast::ExprId> = self.ast[args].to_vec();
let args: Vec<ExprId> = written
.into_iter()
.map(|arg| {
let arg = self.expr(arg);
self.value(arg)
})
.collect();
let wanted = if question.asks.is_pair() { 2 } else { 1 };
if args.len() != wanted {
let how = if args.len() < wanted { "few" } else { "many" };
self.report(
Diagnostic::error(format!("too {how} arguments to function '{spelled}'"), span)
.with_code("E0511"),
);
return self.poison(span);
}
if args.iter().any(|&arg| self.is_poisoned(arg)) {
return self.poison(span);
}
let converted: Vec<ExprId> = match question.at {
None => args,
Some(kind) => {
let ty = self.types.float(kind);
args.into_iter().map(|arg| self.convert_argument(arg, ty)).collect()
}
};
if !converted.iter().all(|&arg| is_real_floating(&self.types, self.tast[arg].ty)) {
let s = if wanted == 2 { "s" } else { "" };
self.report(
Diagnostic::error(
format!("non-floating-point argument{s} in call to function '{spelled}'"),
span,
)
.with_code("E0685"),
);
return self.poison(span);
}
match question.asks {
Asks::Operator(op) => self.comparison(op, converted[0], converted[1], span),
Asks::Node(op) if op.is_pair() => {
let (lhs, rhs) = self
.conv()
.usual_arithmetic(converted[0], converted[1])
.expect("two floating point operands");
self.classify_node(op, lhs, Some(rhs), span)
}
Asks::Node(op) => self.classify_node(op, converted[0], None, span),
}
}
fn convert_argument(&mut self, arg: ExprId, ty: TypeId) -> ExprId {
if rucc_types::is_arithmetic(&self.types, self.tast[arg].ty) {
return self.conv().to_type(arg, ty);
}
arg
}
fn classify_node(
&mut self,
op: Classify,
lhs: ExprId,
rhs: Option<ExprId>,
span: Span,
) -> ExprId {
let ty = self.int();
self.tast.expr(Expr::new(ExprKind::Classify { op, lhs, rhs }, ty, Category::Rvalue), span)
}
}
#[cfg(test)]
mod tests {
use rucc_gnu::{Kind, Status};
use super::*;
#[test]
fn every_name_in_the_family_is_a_row_of_the_table_and_says_it_is_done() {
for question in FAMILY {
let feature = rucc_gnu::lookup(Kind::Builtin, question.name);
let Some(feature) = feature else {
panic!("{} is answered here and is not in features.toml", question.name);
};
assert_eq!(feature.status, Status::Implemented, "{}", question.name);
assert!(feature.signature.is_empty(), "{} is answered and not called", question.name);
}
}
#[test]
fn nothing_that_asks_about_a_pair_names_a_type() {
for question in FAMILY {
assert!(!question.asks.is_pair() || question.at.is_none(), "{}", question.name);
}
}
#[test]
fn no_name_is_in_the_table_twice() {
let mut names: Vec<&str> = FAMILY.iter().map(|question| question.name).collect();
names.sort_unstable();
let all = names.len();
names.dedup();
assert_eq!(names.len(), all, "a name is in the table twice");
}
}