use crate::codec;
use crate::fixed::{compare_fixed, fixed};
use crate::lir;
use crate::storage::{Kind, Val};
use crate::store::compare_national;
use crate::vocab::{SignClause, SignPosition};
use numeric::Numproc;
use numeric::precision::Places;
use std::cmp::Ordering;
use std::fmt;
use std::rc::Rc;
use zarch::check::ProgramCheck;
use zarch::ebcdic::{self, CodePage, Collation};
use zarch::hfp::Hfp;
use zarch::wide::U256;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Format {
Ch,
Ac,
Zd,
Clo,
Csl,
Cst,
Pd,
Bi,
Fi,
}
impl Format {
pub fn of_decimal(kind: Kind) -> Option<Format> {
Some(match kind {
Kind::Packed { .. } => Format::Pd,
Kind::Zoned { sign: Some(SignClause { separate: true, position: SignPosition::Leading }), .. } => Format::Csl,
Kind::Zoned { sign: Some(SignClause { separate: true, position: SignPosition::Trailing }), .. } => Format::Cst,
Kind::Zoned { sign: Some(SignClause { separate: false, position: SignPosition::Leading }), .. } => Format::Clo,
Kind::Zoned { .. } => Format::Zd,
_ => return None,
})
}
fn sign(self) -> Option<SignClause> {
match self {
Format::Clo => Some(SignClause { position: SignPosition::Leading, separate: false }),
Format::Csl => Some(SignClause { position: SignPosition::Leading, separate: true }),
Format::Cst => Some(SignClause { position: SignPosition::Trailing, separate: true }),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Key {
pub position: usize,
pub length: usize,
pub format: Format,
pub ascending: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Collating {
#[default]
Ebcdic,
Positions(Rc<[u8; 256]>),
}
impl Collating {
pub fn ascii(page: &CodePage) -> Self {
let given: Vec<u8> = (0..0x80u8).filter_map(|c| page.encode_char(c as char)).collect();
let mut taken = [false; 256];
given.iter().for_each(|&b| taken[usize::from(b)] = true);
let order = given.into_iter().chain((0..=255u8).filter(|&b| !taken[usize::from(b)]));
let mut positions = [0u8; 256];
for (at, b) in order.enumerate() {
positions[usize::from(b)] = at as u8;
}
Collating::Positions(Rc::new(positions))
}
pub fn of(collating: &lir::Collating) -> Self {
match collating {
lir::Collating::Native => Collating::Ebcdic,
lir::Collating::Sequence(s) => Collating::Positions(Rc::new(*s.positions)),
}
}
pub fn is_ebcdic(&self) -> bool {
matches!(self, Collating::Ebcdic)
}
pub fn collate(&self, bytes: &[u8]) -> Vec<u8> {
match self {
Collating::Ebcdic => bytes.to_vec(),
Collating::Positions(p) => bytes.iter().map(|&b| p[usize::from(b)]).collect(),
}
}
}
#[derive(Clone, Debug)]
pub enum KeyValue {
Read(Val),
Decimal { negative: bool, digits: Vec<u8> },
Collated(Vec<u8>),
}
pub fn order(a: &[KeyValue], b: &[KeyValue], ascending: &[bool]) -> Ordering {
for ((x, y), &up) in a.iter().zip(b).zip(ascending) {
let o = match (x, y) {
(KeyValue::Read(Val::Num(x)), KeyValue::Read(Val::Num(y))) => compare_fixed(x, y),
(KeyValue::Read(Val::Float(x)), KeyValue::Read(Val::Float(y))) => float_order(*x, *y),
(KeyValue::Read(Val::National(x)), KeyValue::Read(Val::National(y))) => compare_national(x, y),
(KeyValue::Read(Val::Bytes(x)), KeyValue::Read(Val::Bytes(y))) => ebcdic::compare_alphanumeric(x, y, &Collation::Native),
(KeyValue::Collated(x), KeyValue::Collated(y)) => x.cmp(y),
(KeyValue::Decimal { negative: false, digits: x }, KeyValue::Decimal { negative: false, digits: y }) => x.cmp(y),
(KeyValue::Decimal { negative: true, digits: x }, KeyValue::Decimal { negative: true, digits: y }) => y.cmp(x),
(KeyValue::Decimal { negative, .. }, KeyValue::Decimal { .. }) => if *negative { Ordering::Less } else { Ordering::Greater },
_ => Ordering::Equal,
};
let o = if up { o } else { o.reverse() };
if o != Ordering::Equal {
return o;
}
}
Ordering::Equal
}
pub fn float_order(a: Hfp, b: Hfp) -> Ordering {
let exact = |h: Hfp| {
if h.fraction == 0 {
return (0, 0, 0);
}
let top = 4 * h.precision.digits() - 4;
let (mut exponent, mut fraction) = (h.characteristic as i32, h.fraction);
while fraction >> top == 0 {
fraction <<= 4;
exponent -= 1;
}
(if h.negative { -1 } else { 1 }, exponent, fraction)
};
let ((sa, ea, fa), (sb, eb, fb)) = (exact(a), exact(b));
match sa.cmp(&sb) {
Ordering::Equal if sa < 0 => (eb, fb).cmp(&(ea, fa)),
Ordering::Equal => (ea, fa).cmp(&(eb, fb)),
other => other,
}
}
pub fn decimal(bytes: &[u8], format: Format) -> Option<(bool, Vec<u8>)> {
let negative = |sign: u8| sign % 2 == 1 && sign != 0xF;
let low = |b: &[u8]| b.iter().map(|b| b & 0x0F).collect();
match format {
Format::Pd => {
let (last, body) = bytes.split_last()?;
let mut digits: Vec<u8> = body.iter().flat_map(|b| [b >> 4, b & 0x0F]).collect();
digits.push(last >> 4);
Some((negative(last & 0x0F), digits))
}
Format::Csl | Format::Cst => {
let (sign, body) = if format == Format::Csl { bytes.split_first()? } else { bytes.split_last()? };
Some((*sign == 0x60, low(body)))
}
Format::Clo => Some((negative(bytes.first()? >> 4), low(bytes))),
Format::Zd => Some((negative(bytes.last()? >> 4), low(bytes))),
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyError {
Short { record: usize, length: usize },
Data { record: usize, key: usize, check: ProgramCheck },
}
impl fmt::Display for KeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyError::Short { length, .. } => write!(f, "a record of {length} bytes ends inside a key"),
KeyError::Data { key, check, .. } => write!(f, "key {} is not valid data for its format: {check}", key + 1),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Keys {
pub keys: Vec<Key>,
pub collating: Collating,
pub ascii: Collating,
pub strict: Option<Numproc>,
}
impl Keys {
pub fn new(keys: Vec<Key>, page: &CodePage) -> Self {
Keys { keys, collating: Collating::Ebcdic, ascii: Collating::ascii(page), strict: None }
}
pub fn ascending(&self) -> Vec<bool> {
self.keys.iter().map(|k| k.ascending).collect()
}
pub fn values(&self, record: &[u8], index: usize) -> Result<Vec<KeyValue>, KeyError> {
let mut out = Vec::with_capacity(self.keys.len());
for (n, k) in self.keys.iter().enumerate() {
let bytes = record.get(k.position..k.position + k.length).ok_or(KeyError::Short { record: index, length: record.len() })?;
out.push(match k.format {
Format::Ch => KeyValue::Collated(self.collating.collate(bytes)),
Format::Ac => KeyValue::Collated(self.ascii.collate(bytes)),
Format::Bi => KeyValue::Collated(bytes.to_vec()),
Format::Fi => KeyValue::Collated(bytes.iter().enumerate().map(|(i, &b)| if i == 0 { b ^ 0x80 } else { b }).collect()),
decimal_format => match self.strict {
Some(numproc) => {
let read = strict(bytes, decimal_format, numproc).map_err(|check| KeyError::Data { record: index, key: n, check })?;
KeyValue::Read(Val::Num(read))
}
None => match decimal(bytes, decimal_format) {
Some((negative, digits)) => KeyValue::Decimal { negative, digits },
None => KeyValue::Collated(Vec::new()),
},
},
});
}
Ok(out)
}
pub fn compare(&self, a: &[u8], b: &[u8]) -> Result<Ordering, KeyError> {
Ok(order(&self.values(a, 0)?, &self.values(b, 1)?, &self.ascending()))
}
pub fn sort(&self, records: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>, KeyError> {
let mut entries = Vec::with_capacity(records.len());
for (i, record) in records.into_iter().enumerate() {
let values = self.values(&record, i)?;
entries.push((record, values));
}
let ascending = self.ascending();
entries.sort_by(|a, b| order(&a.1, &b.1, &ascending));
Ok(entries.into_iter().map(|(record, _)| record).collect())
}
pub fn out_of_order(&self, records: &[Vec<u8>]) -> Result<Option<usize>, KeyError> {
let ascending = self.ascending();
let mut last: Option<Vec<KeyValue>> = None;
for (i, record) in records.iter().enumerate() {
let values = self.values(record, i)?;
if last.as_ref().is_some_and(|l| order(l, &values, &ascending) == Ordering::Greater) {
return Ok(Some(i));
}
last = Some(values);
}
Ok(None)
}
}
fn strict(bytes: &[u8], format: Format, numproc: Numproc) -> Result<numeric::precision::Fixed, ProgramCheck> {
let shortest = if matches!(format, Format::Csl | Format::Cst) { 2 } else { 1 };
if bytes.len() < shortest {
return Err(ProgramCheck::Data);
}
let d = match format {
Format::Pd => codec::packed(bytes, true, numproc)?,
_ => codec::zoned(bytes, true, format.sign(), numproc)?,
};
Ok(fixed(d.negative, U256::from_u128(d.magnitude), Places::new(31, 0)))
}