Skip to main content

ironwork_rt/
store.rs

1//! Storage and MOVE: an item's bytes and value by its `Loc`, numeric stores with their size-error
2//! and TRUNC(OPT) rules, MOVE, and the comparisons and class tests of conditions.
3
4use crate::abend::Abend;
5use crate::codec;
6use crate::edit;
7use crate::fixed::{MAX_DIGITS, align, compare_fixed, fixed, places_of, pow10, scaled_down, scaled_up, zoned_digits};
8use crate::lir::{ByteClass, SignTest};
9use crate::picture::Sym;
10use crate::storage::{Kind, Loc, Val};
11use crate::unit::{Loader, RunUnit};
12use crate::vocab::{Figurative, Pos, SignClause, SignPosition};
13use numeric::binary::{self, Binary};
14use numeric::precision::{Fixed, Places};
15use numeric::{Numproc, Options, Quote, Trunc, float, sign};
16use std::cmp::Ordering;
17use zarch::check::ProgramMask;
18use zarch::decimal::{self, Decimal};
19use zarch::ebcdic::{self, CodePage, Collation};
20use zarch::hfp::{Hfp, Precision};
21use zarch::wide::U256;
22
23type R<T> = Result<T, Abend>;
24
25/// What these semantics read of the running program beyond a `Loc`: its options, code page and
26/// collating sequence, its edited PICTUREs, and its items by the index a `Loc` carries.
27pub trait ProgramFacts {
28    fn options(&self) -> Options;
29    fn page(&self) -> &'static CodePage;
30    /// A figurative constant's character: HIGH-VALUE and LOW-VALUE are the collating sequence's.
31    fn figurative(&self, f: Figurative) -> u8;
32    fn collation(&self) -> &Collation;
33    /// A character's ordinal position in the collating sequence, from 1, which FUNCTION ORD gives.
34    fn ordinal(&self, byte: u8) -> u16;
35    /// The character at an ordinal position, which FUNCTION CHAR gives.
36    fn character(&self, ordinal: i64) -> Option<u8>;
37    /// How many characters the collating sequence orders.
38    fn characters(&self) -> usize;
39    fn decimal_point(&self) -> char;
40    /// An edited PICTURE's symbols, and the currency sign it shows.
41    fn edit(&self, edit: u32) -> (&[Sym], &str);
42    /// PICTURE P positions to the right of an item's digits.
43    fn scaling(&self, item: usize) -> u32;
44    /// The item a TRUNC(OPT) report names.
45    fn item_name(&self, item: usize) -> String;
46}
47
48pub fn bytes(mem: &[u8], loc: Loc) -> &[u8] {
49    &mem[loc.offset..loc.offset + loc.len]
50}
51
52pub fn write(mem: &mut [u8], loc: Loc, bytes: &[u8]) {
53    mem[loc.offset..loc.offset + loc.len].copy_from_slice(bytes);
54}
55
56/// PICTURE scaling positions to the right of a numeric item's digits.
57pub fn scaling(facts: &dyn ProgramFacts, loc: Loc) -> u32 {
58    match loc.kind {
59        Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::NumericEdited { .. } => facts.scaling(loc.item),
60        _ => 0,
61    }
62}
63
64/// The places of the value a numeric item holds, its scaling positions included.
65pub fn places(facts: &dyn ProgramFacts, loc: Loc) -> Places {
66    let places = places_of(loc.kind);
67    Places::new(places.int + scaling(facts, loc), places.dec)
68}
69
70pub fn read(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, pos: Pos) -> R<Val> {
71    let value = read_stored(facts, mem, loc, pos)?;
72    Ok(match value {
73        Val::Num(f) => Val::Num(scaled_up(f, scaling(facts, loc))),
74        other => other,
75    })
76}
77
78/// An item's value as its digits hold it, before any scaling positions to their right.
79pub fn read_stored(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, pos: Pos) -> R<Val> {
80    let cleaned = facts.options().invdata.is_some_and(|i| i.cleansign).then(|| sign_cleaned(bytes(mem, loc), loc.kind)).flatten();
81    let bytes = cleaned.as_deref().unwrap_or(bytes(mem, loc));
82    let places = places_of(loc.kind);
83    Ok(match loc.kind {
84        Kind::Group | Kind::Alnum { .. } | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. } => Val::Bytes(bytes.to_vec()),
85        Kind::National => Val::National(bytes.to_vec()),
86        Kind::Pointer | Kind::ObjectReference | Kind::ProgramPointer => Val::Address(u32::from_be_bytes(bytes.try_into().unwrap())),
87        Kind::Index => Val::Num(Fixed::new(i32::from_be_bytes(bytes.try_into().unwrap()) as i128, Places::new(9, 0))),
88        Kind::Float(p) => Val::Float(Hfp::from_bytes(p, bytes)),
89        Kind::Binary { digits, signed, native, .. } => Val::Num(Fixed::new(Binary { digits: digits as u8, signed, native }.load(bytes), places)),
90        Kind::Packed { signed, .. } => {
91            let d = codec::packed(bytes, signed, facts.options().numproc).map_err(|c| Abend::check(c, pos))?;
92            Val::Num(fixed(d.negative, U256::from_u128(d.magnitude), places))
93        }
94        Kind::Zoned { signed, sign, .. } => Val::Num(zoned_value(facts.options().numproc, bytes, signed, sign, places, pos)?),
95    })
96}
97
98/// INVDATA(CLEANSIGN): a zoned or packed item whose sign half-byte is not a sign code (0 to 9) is
99/// read with that half-byte made F, positive (assumption C222); None when there is nothing to
100/// clean. A separate sign is a character, not a half-byte, and is left alone.
101fn sign_cleaned(bytes: &[u8], kind: Kind) -> Option<Vec<u8>> {
102    let (at, high) = match kind {
103        Kind::Packed { .. } => (bytes.len().checked_sub(1)?, false),
104        Kind::Zoned { sign: Some(SignClause { separate: true, .. }), .. } => return None,
105        Kind::Zoned { sign: Some(SignClause { position: SignPosition::Leading, .. }), .. } => (0, true),
106        Kind::Zoned { .. } => (bytes.len().checked_sub(1)?, true),
107        _ => return None,
108    };
109    let half = if high { bytes[at] >> 4 } else { bytes[at] & 0x0F };
110    if half > 9 {
111        return None;
112    }
113    let mut cleaned = bytes.to_vec();
114    cleaned[at] |= if high { 0xF0 } else { 0x0F };
115    Some(cleaned)
116}
117
118/// A zoned operand enters arithmetic through PACK, which keeps only the sign's zone.
119pub fn zoned_value(numproc: Numproc, bytes: &[u8], signed: bool, sign: Option<SignClause>, places: Places, pos: Pos) -> R<Fixed> {
120    let d = codec::zoned(bytes, signed, sign, numproc).map_err(|c| Abend::check(c, pos))?;
121    Ok(fixed(d.negative, U256::from_u128(d.magnitude), places))
122}
123
124/// A value as STRING, UNSTRING and INSPECT see an operand that is not an item: its bytes, a
125/// figurative constant as one character, a numeric literal as its digits.
126pub fn natural_bytes(facts: &dyn ProgramFacts, val: Val, pos: Pos) -> R<Vec<u8>> {
127    Ok(match val {
128        Val::Bytes(b) | Val::All(b) | Val::National(b) => b,
129        Val::Fig(f) => vec![facts.figurative(f)],
130        Val::Num(f) => zoned_digits(f.magnitude.to_u128().unwrap_or(0), f.places.total() as usize, decimal::UNSIGNED),
131        _ => return Err(Abend::ironwork("this operand has no characters to work on", pos)),
132    })
133}
134
135pub fn set_integer<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, dest: Loc, value: i64, pos: Pos) -> R<()> {
136    store_fixed(facts, unit, dest, &Fixed::new(value as i128, Places::new(19, 0)), false, pos)
137}
138
139/// Stores an arithmetic result; returns whether it was a size error.
140pub fn store_value<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, loc: Loc, value: Val, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
141    match (loc.kind, value) {
142        (Kind::Float(p), Val::Float(h)) => {
143            let h = if h.precision.digits() > p.digits() { float::narrow_rounded(h, p).map_err(|c| Abend::check(c, pos))? } else { h.lengthen(p) };
144            write(&mut unit.mem, loc, &h.to_bytes());
145            Ok(false)
146        }
147        (Kind::Float(p), Val::Num(f)) => {
148            let h = float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?;
149            write(&mut unit.mem, loc, &h.to_bytes());
150            Ok(false)
151        }
152        (_, Val::Float(h)) => {
153            let (f, overflow) = float::to_receiver(h, places(facts, loc));
154            if overflow && keep_on_size_error {
155                return Ok(true);
156            }
157            Ok(store_fixed_checked(facts, unit, loc, &f, false, keep_on_size_error, pos)? || overflow)
158        }
159        (_, Val::Num(f)) => store_fixed_checked(facts, unit, loc, &f, rounded, keep_on_size_error, pos),
160        _ => Err(Abend::ironwork("a non-numeric arithmetic result", pos)),
161    }
162}
163
164pub fn store_fixed<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, loc: Loc, value: &Fixed, rounded: bool, pos: Pos) -> R<()> {
165    store_fixed_checked(facts, unit, loc, value, rounded, false, pos).map(|_| ())
166}
167
168/// Stores a value into a numeric item; returns whether it was a size error, and with
169/// `keep_on_size_error` leaves the item unchanged on one.
170pub fn store_fixed_checked<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, loc: Loc, value: &Fixed, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
171    let beyond = || Abend::ironwork("a value wider than 256 bits", pos);
172    let value = &scaled_down(*value, scaling(facts, loc));
173    let options = facts.options();
174    let (bytes, size_error) = match loc.kind {
175        Kind::Zoned { digits, scale, signed, sign } => {
176            let m = align(value, scale, rounded).ok_or_else(beyond)?;
177            let cap = pow10(digits);
178            let kept = m.div_rem(cap).1.to_u128().unwrap();
179            let negative = signed && value.negative && kept != 0;
180            (zoned_image(kept, digits, signed, negative, sign), m >= cap)
181        }
182        Kind::Packed { digits, scale, signed } => {
183            let m = align(value, scale, rounded).ok_or_else(beyond)?;
184            let cap = pow10(digits);
185            let kept = m.div_rem(cap).1.to_u128().unwrap();
186            let mut out = vec![0u8; loc.len];
187            decimal::encode(&mut out, Decimal { negative: signed && value.negative && kept != 0, magnitude: kept }).map_err(|c| Abend::check(c, pos))?;
188            if !signed {
189                *out.last_mut().unwrap() |= 0x0F;
190            }
191            (out, m >= cap)
192        }
193        Kind::Binary { digits, scale, signed, native } => {
194            let m = align(value, scale, rounded).ok_or_else(beyond)?;
195            let magnitude = m.to_u128().and_then(|m| i128::try_from(m).ok()).ok_or_else(beyond)?;
196            let v = if value.negative { -magnitude } else { magnitude };
197            let item = Binary { digits: digits as u8, signed, native };
198            let stored = binary::store(item, v, &options);
199            if let Some(d) = stored.divergence {
200                let name = facts.item_name(loc.item);
201                let _ = writeln!(
202                    unit.err,
203                    "ironwork: {pos}: TRUNC(OPT) store of {} into {name} PIC {}9({digits}) BINARY: the PICTURE keeps {}, the binary field {}; {} was stored (-silent stops these reports)",
204                    d.value,
205                    if signed { "S" } else { "" },
206                    d.decimal,
207                    d.binary,
208                    d.binary
209                );
210            }
211            let bits = 8 * item.bytes() as u32;
212            let binary_range = if signed { v >= -(1i128 << (bits - 1)) && v < (1i128 << (bits - 1)) } else { (0..(1i128 << bits)).contains(&v.abs()) };
213            let exceeds = if native || options.trunc == Trunc::Bin { !binary_range } else { v.unsigned_abs() >= 10u128.pow(digits) };
214            (stored.bytes, exceeds)
215        }
216        Kind::Float(p) => (float::from_fixed(*value, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?.to_bytes(), false),
217        Kind::Index => {
218            let whole = align(value, 0, false).and_then(|m| m.to_u128()).and_then(|m| i32::try_from(m).ok()).ok_or_else(beyond)?;
219            ((if value.negative { -whole } else { whole }).to_be_bytes().to_vec(), false)
220        }
221        Kind::NumericEdited { edit, digits, scale, blank_when_zero } => {
222            let m = align(value, scale, rounded).ok_or_else(beyond)?;
223            let cap = pow10(digits);
224            let kept = m.div_rem(cap).1.to_u128().unwrap();
225            let (syms, currency) = facts.edit(edit);
226            let text = edit::numeric(syms, digits, value.negative && kept != 0, kept, blank_when_zero, facts.decimal_point(), currency);
227            (facts.page().encode(&text).map_err(|e| Abend::ironwork(e.to_string(), pos))?, m >= cap)
228        }
229        _ => return Err(Abend::ironwork("a numeric value stored into a non-numeric item", pos)),
230    };
231    if size_error && keep_on_size_error {
232        return Ok(true);
233    }
234    write(&mut unit.mem, loc, &bytes);
235    Ok(size_error)
236}
237
238/// A zoned item's bytes for a magnitude, with its sign as the SIGN clause places it.
239pub fn zoned_image(magnitude: u128, digits: u32, signed: bool, negative: bool, sign: Option<SignClause>) -> Vec<u8> {
240    let zone = match (signed, negative) {
241        (false, _) => decimal::UNSIGNED,
242        (true, true) => decimal::MINUS,
243        (true, false) => decimal::PLUS,
244    };
245    match sign {
246        Some(SignClause { separate: true, position }) => {
247            let body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
248            let s = if negative { 0x60 } else { 0x4E };
249            if position == SignPosition::Leading { [&[s][..], &body].concat() } else { [&body[..], &[s]].concat() }
250        }
251        Some(SignClause { separate: false, position: SignPosition::Leading }) => {
252            let mut body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
253            body[0] = (zone << 4) | (body[0] & 0x0F);
254            body
255        }
256        _ => zoned_digits(magnitude, digits as usize, zone),
257    }
258}
259
260pub fn figurative_unit(f: Figurative, quote: Quote) -> u16 {
261    match f {
262        Figurative::Zero => 0x0030,
263        Figurative::Space => 0x0020,
264        Figurative::HighValue => 0xFFFF,
265        Figurative::LowValue => 0x0000,
266        Figurative::Quote => quote.unit(),
267        Figurative::Null => 0,
268    }
269}
270
271/// MOVE, and VALUE at start-up, into one receiving item. `src` is the sending item, when there is
272/// one.
273pub fn assign<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, dest: Loc, val: Val, src: Option<Loc>, pos: Pos) -> R<()> {
274    if let Some(s) = src
275        && s.kind == Kind::Group
276        && matches!(dest.kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. })
277    {
278        // A group move converts nothing (Language Reference SC27-8713-03, p. 410).
279        let mut out = vec![ebcdic::SPACE; dest.len];
280        let n = s.len.min(dest.len);
281        out[..n].copy_from_slice(&bytes(&unit.mem, s)[..n]);
282        write(&mut unit.mem, dest, &out);
283        return Ok(());
284    }
285    let page = facts.page();
286    match dest.kind {
287        Kind::Group | Kind::Alnum { .. } => {
288            let justified = matches!(dest.kind, Kind::Alnum { justified: true });
289            let image = match (dest.kind, src) {
290                (Kind::Group, Some(s)) if matches!(val, Val::Num(_) | Val::Float(_) | Val::Address(_)) => bytes(&unit.mem, s).to_vec(),
291                _ => alnum_image(facts, &val, src, dest.len, pos)?,
292            };
293            let mut out = vec![ebcdic::SPACE; dest.len];
294            if justified && image.len() < dest.len {
295                out[dest.len - image.len()..].copy_from_slice(&image);
296            } else if justified {
297                out.copy_from_slice(&image[image.len() - dest.len..]);
298            } else {
299                let n = image.len().min(dest.len);
300                out[..n].copy_from_slice(&image[..n]);
301            }
302            write(&mut unit.mem, dest, &out);
303        }
304        Kind::National => {
305            let units: Vec<u16> = match val {
306                Val::National(b) => b.chunks(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(),
307                Val::Bytes(b) => page.decode(&b).encode_utf16().collect(),
308                Val::Fig(f) => vec![figurative_unit(f, facts.options().quote); dest.len / 2],
309                _ => return Err(Abend::ironwork("this value cannot be moved to a national item", pos)),
310            };
311            let mut out: Vec<u8> = units.iter().take(dest.len / 2).flat_map(|u| u.to_be_bytes()).collect();
312            while out.len() < dest.len {
313                out.extend_from_slice(&0x0020u16.to_be_bytes());
314            }
315            write(&mut unit.mem, dest, &out);
316        }
317        Kind::Pointer | Kind::ObjectReference | Kind::ProgramPointer => match val {
318            Val::Address(a) => write(&mut unit.mem, dest, &a.to_be_bytes()),
319            Val::Fig(Figurative::Null) => write(&mut unit.mem, dest, &[0; 4]),
320            _ => return Err(Abend::ironwork("a pointer takes an address: use SET ... TO ADDRESS OF or NULL", pos)),
321        },
322        Kind::Index => match val {
323            Val::Num(f) => store_fixed(facts, unit, dest, &f, false, pos)?,
324            _ => return Err(Abend::ironwork("an index takes an occurrence number", pos)),
325        },
326        Kind::AlnumEdited { edit } => {
327            let (syms, _) = facts.edit(edit);
328            let positions = syms.iter().filter(|s| !matches!(s, Sym::Insert(_))).count();
329            let image = alnum_image(facts, &val, src, positions, pos)?;
330            let out = edit::alphanumeric(syms, &image, ebcdic::SPACE, |c| page.encode_char(c).unwrap_or(ebcdic::SPACE));
331            write(&mut unit.mem, dest, &out);
332        }
333        Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::NumericEdited { .. } => match val {
334            Val::Num(f) => {
335                if let (Some(s), Kind::Packed { digits, scale, signed: true }, Numproc::Pfd) = (src, dest.kind, facts.options().numproc)
336                    && s.kind == dest.kind
337                    && scaling(facts, s) == scaling(facts, dest)
338                    && digits > 0
339                    && scale == places_of(s.kind).dec
340                {
341                    let copied = sign::move_packed(bytes(&unit.mem, s), true, Numproc::Pfd);
342                    write(&mut unit.mem, dest, &copied);
343                } else {
344                    store_fixed(facts, unit, dest, &f, false, pos)?;
345                }
346            }
347            Val::Float(h) => {
348                let (f, _) = float::to_receiver(h, places(facts, dest));
349                store_fixed(facts, unit, dest, &f, false, pos)?;
350            }
351            Val::Fig(Figurative::Zero) => store_fixed(facts, unit, dest, &Fixed::new(0, Places::new(1, 0)), false, pos)?,
352            Val::Fig(f) => write(&mut unit.mem, dest, &vec![facts.figurative(f); dest.len]),
353            Val::All(b) => {
354                let fill: Vec<u8> = b.iter().copied().cycle().take(dest.len).collect();
355                write(&mut unit.mem, dest, &fill);
356            }
357            Val::Bytes(b) => {
358                let v = match src.map(|s| (s, s.kind)) {
359                    Some((s, Kind::NumericEdited { edit, .. })) => {
360                        let (syms, currency) = facts.edit(edit);
361                        let (negative, magnitude) = edit::de_edit(syms, &page.decode(&b), currency);
362                        scaled_up(fixed(negative, U256::from_u128(magnitude), places_of(s.kind)), scaling(facts, s))
363                    }
364                    _ => {
365                        let digits = &b[b.len().saturating_sub(MAX_DIGITS)..];
366                        zoned_value(facts.options().numproc, digits, false, None, Places::new(digits.len() as u32, 0), pos)?
367                    }
368                };
369                store_fixed(facts, unit, dest, &v, false, pos)?;
370            }
371            Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to a numeric item", pos)),
372            Val::Address(_) => return Err(Abend::ironwork("a pointer cannot be moved to a numeric item", pos)),
373        },
374        Kind::Float(p) => {
375            let h = match val {
376                Val::Float(h) if h.precision.digits() > p.digits() => float::narrow_rounded(h, p).map_err(|c| Abend::check(c, pos))?,
377                Val::Float(h) => h.lengthen(p),
378                Val::Num(f) => float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
379                Val::Fig(Figurative::Zero) => Hfp::zero(p),
380                _ => return Err(Abend::ironwork("this value cannot be moved to a floating-point item", pos)),
381            };
382            write(&mut unit.mem, dest, &h.to_bytes());
383        }
384    }
385    Ok(())
386}
387
388/// The bytes an alphanumeric receiver of `len` gets from `val`.
389pub fn alnum_image(facts: &dyn ProgramFacts, val: &Val, src: Option<Loc>, len: usize, pos: Pos) -> R<Vec<u8>> {
390    Ok(match val {
391        Val::Bytes(b) => b.clone(),
392        Val::All(b) => b.iter().copied().cycle().take(len.max(b.len())).collect(),
393        Val::Fig(f) => vec![facts.figurative(*f); len],
394        Val::Num(f) if f.places.dec == 0 => {
395            let digits = src.and_then(|s| s.kind.digits_scale().map(|(d, _)| d + scaling(facts, s))).unwrap_or(f.places.total());
396            zoned_digits(f.magnitude.to_u128().unwrap_or(0), digits as usize, decimal::UNSIGNED)
397        }
398        Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to an alphanumeric item", pos)),
399        _ => return Err(Abend::ironwork("only an integer numeric value can be moved to an alphanumeric item", pos)),
400    })
401}
402
403/// NUMERIC or ALPHABETIC, tested on an item's bytes.
404pub fn byte_class(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, test: ByteClass) -> bool {
405    let bytes = bytes(mem, loc);
406    match test {
407        ByteClass::Packed { signed } => decimal::tp(bytes).is_ok_and(|cc| cc.0 == 0) && (signed || bytes.last().is_some_and(|b| b & 0x0F == 0x0F)),
408        ByteClass::Zoned { signed } => bytes.iter().enumerate().all(|(i, &b)| {
409            let zone_ok = if i + 1 == bytes.len() && signed { matches!(b >> 4, 0xC | 0xD | 0xF) } else { b >> 4 == 0xF };
410            zone_ok && b & 0x0F <= 9
411        }),
412        ByteClass::Digits => bytes.iter().all(|b| (0xF0..=0xF9).contains(b)),
413        ByteClass::Alphabetic => bytes.iter().all(|&b| b == ebcdic::SPACE || facts.page().decode_byte(b).is_ascii_alphabetic()),
414    }
415}
416
417/// POSITIVE, NEGATIVE or ZERO, tested on a value.
418pub fn sign_test(val: Val, test: SignTest, pos: Pos) -> R<bool> {
419    let v = match val {
420        Val::Num(f) => f,
421        Val::Float(h) => float::to_fixed(h, Places::new(31, 0), false).0,
422        _ => return Err(Abend::ironwork("a sign condition on a non-numeric operand", pos)),
423    };
424    Ok(match test {
425        SignTest::Positive => !v.negative && !v.magnitude.is_zero(),
426        SignTest::Negative => v.negative,
427        SignTest::Zero => v.magnitude.is_zero(),
428    })
429}
430
431/// Compares two operands, each a value and, for an item, its `Loc`: packed items under
432/// NUMPROC(PFD) as bytes, pointers as addresses, numbers algebraically, national as UTF-16, and
433/// anything else as alphanumeric in the program's collating sequence.
434pub fn compare(facts: &dyn ProgramFacts, mem: &[u8], a: (Val, Option<Loc>), b: (Val, Option<Loc>), pos: Pos) -> R<Ordering> {
435    let ((va, la), (vb, lb)) = (a, b);
436    if let (Some(x), Some(y)) = (la, lb)
437        && let (Kind::Packed { .. }, true, Numproc::Pfd) = (x.kind, x.kind == y.kind && scaling(facts, x) == scaling(facts, y), facts.options().numproc)
438    {
439        return sign::compare_packed(bytes(mem, x), bytes(mem, y), Numproc::Pfd).map_err(|c| Abend::check(c, pos));
440    }
441    let address = |v: &Val| match v {
442        Val::Address(a) => Some(*a),
443        Val::Fig(Figurative::Null) => Some(0),
444        _ => None,
445    };
446    if matches!(va, Val::Address(_)) || matches!(vb, Val::Address(_)) {
447        return match (address(&va), address(&vb)) {
448            (Some(x), Some(y)) => Ok(x.cmp(&y)),
449            _ => Err(Abend::ironwork("a pointer compared with something other than a pointer or NULL", pos)),
450        };
451    }
452    let numeric = |v: &Val| matches!(v, Val::Num(_) | Val::Float(_));
453    match (&va, &vb) {
454        (Val::Float(_), _) | (_, Val::Float(_)) if (numeric(&va) || matches!(va, Val::Fig(Figurative::Zero))) && (numeric(&vb) || matches!(vb, Val::Fig(Figurative::Zero))) => {
455            let to_float = |v: &Val| -> R<Hfp> {
456                Ok(match v {
457                    Val::Float(h) => h.lengthen(Precision::Extended),
458                    Val::Num(f) => float::from_fixed(*f, Precision::Extended, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
459                    _ => Hfp::zero(Precision::Extended),
460                })
461            };
462            Ok(to_float(&va)?.compare(to_float(&vb)?))
463        }
464        (Val::Num(x), Val::Num(y)) => Ok(compare_fixed(x, y)),
465        (Val::Num(x), Val::Fig(Figurative::Zero)) => Ok(compare_fixed(x, &Fixed::new(0, Places::new(1, 0)))),
466        (Val::Fig(Figurative::Zero), Val::Num(y)) => Ok(compare_fixed(&Fixed::new(0, Places::new(1, 0)), y)),
467        (Val::National(x), Val::National(y)) => Ok(compare_national(x, y)),
468        _ => {
469            let (va, la) = stored_digits(facts, mem, va, la, pos)?;
470            let (vb, lb) = stored_digits(facts, mem, vb, lb, pos)?;
471            let len = image_len(&va, la).max(image_len(&vb, lb));
472            let x = alnum_image(facts, &va, la, len, pos)?;
473            let y = alnum_image(facts, &vb, lb, len, pos)?;
474            Ok(ebcdic::compare_alphanumeric(&x, &y, facts.collation()))
475        }
476    }
477}
478
479/// The bytes a zoned integer holds, as a comparison with a nonnumeric operand reads them: as a MOVE
480/// to an alphanumeric item of its size leaves them, so a sign it overpunches is removed under ZWB
481/// and kept under NOZWB, and a separate sign is left out (assumption C221). None for an operand the
482/// comparison reads as a number first: not zoned, not an integer, or scaled.
483pub fn compared_zoned_bytes(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc) -> Option<Vec<u8>> {
484    let Kind::Zoned { scale: 0, signed, sign, .. } = loc.kind else { return None };
485    if scaling(facts, loc) > 0 {
486        return None;
487    }
488    let mut image = bytes(mem, loc).to_vec();
489    match sign {
490        Some(SignClause { separate: true, position: SignPosition::Leading }) => {
491            image.remove(0);
492        }
493        Some(SignClause { separate: true, position: SignPosition::Trailing }) => {
494            image.pop();
495        }
496        _ if !signed || !facts.options().zwb => {}
497        Some(SignClause { position: SignPosition::Leading, .. }) => image[0] |= 0xF0,
498        _ => *image.last_mut()? |= 0xF0,
499    }
500    Some(image)
501}
502
503/// `image`, from [`compared_zoned_bytes`], compared as alphanumeric with `other`, whose own bytes
504/// are taken the same way when it is a zoned integer too; `zoned_first` is whether the zoned item
505/// is the comparison's first operand.
506pub fn compare_zoned_bytes(facts: &dyn ProgramFacts, mem: &[u8], image: &[u8], other: (Val, Option<Loc>), zoned_first: bool, pos: Pos) -> R<Ordering> {
507    let len = image.len().max(image_len(&other.0, other.1));
508    let y = match other.1.and_then(|l| compared_zoned_bytes(facts, mem, l)) {
509        Some(bytes) => bytes,
510        None => alnum_image(facts, &other.0, other.1, len, pos)?,
511    };
512    let o = ebcdic::compare_alphanumeric(image, &y, facts.collation());
513    Ok(if zoned_first { o } else { o.reverse() })
514}
515
516/// A numeric operand compared with a nonnumeric one is its digits, scaling positions ignored
517/// (Language Reference SC27-8713-03, p. 211).
518pub fn stored_digits(facts: &dyn ProgramFacts, mem: &[u8], v: Val, loc: Option<Loc>, pos: Pos) -> R<(Val, Option<Loc>)> {
519    match loc {
520        Some(l) if matches!(v, Val::Num(_)) && scaling(facts, l) > 0 => Ok((read_stored(facts, mem, l, pos)?, None)),
521        _ => Ok((v, loc)),
522    }
523}
524
525/// How many characters an operand has when compared as alphanumeric.
526pub fn image_len(v: &Val, loc: Option<Loc>) -> usize {
527    match (v, loc) {
528        (_, Some(l)) if !l.kind.is_numeric() => l.len,
529        (Val::Bytes(b) | Val::All(b), _) => b.len(),
530        (Val::Num(f), Some(l)) => l.kind.digits_scale().map_or(f.places.total(), |(d, _)| d) as usize,
531        (Val::Num(f), None) => f.places.total() as usize,
532        _ => 1,
533    }
534}
535
536/// National values compared unit by unit, the shorter padded with national spaces.
537pub fn compare_national(a: &[u8], b: &[u8]) -> Ordering {
538    let unit = |s: &[u8], i: usize| if i + 1 < s.len() { u16::from_be_bytes([s[i], s[i + 1]]) } else { 0x0020 };
539    let len = a.len().max(b.len());
540    (0..len).step_by(2).map(|i| unit(a, i).cmp(&unit(b, i))).find(|o| o.is_ne()).unwrap_or(Ordering::Equal)
541}