use super::{Lower, LowerError, R, push, unsupported};
use crate::layout::Resolved;
use crate::machine::literal_fixed;
use crate::oo::{item_type, operand_type};
use rt::lir::{self, AbendId, CallArg, CallId, CallPlan, CallTarget, Chars, InvokeId, InvokePlan, LeService, MethodName, Receiver};
use rt::storage::Kind;
use syntax::Pos;
use syntax::ast::{ArgMode, Call, Invoke, InvokeMethod, Literal, Operand, Ref};
use zarch::decimal;
impl Lower<'_> {
pub(super) fn call_plan(&mut self, c: &Call, pos: Pos) -> R<Result<CallId, AbendId>> {
let target = match &c.target {
Operand::Ref(r) if self.program_pointer(r) => CallTarget::Pointer(self.place(r, false)?),
Operand::Literal(lit) => match program_name(self, lit) {
Ok(name) => CallTarget::Named { name: self.sym(&name), le: le_service(&name, pos)? },
Err(message) => return self.ironwork(&message).map(Err),
},
op => CallTarget::Dynamic(self.operand(op, pos)?.operand),
};
let pointer = matches!(target, CallTarget::Pointer(_));
let mut args = Vec::with_capacity(c.using.len());
for arg in &c.using {
args.push(match (&arg.value, arg.mode) {
(None, _) => CallArg::Omitted,
(Some(op), _) if pointer => CallArg::Value(self.operand(op, pos)?.operand),
(Some(Operand::Ref(r)), ArgMode::Reference) => CallArg::Reference(self.place(r, false)?),
(Some(op), ArgMode::Value) => CallArg::Value(self.operand(op, pos)?.operand),
(Some(op), _) => CallArg::Content(self.content(op, pos)?),
});
}
let returning = c.returning.as_ref().map(|r| self.place(r, false)).transpose()?;
let plan = CallPlan { target, args, returning, on_exception: c.on_exception.is_some(), not_on_exception: c.not_on_exception.is_some() };
push(&mut self.services.calls, plan, "CALL plans").map(Ok)
}
fn program_pointer(&self, r: &Ref) -> bool {
matches!(self.layout.resolve(&r.name, &r.qualifiers, r.pos), Ok(Resolved::Item(i)) if self.layout.items[i].kind == Kind::ProgramPointer)
}
fn content(&mut self, op: &Operand, pos: Pos) -> R<Chars> {
Ok(match op {
Operand::Ref(r) => Chars::Place(self.place(r, false)?),
Operand::Literal(lit) => Chars::Literal(self.content_bytes(lit, pos)?),
_ => Chars::Value(self.operand(op, pos)?.operand),
})
}
fn content_bytes(&mut self, lit: &Literal, pos: Pos) -> R<Vec<u8>> {
Ok(match lit {
Literal::Alnum(s) => self.encode(s, pos)?,
Literal::Hex(b) => b.clone(),
Literal::National(s) => s.encode_utf16().flat_map(u16::to_be_bytes).collect(),
Literal::Number(t) => match literal_fixed(t) {
Some(f) => {
let zone = if f.negative { decimal::MINUS } else { decimal::UNSIGNED };
zoned(f.magnitude.to_u128().unwrap_or(0), f.places.total().max(1) as usize, zone)
}
None => return unsupported("a numeric literal of more than 31 digits", pos),
},
Literal::Figurative(f) => vec![self.c.collating.figurative(*f)],
Literal::All(inner) => match &**inner {
Literal::Alnum(_) | Literal::Hex(_) | Literal::Figurative(_) => self.content_bytes(inner, pos)?,
_ => return unsupported("ALL with a literal that is not alphanumeric", pos),
},
})
}
pub(super) fn entry_points(&mut self) -> R<Vec<lir::EntryPoint>> {
let layout = self.layout;
let mut out = Vec::with_capacity(self.c.entries.len());
for e in &self.c.entries {
let Some(&block) = self.entry_blocks.get(&(e.paragraph, e.statement)) else {
return Err(LowerError::Invalid(format!("ENTRY '{}' has no block", e.name)));
};
let mut using = Vec::with_capacity(e.using.len());
for param in &e.using {
match layout.linkage_roots.iter().position(|&i| layout.items[i].name.as_deref() == Some(param.name.as_str())).map(u16::try_from) {
Some(Ok(ordinal)) => using.push(ordinal),
Some(Err(_)) => return Err(LowerError::Exceeds("LINKAGE records", e.pos)),
None => return unsupported("ENTRY USING an item that is not a LINKAGE record", e.pos),
}
}
out.push(lir::EntryPoint { name: self.sym(&e.name), paragraph: e.paragraph as u32, block, using });
}
Ok(out)
}
pub(super) fn invoke_plan(&mut self, i: &Invoke, pos: Pos) -> R<InvokeId> {
let (layout, oo) = (self.layout, self.program.oo.as_deref());
let method = match &i.method {
InvokeMethod::New => MethodName::New,
InvokeMethod::Named(name) => MethodName::Named(self.sym(name)),
InvokeMethod::Identifier(r) => MethodName::Dynamic(self.place(r, false)?),
};
let t = &i.target;
let plain = t.qualifiers.is_empty() && t.subscripts.is_empty() && t.refmod.is_none() && layout.resolve(&t.name, &[], t.pos).is_err();
let external = oo.and_then(|o| o.external(&t.name));
let receiver = match (t.name.as_str(), external) {
("SELF", _) if plain => Receiver::SelfRef,
("SUPER", _) if plain => Receiver::Super,
(name, Some(external)) if plain => Receiver::Class { name: self.sym(name), external: self.sym(external) },
_ => Receiver::Object(self.place(t, false)?),
};
let mut args = Vec::with_capacity(i.using.len());
for op in &i.using {
let Ok(java) = operand_type(layout, oo, op) else { return unsupported("an INVOKE argument of no Java type", pos) };
args.push((self.operand(op, pos)?.operand, self.sym(&java)));
}
let returning = match &i.returning {
None => None,
Some(r) => {
let java = match layout.resolve(&r.name, &r.qualifiers, r.pos) {
Ok(Resolved::Item(k)) => item_type(layout, oo, k).ok(),
_ => None,
};
let Some(java) = java else { return unsupported("an INVOKE RETURNING item of no Java type", pos) };
Some((self.place(r, false)?, self.sym(&java)))
}
};
let plan = InvokePlan { receiver, method, args, returning, on_exception: i.on_exception.is_some(), not_on_exception: i.not_on_exception.is_some() };
push(&mut self.services.invokes, plan, "INVOKE plans")
}
}
fn program_name(l: &Lower<'_>, lit: &Literal) -> Result<String, String> {
literal_error(l, lit)?;
let bytes = match lit {
Literal::Alnum(s) => l.page.encode(s).map_err(|e| e.to_string())?,
Literal::Hex(b) => b.clone(),
_ => return Err("a program name must be alphanumeric".into()),
};
Ok(l.page.decode(&bytes).trim().to_ascii_uppercase())
}
fn literal_error(l: &Lower<'_>, lit: &Literal) -> Result<(), String> {
match lit {
Literal::Alnum(s) => l.page.encode(s).map(|_| ()).map_err(|e| e.to_string()),
Literal::Number(t) if literal_fixed(t).is_none() => Err(format!("the literal {t} has more than 31 digits")),
Literal::All(inner) => {
literal_error(l, inner)?;
match &**inner {
Literal::Alnum(_) | Literal::Hex(_) | Literal::Figurative(_) => Ok(()),
_ => Err("ALL takes an alphanumeric literal".into()),
}
}
_ => Ok(()),
}
}
fn le_service(name: &str, pos: Pos) -> R<Option<LeService>> {
let service = match name {
"CEE3ABD" => LeService::Cee3abd,
"CEE3DMP" => LeService::Cee3dmp,
"CEEDATE" => LeService::Ceedate,
"CEEDATM" => LeService::Ceedatm,
"CEEDAYS" => LeService::Ceedays,
"CEEDYWK" => LeService::Ceedywk,
"CEEFRST" => LeService::Ceefrst,
"CEEGMT" => LeService::Ceegmt,
"CEEGMTO" => LeService::Ceegmto,
"CEEGTST" => LeService::Ceegtst,
"CEELOCT" => LeService::Ceeloct,
"CEEMOUT" => LeService::Ceemout,
"CEESECS" => LeService::Ceesecs,
"CEEUTC" => LeService::Ceeutc,
_ if crate::le::provides(name) => return unsupported("an LE callable service the LIR does not name", pos),
_ => return Ok(None),
};
Ok(Some(service))
}
fn zoned(magnitude: u128, digits: usize, sign_zone: u8) -> Vec<u8> {
let mut out = vec![0xF0u8; digits];
let mut m = magnitude;
for b in out.iter_mut().rev() {
*b = 0xF0 | (m % 10) as u8;
m /= 10;
}
if let Some(last) = out.last_mut() {
*last = (sign_zone << 4) | (*last & 0x0F);
}
out
}