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>>> {
Ok(arguments(x, args, pos)?.0)
}
pub fn arguments<'w, P: Copy, O>(x: &mut impl Arguments<'w, P, O>, args: &[CallArg<P, O>], pos: Pos) -> R<(Vec<Option<usize>>, Vec<usize>)> {
let (mut addresses, mut lengths) = (Vec::with_capacity(args.len()), Vec::with_capacity(args.len()));
addresses_into(x, args, pos, &mut addresses, &mut lengths)?;
Ok((addresses, lengths))
}
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>>, lengths: &mut Vec<usize>) -> R<()> {
for arg in args {
let (at, length) = match arg {
CallArg::Omitted => (None, 0),
CallArg::Reference(p) => {
let loc = x.locate(*p, false)?;
(Some(loc.offset), loc.len)
}
CallArg::Value(o) => {
let val = x.value(o, pos)?;
let bytes = value_argument(val, pos)?;
(Some(x.unit().push_temporary(&bytes)), bytes.len())
}
CallArg::Content(c) => {
let bytes = content(x, c, pos)?;
(Some(x.unit().push_temporary(&bytes)), bytes.len())
}
};
addresses.push(at);
lengths.push(length);
}
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(crate::refusal::IWR0070.abend("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<'a> {
pub index: usize,
pub by: By,
pub mark: Option<usize>,
pub pos: Pos,
pub lengths: &'a [usize],
}
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;
x.unit().calls.push(index);
let start = x.unit().argument_lengths.len();
x.unit().argument_starts.push(start);
x.unit().argument_lengths.extend_from_slice(callee.lengths);
let ran = run(x);
x.unit().calls.pop();
x.unit().argument_starts.pop();
x.unit().argument_lengths.truncate(start);
let (ending, value) = ran?;
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 = if program == unit {
format!("IGZ0015S A recursive call was attempted to a program that was already active. The program name is {program}.")
} else {
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 names<'a>(names: impl IntoIterator<Item = &'a str>, name: &str) -> bool {
names.into_iter().any(|n| n.eq_ignore_ascii_case(name))
}
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(()) };
if !unit.programs[index].dynamic {
return Ok(());
}
if unit.programs[index].active {
return Err(Abend { code: crate::abend::AbendCode::user(4038), message: format!("IGZ0032S A CANCEL was attempted on active program {}.", unit.programs[index].name), pos, file: None });
}
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(())
}