use super::data::{Side, Value};
use super::{Lower, LowerError, R, push, unsupported};
use crate::layout::Resolved;
use numeric::Numproc;
use rt::lir::{self, AbendId, ByteClass, Comparand, Compare, CondId, Mode, SignTest};
use rt::storage::Kind;
use syntax::Pos;
use syntax::ast::{Class, Cond, Expr, Figurative, Literal, Operand, Ref, RelOp};
pub(super) enum Test {
Cond(CondId),
Abend(AbendId, Pos),
Not(Box<Test>),
And(Box<Test>, Box<Test>),
Or(Box<Test>, Box<Test>),
}
impl Test {
pub(super) fn abends(&self) -> bool {
match self {
Self::Cond(_) => false,
Self::Abend(..) => true,
Self::Not(t) => t.abends(),
Self::And(a, b) | Self::Or(a, b) => a.abends() || b.abends(),
}
}
pub(super) fn not(self) -> Self {
Self::Not(Box::new(self))
}
}
impl Lower<'_> {
pub(super) fn cond(&mut self, c: lir::Cond) -> R<CondId> {
push(&mut self.conds, c, "conditions")
}
pub(super) fn fold(&mut self, t: &Test) -> R<CondId> {
match t {
Test::Cond(c) => Ok(*c),
Test::Abend(..) => Err(LowerError::Invalid("an abending test folded into a condition".into())),
Test::Not(a) => {
let a = self.fold(a)?;
self.cond(lir::Cond::Not(a))
}
Test::And(a, b) => {
let (a, b) = (self.fold(a)?, self.fold(b)?);
self.cond(lir::Cond::And(a, b))
}
Test::Or(a, b) => {
let (a, b) = (self.fold(a)?, self.fold(b)?);
self.cond(lir::Cond::Or(a, b))
}
}
}
pub(super) fn test(&mut self, c: &Cond, pos: Pos) -> R<Test> {
Ok(match c {
Cond::Rel(a, op, b) => self.relation(a, *op, b, pos)?,
Cond::Not(inner) => self.test(inner, pos)?.not(),
Cond::And(a, b) => Test::And(Box::new(self.test(a, pos)?), Box::new(self.test(b, pos)?)),
Cond::Or(a, b) => Test::Or(Box::new(self.test(a, pos)?), Box::new(self.test(b, pos)?)),
Cond::Class(e, class) => self.class(e, *class, pos)?,
Cond::NameOrRel { subject, op, negated, name } => match self.layout.resolve(&name.name, &name.qualifiers, name.pos) {
Ok(Resolved::Condition(_)) => self.condition_name(name, pos)?,
Ok(Resolved::Item(_)) if *negated => self.relation(subject, *op, &Expr::Operand(Operand::Ref(name.clone())), pos)?.not(),
Ok(Resolved::Item(_)) => self.relation(subject, *op, &Expr::Operand(Operand::Ref(name.clone())), pos)?,
Err(e) => Test::Abend(self.ironwork(&e.message)?, name.pos),
},
Cond::Name(r) => self.condition_name(r, pos)?,
})
}
pub(super) fn relation(&mut self, a: &Expr, op: RelOp, b: &Expr, pos: Pos) -> R<Test> {
let (a, b, how) = self.comparison(a, b, pos)?;
Ok(Test::Cond(self.cond(lir::Cond::Rel { a, op, b, how })?))
}
pub(super) fn comparison(&mut self, a: &Expr, b: &Expr, pos: Pos) -> R<(Comparand, Comparand, Compare)> {
let all = |e: &Expr| matches!(e, Expr::Operand(Operand::Literal(Literal::All(_))));
let all_zero = |e: &Expr| matches!(e, Expr::Operand(Operand::Literal(Literal::All(f))) if matches!(**f, Literal::Figurative(Figurative::Zero | Figurative::Null)));
let (a_all, b_all) = (all(a), all(b));
let (a_zero, b_zero) = (all_zero(a), all_zero(b));
let (a, x) = self.comparand(a, pos)?;
let (b, y) = self.comparand(b, pos)?;
let tested = |lower: &Self, c: &Comparand| matches!(c, Comparand::Operand(lir::Operand::Load(p)) if lower.read_tested(*p));
if self.c.options.numcheck.is_some() && (a_zero && tested(self, &b) || b_zero && tested(self, &a)) {
return unsupported("NUMCHECK with ALL ZERO or ALL NULL compared with a data item it may test", pos);
}
let how = if self.zoned_against(&a, &x, &b, &y, b_all) {
Compare::ZonedBytes { zoned_first: true }
} else if self.zoned_against(&b, &y, &a, &x, a_all) {
Compare::ZonedBytes { zoned_first: false }
} else {
self.compare(&x, &y, pos)?
};
Ok((a, b, how))
}
fn zoned_against(&self, c: &Comparand, x: &Side, oc: &Comparand, other: &Side, other_all: bool) -> bool {
let Some(p) = self.unscaled_zoned(c, x) else { return false };
let nonnumeric = other_all || matches!(other.value, Value::Bytes | Value::All) || matches!(other.value, Value::Fig(f) if !matches!(f, Figurative::Zero | Figurative::Null));
let unsigned = |s: &Side| matches!(s.src, Some(Kind::Zoned { signed: false, .. }));
let zero = match oc {
Comparand::Operand(lir::Operand::Const(k)) => match &self.consts[*k as usize] {
lir::Const::Figurative(Figurative::Zero) => true,
lir::Const::Number(f) => f.magnitude == zarch::wide::U256::ZERO,
_ => false,
},
_ => false,
};
let options = self.c.options;
let zones_count = unsigned(x)
&& (options.zones_compared_with_zero() && zero
|| options.zones_compared_between_items() && unsigned(other) && self.unscaled_zoned(oc, other).is_some_and(|q| self.places[q].len == self.places[p].len));
nonnumeric || zones_count
}
fn unscaled_zoned(&self, c: &Comparand, x: &Side) -> Option<usize> {
let Comparand::Operand(lir::Operand::Load(p)) = c else { return None };
let p = *p as usize;
let unscaled = self.place_items[p].is_none_or(|i| self.layout.items[i].scaling == 0);
(matches!(x.src, Some(Kind::Zoned { scale: 0, .. })) && self.places[p].refmod.is_none() && unscaled).then_some(p)
}
pub(super) fn comparand(&mut self, e: &Expr, pos: Pos) -> R<(Comparand, Side)> {
if let Expr::Operand(op) = e {
let lowered = self.operand(op, pos)?;
return Ok((Comparand::Operand(lowered.operand), lowered.side));
}
let computed = self.computed(e, pos)?;
let value = match computed {
Comparand::Expr { mode: Mode::Float(_), .. } => Value::Float,
_ => Value::Num(None),
};
Ok((computed, Side { value, src: None, digits: 0 }))
}
pub(super) fn compare(&mut self, x: &Side, y: &Side, pos: Pos) -> R<Compare> {
if let (Some(Kind::Packed { .. }), Numproc::Pfd) = (x.src, self.c.options.numproc)
&& x.src == y.src
{
return Ok(Compare::PackedPfd);
}
let reference = |s: &Side| s.src == Some(Kind::ObjectReference);
if x.value == Value::Address && y.value == Value::Address && (reference(x) || reference(y)) {
return Ok(Compare::References);
}
let address = |v: Value| matches!(v, Value::Address | Value::Fig(Figurative::Null));
if x.value == Value::Address || y.value == Value::Address {
return Ok(if address(x.value) && address(y.value) {
Compare::Address
} else {
Compare::Refused(self.ironwork("a pointer compared with something other than a pointer or NULL")?)
});
}
let numeric = |v: Value| matches!(v, Value::Num(_) | Value::Float | Value::Fig(Figurative::Zero));
Ok(match (x.value, y.value) {
(Value::Float, _) | (_, Value::Float) if numeric(x.value) && numeric(y.value) => Compare::Float,
(Value::Num(_), Value::Num(_)) | (Value::Num(_), Value::Fig(Figurative::Zero)) | (Value::Fig(Figurative::Zero), Value::Num(_)) => Compare::Fixed,
(Value::National, Value::National) => Compare::National,
(Value::Dbcs, _) | (_, Value::Dbcs) => Compare::Dbcs,
_ => match alphanumeric_refusal(x, pos)?.or(alphanumeric_refusal(y, pos)?) {
Some(message) => Compare::Refused(self.ironwork(message)?),
None => Compare::Alphanumeric,
},
})
}
fn class(&mut self, e: &Expr, class: Class, pos: Pos) -> R<Test> {
if let (Class::Numeric | Class::Alphabetic | Class::AlphabeticLower | Class::AlphabeticUpper | Class::Dbcs | Class::Kanji, Expr::Operand(Operand::Ref(r))) = (class, e) {
let place = self.place(r, false)?;
let test = match (class, self.kind_of(place)) {
(Class::Numeric, Kind::Packed { signed, .. }) => ByteClass::Packed { signed },
(Class::Numeric, Kind::Zoned { signed, sign: None, .. }) => ByteClass::Zoned { signed },
(Class::Numeric, _) => ByteClass::Digits,
(Class::AlphabeticLower, _) => ByteClass::AlphabeticLower,
(Class::AlphabeticUpper, _) => ByteClass::AlphabeticUpper,
(Class::Dbcs, _) => ByteClass::Dbcs,
(Class::Kanji, _) => ByteClass::Kanji,
_ => ByteClass::Alphabetic,
};
return Ok(Test::Cond(self.cond(lir::Cond::Class { place, test })?));
}
let value = match e {
Expr::Operand(op) => Comparand::Operand(self.operand(op, pos)?.operand),
_ => self.computed(e, pos)?,
};
let test = match class {
Class::Positive => SignTest::Positive,
Class::Negative => SignTest::Negative,
Class::Numeric | Class::Alphabetic | Class::AlphabeticLower | Class::AlphabeticUpper | Class::Dbcs | Class::Kanji | Class::Zero => SignTest::Zero,
};
Ok(Test::Cond(self.cond(lir::Cond::Sign { value, test })?))
}
fn condition_name(&mut self, r: &Ref, pos: Pos) -> R<Test> {
let layout = self.layout;
let index = match layout.resolve(&r.name, &r.qualifiers, r.pos) {
Ok(Resolved::Condition(i)) => i,
Ok(Resolved::Item(_)) => return Ok(Test::Abend(self.ironwork(&format!("{} is a data item, not a condition", r.name))?, r.pos)),
Err(e) => return Ok(Test::Abend(self.ironwork(&e.message)?, r.pos)),
};
let subject = match self.conditional_variable(index, r)? {
Ok(place) => place,
Err((abend, at)) => return Ok(Test::Abend(abend, at)),
};
self.condition_values(index, subject, pos)
}
pub(super) fn condition_values(&mut self, index: usize, subject: lir::PlaceId, pos: Pos) -> R<Test> {
let condition = &self.layout.conditions[index];
let kind = self.kind_of(subject);
let x = Side { value: super::data::value_of(kind), src: Some(kind), digits: kind.digits_scale().map_or(0, |(d, _)| d) };
let variable = Comparand::Operand(lir::Operand::Load(subject));
let how = |lower: &mut Self, literal: &Literal| -> R<(lir::ConstId, Compare)> {
let (c, side) = lower.literal_const(literal, pos)?;
let all = matches!(literal, Literal::All(_));
let how = if lower.zoned_against(&variable, &x, &Comparand::Operand(lir::Operand::Const(c)), &side, all) { Compare::ZonedBytes { zoned_first: true } } else { lower.compare(&x, &side, pos)? };
Ok((c, how))
};
let mut values = Vec::new();
let mut hows = Vec::new();
for (low, high) in &condition.values {
let (low, low_how) = how(self, low)?;
hows.push(low_how);
let high = match high {
Some(h) => {
let (h, high_how) = how(self, h)?;
hows.push(high_how);
Some(h)
}
None => None,
};
values.push((low, high));
}
let how = hows.first().copied().unwrap_or(Compare::Alphanumeric);
if hows.iter().all(|&h| h == how) {
return Ok(Test::Cond(self.cond(lir::Cond::Name { subject, values, how })?));
}
if self.c.options.numcheck.is_some() {
return unsupported("NUMCHECK with a condition-name whose values are of different categories", pos);
}
let mut hows = hows.into_iter();
let mut rel = |lower: &mut Self, op: RelOp, value| -> R<Test> {
let how = hows.next().unwrap_or(how);
Ok(Test::Cond(lower.cond(lir::Cond::Rel { a: Comparand::Operand(lir::Operand::Load(subject)), op, b: Comparand::Operand(lir::Operand::Const(value)), how })?))
};
let mut alternatives = Vec::new();
for (low, high) in values {
alternatives.push(match high {
None => rel(self, RelOp::Eq, low)?,
Some(high) => {
let ge = rel(self, RelOp::Ge, low)?;
Test::And(Box::new(ge), Box::new(rel(self, RelOp::Le, high)?))
}
});
}
let mut alternatives = alternatives.into_iter().rev();
let last = alternatives.next().ok_or_else(|| LowerError::Invalid("a condition-name of mixed values with none".into()))?;
Ok(alternatives.fold(last, |rest, t| Test::Or(Box::new(t), Box::new(rest))))
}
}
fn alphanumeric_refusal(side: &Side, pos: Pos) -> R<Option<&'static str>> {
Ok(match side.value {
Value::Bytes | Value::All | Value::Fig(_) | Value::Num(Some(0)) | Value::Dbcs => None,
Value::Num(None) => return super::unsupported("an arithmetic expression compared with a non-numeric operand", pos),
Value::National => Some("a national value cannot be moved to an alphanumeric item"),
Value::Num(Some(_)) | Value::Float | Value::Address => Some("only an integer numeric value can be moved to an alphanumeric item"),
})
}