1use crate::abend::{Abend, AbendCode};
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::{ProgramCheck, 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 fn lax_redefinition(&self, _item: usize) -> Option<LaxRedefinition> {
48 None
49 }
50 fn numcheck_removed(&self, _item: usize, _pos: Pos) -> bool {
53 false
54 }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum LaxRedefinition {
60 Signed,
63 LeadingSpaces(u32),
67}
68
69pub fn bytes(mem: &[u8], loc: Loc) -> &[u8] {
70 &mem[loc.offset..loc.offset + loc.len]
71}
72
73pub fn write(mem: &mut [u8], loc: Loc, bytes: &[u8]) {
74 mem[loc.offset..loc.offset + loc.len].copy_from_slice(bytes);
75}
76
77pub fn scaling(facts: &dyn ProgramFacts, loc: Loc) -> u32 {
79 match loc.kind {
80 Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::NumericEdited { .. } => facts.scaling(loc.item),
81 _ => 0,
82 }
83}
84
85pub fn places(facts: &dyn ProgramFacts, loc: Loc) -> Places {
87 let places = places_of(loc.kind);
88 Places::new(places.int + scaling(facts, loc), places.dec)
89}
90
91pub fn read(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, pos: Pos) -> R<Val> {
92 let value = read_stored(facts, mem, loc, pos)?;
93 Ok(match value {
94 Val::Num(f) => Val::Num(scaled_up(f, scaling(facts, loc))),
95 other => other,
96 })
97}
98
99pub fn read_stored(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, pos: Pos) -> R<Val> {
101 let cleaned = facts.options().invdata.is_some_and(|i| i.cleansign).then(|| sign_cleaned(bytes(mem, loc), loc.kind)).flatten();
102 let bytes = cleaned.as_deref().unwrap_or(bytes(mem, loc));
103 let places = places_of(loc.kind);
104 Ok(match loc.kind {
105 Kind::Group | Kind::Alnum { .. } | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. } => Val::Bytes(bytes.to_vec()),
106 Kind::National => Val::National(bytes.to_vec()),
107 Kind::Pointer | Kind::ObjectReference | Kind::ProgramPointer => Val::Address(u32::from_be_bytes(bytes.try_into().unwrap())),
108 Kind::Index => Val::Num(Fixed::new(i32::from_be_bytes(bytes.try_into().unwrap()) as i128, Places::new(9, 0))),
109 Kind::Float(p) => Val::Float(Hfp::from_bytes(p, bytes)),
110 Kind::Binary { digits, signed, native, .. } => Val::Num(Fixed::new(Binary { digits: digits as u8, signed, native }.load(bytes), places)),
111 Kind::Packed { signed, .. } => {
112 let d = codec::packed(bytes, signed, facts.options().numproc).map_err(|c| Abend::check(c, pos))?;
113 Val::Num(fixed(d.negative, U256::from_u128(d.magnitude), places))
114 }
115 Kind::Zoned { signed, sign, .. } => Val::Num(zoned_value(facts.options().numproc, bytes, signed, sign, places, pos)?),
116 })
117}
118
119fn sign_cleaned(bytes: &[u8], kind: Kind) -> Option<Vec<u8>> {
123 let (at, high) = match kind {
124 Kind::Packed { .. } => (bytes.len().checked_sub(1)?, false),
125 Kind::Zoned { sign: Some(SignClause { separate: true, .. }), .. } => return None,
126 Kind::Zoned { sign: Some(SignClause { position: SignPosition::Leading, .. }), .. } => (0, true),
127 Kind::Zoned { .. } => (bytes.len().checked_sub(1)?, true),
128 _ => return None,
129 };
130 let half = if high { bytes[at] >> 4 } else { bytes[at] & 0x0F };
131 if half > 9 {
132 return None;
133 }
134 let mut cleaned = bytes.to_vec();
135 cleaned[at] |= if high { 0xF0 } else { 0x0F };
136 Some(cleaned)
137}
138
139pub fn zoned_value(numproc: Numproc, bytes: &[u8], signed: bool, sign: Option<SignClause>, places: Places, pos: Pos) -> R<Fixed> {
141 let d = codec::zoned(bytes, signed, sign, numproc).map_err(|c| Abend::check(c, pos))?;
142 Ok(fixed(d.negative, U256::from_u128(d.magnitude), places))
143}
144
145pub fn natural_bytes(facts: &dyn ProgramFacts, val: Val, pos: Pos) -> R<Vec<u8>> {
148 Ok(match val {
149 Val::Bytes(b) | Val::All(b) | Val::National(b) => b,
150 Val::Fig(f) => vec![facts.figurative(f)],
151 Val::Num(f) => zoned_digits(f.magnitude.to_u128().unwrap_or(0), f.places.total() as usize, decimal::UNSIGNED),
152 _ => return Err(Abend::ironwork("this operand has no characters to work on", pos)),
153 })
154}
155
156pub fn set_integer<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, dest: Loc, value: i64, pos: Pos) -> R<()> {
157 store_fixed(facts, unit, dest, &Fixed::new(value as i128, Places::new(19, 0)), false, pos)
158}
159
160pub 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> {
162 match (loc.kind, value) {
163 (Kind::Float(p), Val::Float(h)) => {
164 let h = if h.precision.digits() > p.digits() { float::narrow_rounded(h, p).map_err(|c| Abend::check(c, pos))? } else { h.lengthen(p) };
165 write(&mut unit.mem, loc, &h.to_bytes());
166 Ok(false)
167 }
168 (Kind::Float(p), Val::Num(f)) => {
169 let h = float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?;
170 write(&mut unit.mem, loc, &h.to_bytes());
171 Ok(false)
172 }
173 (_, Val::Float(h)) => {
174 let (f, overflow) = float::to_receiver(h, places(facts, loc));
175 if overflow && keep_on_size_error {
176 return Ok(true);
177 }
178 Ok(store_fixed_checked(facts, unit, loc, &f, false, keep_on_size_error, pos)? || overflow)
179 }
180 (_, Val::Num(f)) => store_fixed_checked(facts, unit, loc, &f, rounded, keep_on_size_error, pos),
181 _ => Err(Abend::ironwork("a non-numeric arithmetic result", pos)),
182 }
183}
184
185pub fn store_fixed<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, loc: Loc, value: &Fixed, rounded: bool, pos: Pos) -> R<()> {
186 store_fixed_checked(facts, unit, loc, value, rounded, false, pos).map(|_| ())
187}
188
189pub 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> {
192 let beyond = || Abend::ironwork("a value wider than 256 bits", pos);
193 let value = &scaled_down(*value, scaling(facts, loc));
194 let options = facts.options();
195 let (bytes, size_error) = match loc.kind {
196 Kind::Zoned { digits, scale, signed, sign } => {
197 let m = align(value, scale, rounded).ok_or_else(beyond)?;
198 let cap = pow10(digits);
199 let kept = m.div_rem(cap).1.to_u128().unwrap();
200 let negative = signed && value.negative && kept != 0;
201 (zoned_image(kept, digits, signed, negative, sign), m >= cap)
202 }
203 Kind::Packed { digits, scale, signed } => {
204 let m = align(value, scale, rounded).ok_or_else(beyond)?;
205 let cap = pow10(digits);
206 let kept = m.div_rem(cap).1.to_u128().unwrap();
207 let mut out = vec![0u8; loc.len];
208 decimal::encode(&mut out, Decimal { negative: signed && value.negative && kept != 0, magnitude: kept }).map_err(|c| Abend::check(c, pos))?;
209 if !signed {
210 *out.last_mut().unwrap() |= 0x0F;
211 }
212 (out, m >= cap)
213 }
214 Kind::Binary { digits, scale, signed, native } => {
215 let m = align(value, scale, rounded).ok_or_else(beyond)?;
216 let magnitude = m.to_u128().and_then(|m| i128::try_from(m).ok()).ok_or_else(beyond)?;
217 let v = if value.negative { -magnitude } else { magnitude };
218 let item = Binary { digits: digits as u8, signed, native };
219 let stored = binary::store(item, v, &options);
220 if let Some(d) = stored.divergence {
221 let name = facts.item_name(loc.item);
222 let _ = writeln!(
223 unit.err,
224 "ironwork: {pos}: TRUNC(OPT) store of {} into {name} PIC {}9({digits}) BINARY: the PICTURE keeps {}, the binary field {}; {} was stored (-silent stops these reports)",
225 d.value,
226 if signed { "S" } else { "" },
227 d.decimal,
228 d.binary,
229 d.binary
230 );
231 }
232 let bits = 8 * item.bytes() as u32;
233 let binary_range = if signed { v >= -(1i128 << (bits - 1)) && v < (1i128 << (bits - 1)) } else { (0..(1i128 << bits)).contains(&v.abs()) };
234 let exceeds = if native || options.trunc == Trunc::Bin { !binary_range } else { v.unsigned_abs() >= 10u128.pow(digits) };
235 (stored.bytes, exceeds)
236 }
237 Kind::Float(p) => (float::from_fixed(*value, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?.to_bytes(), false),
238 Kind::Index => {
239 let whole = align(value, 0, false).and_then(|m| m.to_u128()).and_then(|m| i32::try_from(m).ok()).ok_or_else(beyond)?;
240 ((if value.negative { -whole } else { whole }).to_be_bytes().to_vec(), false)
241 }
242 Kind::NumericEdited { edit, digits, scale, blank_when_zero } => {
243 let m = align(value, scale, rounded).ok_or_else(beyond)?;
244 let cap = pow10(digits);
245 let kept = m.div_rem(cap).1.to_u128().unwrap();
246 let (syms, currency) = facts.edit(edit);
247 let text = edit::numeric(syms, digits, value.negative && kept != 0, kept, blank_when_zero, facts.decimal_point(), currency);
248 (facts.page().encode(&text).map_err(|e| Abend::ironwork(e.to_string(), pos))?, m >= cap)
249 }
250 _ => return Err(Abend::ironwork("a numeric value stored into a non-numeric item", pos)),
251 };
252 if size_error && keep_on_size_error {
253 return Ok(true);
254 }
255 write(&mut unit.mem, loc, &bytes);
256 Ok(size_error)
257}
258
259pub fn zoned_image(magnitude: u128, digits: u32, signed: bool, negative: bool, sign: Option<SignClause>) -> Vec<u8> {
261 let zone = match (signed, negative) {
262 (false, _) => decimal::UNSIGNED,
263 (true, true) => decimal::MINUS,
264 (true, false) => decimal::PLUS,
265 };
266 match sign {
267 Some(SignClause { separate: true, position }) => {
268 let body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
269 let s = if negative { 0x60 } else { 0x4E };
270 if position == SignPosition::Leading { [&[s][..], &body].concat() } else { [&body[..], &[s]].concat() }
271 }
272 Some(SignClause { separate: false, position: SignPosition::Leading }) => {
273 let mut body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
274 body[0] = (zone << 4) | (body[0] & 0x0F);
275 body
276 }
277 _ => zoned_digits(magnitude, digits as usize, zone),
278 }
279}
280
281pub fn figurative_unit(f: Figurative, quote: Quote) -> u16 {
282 match f {
283 Figurative::Zero => 0x0030,
284 Figurative::Space => 0x0020,
285 Figurative::HighValue => 0xFFFF,
286 Figurative::LowValue => 0x0000,
287 Figurative::Quote => quote.unit(),
288 Figurative::Null => 0,
289 }
290}
291
292pub fn assign<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, dest: Loc, val: Val, src: Option<Loc>, pos: Pos) -> R<()> {
295 if let Some(s) = src
296 && s.kind == Kind::Group
297 && matches!(dest.kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. })
298 {
299 let mut out = vec![ebcdic::SPACE; dest.len];
301 let n = s.len.min(dest.len);
302 out[..n].copy_from_slice(&bytes(&unit.mem, s)[..n]);
303 write(&mut unit.mem, dest, &out);
304 return Ok(());
305 }
306 let page = facts.page();
307 match dest.kind {
308 Kind::Group | Kind::Alnum { .. } => {
309 let justified = matches!(dest.kind, Kind::Alnum { justified: true });
310 let image = match (dest.kind, src) {
311 (Kind::Group, Some(s)) if matches!(val, Val::Num(_) | Val::Float(_) | Val::Address(_)) || is_decimal(s.kind) => bytes(&unit.mem, s).to_vec(),
312 _ => alnum_image(facts, &val, src, dest.len, pos)?,
313 };
314 let mut out = vec![ebcdic::SPACE; dest.len];
315 if justified && image.len() < dest.len {
316 out[dest.len - image.len()..].copy_from_slice(&image);
317 } else if justified {
318 out.copy_from_slice(&image[image.len() - dest.len..]);
319 } else {
320 let n = image.len().min(dest.len);
321 out[..n].copy_from_slice(&image[..n]);
322 }
323 write(&mut unit.mem, dest, &out);
324 }
325 Kind::National => {
326 let units: Vec<u16> = match val {
327 Val::National(b) => b.chunks(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(),
328 Val::Bytes(b) => page.decode(&b).encode_utf16().collect(),
329 Val::Fig(f) => vec![figurative_unit(f, facts.options().quote); dest.len / 2],
330 _ => return Err(Abend::ironwork("this value cannot be moved to a national item", pos)),
331 };
332 let mut out: Vec<u8> = units.iter().take(dest.len / 2).flat_map(|u| u.to_be_bytes()).collect();
333 while out.len() < dest.len {
334 out.extend_from_slice(&0x0020u16.to_be_bytes());
335 }
336 write(&mut unit.mem, dest, &out);
337 }
338 Kind::Pointer | Kind::ObjectReference | Kind::ProgramPointer => match val {
339 Val::Address(a) => write(&mut unit.mem, dest, &a.to_be_bytes()),
340 Val::Fig(Figurative::Null) => write(&mut unit.mem, dest, &[0; 4]),
341 _ => return Err(Abend::ironwork("a pointer takes an address: use SET ... TO ADDRESS OF or NULL", pos)),
342 },
343 Kind::Index => match val {
344 Val::Num(f) => store_fixed(facts, unit, dest, &f, false, pos)?,
345 _ => return Err(Abend::ironwork("an index takes an occurrence number", pos)),
346 },
347 Kind::AlnumEdited { edit } => {
348 let (syms, _) = facts.edit(edit);
349 let positions = syms.iter().filter(|s| !matches!(s, Sym::Insert(_))).count();
350 let image = alnum_image(facts, &val, src, positions, pos)?;
351 let out = edit::alphanumeric(syms, &image, ebcdic::SPACE, |c| page.encode_char(c).unwrap_or(ebcdic::SPACE));
352 write(&mut unit.mem, dest, &out);
353 }
354 Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::NumericEdited { .. } => match val {
355 Val::Num(f) => {
356 if let Some(s) = src
357 && packed_copy(facts, s, dest)
358 {
359 let copied = sign::move_packed(bytes(&unit.mem, s), true, Numproc::Pfd);
360 write(&mut unit.mem, dest, &copied);
361 } else {
362 store_fixed(facts, unit, dest, &f, false, pos)?;
363 }
364 }
365 Val::Float(h) => {
366 let (f, _) = float::to_receiver(h, places(facts, dest));
367 store_fixed(facts, unit, dest, &f, false, pos)?;
368 }
369 Val::Fig(Figurative::Zero) => store_fixed(facts, unit, dest, &Fixed::new(0, Places::new(1, 0)), false, pos)?,
370 Val::Fig(f) => write(&mut unit.mem, dest, &vec![facts.figurative(f); dest.len]),
371 Val::All(b) => {
372 let fill: Vec<u8> = b.iter().copied().cycle().take(dest.len).collect();
373 write(&mut unit.mem, dest, &fill);
374 }
375 Val::Bytes(b) if let Some(s) = src
376 && is_decimal(s.kind)
377 && is_decimal(dest.kind) =>
378 {
379 carry_digits(facts, unit, dest, s, &b, pos)?;
380 }
381 Val::Bytes(b) if integer_digits(facts, dest).is_some() && !matches!(src.map(|s| s.kind), Some(Kind::NumericEdited { .. })) => {
382 move_digit_halves(facts, unit, dest, &b, pos)?;
383 }
384 Val::Bytes(b) => {
385 let v = match src.map(|s| (s, s.kind)) {
386 Some((s, Kind::NumericEdited { edit, .. })) => {
387 let (syms, currency) = facts.edit(edit);
388 let (negative, magnitude) = edit::de_edit(syms, &page.decode(&b), currency);
389 scaled_up(fixed(negative, U256::from_u128(magnitude), places_of(s.kind)), scaling(facts, s))
390 }
391 _ => {
392 let digits = &b[b.len().saturating_sub(MAX_DIGITS)..];
393 zoned_value(facts.options().numproc, digits, false, None, Places::new(digits.len() as u32, 0), pos)?
394 }
395 };
396 store_fixed(facts, unit, dest, &v, false, pos)?;
397 }
398 Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to a numeric item", pos)),
399 Val::Address(_) => return Err(Abend::ironwork("a pointer cannot be moved to a numeric item", pos)),
400 },
401 Kind::Float(p) => {
402 let h = match val {
403 Val::Float(h) if h.precision.digits() > p.digits() => float::narrow_rounded(h, p).map_err(|c| Abend::check(c, pos))?,
404 Val::Float(h) => h.lengthen(p),
405 Val::Num(f) => float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
406 Val::Fig(Figurative::Zero) => Hfp::zero(p),
407 _ => return Err(Abend::ironwork("this value cannot be moved to a floating-point item", pos)),
408 };
409 write(&mut unit.mem, dest, &h.to_bytes());
410 }
411 }
412 Ok(())
413}
414
415fn integer_digits(facts: &dyn ProgramFacts, loc: Loc) -> Option<usize> {
417 match loc.kind {
418 Kind::Zoned { digits, scale: 0, .. } | Kind::Packed { digits, scale: 0, .. } if scaling(facts, loc) == 0 => Some(digits as usize),
419 _ => None,
420 }
421}
422
423fn is_decimal(kind: Kind) -> bool {
424 matches!(kind, Kind::Zoned { .. } | Kind::Packed { .. })
425}
426
427fn packed_copy(facts: &dyn ProgramFacts, src: Loc, dest: Loc) -> bool {
429 matches!(dest.kind, Kind::Packed { digits, signed: true, .. } if digits > 0)
430 && src.kind == dest.kind
431 && scaling(facts, src) == scaling(facts, dest)
432 && facts.options().numproc == Numproc::Pfd
433}
434
435fn moved_unchecked(facts: &dyn ProgramFacts, src: Loc, dest: Loc) -> bool {
440 match (src.kind, dest.kind) {
441 (Kind::Packed { .. }, Kind::Packed { .. }) => packed_copy(facts, src, dest),
442 (Kind::Zoned { .. } | Kind::Packed { .. }, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Group) => true,
443 (Kind::Zoned { .. } | Kind::Packed { .. }, Kind::Alnum { .. } | Kind::AlnumEdited { .. }) => places_of(src.kind).dec == 0,
444 _ => false,
445 }
446}
447
448pub fn move_sender(facts: &dyn ProgramFacts, mem: &[u8], src: Loc, dest: Loc, pos: Pos) -> R<Val> {
452 match read(facts, mem, src, pos) {
453 Err(Abend { code: AbendCode::Check(ProgramCheck::Data), .. }) if moved_unchecked(facts, src, dest) => {
454 let stored = bytes(mem, src);
455 let cleaned = facts.options().invdata.is_some_and(|i| i.cleansign).then(|| sign_cleaned(stored, src.kind)).flatten();
456 Ok(Val::Bytes(cleaned.unwrap_or_else(|| stored.to_vec())))
457 }
458 value => value,
459 }
460}
461
462fn digit_halves(kind: Kind, b: &[u8]) -> (Vec<u8>, Option<u8>) {
465 match kind {
466 Kind::Packed { digits, signed, .. } => {
467 let nibbles: Vec<u8> = b.iter().flat_map(|x| [x >> 4, x & 0x0F]).collect();
468 let (sign, body) = nibbles.split_last().expect("a packed item is at least one byte");
469 (body[body.len().saturating_sub(digits as usize)..].to_vec(), signed.then_some(*sign))
470 }
471 Kind::Zoned { sign: Some(SignClause { separate: true, position }), .. } => {
472 let (s, body) = if position == SignPosition::Leading { (b[0], &b[1..]) } else { (b[b.len() - 1], &b[..b.len() - 1]) };
473 (body.iter().map(|x| x & 0x0F).collect(), Some(if s == 0x60 { 0x0D } else { 0x0C }))
474 }
475 Kind::Zoned { signed, sign, .. } => {
476 let at = if matches!(sign, Some(SignClause { position: SignPosition::Leading, .. })) { 0 } else { b.len() - 1 };
477 (b.iter().map(|x| x & 0x0F).collect(), signed.then(|| b[at] >> 4))
478 }
479 _ => (Vec::new(), None),
480 }
481}
482
483fn carry_digits<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, dest: Loc, src: Loc, sender: &[u8], pos: Pos) -> R<()> {
488 if packed_copy(facts, src, dest) {
489 write(&mut unit.mem, dest, sender);
490 return Ok(());
491 }
492 let (halves, sign) = digit_halves(src.kind, sender);
493 let power = |loc: Loc| i64::from(scaling(facts, loc)) - i64::from(places_of(loc.kind).dec);
494 let (m, n) = (halves.len() as i64, dest.kind.digits_scale().map_or(0, |(d, _)| d) as i64);
495 let shift = m - n + power(src) - power(dest);
496 let aligned: Vec<u8> = (0..n).map(|j| usize::try_from(j + shift).ok().and_then(|i| halves.get(i)).copied().unwrap_or(0)).collect();
497 store_digit_halves(facts, unit, dest, &aligned, sign, pos)
498}
499
500fn move_digit_halves<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, dest: Loc, sender: &[u8], pos: Pos) -> R<()> {
505 let n = integer_digits(facts, dest).unwrap_or(0);
506 let halves: Vec<u8> = (0..n).map(|i| (sender.len() + i).checked_sub(n).map_or(0, |k| sender[k] & 0x0F)).collect();
507 store_digit_halves(facts, unit, dest, &halves, None, pos)
508}
509
510fn store_digit_halves<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, dest: Loc, halves: &[u8], sign: Option<u8>, pos: Pos) -> R<()> {
514 let n = halves.len();
515 let value = halves.iter().fold(0u128, |v, &h| v * 10 + if h > 9 { 0 } else { u128::from(h) });
516 let magnitude = U256::from_u128(value).checked_mul(pow10(scaling(facts, dest))).unwrap_or_default();
517 let negative = sign.is_some_and(|s| s > 9 && decimal::is_minus(s));
518 store_fixed(facts, unit, dest, &fixed(negative, magnitude, places(facts, dest)), false, pos)?;
519 let sign_digit = sign.filter(|&s| s <= 9);
520 if halves.iter().all(|&h| h <= 9) && sign_digit.is_none() {
521 return Ok(());
522 }
523 let mut out = bytes(&unit.mem, dest).to_vec();
524 for (i, &h) in halves.iter().enumerate() {
525 let (at, high) = match dest.kind {
526 Kind::Packed { .. } => {
527 let nibble = 2 * out.len() - 1 - n + i;
528 (nibble / 2, nibble.is_multiple_of(2))
529 }
530 Kind::Zoned { sign: Some(SignClause { separate: true, position: SignPosition::Leading }), .. } => (i + 1, false),
531 _ => (i, false),
532 };
533 out[at] = if high { (out[at] & 0x0F) | (h << 4) } else { (out[at] & 0xF0) | h };
534 }
535 if let Some(s) = sign_digit {
536 let last = out.len() - 1;
537 match dest.kind {
538 Kind::Packed { signed: true, .. } => out[last] = (out[last] & 0xF0) | s,
539 Kind::Zoned { signed: true, sign: Some(SignClause { separate: true, .. }), .. } => {}
540 Kind::Zoned { signed: true, sign: Some(SignClause { position: SignPosition::Leading, .. }), .. } => out[0] = (out[0] & 0x0F) | (s << 4),
541 Kind::Zoned { signed: true, .. } => out[last] = (out[last] & 0x0F) | (s << 4),
542 _ => {}
543 }
544 }
545 write(&mut unit.mem, dest, &out);
546 Ok(())
547}
548
549fn unchecked_digit_bytes(facts: &dyn ProgramFacts, src: Loc, b: &[u8]) -> Vec<u8> {
553 let mut out = match src.kind {
554 Kind::Zoned { sign: Some(SignClause { separate: true, position }), .. } => {
555 if position == SignPosition::Leading { b[1..].to_vec() } else { b[..b.len() - 1].to_vec() }
556 }
557 Kind::Zoned { signed: true, sign, .. } => {
558 let mut digits = b.to_vec();
559 let at = if matches!(sign, Some(SignClause { position: SignPosition::Leading, .. })) { 0 } else { digits.len() - 1 };
560 digits[at] |= 0xF0;
561 digits
562 }
563 Kind::Zoned { .. } => b.to_vec(),
564 _ => digit_halves(src.kind, b).0.iter().map(|h| 0xF0 | h).collect(),
565 };
566 out.resize(out.len() + scaling(facts, src) as usize, 0xF0);
567 out
568}
569
570pub fn alnum_image(facts: &dyn ProgramFacts, val: &Val, src: Option<Loc>, len: usize, pos: Pos) -> R<Vec<u8>> {
572 Ok(match val {
573 Val::Bytes(b) => match src {
574 Some(s) if is_decimal(s.kind) => unchecked_digit_bytes(facts, s, b),
575 _ => b.clone(),
576 },
577 Val::All(b) => b.iter().copied().cycle().take(len.max(b.len())).collect(),
578 Val::Fig(f) => vec![facts.figurative(*f); len],
579 Val::Num(f) if f.places.dec == 0 => {
580 let digits = src.and_then(|s| s.kind.digits_scale().map(|(d, _)| d + scaling(facts, s))).unwrap_or(f.places.total());
581 zoned_digits(f.magnitude.to_u128().unwrap_or(0), digits as usize, decimal::UNSIGNED)
582 }
583 Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to an alphanumeric item", pos)),
584 _ => return Err(Abend::ironwork("only an integer numeric value can be moved to an alphanumeric item", pos)),
585 })
586}
587
588pub fn numcheck_fault(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, as_integer: bool) -> Option<&'static str> {
590 numcheck_fault_in(&facts.options(), loc.kind, bytes(mem, loc), facts.lax_redefinition(loc.item), as_integer)
591}
592
593pub fn numcheck_fault_in(options: &Options, kind: Kind, stored: &[u8], lax: Option<LaxRedefinition>, as_integer: bool) -> Option<&'static str> {
600 let check = options.numcheck?;
601 let cleaned = options.invdata.is_some_and(|i| i.cleansign).then(|| sign_cleaned(stored, kind)).flatten();
602 let b = cleaned.as_deref().unwrap_or(stored);
603 let lax = lax.filter(|_| check.zon.is_some_and(|z| z.lax));
604 let spaces = match lax {
605 Some(LaxRedefinition::LeadingSpaces(n)) => n as usize,
606 _ => 0,
607 };
608 let digit = |x: &u8| (0xF0..=0xF9).contains(x);
609 let digits = |from: usize, bytes: &[u8]| bytes.iter().enumerate().all(|(i, x)| digit(x) || from + i < spaces && *x == ebcdic::SPACE);
610 let overpunch = |at: usize, x: u8| matches!(x >> 4, 0xC | 0xD | 0xF) && x & 0x0F <= 9 || at < spaces && x == ebcdic::SPACE;
611 let valid = match kind {
612 Kind::Zoned { signed, sign, .. } if check.zon.is_some() => {
613 let last = b.len() - 1;
614 match (signed || lax == Some(LaxRedefinition::Signed), sign) {
615 (false, _) => digits(0, b),
616 (true, Some(SignClause { separate: true, position })) => {
617 let (s, rest) = if position == SignPosition::Leading { (b[0], &b[1..]) } else { (b[last], &b[..last]) };
618 matches!(s, 0x4E | 0x60) && rest.iter().all(digit)
619 }
620 (true, Some(SignClause { position: SignPosition::Leading, .. })) => overpunch(0, b[0]) && digits(1, &b[1..]),
621 (true, _) => overpunch(last, b[last]) && digits(0, &b[..last]),
622 }
623 }
624 Kind::Group | Kind::Alnum { .. } if as_integer && check.zon.is_some() => b.iter().all(digit),
625 Kind::Packed { digits, signed, .. } if check.pac => {
626 let spare_clear = digits % 2 == 1 || b[0] >> 4 == 0;
627 decimal::tp(b).is_ok_and(|cc| cc.0 == 0) && (signed || b[b.len() - 1] & 0x0F == 0x0F) && spare_clear
628 }
629 Kind::Binary { digits, signed, native: false, .. } if check.bin.is_some_and(|c| c.truncbin || options.trunc != Trunc::Bin) => {
630 let raw = Binary { digits: digits as u8, signed, native: true }.load(b);
631 raw.unsigned_abs() < 10u128.pow(digits)
632 }
633 _ => true,
634 };
635 (!valid).then_some(match kind {
636 Kind::Binary { .. } => "has more digits than its PICTURE allows",
637 _ => "is not NUMERIC",
638 })
639}
640
641pub fn numcheck<H, L: Loader<H>>(facts: &dyn ProgramFacts, unit: &mut RunUnit<'_, H, L>, loc: Loc, as_integer: bool, program: &str, pos: Pos) -> R<()> {
646 if facts.numcheck_removed(loc.item, pos) {
647 return Ok(());
648 }
649 let Some(why) = numcheck_fault(facts, &unit.mem, loc, as_integer) else { return Ok(()) };
650 let message = format!("NUMCHECK: {} X'{}' in program {program} {why}", facts.item_name(loc.item), crate::digest::hex(bytes(&unit.mem, loc)).to_ascii_uppercase());
651 if facts.options().numcheck.is_some_and(|c| c.abd) {
652 return Err(Abend { code: crate::abend::AbendCode::user(4038), message, pos, file: None });
653 }
654 let _ = writeln!(unit.err, "ironwork: {pos}: {message}; the statement runs");
655 Ok(())
656}
657
658pub fn byte_class(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc, test: ByteClass) -> bool {
660 let bytes = bytes(mem, loc);
661 match test {
662 ByteClass::Packed { signed } => decimal::tp(bytes).is_ok_and(|cc| cc.0 == 0) && (signed || bytes.last().is_some_and(|b| b & 0x0F == 0x0F)),
663 ByteClass::Zoned { signed } => bytes.iter().enumerate().all(|(i, &b)| {
664 let zone_ok = if i + 1 == bytes.len() && signed { matches!(b >> 4, 0xC | 0xD | 0xF) } else { b >> 4 == 0xF };
665 zone_ok && b & 0x0F <= 9
666 }),
667 ByteClass::Digits => bytes.iter().all(|b| (0xF0..=0xF9).contains(b)),
668 ByteClass::Alphabetic => bytes.iter().all(|&b| b == ebcdic::SPACE || facts.page().decode_byte(b).is_ascii_alphabetic()),
669 ByteClass::AlphabeticLower => bytes.iter().all(|&b| b == ebcdic::SPACE || facts.page().decode_byte(b).is_ascii_lowercase()),
670 ByteClass::AlphabeticUpper => bytes.iter().all(|&b| b == ebcdic::SPACE || facts.page().decode_byte(b).is_ascii_uppercase()),
671 }
672}
673
674pub fn sign_test(val: Val, test: SignTest, pos: Pos) -> R<bool> {
676 let v = match val {
677 Val::Num(f) => f,
678 Val::Float(h) => float::to_fixed(h, Places::new(31, 0), false).0,
679 _ => return Err(Abend::ironwork("a sign condition on a non-numeric operand", pos)),
680 };
681 Ok(match test {
682 SignTest::Positive => !v.negative && !v.magnitude.is_zero(),
683 SignTest::Negative => v.negative,
684 SignTest::Zero => v.magnitude.is_zero(),
685 })
686}
687
688pub fn compare(facts: &dyn ProgramFacts, mem: &[u8], a: (Val, Option<Loc>), b: (Val, Option<Loc>), pos: Pos) -> R<Ordering> {
692 let ((va, la), (vb, lb)) = (a, b);
693 if let (Some(x), Some(y)) = (la, lb)
694 && let (Kind::Packed { .. }, true, Numproc::Pfd) = (x.kind, x.kind == y.kind && scaling(facts, x) == scaling(facts, y), facts.options().numproc)
695 {
696 return sign::compare_packed(bytes(mem, x), bytes(mem, y), Numproc::Pfd).map_err(|c| Abend::check(c, pos));
697 }
698 let address = |v: &Val| match v {
699 Val::Address(a) => Some(*a),
700 Val::Fig(Figurative::Null) => Some(0),
701 _ => None,
702 };
703 if matches!(va, Val::Address(_)) || matches!(vb, Val::Address(_)) {
704 return match (address(&va), address(&vb)) {
705 (Some(x), Some(y)) => Ok(x.cmp(&y)),
706 _ => Err(Abend::ironwork("a pointer compared with something other than a pointer or NULL", pos)),
707 };
708 }
709 let numeric = |v: &Val| matches!(v, Val::Num(_) | Val::Float(_));
710 match (&va, &vb) {
711 (Val::Float(_), _) | (_, Val::Float(_)) if (numeric(&va) || matches!(va, Val::Fig(Figurative::Zero))) && (numeric(&vb) || matches!(vb, Val::Fig(Figurative::Zero))) => {
712 let to_float = |v: &Val| -> R<Hfp> {
713 Ok(match v {
714 Val::Float(h) => h.lengthen(Precision::Extended),
715 Val::Num(f) => float::from_fixed(*f, Precision::Extended, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
716 _ => Hfp::zero(Precision::Extended),
717 })
718 };
719 Ok(to_float(&va)?.compare(to_float(&vb)?))
720 }
721 (Val::Num(x), Val::Num(y)) => Ok(compare_fixed(x, y)),
722 (Val::Num(x), Val::Fig(Figurative::Zero)) => Ok(compare_fixed(x, &Fixed::new(0, Places::new(1, 0)))),
723 (Val::Fig(Figurative::Zero), Val::Num(y)) => Ok(compare_fixed(&Fixed::new(0, Places::new(1, 0)), y)),
724 (Val::National(x), Val::National(y)) => Ok(compare_national(x, y)),
725 _ => {
726 let (va, la) = stored_digits(facts, mem, va, la, pos)?;
727 let (vb, lb) = stored_digits(facts, mem, vb, lb, pos)?;
728 let len = image_len(&va, la).max(image_len(&vb, lb));
729 let x = alnum_image(facts, &va, la, len, pos)?;
730 let y = alnum_image(facts, &vb, lb, len, pos)?;
731 Ok(ebcdic::compare_alphanumeric(&x, &y, facts.collation()))
732 }
733 }
734}
735
736pub fn compared_zoned_bytes(facts: &dyn ProgramFacts, mem: &[u8], loc: Loc) -> Option<Vec<u8>> {
741 let Kind::Zoned { scale: 0, signed, sign, .. } = loc.kind else { return None };
742 if scaling(facts, loc) > 0 {
743 return None;
744 }
745 let mut image = bytes(mem, loc).to_vec();
746 match sign {
747 Some(SignClause { separate: true, position: SignPosition::Leading }) => {
748 image.remove(0);
749 }
750 Some(SignClause { separate: true, position: SignPosition::Trailing }) => {
751 image.pop();
752 }
753 _ if !signed || !facts.options().zwb => {}
754 Some(SignClause { position: SignPosition::Leading, .. }) => image[0] |= 0xF0,
755 _ => *image.last_mut()? |= 0xF0,
756 }
757 Some(image)
758}
759
760pub fn compare_zoned_bytes(facts: &dyn ProgramFacts, mem: &[u8], image: &[u8], other: (Val, Option<Loc>), zoned_first: bool, pos: Pos) -> R<Ordering> {
764 let len = image.len().max(image_len(&other.0, other.1));
765 let y = match other.1.and_then(|l| compared_zoned_bytes(facts, mem, l)) {
766 Some(bytes) => bytes,
767 None => alnum_image(facts, &other.0, other.1, len, pos)?,
768 };
769 let o = ebcdic::compare_alphanumeric(image, &y, facts.collation());
770 Ok(if zoned_first { o } else { o.reverse() })
771}
772
773pub fn stored_digits(facts: &dyn ProgramFacts, mem: &[u8], v: Val, loc: Option<Loc>, pos: Pos) -> R<(Val, Option<Loc>)> {
776 match loc {
777 Some(l) if matches!(v, Val::Num(_)) && scaling(facts, l) > 0 => Ok((read_stored(facts, mem, l, pos)?, None)),
778 _ => Ok((v, loc)),
779 }
780}
781
782pub fn image_len(v: &Val, loc: Option<Loc>) -> usize {
784 match (v, loc) {
785 (_, Some(l)) if !l.kind.is_numeric() => l.len,
786 (Val::Bytes(b) | Val::All(b), _) => b.len(),
787 (Val::Num(f), Some(l)) => l.kind.digits_scale().map_or(f.places.total(), |(d, _)| d) as usize,
788 (Val::Num(f), None) => f.places.total() as usize,
789 _ => 1,
790 }
791}
792
793pub fn compare_national(a: &[u8], b: &[u8]) -> Ordering {
795 let unit = |s: &[u8], i: usize| if i + 1 < s.len() { u16::from_be_bytes([s[i], s[i + 1]]) } else { 0x0020 };
796 let len = a.len().max(b.len());
797 (0..len).step_by(2).map(|i| unit(a, i).cmp(&unit(b, i))).find(|o| o.is_ne()).unwrap_or(Ordering::Equal)
798}