1use crate::layout::{Item, Kind, Layout, Resolved};
6use crate::unit::{ADDRESS_BASE, LoadError, RETURN_CODE, RunUnit};
7use crate::Compiled;
8use numeric::binary::{self, Binary};
9use numeric::precision::{ArithError, Fixed, Places};
10use numeric::{Numproc, Options, Trunc, float, sign};
11use std::cmp::Ordering;
12use std::collections::HashMap;
13use syntax::Pos;
14use syntax::ast::*;
15use zarch::check::{ProgramCheck, ProgramMask};
16use zarch::decimal::{self, Decimal};
17use zarch::ebcdic::{self, CodePage, Collation};
18use zarch::hfp::{Hfp, Precision};
19use zarch::wide::U256;
20
21mod cics;
22mod cics_bms;
23mod cics_files;
24mod cics_services;
25mod file_io;
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Abend {
29 pub code: String,
30 pub message: String,
31 pub pos: Pos,
32}
33
34impl Abend {
35 fn check(c: ProgramCheck, pos: Pos) -> Self {
36 Self { code: c.abend(), message: format!("{c:?} exception"), pos }
37 }
38
39 fn ironwork(message: impl Into<String>, pos: Pos) -> Self {
40 Self { code: "IRONWORK".into(), message: message.into(), pos }
41 }
42}
43
44type R<T> = Result<T, Abend>;
45
46const DIVIDE_BY_ZERO: &str = "DIVIDE-BY-ZERO";
47pub const CLOSED_OUTPUT: &str = "CLOSED-OUTPUT";
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum Ending {
52 Goback,
53 StopRun,
54 EndOfProgram,
55}
56
57enum Flow {
58 Next,
59 End(Ending),
60 GoTo(usize),
61 ExitParagraph,
62 ExitSection,
63 ExitPerform,
64 ExitPerformCycle,
65 NextSentence,
66}
67
68#[derive(Clone, Copy, Debug)]
69struct Loc {
70 offset: usize,
71 len: usize,
72 kind: Kind,
73 item: usize,
74}
75
76#[derive(Clone, Debug)]
77enum Val {
78 Bytes(Vec<u8>),
79 National(Vec<u8>),
80 Num(Fixed),
81 Float(Hfp),
82 Fig(Figurative),
83 All(Vec<u8>),
84 Address(u32),
86}
87
88pub struct Machine<'p, 'u, 'w> {
89 program: &'p Program,
90 layout: &'p Layout,
91 options: Options,
92 ssrange: bool,
93 page: &'static CodePage,
94 resolved: HashMap<(String, Vec<String>), Resolved>,
95 me: usize,
97 base: usize,
98 linkage: Vec<Option<usize>>,
100 local_base: usize,
102 main: bool,
104 cics_handlers: cics::Handlers,
106 unit: &'u mut RunUnit<'w>,
107}
108
109fn figurative_byte(f: Figurative) -> u8 {
110 match f {
111 Figurative::Zero => ebcdic::ZERO,
112 Figurative::Space => ebcdic::SPACE,
113 Figurative::HighValue => ebcdic::HIGH_VALUE,
114 Figurative::LowValue => ebcdic::LOW_VALUE,
115 Figurative::Quote => ebcdic::QUOTE,
116 Figurative::Null => 0,
117 }
118}
119
120fn figurative_unit(f: Figurative) -> u16 {
121 match f {
122 Figurative::Zero => 0x0030,
123 Figurative::Space => 0x0020,
124 Figurative::HighValue => 0xFFFF,
125 Figurative::LowValue => 0x0000,
126 Figurative::Quote => 0x0022,
127 Figurative::Null => 0,
128 }
129}
130
131fn fixed(negative: bool, magnitude: U256, places: Places) -> Fixed {
132 Fixed { negative: negative && !magnitude.is_zero(), magnitude, places }
133}
134
135pub(crate) fn literal_fixed(text: &str) -> Option<Fixed> {
136 let (negative, body) = match text.as_bytes().first() {
137 Some(b'-') => (true, &text[1..]),
138 Some(b'+') => (false, &text[1..]),
139 _ => (false, text),
140 };
141 let (int, frac) = body.split_once('.').unwrap_or((body, ""));
142 let digits = format!("{int}{frac}");
143 if digits.is_empty() || digits.len() > 31 {
144 return None;
145 }
146 let magnitude: u128 = digits.parse().ok()?;
147 Some(fixed(negative, U256::from_u128(magnitude), Places::new(int.len().max(1) as u32, frac.len() as u32)))
148}
149
150fn pow10(n: u32) -> U256 {
151 U256::pow10(n)
152}
153
154fn align(value: &Fixed, scale: u32, rounded: bool) -> Option<U256> {
156 let from = value.places.dec;
157 if scale >= from {
158 return value.magnitude.checked_mul(pow10(scale - from));
159 }
160 let (q, r) = value.magnitude.div_rem(pow10(from - scale));
161 let half = pow10(from - scale - 1).checked_mul(U256::from_u128(5))?;
162 Some(if rounded && r >= half { q + U256::from_u128(1) } else { q })
163}
164
165fn compare_fixed(a: &Fixed, b: &Fixed) -> Ordering {
166 let dec = a.places.dec.max(b.places.dec);
167 let (ma, mb) = (align(a, dec, false).unwrap_or_default(), align(b, dec, false).unwrap_or_default());
168 match (a.negative, b.negative) {
169 (false, false) => ma.cmp(&mb),
170 (true, true) => mb.cmp(&ma),
171 (true, false) => Ordering::Less,
172 (false, true) => Ordering::Greater,
173 }
174}
175
176fn places_of(kind: Kind) -> Places {
177 let (digits, scale) = kind.digits_scale().unwrap_or((0, 0));
178 Places::new(digits - scale, scale)
179}
180
181fn zoned_digits(magnitude: u128, digits: usize, sign_zone: u8) -> Vec<u8> {
182 let mut out = vec![0xF0u8; digits];
183 let mut m = magnitude;
184 for b in out.iter_mut().rev() {
185 *b = 0xF0 | (m % 10) as u8;
186 m /= 10;
187 }
188 if let Some(last) = out.last_mut() {
189 *last = (sign_zone << 4) | (*last & 0x0F);
190 }
191 out
192}
193
194impl<'p, 'u, 'w> Machine<'p, 'u, 'w> {
195 pub fn activation(compiled: &'p Compiled, me: usize, unit: &'u mut RunUnit<'w>, main: bool) -> R<Self> {
198 let base = unit.programs[me].base;
199 let fresh = !unit.programs[me].initialized || compiled.program.initial;
200 unit.programs[me].active = true;
201 let mut m = Self {
202 program: &compiled.program,
203 layout: &compiled.layout,
204 options: compiled.options,
205 ssrange: compiled.ssrange,
206 page: compiled.options.code_page(),
207 resolved: HashMap::new(),
208 me,
209 base,
210 linkage: vec![None; compiled.layout.linkage_roots.len()],
211 local_base: 0,
212 main,
213 cics_handlers: cics::Handlers::default(),
214 unit,
215 };
216 if compiled.layout.local_size > 0 {
217 m.local_base = m.unit.push_temporary(&vec![0; compiled.layout.local_size as usize]);
218 m.initialize_values(true)?;
219 }
220 if fresh {
221 m.unit.mem[base..base + compiled.layout.size as usize].fill(0);
222 m.initialize_values(false)?;
223 m.unit.programs[me].initialized = true;
224 }
225 Ok(m)
226 }
227
228 fn initialize_values(&mut self, local: bool) -> R<()> {
230 let base = if local { self.local_base } else { self.base };
231 for index in 0..self.layout.items.len() {
232 let item = &self.layout.items[index];
233 let Some(value) = item.value.clone().filter(|_| item.linkage.is_none() && item.local == local) else { continue };
234 let occurrences: u32 = item.dims.iter().map(|&(_, n)| n).product::<u32>().max(1);
235 for k in 0..occurrences {
236 let offset = base + item.offset as usize + self.occurrence_offset(item, k);
237 let loc = Loc { offset, len: item.size as usize, kind: item.kind, item: index };
238 let val = self.literal_value(&value, item.pos)?;
239 self.assign(loc, val, None, item.pos)?;
240 }
241 }
242 Ok(())
243 }
244
245 fn occurrence_offset(&self, item: &Item, mut k: u32) -> usize {
246 let mut offset = 0usize;
247 for &(stride, count) in item.dims.iter().rev() {
248 offset += (k % count) as usize * stride as usize;
249 k /= count;
250 }
251 offset
252 }
253
254 pub fn run_procedure(&mut self) -> R<Ending> {
255 if self.program.paragraphs.is_empty() {
256 return Ok(Ending::EndOfProgram);
257 }
258 let last = self.program.paragraphs.len() - 1;
259 let mut start = 0;
260 loop {
261 match self.run_paragraphs(start, last)? {
262 Flow::End(e) => return Ok(e),
263 Flow::GoTo(t) => start = t,
264 _ => return Ok(Ending::EndOfProgram),
265 }
266 }
267 }
268
269 fn run_paragraphs(&mut self, from: usize, to: usize) -> R<Flow> {
270 let program = self.program;
271 let mut i = from;
272 while i <= to {
273 match self.run_sentences(&program.paragraphs[i].statements)? {
274 Flow::Next | Flow::ExitParagraph => i += 1,
275 Flow::ExitSection => i = crate::section_end(program, i) + 1,
276 Flow::GoTo(t) if (from..=to).contains(&t) => i = t,
277 Flow::ExitPerform | Flow::ExitPerformCycle => i += 1,
278 other => return Ok(other),
279 }
280 }
281 Ok(Flow::Next)
282 }
283
284 fn run_sentences(&mut self, stmts: &'p [Stmt]) -> R<Flow> {
286 let mut i = 0;
287 while i < stmts.len() {
288 match self.exec(&stmts[i])? {
289 Flow::Next => i += 1,
290 Flow::NextSentence => i = stmts[i..].iter().position(|s| *s == Stmt::SentenceEnd).map_or(stmts.len(), |j| i + j + 1),
291 other => return Ok(other),
292 }
293 }
294 Ok(Flow::Next)
295 }
296
297 fn run_block(&mut self, stmts: &'p [Stmt]) -> R<Flow> {
298 for s in stmts {
299 match self.exec(s)? {
300 Flow::Next => {}
301 other => return Ok(other),
302 }
303 }
304 Ok(Flow::Next)
305 }
306
307 fn procedure(&self, p: &ProcName, pos: Pos) -> R<(usize, usize)> {
308 crate::procedure(self.program, p).map_err(|m| Abend::ironwork(m, pos))
309 }
310
311 fn exec(&mut self, s: &'p Stmt) -> R<Flow> {
312 match s {
313 Stmt::Move { from, to, pos } => {
314 for r in to {
315 let dest = self.locate(r)?;
316 let (val, src) = self.operand_with_loc(from, *pos)?;
317 self.assign(dest, val, src, *pos)?;
318 }
319 }
320 Stmt::Compute { targets, expr, size_error, pos } => {
321 let computations: Vec<(Target, Expr)> = targets.iter().map(|t| (t.clone(), expr.clone())).collect();
322 return self.arithmetic(&computations, None, size_error.as_ref(), *pos);
323 }
324 Stmt::Arith(a) => return self.arithmetic(&a.computations, a.remainder.as_ref(), a.size_error.as_ref(), a.pos),
325 Stmt::If { cond, then, otherwise, pos } => {
326 let branch = if self.condition(cond, *pos)? { then } else { otherwise };
327 return self.run_block(branch);
328 }
329 Stmt::Evaluate { subjects, whens, other, pos } => {
330 for w in whens {
331 for alternative in &w.alternatives {
332 if self.alternative_matches(subjects, alternative, *pos)? {
333 return self.run_block(&w.body);
334 }
335 }
336 }
337 return self.run_block(other);
338 }
339 Stmt::PerformProc { from, thru, repeat, pos } => {
340 let (start, first_end) = self.procedure(from, *pos)?;
341 let end = match thru {
342 Some(t) => self.procedure(t, *pos)?.1,
343 None => first_end,
344 };
345 return self.repeat(repeat, *pos, &mut |m: &mut Self| m.run_paragraphs(start, end));
346 }
347 Stmt::PerformInline { body, repeat, pos } => return self.repeat(repeat, *pos, &mut |m: &mut Self| m.run_block(body)),
348 Stmt::Display { items, no_advancing, pos } => self.display(items, *no_advancing, *pos)?,
349 Stmt::Open { files, pos } => {
350 for (mode, name) in files {
351 self.open_file(*mode, name, *pos)?;
352 }
353 }
354 Stmt::Close { files, pos } => {
355 for name in files {
356 self.close_file(name, *pos)?;
357 }
358 }
359 Stmt::Read(r) => return self.read_stmt(r),
360 Stmt::Write { record, from, advancing, invalid, pos } => return self.write_stmt(record, from.as_ref(), advancing.as_ref(), invalid, *pos),
361 Stmt::Rewrite { record, from, invalid, pos } => return self.rewrite_stmt(record, from.as_ref(), invalid, *pos),
362 Stmt::Delete { file, invalid, pos } => return self.delete_stmt(file, invalid, *pos),
363 Stmt::Start { file, key, invalid, pos } => return self.start_stmt(file, key.as_ref(), invalid, *pos),
364 Stmt::Initialize { targets, pos } => {
365 for r in targets {
366 let loc = self.locate(r)?;
367 if loc.item == usize::MAX {
368 self.write(loc, &[0, 0]);
369 } else {
370 self.initialize(loc.item, loc.offset, *pos)?;
371 }
372 }
373 }
374 Stmt::GoTo { target, pos } => return Ok(Flow::GoTo(self.procedure(target, *pos)?.0)),
375 Stmt::Goback { .. } => return Ok(Flow::End(Ending::Goback)),
376 Stmt::ExitProgram { .. } if self.main => {}
377 Stmt::ExitProgram { .. } => return Ok(Flow::End(Ending::Goback)),
378 Stmt::Call(c) => return self.call(c),
379 Stmt::Cancel { targets, pos } => {
380 for t in targets {
381 let name = self.program_name(t, *pos)?;
382 self.cancel(&name, *pos)?;
383 }
384 }
385 Stmt::Set { set, pos } => self.set(set, *pos)?,
386 Stmt::Accept { target, from, pos } => self.accept(target, *from, *pos)?,
387 Stmt::String(st) => return self.string_stmt(st),
388 Stmt::Unstring(u) => return self.unstring(u),
389 Stmt::Inspect(i) => self.inspect(i)?,
390 Stmt::Search(se) => return self.search(se),
391 Stmt::NextSentence => return Ok(Flow::NextSentence),
392 Stmt::Exec(block) if block.declarative() => {}
393 Stmt::Exec(block) if block.kind == ExecKind::Cics => return self.cics(block),
394 Stmt::Exec(block) => {
395 let kind = match block.kind {
396 ExecKind::Sql => "SQL",
397 ExecKind::Cics => "CICS",
398 ExecKind::Dli => "DLI",
399 ExecKind::Other => "",
400 };
401 return Err(Abend {
402 code: "EXEC".into(),
403 message: format!("EXEC {kind} {} was reached: ironwork for COBOL checks EXEC statements but does not run them yet", block.command),
404 pos: block.pos,
405 });
406 }
407 Stmt::SentenceEnd => {}
408 Stmt::StopRun { .. } => return Ok(Flow::End(Ending::StopRun)),
409 Stmt::Exit(ExitKind::Paragraph) => return Ok(Flow::ExitParagraph),
410 Stmt::Exit(ExitKind::Section) => return Ok(Flow::ExitSection),
411 Stmt::Exit(ExitKind::Perform) => return Ok(Flow::ExitPerform),
412 Stmt::Exit(ExitKind::PerformCycle) => return Ok(Flow::ExitPerformCycle),
413 Stmt::Continue | Stmt::Exit(ExitKind::Plain) => {}
414 }
415 Ok(Flow::Next)
416 }
417
418 fn alternative_matches(&mut self, subjects: &[Subject], objects: &[Object], pos: Pos) -> R<bool> {
419 for (subject, object) in subjects.iter().zip(objects) {
420 let hit = match (subject, object) {
421 (_, Object::Any) => true,
422 (Subject::Bool(b), Object::Bool(o)) => b == o,
423 (Subject::Bool(b), Object::Cond(c)) => self.condition(c, pos)? == *b,
424 (Subject::Cond(c), Object::Bool(o)) => self.condition(c, pos)? == *o,
425 (Subject::Cond(c), Object::Cond(d)) => self.condition(c, pos)? == self.condition(d, pos)?,
426 (Subject::Expr(e), Object::Value { not, from, thru }) => {
427 let inside = match thru {
428 None => self.compare(e, from, pos)? == Ordering::Equal,
429 Some(t) => self.compare(e, from, pos)? != Ordering::Less && self.compare(e, t, pos)? != Ordering::Greater,
430 };
431 inside != *not
432 }
433 _ => return Err(Abend::ironwork("a WHEN object of a different kind from its subject", pos)),
434 };
435 if !hit {
436 return Ok(false);
437 }
438 }
439 Ok(true)
440 }
441
442 fn repeat(&mut self, repeat: &'p Loop, pos: Pos, body: &mut dyn FnMut(&mut Self) -> R<Flow>) -> R<Flow> {
445 self.nest(pos)?;
446 let flow = self.repeat_nested(repeat, pos, body);
447 self.unit.depth -= 1;
448 flow
449 }
450
451 fn nest(&mut self, pos: Pos) -> R<()> {
452 if self.unit.depth >= crate::unit::MAX_DEPTH {
453 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {}", crate::unit::MAX_DEPTH), pos));
454 }
455 self.unit.depth += 1;
456 Ok(())
457 }
458
459 fn repeat_nested(&mut self, repeat: &'p Loop, pos: Pos, body: &mut dyn FnMut(&mut Self) -> R<Flow>) -> R<Flow> {
460 enum Step {
461 Again,
462 Leave,
463 Out(Flow),
464 }
465 let mut run = |m: &mut Self| -> R<Step> {
466 Ok(match body(m)? {
467 Flow::Next | Flow::ExitPerformCycle => Step::Again,
468 Flow::ExitPerform => Step::Leave,
469 other => Step::Out(other),
470 })
471 };
472 match repeat {
473 Loop::Once => match run(self)? {
474 Step::Out(f) => return Ok(f),
475 Step::Again | Step::Leave => {}
476 },
477 Loop::Times(count) => {
478 for _ in 0..self.integer(count, pos)?.max(0) {
479 match run(self)? {
480 Step::Again => {}
481 Step::Leave => break,
482 Step::Out(f) => return Ok(f),
483 }
484 }
485 }
486 Loop::Until { cond, test_after } => loop {
487 if !test_after && self.condition(cond, pos)? {
488 break;
489 }
490 match run(self)? {
491 Step::Again => {}
492 Step::Leave => break,
493 Step::Out(f) => return Ok(f),
494 }
495 if *test_after && self.condition(cond, pos)? {
496 break;
497 }
498 },
499 Loop::Varying { varying, test_after } => {
500 let var = self.locate(&varying.var)?;
501 let start = self.expr_value(&varying.from, pos)?;
502 self.assign(var, start, None, pos)?;
503 loop {
504 if !test_after && self.condition(&varying.until, pos)? {
505 break;
506 }
507 match run(self)? {
508 Step::Again => {}
509 Step::Leave => break,
510 Step::Out(f) => return Ok(f),
511 }
512 if *test_after && self.condition(&varying.until, pos)? {
513 break;
514 }
515 let var = self.locate(&varying.var)?;
516 let step = Expr::Bin(Box::new(Expr::Operand(Operand::Ref(varying.var.clone()))), BinOp::Add, Box::new(varying.by.clone()));
517 let dmax = var.kind.digits_scale().map_or(0, |(_, s)| s).max(self.dmax(&step)?);
518 let next = self.eval_fixed(&step, dmax, pos)?;
519 self.store_fixed(var, &next, false, pos)?;
520 }
521 }
522 }
523 Ok(Flow::Next)
524 }
525
526 fn resolve(&mut self, r: &Ref) -> R<Resolved> {
527 let key = (r.name.clone(), r.qualifiers.clone());
528 if let Some(&hit) = self.resolved.get(&key) {
529 return Ok(hit);
530 }
531 let found = self.layout.resolve(&r.name, &r.qualifiers, r.pos).map_err(|e| Abend::ironwork(e.message, r.pos))?;
532 self.resolved.insert(key, found);
533 Ok(found)
534 }
535
536 fn locate(&mut self, r: &Ref) -> R<Loc> {
537 if r.name == "RETURN-CODE" && r.qualifiers.is_empty() && !self.layout.items.iter().any(|i| i.name.as_deref() == Some("RETURN-CODE")) {
538 return Ok(Loc { offset: RETURN_CODE, len: 2, kind: Kind::Binary { digits: 4, scale: 0, signed: true, native: false }, item: usize::MAX });
539 }
540 let Resolved::Item(index) = self.resolve(r)? else {
541 return Err(Abend::ironwork(format!("{} is a condition-name, not a data item", r.name), r.pos));
542 };
543 let layout = self.layout;
544 let item = &layout.items[index];
545 if r.subscripts.len() != item.dims.len() {
546 return Err(Abend::ironwork(format!("{} takes {} subscripts, not {}", r.name, item.dims.len(), r.subscripts.len()), r.pos));
547 }
548 let base = match item.linkage {
549 Some(l) => self.linkage[l as usize].ok_or_else(|| Abend {
550 code: "S0C4".into(),
551 message: format!("{} is a LINKAGE item with no address: no argument was passed for it, and no SET ADDRESS OF gave it one", r.name),
552 pos: r.pos,
553 })?,
554 None if item.local => self.local_base,
555 None => self.base,
556 };
557 let mut offset = (base + item.offset as usize) as i64;
558 for (&(stride, count), sub) in item.dims.iter().zip(&r.subscripts) {
559 let s = self.integer(sub, r.pos)?;
560 if self.ssrange && (s < 1 || s > count as i64) {
561 return Err(Abend::ironwork(format!("subscript {s} of {} is out of range 1 to {count} (SSRANGE)", r.name), r.pos));
562 }
563 offset += (s - 1) * stride as i64;
564 }
565 let (mut len, mut kind) = (item.size as i64, item.kind);
566 if let Some(t) = item.odo {
567 let table = &layout.items[t];
568 let current = self.occurrences(t, r.pos)?;
569 len -= (table.occurs - current) as i64 * table.size as i64;
570 }
571 if let Some(rm) = &r.refmod {
572 let start = self.integer(&rm.start, r.pos)?;
573 let length = match &rm.length {
574 Some(l) => self.integer(l, r.pos)?,
575 None => len - start + 1,
576 };
577 if self.ssrange && (start < 1 || length < 1 || start + length - 1 > len) {
578 return Err(Abend::ironwork(format!("reference modification ({start}:{length}) of {} is out of range (SSRANGE)", r.name), r.pos));
579 }
580 offset += start - 1;
581 len = length;
582 kind = Kind::Alnum { justified: false };
583 }
584 if offset < 0 || len < 0 || offset + len > self.unit.mem.len() as i64 {
585 return Err(Abend::ironwork(format!("{} reaches outside the run unit's storage", r.name), r.pos));
586 }
587 Ok(Loc { offset: offset as usize, len: len as usize, kind, item: index })
588 }
589
590 fn occurrences(&mut self, table: usize, pos: Pos) -> R<u32> {
593 let layout = self.layout;
594 let item = &layout.items[table];
595 let Some(object) = &item.depending_on else { return Ok(item.occurs) };
596 let count = self.integer(&Expr::Operand(Operand::Ref(object.clone())), pos)?;
597 if self.ssrange && !(0..=item.occurs as i64).contains(&count) {
598 return Err(Abend::ironwork(format!("{} = {count} is outside the OCCURS DEPENDING ON range 0 to {} (SSRANGE)", object.name, item.occurs), pos));
599 }
600 Ok(count.clamp(0, item.occurs as i64) as u32)
601 }
602
603 fn bytes(&self, loc: Loc) -> &[u8] {
604 &self.unit.mem[loc.offset..loc.offset + loc.len]
605 }
606
607 fn write(&mut self, loc: Loc, bytes: &[u8]) {
608 self.unit.mem[loc.offset..loc.offset + loc.len].copy_from_slice(bytes);
609 }
610
611 fn integer(&mut self, e: &Expr, pos: Pos) -> R<i64> {
612 let dmax = self.dmax(e)?;
613 let v = self.eval_fixed(e, dmax, pos)?;
614 let whole = align(&v, 0, false).and_then(|m| m.to_u128()).and_then(|m| i64::try_from(m).ok());
615 let whole = whole.ok_or_else(|| Abend::ironwork("an integer operand beyond 64 bits", pos))?;
616 Ok(if v.negative { -whole } else { whole })
617 }
618
619 fn literal_value(&self, lit: &Literal, pos: Pos) -> R<Val> {
620 Ok(match lit {
621 Literal::Alnum(s) => Val::Bytes(self.page.encode(s).map_err(|e| Abend::ironwork(e.to_string(), pos))?),
622 Literal::Hex(b) => Val::Bytes(b.clone()),
623 Literal::National(s) => Val::National(s.encode_utf16().flat_map(u16::to_be_bytes).collect()),
624 Literal::Number(t) => Val::Num(literal_fixed(t).ok_or_else(|| Abend::ironwork(format!("the literal {t} has more than 31 digits"), pos))?),
625 Literal::Figurative(f) => Val::Fig(*f),
626 Literal::All(inner) => match self.literal_value(inner, pos)? {
627 Val::Bytes(b) => Val::All(b),
628 Val::Fig(f) => Val::Fig(f),
629 _ => return Err(Abend::ironwork("ALL takes an alphanumeric literal", pos)),
630 },
631 })
632 }
633
634 fn read(&self, loc: Loc, pos: Pos) -> R<Val> {
635 let bytes = self.bytes(loc);
636 let places = places_of(loc.kind);
637 Ok(match loc.kind {
638 Kind::Group | Kind::Alnum { .. } | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. } => Val::Bytes(bytes.to_vec()),
639 Kind::National => Val::National(bytes.to_vec()),
640 Kind::Pointer => Val::Address(u32::from_be_bytes(bytes.try_into().unwrap())),
641 Kind::Index => Val::Num(Fixed::new(i32::from_be_bytes(bytes.try_into().unwrap()) as i128, Places::new(9, 0))),
642 Kind::Float(p) => Val::Float(Hfp::from_bytes(p, bytes)),
643 Kind::Binary { digits, signed, native, .. } => Val::Num(Fixed::new(Binary { digits: digits as u8, signed, native }.load(bytes), places)),
644 Kind::Packed { signed, .. } => {
645 let mut p = bytes.to_vec();
646 if !signed && self.options.numproc == Numproc::Nopfd {
647 *p.last_mut().unwrap() |= 0x0F;
648 }
649 Val::Num(self.decode(&p, places, pos)?)
650 }
651 Kind::Zoned { digits, signed, sign, .. } => Val::Num(self.zoned_value(bytes, digits, signed, sign, places, pos)?),
652 })
653 }
654
655 fn decode(&self, packed: &[u8], places: Places, pos: Pos) -> R<Fixed> {
656 let d = decimal::decode(packed).map_err(|c| Abend::check(c, pos))?;
657 Ok(fixed(d.negative, U256::from_u128(d.magnitude), places))
658 }
659
660 fn zoned_value(&self, bytes: &[u8], digits: u32, signed: bool, sign: Option<SignClause>, places: Places, pos: Pos) -> R<Fixed> {
662 let mut zoned = bytes.to_vec();
663 let mut separate_negative = None;
664 match sign {
665 Some(SignClause { separate: true, position }) => {
666 let s = if position == SignPosition::Leading { zoned.remove(0) } else { zoned.pop().unwrap() };
667 separate_negative = Some(match s {
668 0x60 => true,
669 0x4E => false,
670 _ => return Err(Abend::check(ProgramCheck::Data, pos)),
671 });
672 *zoned.last_mut().unwrap() |= 0xF0;
673 }
674 Some(SignClause { separate: false, position: SignPosition::Leading }) => {
675 let zone = zoned[0] & 0xF0;
676 zoned[0] |= 0xF0;
677 let last = zoned.len() - 1;
678 zoned[last] = zone | (zoned[last] & 0x0F);
679 }
680 _ => {}
681 }
682 let mut packed = vec![0u8; digits as usize / 2 + 1];
683 decimal::pack(&mut packed, &zoned).map_err(|c| Abend::check(c, pos))?;
684 if !signed && self.options.numproc == Numproc::Nopfd {
685 *packed.last_mut().unwrap() |= 0x0F;
686 }
687 let v = self.decode(&packed, places, pos)?;
688 Ok(match separate_negative {
689 Some(negative) => fixed(negative, v.magnitude, places),
690 None => v,
691 })
692 }
693
694 fn operand_with_loc(&mut self, op: &Operand, pos: Pos) -> R<(Val, Option<Loc>)> {
695 if let Operand::Ref(r) = op {
696 let loc = self.locate(r)?;
697 return Ok((self.read(loc, r.pos)?, Some(loc)));
698 }
699 Ok((self.operand(op, pos)?, None))
700 }
701
702 fn operand(&mut self, op: &Operand, pos: Pos) -> R<Val> {
703 match op {
704 Operand::Ref(r) => {
705 let loc = self.locate(r)?;
706 self.read(loc, r.pos)
707 }
708 Operand::Literal(lit) => self.literal_value(lit, pos),
709 Operand::LengthOf(r) => {
710 let loc = self.locate(r)?;
711 Ok(Val::Num(Fixed::new(loc.len as i128, Places::new(9, 0))))
712 }
713 Operand::Function(f) => self.function(f),
714 Operand::AddressOf(r) => Ok(Val::Address(self.address_of(r)?)),
715 }
716 }
717
718 fn natural_bytes(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
721 if let Operand::Ref(r) = op {
722 let loc = self.locate(r)?;
723 return Ok(self.bytes(loc).to_vec());
724 }
725 Ok(match self.operand(op, pos)? {
726 Val::Bytes(b) | Val::All(b) | Val::National(b) => b,
727 Val::Fig(f) => vec![figurative_byte(f)],
728 Val::Num(f) => zoned_digits(f.magnitude.to_u128().unwrap_or(0), f.places.total() as usize, decimal::UNSIGNED),
729 _ => return Err(Abend::ironwork("this operand has no characters to work on", pos)),
730 })
731 }
732
733 fn set_integer(&mut self, r: &Ref, value: i64, pos: Pos) -> R<()> {
734 let dest = self.locate(r)?;
735 self.store_fixed(dest, &Fixed::new(value as i128, Places::new(19, 0)), false, pos)
736 }
737
738 fn overflow_branch(&mut self, overflow: bool, on: &'p Option<Vec<Stmt>>, not_on: &'p Option<Vec<Stmt>>) -> R<Flow> {
739 match (overflow, on, not_on) {
740 (true, Some(body), _) | (false, _, Some(body)) => self.run_block(body),
741 _ => Ok(Flow::Next),
742 }
743 }
744
745 fn string_stmt(&mut self, st: &'p StringStmt) -> R<Flow> {
746 let pos = st.pos;
747 let dest = self.locate(&st.into)?;
748 let mut pointer = match &st.pointer {
749 Some(r) => self.integer(&Expr::Operand(Operand::Ref(r.clone())), pos)?,
750 None => 1,
751 };
752 let len = dest.len as i64;
753 let mut overflow = pointer < 1 || pointer > len;
754 if !overflow {
755 'sources: for (op, delimiter) in &st.sources {
756 let bytes = self.natural_bytes(op, pos)?;
757 let delimiter = match delimiter {
758 Delimiter::Size => None,
759 Delimiter::By(d) => Some(self.natural_bytes(d, pos)?),
760 };
761 for b in crate::strings::delimited(&bytes, delimiter.as_deref()) {
762 if pointer > len {
763 overflow = true;
764 break 'sources;
765 }
766 self.unit.mem[dest.offset + pointer as usize - 1] = b;
767 pointer += 1;
768 }
769 }
770 }
771 if let Some(r) = &st.pointer {
772 self.set_integer(r, pointer, pos)?;
773 }
774 self.overflow_branch(overflow, &st.on_overflow, &st.not_on_overflow)
775 }
776
777 fn unstring(&mut self, u: &'p Unstring) -> R<Flow> {
778 let pos = u.pos;
779 let source_loc = self.locate(&u.source)?;
780 let source = self.bytes(source_loc).to_vec();
781 let len = source.len() as i64;
782 let mut pointer = match &u.pointer {
783 Some(r) => self.integer(&Expr::Operand(Operand::Ref(r.clone())), pos)?,
784 None => 1,
785 };
786 let mut delimiters = Vec::new();
787 for (all, d) in &u.delimiters {
788 delimiters.push((*all, self.natural_bytes(d, pos)?));
789 }
790 let mut overflow = pointer < 1 || pointer > len;
791 let mut fields = 0i64;
792 if !overflow {
793 for into in &u.into {
794 if pointer > len {
795 break;
796 }
797 let start = pointer as usize - 1;
798 let dest = self.locate(&into.target)?;
799 let (end, matched) = if delimiters.is_empty() {
800 ((start + dest.len).min(source.len()), None)
801 } else {
802 match crate::strings::next_delimiter(&source, start, &delimiters) {
803 Some((at, k)) => (at, Some(k)),
804 None => (source.len(), None),
805 }
806 };
807 self.assign(dest, Val::Bytes(source[start..end].to_vec()), None, pos)?;
808 let delimiter = matched.map(|k| delimiters[k].1.clone());
809 if let Some(r) = &into.delimiter_in {
810 let d = self.locate(r)?;
811 self.assign(d, delimiter.clone().map_or(Val::Fig(Figurative::Space), Val::Bytes), None, pos)?;
812 }
813 if let Some(r) = &into.count_in {
814 self.set_integer(r, (end - start) as i64, pos)?;
815 }
816 let next = match matched {
817 Some(k) => crate::strings::past_delimiter(&source, end, &delimiters[k].1, delimiters[k].0),
818 None => end,
819 };
820 pointer = next as i64 + 1;
821 fields += 1;
822 }
823 overflow = pointer <= len;
824 }
825 if let Some(r) = &u.pointer {
826 self.set_integer(r, pointer, pos)?;
827 }
828 if let Some(r) = &u.tallying {
829 let dest = self.locate(r)?;
830 let Val::Num(current) = self.read(dest, pos)? else {
831 return Err(Abend::ironwork("TALLYING IN needs a numeric item", pos));
832 };
833 let total = current.add(Fixed::new(fields as i128, Places::new(19, 0)), 0, self.options.arith).map_err(|_| Abend::ironwork("TALLYING", pos))?;
834 self.store_fixed(dest, &total, false, pos)?;
835 }
836 self.overflow_branch(overflow, &u.on_overflow, &u.not_on_overflow)
837 }
838
839 fn inspect(&mut self, i: &Inspect) -> R<()> {
840 let pos = i.pos;
841 let loc = self.locate(&i.target)?;
842 let mut data = self.bytes(loc).to_vec();
843 let tallied = self.phrases(&data, &i.tallying, pos)?;
844 let counts = crate::strings::inspect(&mut data, &tallied);
845 for (phrase, count) in i.tallying.iter().zip(counts) {
846 let Some(counter) = &phrase.counter else { continue };
847 let dest = self.locate(counter)?;
848 let Val::Num(current) = self.read(dest, pos)? else {
849 return Err(Abend::ironwork("a TALLYING counter must be numeric", pos));
850 };
851 let total = current.add(Fixed::new(count as i128, Places::new(19, 0)), 0, self.options.arith).map_err(|_| Abend::ironwork("TALLYING", pos))?;
852 self.store_fixed(dest, &total, false, pos)?;
853 }
854 let mut replacing = self.phrases(&data, &i.replacing, pos)?;
855 if let Some((from, to, bounds)) = &i.converting {
856 let (from, to) = (self.natural_bytes(from, pos)?, self.natural_bytes(to, pos)?);
857 if from.len() != to.len() {
858 return Err(Abend::ironwork("CONVERTING needs operands of the same length", pos));
859 }
860 let (start, end) = self.region(&data, bounds, pos)?;
861 let mut seen = Vec::new();
862 for (f, t) in from.into_iter().zip(to) {
863 if !seen.contains(&f) {
864 seen.push(f);
865 replacing.push(crate::strings::Phrase { mode: InspectMode::All, pattern: vec![f], by: Some(vec![t]), start, end });
866 }
867 }
868 }
869 crate::strings::inspect(&mut data, &replacing);
870 self.write(loc, &data);
871 Ok(())
872 }
873
874 fn region(&mut self, data: &[u8], bounds: &[Bound], pos: Pos) -> R<(usize, usize)> {
875 let (mut before, mut after) = (None, None);
876 for b in bounds {
877 let v = self.natural_bytes(&b.value, pos)?;
878 if b.after { after = Some(v) } else { before = Some(v) }
879 }
880 Ok(crate::strings::region(data, before.as_deref(), after.as_deref()))
881 }
882
883 fn phrases(&mut self, data: &[u8], phrases: &[InspectPhrase], pos: Pos) -> R<Vec<crate::strings::Phrase>> {
884 let mut out = Vec::new();
885 for p in phrases {
886 let pattern = match &p.pattern {
887 Some(op) => self.natural_bytes(op, pos)?,
888 None => Vec::new(),
889 };
890 let len = pattern.len().max(1);
891 let by = match &p.by {
892 Some(Operand::Literal(Literal::Figurative(f))) => Some(vec![figurative_byte(*f); len]),
893 Some(op) => Some(self.natural_bytes(op, pos)?),
894 None => None,
895 };
896 if by.as_ref().is_some_and(|b| b.len() != len) {
897 return Err(Abend::ironwork("a REPLACING value must be as long as what it replaces", pos));
898 }
899 let (start, end) = self.region(data, &p.bounds, pos)?;
900 out.push(crate::strings::Phrase { mode: p.mode, pattern, by, start, end });
901 }
902 Ok(out)
903 }
904
905 fn search(&mut self, se: &'p Search) -> R<Flow> {
906 let pos = se.pos;
907 let Resolved::Item(t) = self.resolve(&se.table)? else {
908 return Err(Abend::ironwork(format!("SEARCH {}: not a table", se.table.name), pos));
909 };
910 let layout = self.layout;
911 let table = &layout.items[t];
912 let count = self.occurrences(t, pos)? as i64;
913 let index = match (&se.varying, table.index_names.first()) {
914 (_, Some(name)) => Ref { name: name.clone(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos },
915 (Some(v), None) => v.clone(),
916 (None, None) => return Err(Abend::ironwork(format!("SEARCH {}: the table has no INDEXED BY", se.table.name), pos)),
917 };
918 let index_expr = Expr::Operand(Operand::Ref(index.clone()));
919 if !se.all {
920 loop {
921 let i = self.integer(&index_expr, pos)?;
922 if i < 1 || i > count {
923 return match &se.at_end {
924 Some(body) => self.run_block(body),
925 None => Ok(Flow::Next),
926 };
927 }
928 for (cond, body) in &se.whens {
929 if self.condition(cond, pos)? {
930 return self.run_block(body);
931 }
932 }
933 self.set_integer(&index, i + 1, pos)?;
934 if let Some(v) = &se.varying
935 && v.name != index.name
936 {
937 let current = self.integer(&Expr::Operand(Operand::Ref(v.clone())), pos)?;
938 self.set_integer(v, current + 1, pos)?;
939 }
940 }
941 }
942 let (cond, body) = &se.whens[0];
943 let mut terms = Vec::new();
944 flatten_and(cond, &mut terms);
945 let (mut low, mut high) = (1i64, count);
946 while low <= high {
947 let mid = (low + high) / 2;
948 self.set_integer(&index, mid, pos)?;
949 let mut order = Ordering::Equal;
950 for (ascending, key) in &table.keys {
951 let Some((subject, value)) = key_term(&terms, &key.name) else { continue };
952 let o = self.compare(subject, value, pos)?;
953 order = if *ascending { o } else { o.reverse() };
954 if order != Ordering::Equal {
955 break;
956 }
957 }
958 match order {
959 Ordering::Less => low = mid + 1,
960 Ordering::Greater => high = mid - 1,
961 Ordering::Equal => {
962 return if self.condition(cond, pos)? {
963 self.run_block(body)
964 } else {
965 match &se.at_end {
966 Some(b) => self.run_block(b),
967 None => Ok(Flow::Next),
968 }
969 };
970 }
971 }
972 }
973 match &se.at_end {
974 Some(b) => self.run_block(b),
975 None => Ok(Flow::Next),
976 }
977 }
978
979 fn address_of(&mut self, r: &Ref) -> R<u32> {
981 if let Ok(Resolved::Item(i)) = self.resolve(r)
982 && let Some(l) = self.layout.items[i].linkage
983 && self.linkage[l as usize].is_none()
984 {
985 return Ok(0);
986 }
987 let loc = self.locate(r)?;
988 Ok(ADDRESS_BASE + loc.offset as u32)
989 }
990
991 fn offset_of(&self, address: u32, pos: Pos) -> R<Option<usize>> {
993 if address == 0 {
994 return Ok(None);
995 }
996 let offset = address.checked_sub(ADDRESS_BASE).map(|o| o as usize).filter(|&o| o < self.unit.mem.len());
997 offset.map(Some).ok_or_else(|| Abend { code: "S0C4".into(), message: format!("address {address:08X} is outside the run unit's storage"), pos })
998 }
999
1000 fn program_name(&mut self, op: &Operand, pos: Pos) -> R<String> {
1001 Ok(match self.operand(op, pos)? {
1002 Val::Bytes(b) => self.page.decode(&b).trim().to_ascii_uppercase(),
1003 _ => return Err(Abend::ironwork("a program name must be alphanumeric", pos)),
1004 })
1005 }
1006
1007 fn call(&mut self, c: &'p Call) -> R<Flow> {
1008 let pos = c.pos;
1009 let name = self.program_name(&c.target, pos)?;
1010 let index = match self.unit.load(&name) {
1011 Ok(i) => i,
1012 Err(LoadError::NotFound) => {
1013 return match &c.on_exception {
1014 Some(body) => self.run_block(body),
1015 None => Err(Abend { code: "S806".into(), message: format!("CALL {name}: no such program in the run unit or its program libraries"), pos }),
1016 };
1017 }
1018 Err(LoadError::Compile(message)) => return Err(Abend::ironwork(format!("CALL {name}: {message}"), pos)),
1019 };
1020 let Some(compiled) = self.unit.programs[index].compiled.clone() else {
1021 return Err(Abend::ironwork(format!("CALL {name}: the first program of the run unit is already active"), pos));
1022 };
1023 if self.unit.programs[index].active && !compiled.program.recursive {
1024 return Err(Abend::ironwork(format!("CALL {name}: the program is already active and is not RECURSIVE"), pos));
1025 }
1026 self.nest(pos)?;
1027 let result = self.call_nested(c, index, compiled);
1028 self.unit.depth -= 1;
1029 result
1030 }
1031
1032 fn call_nested(&mut self, c: &'p Call, index: usize, compiled: std::rc::Rc<Compiled>) -> R<Flow> {
1033 let pos = c.pos;
1034 let mark = self.unit.mem.len();
1035 let mut addresses = Vec::new();
1036 for arg in &c.using {
1037 let Some(op) = &arg.value else {
1038 addresses.push(None);
1039 continue;
1040 };
1041 let at = match (arg.mode, op) {
1042 (ArgMode::Reference, Operand::Ref(r)) => {
1043 let loc = self.locate(r)?;
1044 loc.offset
1045 }
1046 (ArgMode::Value, _) => {
1047 let bytes = self.value_argument(op, pos)?;
1048 self.unit.push_temporary(&bytes)
1049 }
1050 (_, _) => {
1051 let bytes = self.content_argument(op, pos)?;
1052 self.unit.push_temporary(&bytes)
1053 }
1054 };
1055 addresses.push(Some(at));
1056 }
1057 let outcome = {
1058 let mut callee = Machine::activation(&compiled, index, &mut *self.unit, false)?;
1059 callee.bind(&addresses);
1060 callee.bind_returning();
1061 let ending = callee.run_procedure();
1062 let returned = match (&compiled.program.returning, &ending) {
1063 (Some(item), Ok(_)) => Some(callee.returned(item, pos)?),
1064 _ => None,
1065 };
1066 (ending, returned)
1067 };
1068 self.unit.programs[index].active = false;
1069 if compiled.program.initial {
1070 self.unit.programs[index].initialized = false;
1071 }
1072 self.unit.release_temporaries(mark);
1073 let (ending, returned) = outcome;
1074 if ending? == Ending::StopRun {
1075 return Ok(Flow::End(Ending::StopRun));
1076 }
1077 if let (Some(target), Some(val)) = (&c.returning, returned) {
1078 let dest = self.locate(target)?;
1079 self.assign(dest, val, None, pos)?;
1080 }
1081 match &c.not_on_exception {
1082 Some(body) => self.run_block(body),
1083 None => Ok(Flow::Next),
1084 }
1085 }
1086
1087 fn bind(&mut self, addresses: &[Option<usize>]) {
1089 for (param, address) in self.program.using.iter().zip(addresses) {
1090 if let Some(ordinal) = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some(param.name.as_str())) {
1091 self.linkage[ordinal] = *address;
1092 }
1093 }
1094 }
1095
1096 fn bind_returning(&mut self) {
1099 let Some(name) = &self.program.returning else { return };
1100 let Some(ordinal) = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some(name.as_str())) else { return };
1101 let size = self.layout.items[self.layout.linkage_roots[ordinal]].size as usize;
1102 self.linkage[ordinal] = Some(self.unit.push_temporary(&vec![0; size]));
1103 }
1104
1105 fn returned(&mut self, name: &str, pos: Pos) -> R<Val> {
1106 let r = Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos };
1107 let loc = self.locate(&r)?;
1108 self.read(loc, pos)
1109 }
1110
1111 fn content_argument(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
1113 if let Operand::Ref(r) = op {
1114 let loc = self.locate(r)?;
1115 return Ok(self.bytes(loc).to_vec());
1116 }
1117 Ok(match self.operand(op, pos)? {
1118 Val::Bytes(b) | Val::All(b) | Val::National(b) => b,
1119 Val::Fig(f) => vec![figurative_byte(f)],
1120 Val::Address(a) => a.to_be_bytes().to_vec(),
1121 Val::Num(f) if matches!(op, Operand::LengthOf(_)) => (align(&f, 0, false).and_then(|m| m.to_u128()).unwrap_or(0) as u32).to_be_bytes().to_vec(),
1122 Val::Num(f) => {
1123 let digits = f.places.total().max(1);
1124 let magnitude = align(&f, f.places.dec, false).and_then(|m| m.to_u128()).unwrap_or(0);
1125 zoned_digits(magnitude, digits as usize, if f.negative { decimal::MINUS } else { decimal::UNSIGNED })
1126 }
1127 Val::Float(h) => h.to_bytes(),
1128 })
1129 }
1130
1131 fn value_argument(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
1133 Ok(match self.operand(op, pos)? {
1134 Val::Num(f) => {
1135 let whole = align(&f, 0, false).and_then(|m| m.to_u128()).and_then(|m| i32::try_from(m).ok()).ok_or_else(|| Abend::ironwork("a BY VALUE integer beyond a fullword", pos))?;
1136 (if f.negative { -whole } else { whole }).to_be_bytes().to_vec()
1137 }
1138 Val::Address(a) => a.to_be_bytes().to_vec(),
1139 Val::Fig(Figurative::Null) => vec![0; 4],
1140 Val::Bytes(b) => b,
1141 _ => return Err(Abend::ironwork("this BY VALUE argument is not supported", pos)),
1142 })
1143 }
1144
1145 fn cancel(&mut self, name: &str, pos: Pos) -> R<()> {
1146 let Some(index) = self.unit.find(name) else { return Ok(()) };
1147 if self.unit.programs[index].active {
1148 return Err(Abend::ironwork(format!("CANCEL {name}: the program is active"), pos));
1149 }
1150 let files: Vec<_> = self.unit.programs[index].files.iter_mut().filter_map(Option::take).collect();
1151 for f in files {
1152 f.close().map_err(|e| Abend::ironwork(format!("CANCEL {name}: {e}"), pos))?;
1153 }
1154 self.unit.programs[index].initialized = false;
1155 Ok(())
1156 }
1157
1158 fn set(&mut self, set: &SetStmt, pos: Pos) -> R<()> {
1159 match set {
1160 SetStmt::ConditionTrue(targets) => {
1161 for r in targets {
1162 let Resolved::Condition(index) = self.resolve(r)? else {
1163 return Err(Abend::ironwork(format!("SET {} TO TRUE: not a condition-name", r.name), pos));
1164 };
1165 let condition = &self.layout.conditions[index];
1166 let Some((value, _)) = condition.values.first().cloned() else { continue };
1167 let item = &self.layout.items[condition.item];
1168 let subject = Ref { name: item.name.clone().unwrap_or_default(), qualifiers: Vec::new(), subscripts: r.subscripts.clone(), refmod: None, pos };
1169 let dest = self.locate(&subject)?;
1170 let val = self.literal_value(&value, pos)?;
1171 self.assign(dest, val, None, pos)?;
1172 }
1173 }
1174 SetStmt::To { targets, value } => {
1175 for r in targets {
1176 let dest = self.locate(r)?;
1177 let (val, src) = self.operand_with_loc(value, pos)?;
1178 match (dest.kind, val) {
1179 (Kind::Pointer, v @ (Val::Address(_) | Val::Fig(Figurative::Null))) => self.assign(dest, v, None, pos)?,
1180 (Kind::Pointer, _) => return Err(Abend::ironwork("SET a pointer TO ADDRESS OF, NULL or another pointer", pos)),
1181 (_, v) => self.assign(dest, v, src, pos)?,
1182 }
1183 }
1184 }
1185 SetStmt::AddressOf { targets, value } => {
1186 let address = match self.operand(value, pos)? {
1187 Val::Address(a) => a,
1188 Val::Fig(Figurative::Null) => 0,
1189 _ => return Err(Abend::ironwork("SET ADDRESS OF takes a pointer, ADDRESS OF or NULL", pos)),
1190 };
1191 let offset = self.offset_of(address, pos)?;
1192 for r in targets {
1193 let Resolved::Item(i) = self.resolve(r)? else {
1194 return Err(Abend::ironwork(format!("SET ADDRESS OF {}: not a data item", r.name), pos));
1195 };
1196 let Some(ordinal) = self.layout.items[i].linkage.filter(|_| self.layout.items[i].parent.is_none()) else {
1197 return Err(Abend::ironwork(format!("SET ADDRESS OF {}: only a LINKAGE record can be given an address", r.name), pos));
1198 };
1199 self.linkage[ordinal as usize] = offset;
1200 }
1201 }
1202 SetStmt::UpDown { targets, down, by } => {
1203 let step = self.integer(by, pos)?;
1204 let step = if *down { -step } else { step };
1205 for r in targets {
1206 let dest = self.locate(r)?;
1207 match self.read(dest, pos)? {
1208 Val::Address(a) => {
1209 let moved = u32::try_from(a as i64 + step).map_err(|_| Abend::ironwork("a pointer moved below zero", pos))?;
1210 self.write(dest, &moved.to_be_bytes());
1211 }
1212 Val::Num(f) => {
1213 let next = f.add(Fixed::new(step as i128, Places::new(19, 0)), 0, self.options.arith).map_err(|_| Abend::ironwork("SET UP/DOWN", pos))?;
1214 self.store_fixed(dest, &next, false, pos)?;
1215 }
1216 _ => return Err(Abend::ironwork("SET UP BY and DOWN BY take an index, integer or pointer", pos)),
1217 }
1218 }
1219 }
1220 }
1221 Ok(())
1222 }
1223
1224 fn accept(&mut self, target: &Ref, from: AcceptFrom, pos: Pos) -> R<()> {
1225 let dest = self.locate(target)?;
1226 let (seconds, hundredths) = self.unit.now();
1227 let (year, month, day, hour, minute, second, yday, wday) = crate::unit::civil(seconds);
1228 let digits = |text: String| Val::Num(literal_fixed(&text).expect("digits"));
1229 let val = match from {
1230 AcceptFrom::Date { four_digit_year: true } => digits(format!("{year:04}{month:02}{day:02}")),
1231 AcceptFrom::Date { four_digit_year: false } => digits(format!("{:02}{month:02}{day:02}", year % 100)),
1232 AcceptFrom::Day { four_digit_year: true } => digits(format!("{year:04}{yday:03}")),
1233 AcceptFrom::Day { four_digit_year: false } => digits(format!("{:02}{yday:03}", year % 100)),
1234 AcceptFrom::DayOfWeek => digits(format!("{wday}")),
1235 AcceptFrom::Time => digits(format!("{hour:02}{minute:02}{second:02}{hundredths:02}")),
1236 AcceptFrom::Sysin => {
1237 let mut line = String::new();
1238 let read = match self.unit.sysin.as_mut() {
1239 Some(r) => r.read_line(&mut line).map_err(|e| Abend::ironwork(format!("ACCEPT: {e}"), pos))?,
1240 None => 0,
1241 };
1242 if read == 0 {
1243 let _ = writeln!(self.unit.err, "ironwork: {pos}: ACCEPT found SYSIN at its end; {} is unchanged", target.name);
1244 return Ok(());
1245 }
1246 let unknown = self.page.encode_char('?').unwrap_or(0x6F);
1247 Val::Bytes(line.trim_end_matches(['\n', '\r']).chars().map(|c| self.page.encode_char(c).unwrap_or(unknown)).collect())
1248 }
1249 };
1250 self.assign(dest, val, None, pos)
1251 }
1252
1253 fn function(&mut self, f: &FunctionCall) -> R<Val> {
1254 let pos = f.pos;
1255 let mut args: Vec<Val> = Vec::new();
1256 for a in &f.args {
1257 args.push(self.expr_value(a, pos)?);
1258 }
1259 let arity = |n: std::ops::RangeInclusive<usize>| {
1260 if n.contains(&args.len()) { Ok(()) } else { Err(Abend::ironwork(format!("FUNCTION {} takes {n:?} arguments", f.name), pos)) }
1261 };
1262 let bytes_of = |v: &Val| match v {
1263 Val::Bytes(b) | Val::All(b) => Ok(b.clone()),
1264 Val::Fig(fig) => Ok(vec![figurative_byte(*fig)]),
1265 _ => Err(Abend::ironwork(format!("FUNCTION {} needs an alphanumeric argument", f.name), pos)),
1266 };
1267 let value = match f.name.as_str() {
1268 "CHAR" => {
1269 arity(1..=1)?;
1270 let n = self.integer(&f.args[0], pos)?;
1271 if !(1..=256).contains(&n) {
1272 return Err(Abend::ironwork(format!("FUNCTION CHAR({n}) is outside 1 to 256"), pos));
1273 }
1274 Val::Bytes(vec![(n - 1) as u8])
1275 }
1276 "ORD" => {
1277 arity(1..=1)?;
1278 let b = bytes_of(&args[0])?;
1279 let first = *b.first().ok_or_else(|| Abend::ironwork("FUNCTION ORD of an empty argument", pos))?;
1280 Val::Num(Fixed::new(first as i128 + 1, Places::new(3, 0)))
1281 }
1282 "NATIONAL-OF" => {
1283 arity(1..=2)?;
1284 let ccsid = if args.len() == 2 { self.integer(&f.args[1], pos)? as u16 } else { self.options.codepage };
1285 let page = CodePage::by_ccsid(ccsid).ok_or_else(|| Abend::ironwork(format!("CCSID {ccsid} is not a code page ironwork for COBOL carries"), pos))?;
1286 Val::National(page.to_utf16be(&bytes_of(&args[0])?))
1287 }
1288 "LENGTH" => {
1289 arity(1..=1)?;
1290 let n = match &args[0] {
1291 Val::Bytes(b) | Val::All(b) => b.len(),
1292 Val::National(b) => b.len() / 2,
1293 Val::Num(v) => v.places.total() as usize,
1294 _ => return Err(Abend::ironwork("FUNCTION LENGTH of this argument is not supported yet", pos)),
1295 };
1296 Val::Num(Fixed::new(n as i128, Places::new(9, 0)))
1297 }
1298 "NUMVAL" | "NUMVAL-C" => {
1299 arity(1..=2)?;
1300 let currency = match args.get(1) {
1301 Some(v) => self.page.decode(&bytes_of(v)?),
1302 None => "$".to_owned(),
1303 };
1304 let text = self.page.decode(&bytes_of(&args[0])?);
1305 Val::Num(numval(&text, (f.name == "NUMVAL-C").then_some(currency.as_str())).unwrap_or_else(|| Fixed::new(0, Places::new(1, 0))))
1306 }
1307 "TRIM" => {
1308 arity(1..=1)?;
1309 let b = bytes_of(&args[0])?;
1310 let first = b.iter().position(|&c| c != ebcdic::SPACE);
1311 let last = b.iter().rposition(|&c| c != ebcdic::SPACE);
1312 let trimmed = match (first, last, f.modifier.as_deref()) {
1313 (None, _, _) | (_, None, _) => Vec::new(),
1314 (Some(s), _, Some("LEADING")) => b[s..].to_vec(),
1315 (_, Some(e), Some("TRAILING")) => b[..=e].to_vec(),
1316 (Some(s), Some(e), _) => b[s..=e].to_vec(),
1317 };
1318 Val::Bytes(trimmed)
1319 }
1320 "MOD" | "REM" | "INTEGER" | "INTEGER-PART" | "ABS" => {
1321 arity(if matches!(f.name.as_str(), "MOD" | "REM") { 2..=2 } else { 1..=1 })?;
1322 let number = |v: &Val| match v {
1323 Val::Num(x) => Ok(*x),
1324 _ => Err(Abend::ironwork(format!("FUNCTION {} needs numeric arguments", f.name), pos)),
1325 };
1326 let x = number(&args[0])?;
1327 let dec = if args.len() == 2 { x.places.dec.max(number(&args[1])?.places.dec) } else { x.places.dec };
1328 let scaled = |v: &Fixed| -> R<i128> {
1329 let m = align(v, dec, false).and_then(|m| m.to_u128()).and_then(|m| i128::try_from(m).ok()).ok_or_else(|| Abend::ironwork("an argument beyond 38 digits", pos))?;
1330 Ok(if v.negative { -m } else { m })
1331 };
1332 let unit = 10i128.pow(dec);
1333 let a = scaled(&x)?;
1334 let result = match f.name.as_str() {
1335 "ABS" => a.abs(),
1336 "INTEGER" => a.div_euclid(unit) * unit,
1337 "INTEGER-PART" => a / unit * unit,
1338 other => {
1339 let b = scaled(&number(&args[1])?)?;
1340 if b == 0 {
1341 return Err(Abend::ironwork(format!("FUNCTION {other} by zero"), pos));
1342 }
1343 if other == "MOD" { a - b * a.div_euclid(b) } else { a - b * (a / b) }
1344 }
1345 };
1346 Val::Num(Fixed::new(result, Places::new(31 - dec.min(31), dec)))
1347 }
1348 "MIN" | "MAX" => {
1349 if args.is_empty() {
1350 return Err(Abend::ironwork(format!("FUNCTION {} needs arguments", f.name), pos));
1351 }
1352 let want = if f.name == "MIN" { Ordering::Less } else { Ordering::Greater };
1353 let mut best = 0;
1354 for i in 1..args.len() {
1355 let o = match (&args[i], &args[best]) {
1356 (Val::Num(x), Val::Num(y)) => compare_fixed(x, y),
1357 (x, y) => bytes_of(x)?.cmp(&bytes_of(y)?),
1358 };
1359 if o == want {
1360 best = i;
1361 }
1362 }
1363 args.swap_remove(best)
1364 }
1365 "INTEGER-OF-DATE" => {
1366 arity(1..=1)?;
1367 let n = self.integer(&f.args[0], pos)?;
1368 let (y, m, d) = (n / 10000, n / 100 % 100, n % 100);
1369 if !(1601..=9999).contains(&y) || !(1..=12).contains(&m) || !(1..=days_in_month(y, m)).contains(&d) {
1370 return Err(Abend::ironwork(format!("FUNCTION INTEGER-OF-DATE({n}): not a date from 1601 to 9999"), pos));
1371 }
1372 Val::Num(Fixed::new((days_from_civil(y, m, d) - days_from_civil(1600, 12, 31)) as i128, Places::new(7, 0)))
1373 }
1374 "DATE-OF-INTEGER" => {
1375 arity(1..=1)?;
1376 let n = self.integer(&f.args[0], pos)?;
1377 if !(1..=3_067_671).contains(&n) {
1378 return Err(Abend::ironwork(format!("FUNCTION DATE-OF-INTEGER({n}): outside 1 to 3067671"), pos));
1379 }
1380 let (y, m, d, ..) = crate::unit::civil((days_from_civil(1600, 12, 31) + n) * 86_400);
1381 Val::Num(Fixed::new((y * 10000 + m as i64 * 100 + d as i64) as i128, Places::new(8, 0)))
1382 }
1383 "CURRENT-DATE" => {
1384 arity(0..=0)?;
1385 let (seconds, hundredths) = self.unit.now();
1386 let (y, mo, d, h, mi, s, _, _) = crate::unit::civil(seconds);
1387 let text = format!("{y:04}{mo:02}{d:02}{h:02}{mi:02}{s:02}{hundredths:02}+0000");
1388 Val::Bytes(self.page.encode(&text).map_err(|e| Abend::ironwork(e.to_string(), pos))?)
1389 }
1390 "UPPER-CASE" | "LOWER-CASE" | "REVERSE" => {
1391 arity(1..=1)?;
1392 let text = self.page.decode(&bytes_of(&args[0])?);
1393 let changed: String = match f.name.as_str() {
1394 "UPPER-CASE" => text.to_uppercase(),
1395 "LOWER-CASE" => text.to_lowercase(),
1396 _ => text.chars().rev().collect(),
1397 };
1398 Val::Bytes(self.page.encode(&changed).map_err(|e| Abend::ironwork(e.to_string(), pos))?)
1399 }
1400 other => return Err(Abend::ironwork(format!("FUNCTION {other} is not supported yet"), pos)),
1401 };
1402 match (&f.refmod, value) {
1403 (None, v) => Ok(v),
1404 (Some(rm), Val::Bytes(b)) => {
1405 let start = self.integer(&rm.start, pos)? as usize;
1406 let len = match &rm.length {
1407 Some(l) => self.integer(l, pos)? as usize,
1408 None => b.len() + 1 - start,
1409 };
1410 b.get(start - 1..start - 1 + len).map(|s| Val::Bytes(s.to_vec())).ok_or_else(|| Abend::ironwork("reference modification past the function result", pos))
1411 }
1412 (Some(_), _) => Err(Abend::ironwork("reference modification of a non-alphanumeric function result", pos)),
1413 }
1414 }
1415
1416 fn expr_value(&mut self, e: &Expr, pos: Pos) -> R<Val> {
1417 match e {
1418 Expr::Operand(op) => self.operand(op, pos),
1419 _ if self.uses_float(e)? => Ok(Val::Float(self.eval_float(e, self.options.arith.float_intermediate(), pos)?)),
1420 _ => {
1421 let dmax = self.dmax(e)?;
1422 Ok(Val::Num(self.eval_fixed(e, dmax, pos)?))
1423 }
1424 }
1425 }
1426
1427 fn operand_kind(&mut self, op: &Operand) -> R<Option<Kind>> {
1428 Ok(match op {
1429 Operand::Ref(r) => Some(self.locate(r)?.kind),
1430 _ => None,
1431 })
1432 }
1433
1434 fn uses_float(&mut self, e: &Expr) -> R<bool> {
1435 Ok(match e {
1436 Expr::Operand(op) => matches!(self.operand_kind(op)?, Some(Kind::Float(_))),
1437 Expr::Neg(inner) => self.uses_float(inner)?,
1438 Expr::Bin(a, _, b) => self.uses_float(a)? || self.uses_float(b)?,
1439 })
1440 }
1441
1442 fn dmax(&mut self, e: &Expr) -> R<u32> {
1444 Ok(match e {
1445 Expr::Operand(Operand::Literal(Literal::Number(t))) => literal_fixed(t).map_or(0, |f| f.places.dec),
1446 Expr::Operand(op) => self.operand_kind(op)?.and_then(Kind::digits_scale).map_or(0, |(_, s)| s),
1447 Expr::Neg(inner) => self.dmax(inner)?,
1448 Expr::Bin(a, BinOp::Div | BinOp::Pow, _) => self.dmax(a)?,
1449 Expr::Bin(a, _, b) => self.dmax(a)?.max(self.dmax(b)?),
1450 })
1451 }
1452
1453 fn eval_fixed(&mut self, e: &Expr, dmax: u32, pos: Pos) -> R<Fixed> {
1454 let arith = self.options.arith;
1455 let wrap = |r: Result<Fixed, ArithError>| {
1456 r.map_err(|e| match e {
1457 ArithError::DivideByZero => Abend { code: DIVIDE_BY_ZERO.into(), message: "division by zero".into(), pos },
1458 ArithError::BeyondModel => Abend::ironwork("an intermediate result wider than 256 bits", pos),
1459 })
1460 };
1461 match e {
1462 Expr::Operand(op) => match self.operand(op, pos)? {
1463 Val::Num(f) => Ok(f),
1464 Val::Fig(Figurative::Zero) => Ok(Fixed::new(0, Places::new(1, 0))),
1465 _ => Err(Abend::ironwork("a non-numeric operand in arithmetic", pos)),
1466 },
1467 Expr::Neg(inner) => {
1468 let v = self.eval_fixed(inner, dmax, pos)?;
1469 Ok(fixed(!v.negative, v.magnitude, v.places))
1470 }
1471 Expr::Bin(a, op, b) => {
1472 let x = self.eval_fixed(a, dmax, pos)?;
1473 if *op == BinOp::Pow {
1474 let n = self.integer(b, pos)?;
1475 if !(0..=31).contains(&n) {
1476 return Err(Abend::ironwork("exponentiation other than by an integer from 0 to 31 is not supported yet", pos));
1477 }
1478 let mut acc = Fixed::new(1, Places::new(1, 0));
1479 for _ in 0..n {
1480 acc = wrap(acc.mul(x, dmax, arith))?;
1481 }
1482 return Ok(acc);
1483 }
1484 let y = self.eval_fixed(b, dmax, pos)?;
1485 wrap(match op {
1486 BinOp::Add => x.add(y, dmax, arith),
1487 BinOp::Sub => x.sub(y, dmax, arith),
1488 BinOp::Mul => x.mul(y, dmax, arith),
1489 _ => x.div(y, dmax, arith),
1490 })
1491 }
1492 }
1493 }
1494
1495 fn eval_float(&mut self, e: &Expr, p: Precision, pos: Pos) -> R<Hfp> {
1496 let mask = ProgramMask::default();
1497 let check = |r: Result<Hfp, ProgramCheck>| r.map_err(|c| Abend::check(c, pos));
1498 match e {
1499 Expr::Operand(op) => match self.operand(op, pos)? {
1500 Val::Float(h) if h.precision.digits() <= p.digits() => Ok(h.lengthen(p)),
1501 Val::Float(h) => Ok(float::narrow(h, p)),
1502 Val::Num(f) => check(float::from_fixed(f, p, mask)),
1503 Val::Fig(Figurative::Zero) => Ok(Hfp::zero(p)),
1504 _ => Err(Abend::ironwork("a non-numeric operand in arithmetic", pos)),
1505 },
1506 Expr::Neg(inner) => {
1507 let v = self.eval_float(inner, p, pos)?;
1508 Ok(if v.fraction == 0 { v } else { Hfp { negative: !v.negative, ..v } })
1509 }
1510 Expr::Bin(a, op, b) => {
1511 let (x, y) = (self.eval_float(a, p, pos)?, self.eval_float(b, p, pos)?);
1512 check(match op {
1513 BinOp::Add => x.add(y, mask),
1514 BinOp::Sub => x.sub(y, mask),
1515 BinOp::Mul => x.mul(y, p, mask),
1516 BinOp::Div => x.div(y, mask),
1517 BinOp::Pow => return Err(Abend::ironwork("floating-point exponentiation is not supported yet", pos)),
1518 })
1519 }
1520 }
1521 }
1522
1523 fn arithmetic(&mut self, computations: &[(Target, Expr)], remainder: Option<&(Target, Expr, Expr)>, handler: Option<&'p SizeError>, pos: Pos) -> R<Flow> {
1526 let mut size_error = false;
1527 let mut dmax = 0;
1528 for (t, e) in computations {
1529 let loc = self.locate(&t.r)?;
1530 dmax = dmax.max(loc.kind.digits_scale().map_or(0, |(_, s)| s)).max(self.dmax(e)?);
1531 }
1532 if let Some((t, dividend, _)) = remainder {
1533 let loc = self.locate(&t.r)?;
1534 dmax = dmax.max(loc.kind.digits_scale().map_or(0, |(_, s)| s)).max(self.dmax(dividend)?);
1535 }
1536 let mut quotient_target: Option<Loc> = None;
1537 for (t, e) in computations {
1538 let loc = self.locate(&t.r)?;
1539 quotient_target.get_or_insert(loc);
1540 let outcome = if self.uses_float(e)? {
1541 self.eval_float(e, self.options.arith.float_intermediate(), pos).map(Val::Float)
1542 } else {
1543 self.eval_fixed(e, dmax, pos).map(Val::Num)
1544 };
1545 let value = match outcome {
1546 Err(a) if a.code == DIVIDE_BY_ZERO => {
1547 if handler.is_none() {
1548 return Err(Abend::check(ProgramCheck::DecimalDivide, pos));
1549 }
1550 size_error = true;
1551 continue;
1552 }
1553 other => other?,
1554 };
1555 size_error |= self.store_value(loc, value, t.rounded, handler.is_some(), pos)?;
1556 }
1557 if let (Some((t, dividend, divisor)), Some(q_loc)) = (remainder, quotient_target) {
1558 let arith = self.options.arith;
1559 let (x, y) = (self.eval_fixed(dividend, dmax, pos)?, self.eval_fixed(divisor, dmax, pos)?);
1560 if !y.magnitude.is_zero() {
1561 let q = x.div(y, dmax, arith).map_err(|_| Abend::ironwork("remainder", pos))?;
1562 let q_places = places_of(q_loc.kind);
1563 let q = fixed(q.negative, align(&q, q_places.dec, false).unwrap_or_default(), Places::new(q.places.int, q_places.dec));
1564 let r = q.mul(y, dmax, arith).and_then(|p| x.sub(p, dmax, arith)).map_err(|_| Abend::ironwork("remainder", pos))?;
1565 let r_loc = self.locate(&t.r)?;
1566 size_error |= self.store_value(r_loc, Val::Num(r), false, handler.is_some(), pos)?;
1567 }
1568 }
1569 if let Some(h) = handler {
1570 return self.run_block(if size_error { &h.on } else { &h.not_on });
1571 }
1572 Ok(Flow::Next)
1573 }
1574
1575 fn store_value(&mut self, loc: Loc, value: Val, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
1577 match (loc.kind, value) {
1578 (Kind::Float(p), Val::Float(h)) => {
1579 let h = if h.precision.digits() > p.digits() { float::narrow(h, p) } else { h.lengthen(p) };
1580 self.write(loc, &h.to_bytes());
1581 Ok(false)
1582 }
1583 (Kind::Float(p), Val::Num(f)) => {
1584 let h = float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?;
1585 self.write(loc, &h.to_bytes());
1586 Ok(false)
1587 }
1588 (kind, Val::Float(h)) => {
1589 let (f, overflow) = float::to_fixed(h, places_of(kind), rounded);
1590 if overflow && keep_on_size_error {
1591 return Ok(true);
1592 }
1593 Ok(self.store_fixed_checked(loc, &f, false, keep_on_size_error, pos)? || overflow)
1594 }
1595 (_, Val::Num(f)) => self.store_fixed_checked(loc, &f, rounded, keep_on_size_error, pos),
1596 _ => Err(Abend::ironwork("a non-numeric arithmetic result", pos)),
1597 }
1598 }
1599
1600 fn store_fixed(&mut self, loc: Loc, value: &Fixed, rounded: bool, pos: Pos) -> R<()> {
1601 self.store_fixed_checked(loc, value, rounded, false, pos).map(|_| ())
1602 }
1603
1604 fn store_fixed_checked(&mut self, loc: Loc, value: &Fixed, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
1605 let beyond = || Abend::ironwork("a value wider than 256 bits", pos);
1606 let (bytes, size_error) = match loc.kind {
1607 Kind::Zoned { digits, scale, signed, sign } => {
1608 let m = align(value, scale, rounded).ok_or_else(beyond)?;
1609 let cap = pow10(digits);
1610 let kept = m.div_rem(cap).1.to_u128().unwrap();
1611 let negative = signed && value.negative && kept != 0;
1612 (self.zoned_image(kept, digits, signed, negative, sign), m >= cap)
1613 }
1614 Kind::Packed { digits, scale, signed } => {
1615 let m = align(value, scale, rounded).ok_or_else(beyond)?;
1616 let cap = pow10(digits);
1617 let kept = m.div_rem(cap).1.to_u128().unwrap();
1618 let mut out = vec![0u8; loc.len];
1619 decimal::encode(&mut out, Decimal { negative: signed && value.negative && kept != 0, magnitude: kept }).map_err(|c| Abend::check(c, pos))?;
1620 if !signed {
1621 *out.last_mut().unwrap() |= 0x0F;
1622 }
1623 (out, m >= cap)
1624 }
1625 Kind::Binary { digits, scale, signed, native } => {
1626 let m = align(value, scale, rounded).ok_or_else(beyond)?;
1627 let magnitude = m.to_u128().and_then(|m| i128::try_from(m).ok()).ok_or_else(beyond)?;
1628 let v = if value.negative { -magnitude } else { magnitude };
1629 let item = Binary { digits: digits as u8, signed, native };
1630 let stored = binary::store(item, v, &self.options);
1631 if let Some(d) = stored.divergence {
1632 let name = self.layout.items.get(loc.item).map_or("RETURN-CODE".into(), |i| i.name.clone().unwrap_or_else(|| "FILLER".into()));
1633 let _ = writeln!(
1634 self.unit.err,
1635 "ironwork: {pos}: TRUNC(OPT) store of {} into {name} PIC {}9({digits}) BINARY: the PICTURE keeps {}, the binary field {}; {} was stored (-silent stops these reports)",
1636 d.value,
1637 if signed { "S" } else { "" },
1638 d.decimal,
1639 d.binary,
1640 d.binary
1641 );
1642 }
1643 let bits = 8 * item.bytes() as u32;
1644 let binary_range = if signed { v >= -(1i128 << (bits - 1)) && v < (1i128 << (bits - 1)) } else { (0..(1i128 << bits)).contains(&v.abs()) };
1645 let exceeds = if native || self.options.trunc == Trunc::Bin { !binary_range } else { v.unsigned_abs() >= 10u128.pow(digits) };
1646 (stored.bytes, exceeds)
1647 }
1648 Kind::Float(p) => (float::from_fixed(*value, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?.to_bytes(), false),
1649 Kind::Index => {
1650 let whole = align(value, 0, false).and_then(|m| m.to_u128()).and_then(|m| i32::try_from(m).ok()).ok_or_else(beyond)?;
1651 ((if value.negative { -whole } else { whole }).to_be_bytes().to_vec(), false)
1652 }
1653 Kind::NumericEdited { edit, digits, scale, blank_when_zero } => {
1654 let m = align(value, scale, rounded).ok_or_else(beyond)?;
1655 let cap = pow10(digits);
1656 let kept = m.div_rem(cap).1.to_u128().unwrap();
1657 let text = crate::edit::numeric(&self.layout.edits[edit as usize], digits, value.negative && kept != 0, kept, blank_when_zero);
1658 (self.page.encode(&text).map_err(|e| Abend::ironwork(e.to_string(), pos))?, m >= cap)
1659 }
1660 _ => return Err(Abend::ironwork("a numeric value stored into a non-numeric item", pos)),
1661 };
1662 if size_error && keep_on_size_error {
1663 return Ok(true);
1664 }
1665 self.write(loc, &bytes);
1666 Ok(size_error)
1667 }
1668
1669 fn zoned_image(&self, magnitude: u128, digits: u32, signed: bool, negative: bool, sign: Option<SignClause>) -> Vec<u8> {
1670 let zone = match (signed, negative) {
1671 (false, _) => decimal::UNSIGNED,
1672 (true, true) => decimal::MINUS,
1673 (true, false) => decimal::PLUS,
1674 };
1675 match sign {
1676 Some(SignClause { separate: true, position }) => {
1677 let body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
1678 let s = if negative { 0x60 } else { 0x4E };
1679 if position == SignPosition::Leading { [&[s][..], &body].concat() } else { [&body[..], &[s]].concat() }
1680 }
1681 Some(SignClause { separate: false, position: SignPosition::Leading }) => {
1682 let mut body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
1683 body[0] = (zone << 4) | (body[0] & 0x0F);
1684 body
1685 }
1686 _ => zoned_digits(magnitude, digits as usize, zone),
1687 }
1688 }
1689
1690 fn assign(&mut self, dest: Loc, val: Val, src: Option<Loc>, pos: Pos) -> R<()> {
1692 match dest.kind {
1693 Kind::Group | Kind::Alnum { .. } => {
1694 let justified = matches!(dest.kind, Kind::Alnum { justified: true });
1695 let image = self.alnum_image(&val, src, dest.len, pos)?;
1696 let mut out = vec![ebcdic::SPACE; dest.len];
1697 if justified && image.len() < dest.len {
1698 out[dest.len - image.len()..].copy_from_slice(&image);
1699 } else if justified {
1700 out.copy_from_slice(&image[image.len() - dest.len..]);
1701 } else {
1702 let n = image.len().min(dest.len);
1703 out[..n].copy_from_slice(&image[..n]);
1704 }
1705 self.write(dest, &out);
1706 }
1707 Kind::National => {
1708 let units: Vec<u16> = match val {
1709 Val::National(b) => b.chunks(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(),
1710 Val::Bytes(b) => self.page.decode(&b).encode_utf16().collect(),
1711 Val::Fig(f) => vec![figurative_unit(f); dest.len / 2],
1712 _ => return Err(Abend::ironwork("this value cannot be moved to a national item", pos)),
1713 };
1714 let mut out: Vec<u8> = units.iter().take(dest.len / 2).flat_map(|u| u.to_be_bytes()).collect();
1715 while out.len() < dest.len {
1716 out.extend_from_slice(&0x0020u16.to_be_bytes());
1717 }
1718 self.write(dest, &out);
1719 }
1720 Kind::Pointer => match val {
1721 Val::Address(a) => self.write(dest, &a.to_be_bytes()),
1722 Val::Fig(Figurative::Null) => self.write(dest, &[0; 4]),
1723 _ => return Err(Abend::ironwork("a pointer takes an address: use SET ... TO ADDRESS OF or NULL", pos)),
1724 },
1725 Kind::Index => match val {
1726 Val::Num(f) => self.store_fixed(dest, &f, false, pos)?,
1727 _ => return Err(Abend::ironwork("an index takes an occurrence number", pos)),
1728 },
1729 Kind::AlnumEdited { edit } => {
1730 let positions = self.layout.edits[edit as usize].iter().filter(|s| !matches!(s, crate::picture::Sym::Insert(_))).count();
1731 let image = self.alnum_image(&val, src, positions, pos)?;
1732 let page = self.page;
1733 let out = crate::edit::alphanumeric(&self.layout.edits[edit as usize], &image, ebcdic::SPACE, |c| page.encode_char(c).unwrap_or(ebcdic::SPACE));
1734 self.write(dest, &out);
1735 }
1736 Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::NumericEdited { .. } => match val {
1737 Val::Num(f) => {
1738 if let (Some(s), Kind::Packed { digits, scale, signed: true }, Numproc::Pfd) = (src, dest.kind, self.options.numproc)
1739 && s.kind == dest.kind
1740 && digits > 0
1741 && scale == places_of(s.kind).dec
1742 {
1743 let copied = sign::move_packed(self.bytes(s), true, Numproc::Pfd);
1744 self.write(dest, &copied);
1745 } else {
1746 self.store_fixed(dest, &f, false, pos)?;
1747 }
1748 }
1749 Val::Float(h) => {
1750 let (f, _) = float::to_fixed(h, places_of(dest.kind), false);
1751 self.store_fixed(dest, &f, false, pos)?;
1752 }
1753 Val::Fig(Figurative::Zero) => self.store_fixed(dest, &Fixed::new(0, Places::new(1, 0)), false, pos)?,
1754 Val::Fig(f) => self.write(dest, &vec![figurative_byte(f); dest.len]),
1755 Val::All(b) => {
1756 let fill: Vec<u8> = b.iter().copied().cycle().take(dest.len).collect();
1757 self.write(dest, &fill);
1758 }
1759 Val::Bytes(b) => {
1760 let v = match src.map(|s| s.kind) {
1761 Some(Kind::NumericEdited { edit, digits, scale, .. }) => {
1762 let (negative, magnitude) = crate::edit::de_edit(&self.layout.edits[edit as usize], &self.page.decode(&b));
1763 fixed(negative, U256::from_u128(magnitude), Places::new(digits - scale, scale))
1764 }
1765 _ => {
1766 let places = Places::new(b.len() as u32, 0);
1767 self.zoned_value(&b, b.len() as u32, false, None, places, pos)?
1768 }
1769 };
1770 self.store_fixed(dest, &v, false, pos)?;
1771 }
1772 Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to a numeric item", pos)),
1773 Val::Address(_) => return Err(Abend::ironwork("a pointer cannot be moved to a numeric item", pos)),
1774 },
1775 Kind::Float(p) => {
1776 let h = match val {
1777 Val::Float(h) if h.precision.digits() > p.digits() => float::narrow(h, p),
1778 Val::Float(h) => h.lengthen(p),
1779 Val::Num(f) => float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
1780 Val::Fig(Figurative::Zero) => Hfp::zero(p),
1781 _ => return Err(Abend::ironwork("this value cannot be moved to a floating-point item", pos)),
1782 };
1783 self.write(dest, &h.to_bytes());
1784 }
1785 }
1786 Ok(())
1787 }
1788
1789 fn alnum_image(&self, val: &Val, src: Option<Loc>, len: usize, pos: Pos) -> R<Vec<u8>> {
1791 Ok(match val {
1792 Val::Bytes(b) => b.clone(),
1793 Val::All(b) => b.iter().copied().cycle().take(len.max(b.len())).collect(),
1794 Val::Fig(f) => vec![figurative_byte(*f); len],
1795 Val::Num(f) if f.places.dec == 0 => {
1796 let digits = src.and_then(|s| s.kind.digits_scale()).map_or(f.places.total(), |(d, _)| d);
1797 zoned_digits(f.magnitude.to_u128().unwrap_or(0), digits as usize, decimal::UNSIGNED)
1798 }
1799 Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to an alphanumeric item", pos)),
1800 _ => return Err(Abend::ironwork("only an integer numeric value can be moved to an alphanumeric item", pos)),
1801 })
1802 }
1803
1804 fn condition(&mut self, c: &Cond, pos: Pos) -> R<bool> {
1805 Ok(match c {
1806 Cond::Rel(a, op, b) => {
1807 let o = self.compare(a, b, pos)?;
1808 match op {
1809 RelOp::Eq => o == Ordering::Equal,
1810 RelOp::Ne => o != Ordering::Equal,
1811 RelOp::Lt => o == Ordering::Less,
1812 RelOp::Le => o != Ordering::Greater,
1813 RelOp::Gt => o == Ordering::Greater,
1814 RelOp::Ge => o != Ordering::Less,
1815 }
1816 }
1817 Cond::Not(inner) => !self.condition(inner, pos)?,
1818 Cond::And(a, b) => self.condition(a, pos)? && self.condition(b, pos)?,
1819 Cond::Or(a, b) => self.condition(a, pos)? || self.condition(b, pos)?,
1820 Cond::Class(e, class) => self.class(e, *class, pos)?,
1821 Cond::NameOrRel { subject, op, name } => match self.resolve(name)? {
1822 Resolved::Condition(_) => self.condition(&Cond::Name(name.clone()), pos)?,
1823 Resolved::Item(_) => self.condition(&Cond::Rel(subject.clone(), *op, Expr::Operand(Operand::Ref(name.clone()))), pos)?,
1824 },
1825 Cond::Name(r) => {
1826 let Resolved::Condition(index) = self.resolve(r)? else {
1827 return Err(Abend::ironwork(format!("{} is a data item, not a condition", r.name), r.pos));
1828 };
1829 let condition = &self.layout.conditions[index];
1830 let item = &self.layout.items[condition.item];
1831 let subject = Ref {
1832 name: item.name.clone().unwrap_or_default(),
1833 qualifiers: Vec::new(),
1834 subscripts: r.subscripts.clone(),
1835 refmod: None,
1836 pos: r.pos,
1837 };
1838 let subject = Expr::Operand(Operand::Ref(subject));
1839 let values = condition.values.clone();
1840 for (low, high) in values {
1841 let low = Expr::Operand(Operand::Literal(low));
1842 let hit = match high {
1843 None => self.compare(&subject, &low, pos)? == Ordering::Equal,
1844 Some(high) => {
1845 self.compare(&subject, &low, pos)? != Ordering::Less
1846 && self.compare(&subject, &Expr::Operand(Operand::Literal(high)), pos)? != Ordering::Greater
1847 }
1848 };
1849 if hit {
1850 return Ok(true);
1851 }
1852 }
1853 false
1854 }
1855 })
1856 }
1857
1858 fn class(&mut self, e: &Expr, class: Class, pos: Pos) -> R<bool> {
1859 if let (Class::Numeric | Class::Alphabetic, Expr::Operand(Operand::Ref(r))) = (class, e) {
1860 let loc = self.locate(r)?;
1861 let bytes = self.bytes(loc).to_vec();
1862 return Ok(match (class, loc.kind) {
1863 (Class::Numeric, Kind::Packed { signed, .. }) => {
1864 decimal::tp(&bytes).is_ok_and(|cc| cc.0 == 0) && (signed || bytes.last().is_some_and(|b| b & 0x0F == 0x0F))
1865 }
1866 (Class::Numeric, Kind::Zoned { signed, sign: None, .. }) => bytes.iter().enumerate().all(|(i, &b)| {
1867 let zone_ok = if i + 1 == bytes.len() && signed { matches!(b >> 4, 0xC | 0xD | 0xF) } else { b >> 4 == 0xF };
1868 zone_ok && b & 0x0F <= 9
1869 }),
1870 (Class::Numeric, _) => bytes.iter().all(|b| (0xF0..=0xF9).contains(b)),
1871 (_, _) => bytes.iter().all(|&b| b == ebcdic::SPACE || self.page.decode_byte(b).is_ascii_alphabetic()),
1872 });
1873 }
1874 let v = match self.expr_value(e, pos)? {
1875 Val::Num(f) => f,
1876 Val::Float(h) => float::to_fixed(h, Places::new(31, 0), false).0,
1877 _ => return Err(Abend::ironwork("a sign condition on a non-numeric operand", pos)),
1878 };
1879 Ok(match class {
1880 Class::Positive => !v.negative && !v.magnitude.is_zero(),
1881 Class::Negative => v.negative,
1882 _ => v.magnitude.is_zero(),
1883 })
1884 }
1885
1886 fn compare(&mut self, a: &Expr, b: &Expr, pos: Pos) -> R<Ordering> {
1887 let (va, la) = self.comparand(a, pos)?;
1888 let (vb, lb) = self.comparand(b, pos)?;
1889 if let (Some(x), Some(y)) = (la, lb)
1890 && let (Kind::Packed { .. }, true, Numproc::Pfd) = (x.kind, x.kind == y.kind, self.options.numproc)
1891 {
1892 return sign::compare_packed(self.bytes(x), self.bytes(y), Numproc::Pfd).map_err(|c| Abend::check(c, pos));
1893 }
1894 let address = |v: &Val| match v {
1895 Val::Address(a) => Some(*a),
1896 Val::Fig(Figurative::Null) => Some(0),
1897 _ => None,
1898 };
1899 if matches!(va, Val::Address(_)) || matches!(vb, Val::Address(_)) {
1900 return match (address(&va), address(&vb)) {
1901 (Some(x), Some(y)) => Ok(x.cmp(&y)),
1902 _ => Err(Abend::ironwork("a pointer compared with something other than a pointer or NULL", pos)),
1903 };
1904 }
1905 let numeric = |v: &Val| matches!(v, Val::Num(_) | Val::Float(_));
1906 match (&va, &vb) {
1907 (Val::Float(_), _) | (_, Val::Float(_)) if (numeric(&va) || matches!(va, Val::Fig(Figurative::Zero))) && (numeric(&vb) || matches!(vb, Val::Fig(Figurative::Zero))) => {
1908 let to_float = |v: &Val| -> R<Hfp> {
1909 Ok(match v {
1910 Val::Float(h) => h.lengthen(Precision::Extended),
1911 Val::Num(f) => float::from_fixed(*f, Precision::Extended, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
1912 _ => Hfp::zero(Precision::Extended),
1913 })
1914 };
1915 Ok(to_float(&va)?.compare(to_float(&vb)?))
1916 }
1917 (Val::Num(x), Val::Num(y)) => Ok(compare_fixed(x, y)),
1918 (Val::Num(x), Val::Fig(Figurative::Zero)) => Ok(compare_fixed(x, &Fixed::new(0, Places::new(1, 0)))),
1919 (Val::Fig(Figurative::Zero), Val::Num(y)) => Ok(compare_fixed(&Fixed::new(0, Places::new(1, 0)), y)),
1920 (Val::National(x), Val::National(y)) => Ok(compare_national(x, y)),
1921 _ => {
1922 let len = self.image_len(&va, la).max(self.image_len(&vb, lb));
1923 let x = self.alnum_image(&va, la, len, pos)?;
1924 let y = self.alnum_image(&vb, lb, len, pos)?;
1925 Ok(ebcdic::compare_alphanumeric(&x, &y, &Collation::Native))
1926 }
1927 }
1928 }
1929
1930 fn image_len(&self, v: &Val, loc: Option<Loc>) -> usize {
1931 match (v, loc) {
1932 (_, Some(l)) if !l.kind.is_numeric() => l.len,
1933 (Val::Bytes(b) | Val::All(b), _) => b.len(),
1934 (Val::Num(f), Some(l)) => l.kind.digits_scale().map_or(f.places.total(), |(d, _)| d) as usize,
1935 (Val::Num(f), None) => f.places.total() as usize,
1936 _ => 1,
1937 }
1938 }
1939
1940 fn comparand(&mut self, e: &Expr, pos: Pos) -> R<(Val, Option<Loc>)> {
1941 match e {
1942 Expr::Operand(op) => self.operand_with_loc(op, pos),
1943 _ => Ok((self.expr_value(e, pos)?, None)),
1944 }
1945 }
1946
1947 fn display(&mut self, items: &[Operand], no_advancing: bool, pos: Pos) -> R<()> {
1948 let mut text = String::new();
1949 for op in items {
1950 match op {
1951 Operand::Ref(r) => {
1952 let loc = self.locate(r)?;
1953 match loc.kind {
1954 Kind::National => text.push_str(&utf16_text(self.bytes(loc))),
1955 Kind::Packed { digits, signed, .. } | Kind::Binary { digits, signed, .. } => {
1956 let Val::Num(f) = self.read(loc, r.pos)? else { unreachable!() };
1957 let zone = match (signed, f.negative) {
1958 (false, _) => decimal::UNSIGNED,
1959 (true, true) => decimal::MINUS,
1960 (true, false) => decimal::PLUS,
1961 };
1962 let whole = match loc.kind {
1963 Kind::Binary { native, .. } => native || self.options.trunc == Trunc::Bin,
1964 _ => false,
1965 };
1966 let shown = if whole {
1967 let width = match loc.len {
1968 2 => 5,
1969 4 => 10,
1970 _ if signed => 19,
1971 _ => 20,
1972 };
1973 zoned_digits(f.magnitude.to_u128().unwrap_or(0), width, zone)
1974 } else {
1975 zoned_digits(f.magnitude.div_rem(pow10(digits)).1.to_u128().unwrap_or(0), digits as usize, zone)
1976 };
1977 text.push_str(&self.page.decode(&shown));
1978 }
1979 Kind::Float(_) => return Err(Abend::ironwork("DISPLAY of a floating-point item is not supported yet", r.pos)),
1980 Kind::Pointer | Kind::Index => return Err(Abend::ironwork("DISPLAY of a pointer or index is not supported", r.pos)),
1981 _ => text.push_str(&self.page.decode(self.bytes(loc))),
1982 }
1983 }
1984 Operand::Literal(Literal::Number(t)) => text.push_str(t),
1985 other => match self.operand(other, pos)? {
1986 Val::Bytes(b) | Val::All(b) => text.push_str(&self.page.decode(&b)),
1987 Val::National(b) => text.push_str(&utf16_text(&b)),
1988 Val::Fig(f) => text.push(self.page.decode_byte(figurative_byte(f))),
1989 Val::Num(f) => text.push_str(&self.page.decode(&zoned_digits(f.magnitude.to_u128().unwrap_or(0), f.places.total() as usize, decimal::UNSIGNED))),
1990 Val::Float(_) => return Err(Abend::ironwork("DISPLAY of a floating-point value is not supported yet", pos)),
1991 Val::Address(_) => return Err(Abend::ironwork("DISPLAY of a pointer is not supported", pos)),
1992 },
1993 }
1994 }
1995 let result = if no_advancing { write!(self.unit.out, "{text}") } else { writeln!(self.unit.out, "{text}") };
1996 result.map_err(|e| match e.kind() {
1997 std::io::ErrorKind::BrokenPipe => Abend { code: CLOSED_OUTPUT.into(), message: "standard output closed".into(), pos },
1998 _ => Abend::ironwork(format!("DISPLAY: {e}"), pos),
1999 })
2000 }
2001
2002 fn initialize(&mut self, index: usize, offset: usize, pos: Pos) -> R<()> {
2003 let layout = self.layout;
2004 let item = &layout.items[index];
2005 if item.kind == Kind::Index {
2006 return Ok(());
2007 }
2008 if item.kind != Kind::Group {
2009 let loc = Loc { offset, len: item.size as usize, kind: item.kind, item: index };
2010 let val = match item.kind {
2011 Kind::Pointer => Val::Address(0),
2012 Kind::Alnum { .. } | Kind::National => Val::Fig(Figurative::Space),
2013 _ => Val::Fig(Figurative::Zero),
2014 };
2015 return self.assign(loc, val, None, pos);
2016 }
2017 for &c in &item.children {
2018 let child = &layout.items[c];
2019 if child.redefines.is_some() || child.name.is_none() {
2020 continue;
2021 }
2022 for k in 0..child.occurs {
2023 self.initialize(c, offset + (child.offset - item.offset) as usize + (k * child.size) as usize, pos)?;
2024 }
2025 }
2026 Ok(())
2027 }
2028}
2029
2030fn utf16_text(bytes: &[u8]) -> String {
2031 let units: Vec<u16> = bytes.chunks(2).map(|c| u16::from_be_bytes([c[0], *c.get(1).unwrap_or(&0)])).collect();
2032 String::from_utf16_lossy(&units)
2033}
2034
2035fn compare_national(a: &[u8], b: &[u8]) -> Ordering {
2036 let unit = |s: &[u8], i: usize| if i + 1 < s.len() { u16::from_be_bytes([s[i], s[i + 1]]) } else { 0x0020 };
2037 let len = a.len().max(b.len());
2038 (0..len).step_by(2).map(|i| unit(a, i).cmp(&unit(b, i))).find(|o| o.is_ne()).unwrap_or(Ordering::Equal)
2039}
2040
2041fn flatten_and<'c>(cond: &'c Cond, out: &mut Vec<&'c Cond>) {
2042 match cond {
2043 Cond::And(a, b) => {
2044 flatten_and(a, out);
2045 flatten_and(b, out);
2046 }
2047 other => out.push(other),
2048 }
2049}
2050
2051fn key_term<'c>(terms: &[&'c Cond], key: &str) -> Option<(&'c Expr, &'c Expr)> {
2053 let is_key = |e: &Expr| matches!(e, Expr::Operand(Operand::Ref(r)) if r.name == key);
2054 terms.iter().find_map(|t| match t {
2055 Cond::Rel(a, RelOp::Eq, b) if is_key(a) => Some((a, b)),
2056 Cond::Rel(a, RelOp::Eq, b) if is_key(b) => Some((b, a)),
2057 _ => None,
2058 })
2059}
2060
2061fn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
2063 let (y, m) = if month <= 2 { (year - 1, month + 9) } else { (year, month - 3) };
2064 let era = y.div_euclid(400);
2065 let yoe = y.rem_euclid(400);
2066 let doe = yoe * 365 + yoe / 4 - yoe / 100 + (153 * m + 2) / 5 + day - 1;
2067 era * 146_097 + doe - 719_468
2068}
2069
2070fn days_in_month(year: i64, month: i64) -> i64 {
2071 match month {
2072 2 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 => 29,
2073 2 => 28,
2074 4 | 6 | 9 | 11 => 30,
2075 _ => 31,
2076 }
2077}
2078
2079fn numval(text: &str, currency: Option<&str>) -> Option<Fixed> {
2083 let mut t = text.trim().to_ascii_uppercase();
2084 let mut negative = false;
2085 for (suffix, minus) in [("CR", true), ("DB", true), ("-", true), ("+", false)] {
2086 if let Some(rest) = t.strip_suffix(suffix) {
2087 negative = minus;
2088 t = rest.trim_end().to_owned();
2089 break;
2090 }
2091 }
2092 if let Some(rest) = t.strip_prefix('-') {
2093 negative = true;
2094 t = rest.trim_start().to_owned();
2095 } else if let Some(rest) = t.strip_prefix('+') {
2096 t = rest.trim_start().to_owned();
2097 }
2098 if let Some(c) = currency {
2099 t = t.trim_start_matches(c.trim()).trim_start().replace(',', "");
2100 }
2101 let (int, frac) = t.split_once('.').unwrap_or((&t, ""));
2102 if int.is_empty() && frac.is_empty() || !int.chars().chain(frac.chars()).all(|c| c.is_ascii_digit()) || int.len() + frac.len() > 31 {
2103 return None;
2104 }
2105 let digits: u128 = format!("{int}{frac}").parse().unwrap_or(0);
2106 let f = Fixed::new(digits as i128, Places::new(int.len().max(1) as u32, frac.len() as u32));
2107 Some(if negative { Fixed { negative: digits != 0, ..f } } else { f })
2108}
2109