use crate::abend::{Abend, AbendCode};
use crate::fixed::{align, zoned_digits};
use crate::host::{Host, Values};
use crate::lir::{CallArg, Chars};
use crate::storage::{Loc, Val};
use crate::store::{self, ProgramFacts};
use crate::unit::{Loader, RunUnit, UnitHost};
use crate::vocab::{Figurative, Pos};
use numeric::Dialect;
use zarch::decimal;
type R<T> = Result<T, Abend>;
pub trait Arguments<'w, P: Copy, O>: Values<P, O> + UnitHost<'w> {
fn item(&self, operand: &O) -> Option<P>;
fn length_of(&self, operand: &O) -> bool;
fn content_item(&mut self, place: P) -> R<Loc>;
}
pub fn addresses<'w, P: Copy, O>(x: &mut impl Arguments<'w, P, O>, args: &[CallArg<P, O>], pos: Pos) -> R<Vec<Option<usize>>> {
let mut addresses = Vec::with_capacity(args.len());
addresses_into(x, args, pos, &mut addresses)?;
Ok(addresses)
}
pub fn addresses_into<'w, P: Copy, O>(x: &mut impl Arguments<'w, P, O>, args: &[CallArg<P, O>], pos: Pos, addresses: &mut Vec<Option<usize>>) -> R<()> {
for arg in args {
let at = match arg {
CallArg::Omitted => None,
CallArg::Reference(p) => Some(x.locate(*p, false)?.offset),
CallArg::Value(o) => {
let val = x.value(o, pos)?;
let bytes = value_argument(val, pos)?;
Some(x.unit().push_temporary(&bytes))
}
CallArg::Content(c) => {
let bytes = content(x, c, pos)?;
Some(x.unit().push_temporary(&bytes))
}
};
addresses.push(at);
}
Ok(())
}
pub fn content<'w, P: Copy, O>(x: &mut impl Arguments<'w, P, O>, chars: &Chars<P, O>, pos: Pos) -> R<Vec<u8>> {
match chars {
Chars::Literal(bytes) => Ok(bytes.clone()),
Chars::Place(p) => {
let loc = x.content_item(*p)?;
Ok(store::bytes(x.mem(), loc).to_vec())
}
Chars::Value(o) => value_content(x, o, pos),
}
}
fn value_content<'w, P: Copy, O>(x: &mut impl Arguments<'w, P, O>, operand: &O, pos: Pos) -> R<Vec<u8>> {
let val = x.value(operand, pos)?;
Ok(content_argument(&x.facts(), val, x.length_of(operand)))
}
pub fn content_argument(facts: &dyn ProgramFacts, val: Val, length_of: bool) -> Vec<u8> {
match val {
Val::Bytes(b) | Val::All(b) | Val::National(b) | Val::AllNational(b) | Val::Dbcs(b) => b,
Val::Fig(f) => vec![facts.figurative(f)],
Val::Address(a) => a.to_be_bytes().to_vec(),
Val::Num(f) if length_of => (align(&f, 0, false).and_then(|m| m.to_u128()).unwrap_or(0) as u32).to_be_bytes().to_vec(),
Val::Num(f) => {
let digits = f.places.total().max(1);
let magnitude = align(&f, f.places.dec, false).and_then(|m| m.to_u128()).unwrap_or(0);
zoned_digits(magnitude, digits as usize, if f.negative { decimal::MINUS } else { decimal::UNSIGNED })
}
Val::Float(h) => h.to_bytes(),
}
}
pub fn value_argument(val: Val, pos: Pos) -> R<Vec<u8>> {
Ok(match val {
Val::Num(f) => {
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))?;
(if f.negative { -whole } else { whole }).to_be_bytes().to_vec()
}
Val::Address(a) => a.to_be_bytes().to_vec(),
Val::Fig(Figurative::Null) => vec![0; 4],
Val::Bytes(b) => b,
_ => return Err(Abend::ironwork("this BY VALUE argument is not supported", pos)),
})
}
pub fn arguments_text<'w, P: Copy, O>(x: &mut impl Arguments<'w, P, O>, args: &[CallArg<P, O>], pos: Pos) -> R<String> {
let mut text = String::new();
for arg in args {
let bytes = match arg {
CallArg::Omitted => continue,
CallArg::Reference(p) | CallArg::Content(Chars::Place(p)) => item_bytes(x, *p)?,
CallArg::Value(o) => match x.item(o) {
Some(p) => item_bytes(x, p)?,
None => value_content(x, o, pos)?,
},
CallArg::Content(c) => content(x, c, pos)?,
};
text.push_str(&x.facts().page().decode(&bytes));
}
Ok(text)
}
fn item_bytes<P: Copy>(x: &mut impl Host<P>, place: P) -> R<Vec<u8>> {
let loc = x.locate(place, false)?;
Ok(store::bytes(x.mem(), loc).to_vec())
}
pub struct Bindings<'a> {
pub records: &'a [(usize, usize)],
pub using: Vec<Option<usize>>,
pub addresses: &'a [Option<usize>],
pub returning: Option<(usize, usize)>,
}
impl Bindings<'_> {
pub fn bind<H: Clone, L: Loader<H>>(&self, unit: &mut RunUnit<'_, H, L>, linkage: &mut [Option<usize>]) {
for &(ordinal, address) in self.records {
if let Some(slot) = linkage.get_mut(ordinal) {
*slot = Some(address);
}
}
for (ordinal, address) in self.using.iter().zip(self.addresses) {
if let Some(slot) = ordinal.and_then(|o| linkage.get_mut(o)) {
*slot = *address;
}
}
if let Some((ordinal, size)) = self.returning {
linkage[ordinal] = Some(unit.push_temporary(&vec![0; size]));
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum By {
Call { initial: bool },
Link,
Invoke,
Function,
}
#[derive(Clone, Copy, Debug)]
pub struct Callee {
pub index: usize,
pub by: By,
pub mark: Option<usize>,
pub pos: Pos,
}
pub fn run<'w, X: UnitHost<'w>, O, T, E: From<Abend>>(x: &mut X, callee: &Callee, run: impl FnOnce(&mut X) -> Result<(R<O>, T), E>) -> Result<(R<O>, T), E> {
let index = callee.index;
let active = x.unit().programs[index].active;
let (ending, value) = run(x)?;
let unit = x.unit();
unit.programs[index].active = active;
if callee.by == (By::Call { initial: true }) {
cancel_program(unit, index, callee.pos)?;
}
if let Some(mark) = callee.mark {
unit.release_temporaries(mark);
}
Ok((ending.map_err(|a| if callee.by == By::Invoke { a } else { in_loaded(unit, index, a) }), value))
}
#[derive(Clone, Debug)]
pub enum Bound {
At(usize),
Value(Val),
}
pub fn bound_addresses<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, bound: &[Bound], sizes: impl Iterator<Item = usize>) -> Vec<Option<usize>> {
bound
.iter()
.zip(sizes)
.map(|(b, size)| {
Some(match b {
Bound::At(at) => *at,
Bound::Value(_) => unit.push_temporary(&vec![0; size]),
})
})
.collect()
}
pub fn in_loaded<H: Clone, L: Loader<H>>(unit: &RunUnit<'_, H, L>, index: usize, mut abend: Abend) -> Abend {
let program = &unit.programs[index];
if abend.file.is_none() {
abend.file = Some(match (abend.pos.file, &program.source) {
(0, Some(source)) => source.display().to_string(),
(i, _) => program.compiled.as_ref().and_then(|c| L::source(c, usize::from(i))).unwrap_or_default(),
});
}
abend
}
pub fn recursive_call(program: &str, unit: &str, pos: Pos) -> Abend {
let (program, unit) = (program.to_ascii_uppercase(), unit.to_ascii_uppercase());
let message = format!("IGZ0064S A recursive call to active program {program} in compilation unit {unit} was attempted.");
Abend { code: AbendCode::user(4038), message, pos, file: None }
}
pub fn entry_copy(dynamic: bool, dialect: Dialect) -> bool {
dynamic && dialect == Dialect::Ibm
}
pub fn cancel<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, name: &str, pos: Pos) -> R<()> {
let Some(index) = unit.find(name) else { return Ok(()) };
let target = &unit.programs[index].name;
let contained = unit.programs.iter().any(|p| p.compiled.as_ref().is_some_and(|c| L::nested(c).contains(target)));
if !unit.programs[index].dynamic && !contained {
return Ok(());
}
if unit.programs[index].active {
return Err(Abend::ironwork(format!("CANCEL {name}: the program is active"), pos));
}
cancel_program(unit, index, pos)
}
pub fn cancel_program<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, index: usize, pos: Pos) -> R<()> {
let files: Vec<_> = unit.programs[index].files.iter_mut().filter_map(Option::take).collect();
for f in files {
f.close().map_err(|e| Abend::ironwork(format!("CANCEL {}: {e}", unit.programs[index].name), pos))?;
}
unit.programs[index].initialized = false;
let nested = unit.programs[index].compiled.as_ref().map(|c| L::nested(c).to_vec()).unwrap_or_default();
for name in nested {
if let Some(contained) = unit.find(&name) {
cancel_program(unit, contained, pos)?;
}
}
Ok(())
}