use crate::Check;
use crate::layout::{Layout, Resolved};
use rt::storage::Kind;
use syntax::ast::{Expr, Figurative, FunctionCall, Literal, Operand, Ref, RefMod};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Class {
Numeric,
Alphabetic,
Alphanumeric,
National,
Dbcs,
}
impl Class {
fn word(self) -> &'static str {
match self {
Class::Numeric => "numeric",
Class::Alphabetic => "alphabetic",
Class::Alphanumeric => "alphanumeric",
Class::National => "national",
Class::Dbcs => "DBCS",
}
}
}
const CHARACTER_ARGUMENT: [&str; 4] = ["LOWER-CASE", "REVERSE", "TRIM", "UPPER-CASE"];
pub fn as_characters(layout: &Layout, f: &FunctionCall) -> Option<Expr> {
let Some(Expr::Operand(Operand::Ref(r))) = f.args.first() else { return None };
let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos) else { return None };
let kind = &layout.items[i].kind;
let rewritten = match f.name.as_str() {
"LENGTH" => matches!(
kind,
Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::Pointer | Kind::Index | Kind::ObjectReference | Kind::ProgramPointer
),
name => CHARACTER_ARGUMENT.contains(&name) && matches!(kind, Kind::Zoned { scale: 0, signed: false, .. }),
};
if r.refmod.is_some() || !rewritten {
return None;
}
let start = Box::new(Expr::Operand(Operand::Literal(Literal::Number("1".into()))));
Some(Expr::Operand(Operand::Ref(Ref { refmod: Some(RefMod { start, length: None }), ..r.clone() })))
}
impl Check<'_> {
pub(crate) fn function_arguments(&mut self, f: &FunctionCall) {
if !f.all_subscripts.is_empty()
&& let Some(func) = rt::lir::Func::named(&f.name)
&& *func.arity().end() != usize::MAX
{
let (least, most) = (*func.arity().start(), *func.arity().end());
let takes = match most - least {
0 => format!("{least}"),
1 => format!("{least} or {most}"),
_ => format!("{least} to {most}"),
};
let message = format!("FUNCTION {}: an ALL subscript stands for a varying number of arguments, and {} takes {takes}", f.name, f.name);
self.errors.push(syntax::messages::IWC0287.at(f.pos, message));
}
let intrinsic = self.intrinsic(&f.name);
if intrinsic && f.args.iter().any(|a| matches!(a, Expr::Operand(Operand::Literal(Literal::Figurative(_) | Literal::All(_))))) {
self.errors.push(syntax::messages::IWC0141.at(f.pos, format!("FUNCTION {}: a figurative constant is an argument only inside an arithmetic expression", f.name)));
}
if intrinsic
&& CHARACTER_ARGUMENT.contains(&f.name.as_str())
&& let Some(first) = f.args.first()
&& self.class(first) == Some(Class::Numeric)
{
let argument = match first {
Expr::Operand(Operand::Ref(r)) => r.name.clone(),
Expr::Operand(Operand::Literal(_)) => "a numeric literal".into(),
Expr::Operand(Operand::Function(g)) => format!("FUNCTION {}", g.name),
_ => "an arithmetic expression".into(),
};
if self.extended && as_characters(self.layout, f).is_some() {
let message = format!("a numeric argument to FUNCTION {} (GnuCOBOL; Enterprise COBOL takes an alphabetic, alphanumeric or national one): {argument}'s digits are read as its characters", f.name);
self.errors.push(syntax::messages::IWX0018.at(f.pos, message));
} else {
let message = format!("FUNCTION {}: {argument} is numeric, where {} takes an alphabetic, alphanumeric or national argument", f.name, f.name);
self.errors.push(syntax::messages::IWC0297.at(f.pos, message));
}
}
if !matches!(f.name.as_str(), "MAX" | "MIN" | "ORD-MAX" | "ORD-MIN") {
return;
}
for a in &f.args {
if let Expr::Operand(Operand::Ref(r)) = a
&& let Ok(Resolved::Item(i)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
&& matches!(self.layout.items[i].kind, Kind::Pointer | Kind::ObjectReference | Kind::ProgramPointer)
{
self.errors.push(syntax::messages::IWC0142.at(f.pos, format!("FUNCTION {}: {} is a pointer or object reference, where an argument is alphabetic, alphanumeric, national or numeric", f.name, r.name)));
}
}
let classes: Vec<Class> = f.args.iter().filter_map(|a| self.class(a)).collect();
let character = |c: Class| if c == Class::Alphabetic { Class::Alphanumeric } else { c };
if let Some((first, rest)) = classes.split_first()
&& let Some(other) = rest.iter().find(|&&c| character(c) != character(*first))
{
let message = format!("FUNCTION {}: {} and {} arguments, where all must be of the same class", f.name, first.word(), other.word());
self.errors.push(syntax::messages::IWC0288.at(f.pos, message));
}
}
pub(crate) fn comparison(&mut self, a: &Expr, b: &Expr) {
for (x, y) in [(a, b), (b, a)] {
if let (Some(left), Some(right)) = (self.native_numeric(x), self.nonnumeric(y)) {
let (first, second) = if std::ptr::eq(x, a) { (left, right) } else { (right, left) };
self.errors.push(syntax::messages::IWC0143.at(self.at, format!("{first} compared with {second}: an arithmetic expression or a numeric function is compared only with a numeric operand")));
return;
}
}
}
fn native_numeric(&self, e: &Expr) -> Option<String> {
match e {
Expr::Bin(..) | Expr::Neg(_) => Some("an arithmetic expression".into()),
Expr::Operand(Operand::Function(f)) if self.function_class(f) == Some(Class::Numeric) => Some(format!("FUNCTION {}", f.name)),
_ => None,
}
}
fn nonnumeric(&self, e: &Expr) -> Option<String> {
let Expr::Operand(op) = e else { return None };
match op {
Operand::Literal(l) => nonnumeric_literal(l).map(str::to_owned),
Operand::Ref(_) if matches!(self.class(e), Some(Class::Numeric) | None) => None,
Operand::Ref(r) => Some(r.name.clone()),
Operand::Function(f) if matches!(self.function_class(f), Some(Class::Alphabetic | Class::Alphanumeric | Class::National)) => Some(format!("FUNCTION {}", f.name)),
_ => None,
}
}
fn class(&self, e: &Expr) -> Option<Class> {
let Expr::Operand(op) = e else { return Some(Class::Numeric) };
match op {
Operand::Literal(Literal::Number(_)) | Operand::LengthOf(_) => Some(Class::Numeric),
Operand::Literal(Literal::Alnum(_) | Literal::Hex(_)) => Some(Class::Alphanumeric),
Operand::Literal(Literal::National(_)) => Some(Class::National),
Operand::Literal(Literal::Dbcs(_)) => Some(Class::Dbcs),
Operand::Literal(_) | Operand::AddressOf(_) => None,
Operand::Ref(r) => match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
Ok(Resolved::Item(i)) => {
let item = &self.layout.items[i];
match item.kind {
Kind::National if r.refmod.is_some() => return Some(Class::National),
Kind::Dbcs { .. } if r.refmod.is_some() => return Some(Class::Dbcs),
_ if r.refmod.is_some() => return Some(Class::Alphanumeric),
_ => {}
}
match item.kind {
Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::Index => Some(Class::Numeric),
Kind::Alnum { .. } if item.alphabetic => Some(Class::Alphabetic),
Kind::Alnum { .. } | Kind::AlnumEdited { .. } | Kind::NumericEdited { .. } | Kind::Group => Some(Class::Alphanumeric),
Kind::National => Some(Class::National),
Kind::Dbcs { .. } => Some(Class::Dbcs),
Kind::Pointer | Kind::ObjectReference | Kind::ProgramPointer => None,
}
}
_ => None,
},
Operand::Function(f) => self.function_class(f),
}
}
pub(crate) fn numeric_max_or_min(&self, f: &FunctionCall) -> bool {
matches!(f.name.as_str(), "MAX" | "MIN") && self.function_class(f) == Some(Class::Numeric)
}
fn function_class(&self, f: &FunctionCall) -> Option<Class> {
let name = f.name.as_str();
if !self.intrinsic(name) {
return None;
}
if !rt::intrinsic::CHARACTER_VALUED.contains(&name) {
return Some(Class::Numeric);
}
match name {
"MAX" | "MIN" => f.args.first().and_then(|a| self.class(a)),
_ if CHARACTER_ARGUMENT.contains(&name) => f.args.first().and_then(|a| self.class(a)).map(|c| if c == Class::Numeric { Class::Alphanumeric } else { c }),
"NATIONAL-OF" => Some(Class::National),
"CONTENT-OF" | "USUBSTR" => None,
_ => Some(Class::Alphanumeric),
}
}
}
fn nonnumeric_literal(l: &Literal) -> Option<&'static str> {
Some(match l {
Literal::Alnum(_) | Literal::Hex(_) => "an alphanumeric literal",
Literal::National(_) => "a national literal",
Literal::Dbcs(_) => "a DBCS literal",
Literal::Figurative(Figurative::Space) => "SPACE",
Literal::Figurative(Figurative::HighValue) => "HIGH-VALUE",
Literal::Figurative(Figurative::LowValue) => "LOW-VALUE",
Literal::Figurative(Figurative::Quote) => "QUOTE",
Literal::Figurative(Figurative::Zero | Figurative::Null) | Literal::Number(_) => return None,
Literal::All(inner) => match &**inner {
Literal::Figurative(_) => return nonnumeric_literal(inner),
Literal::National(_) => "an ALL national literal",
Literal::Dbcs(_) => "an ALL DBCS literal",
_ => "an ALL literal",
},
})
}