1use 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
25pub trait ProgramFacts {
28 fn options(&self) -> Options;
29 fn page(&self) -> &'static CodePage;
30 fn figurative(&self, f: Figurative) -> u8;
32 fn collation(&self) -> &Collation;
33 fn ordinal(&self, byte: u8) -> u16;
35 fn character(&self, ordinal: i64) -> Option<u8>;
37 fn characters(&self) -> usize;
39 fn decimal_point(&self) -> char;
40 fn edit(&self, edit: u32) -> (&[Sym], &str);
42 fn scaling(&self, item: usize) -> u32;
44 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
56pub 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
64pub 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
78pub 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
98fn 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
118pub 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
124pub 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
139pub 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
168pub 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
238pub 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
271pub 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 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
388pub 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
403pub 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
417pub 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
431pub 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
479pub 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
503pub 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
516pub 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
525pub 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
536pub 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}