use super::cics::{EIBAID, EIBCPOSN, has, operand};
use super::*;
use crate::terminal::{self, attribute_byte};
use syntax::bms::{self, Initial, Intensity, Map, Protection};
fn attribute_of(attrb: &bms::Attrb) -> u8 {
let mut bits = match attrb.protection {
Protection::Askip => terminal::PROTECTED | terminal::NUMERIC,
Protection::Prot => terminal::PROTECTED,
Protection::Unprot => 0,
};
if attrb.numeric {
bits |= terminal::NUMERIC;
}
bits |= match attrb.intensity {
Intensity::Norm => 0,
Intensity::Brt => terminal::INTENSIFIED,
Intensity::Drk => terminal::NON_DISPLAY,
};
if attrb.detectable && attrb.intensity != Intensity::Drk {
bits |= 0x04;
}
if attrb.fset {
bits |= terminal::MDT;
}
attribute_byte(bits)
}
impl<'p> Machine<'p, '_, '_> {
fn bms_map(&mut self, block: &ExecBlock) -> R<Result<Map, &'static str>> {
let Some(map_name) = self.arg_text(block, "MAP")?.map(|m| m.to_ascii_uppercase()) else {
return Err(Abend::ironwork(format!("EXEC CICS {} needs MAP", block.command), block.pos));
};
let set_name = self.arg_text(block, "MAPSET")?.map_or_else(|| map_name.clone(), |m| m.to_ascii_uppercase());
let cached = self.unit.cics.as_ref().and_then(|t| t.mapsets.get(&set_name)).cloned();
let mapset = match cached {
Some(m) => m,
None => match bms::find_mapset(self.unit.copy_libraries(), &set_name) {
None => return Ok(Err("PGMIDERR")),
Some(Err(e)) => return Err(Abend::ironwork(format!("EXEC CICS {} MAPSET({set_name}): {}", block.command, e.message), block.pos)),
Some(Ok(m)) => {
if let Some(task) = self.unit.cics.as_mut() {
task.mapsets.insert(set_name.clone(), m.clone());
}
m
}
},
};
Ok(mapset.maps.into_iter().find(|m| m.name == map_name).ok_or("INVREQ"))
}
fn area_bytes(&mut self, block: &ExecBlock, option: &str, default: &str) -> R<Option<Vec<u8>>> {
if let Some(bytes) = self.arg_bytes(block, option)? {
return Ok(Some(bytes));
}
let r = Ref { name: default.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos: block.pos };
match self.resolve(&r) {
Ok(Resolved::Item(_)) => {
let loc = self.locate(&r)?;
Ok(Some(self.bytes(loc).to_vec()))
}
_ => Ok(None),
}
}
fn with_terminal<T>(&mut self, block: &ExecBlock, op: impl FnOnce(&mut dyn crate::cics::Terminal) -> Result<T, String>) -> R<T> {
let Some(term) = self.unit.cics.as_mut().and_then(|t| t.terminal.as_mut()) else {
return Err(Abend::ironwork(format!("EXEC CICS {} needs a terminal: run with --screens, or through the TN3270 server", block.command), block.pos));
};
op(term.as_mut()).map_err(|m| Abend::ironwork(format!("EXEC CICS {}: {m}", block.command), block.pos))
}
fn wcc(block: &ExecBlock, ctrl: &[String]) -> u8 {
let on = |name: &str| has(block, name) || ctrl.iter().any(|c| c == name);
let mut wcc = 0;
if on("FREEKB") {
wcc |= terminal::WCC_RESTORE;
}
if on("ALARM") {
wcc |= terminal::WCC_ALARM;
}
if on("FRSET") {
wcc |= terminal::WCC_RESET_MDT;
}
wcc
}
pub(super) fn send_map(&mut self, block: &ExecBlock) -> R<Flow> {
let map = match self.bms_map(block)? {
Ok(m) => m,
Err(c) => return self.raise(block, c, 0),
};
let (rows, columns) = self.with_terminal(block, |t| Ok(t.size()))?;
let (maponly, dataonly) = (has(block, "MAPONLY"), has(block, "DATAONLY"));
let area = if maponly { None } else { self.area_bytes(block, "FROM", &format!("{}O", map.name))? };
let ctrl: Vec<String> = map.ctrl.clone();
let slots = bms::slots(&map, false);
let command = if has(block, "ERASE") { terminal::ERASE_WRITE } else { terminal::WRITE };
let mut stream = vec![command, Self::wcc(block, &ctrl)];
let (mut cursor, mut symbolic_cursor) = (None, None);
for (i, field) in map.fields.iter().enumerate() {
let copies = if field.group.is_none() { field.occurs.max(1) } else { 1 };
for occurrence in 0..copies {
let row = usize::from(map.line) + usize::from(field.line) - 1;
let column = usize::from(map.column) + usize::from(field.column) - 1 + usize::from(occurrence) * (usize::from(field.length) + 1);
if row > rows || column > columns {
return self.raise(block, "INVMPSZ", 0);
}
let at = (row - 1) * columns + column - 1;
let slot = slots.iter().find(|s| s.field == i && s.occurrence == occurrence);
let symbolic = |offset: usize, len: usize| area.as_ref().and_then(|a| a.get(offset..offset + len)).filter(|b| b.first().is_some_and(|&x| x != 0));
let attribute = slot.and_then(|s| s.attribute_at()).and_then(|o| symbolic(o, 1)).map(|b| b[0]);
let data = slot.and_then(|s| symbolic(s.data, s.size.min(usize::from(field.length))).map(<[u8]>::to_vec));
if slot.and_then(|s| s.length_at()).and_then(|o| area.as_ref().and_then(|a| a.get(o..o + 2))) == Some(&[0xFF, 0xFF][..]) {
symbolic_cursor.get_or_insert(at + 1);
}
let initial = match &field.initial {
Some(Initial::Text(t)) => Some(t.chars().map(|c| self.page.encode_char(c).unwrap_or(ebcdic::SPACE)).collect::<Vec<u8>>()),
Some(Initial::Bytes(b)) => Some(b.clone()),
None => None,
};
if dataonly && attribute.is_none() && data.is_none() {
continue;
}
stream.push(terminal::SBA);
stream.extend(terminal::encode_address(at));
stream.extend([terminal::SF, attribute.unwrap_or_else(|| attribute_of(&field.attrb))]);
if let Some(bytes) = data.or(if dataonly { None } else { initial }) {
stream.extend(bytes.iter().take(usize::from(field.length)));
}
if field.attrb.cursor && cursor.is_none() {
cursor = Some(at + 1);
}
}
}
if has(block, "CURSOR") {
cursor = match self.arg_int(block, "CURSOR")? {
Some(n) => Some(n.max(0) as usize),
None => symbolic_cursor.or(cursor),
};
}
if let Some(at) = cursor {
stream.push(terminal::SBA);
stream.extend(terminal::encode_address(at));
stream.push(terminal::IC);
}
self.with_terminal(block, |t| t.send(&stream))?;
self.cics_ok(block)
}
pub(super) fn send_control(&mut self, block: &ExecBlock) -> R<Flow> {
let command = if has(block, "ERASE") { terminal::ERASE_WRITE } else { terminal::WRITE };
let mut stream = vec![command, Self::wcc(block, &[])];
if let Some(n) = self.arg_int(block, "CURSOR")? {
stream.push(terminal::SBA);
stream.extend(terminal::encode_address(n.max(0) as usize));
stream.push(terminal::IC);
}
self.with_terminal(block, |t| t.send(&stream))?;
self.cics_ok(block)
}
fn next_inbound(&mut self, block: &ExecBlock) -> R<terminal::Inbound> {
let Some(stream) = self.with_terminal(block, |t| t.receive())? else {
return Err(Abend::ironwork(format!("EXEC CICS {}: the terminal has no more input", block.command), block.pos));
};
let read = terminal::parse_inbound(&stream).map_err(|m| Abend::ironwork(format!("EXEC CICS {}: {m}", block.command), block.pos))?;
self.eib_bytes(EIBAID, &[read.aid]);
self.eib_halfword(EIBCPOSN, read.cursor.unwrap_or(0) as i16);
Ok(read)
}
pub(super) fn receive_map(&mut self, block: &ExecBlock) -> R<Flow> {
let map = match self.bms_map(block)? {
Ok(m) => m,
Err(c) => return self.raise(block, c, 0),
};
let (_, columns) = self.with_terminal(block, |t| Ok(t.size()))?;
let read = self.next_inbound(block)?;
if read.fields.is_empty() {
return self.raise(block, "MAPFAIL", 0);
}
let slots = bms::slots(&map, true);
let size = slots.last().map_or(0, |s| s.data + s.size);
let mut area = vec![0u8; size];
for slot in &slots {
let field = &map.fields[slot.field];
let row = usize::from(map.line) + usize::from(field.line) - 1;
let column = usize::from(map.column) + usize::from(field.column) - 1 + usize::from(slot.occurrence) * (usize::from(field.length) + 1);
let start = (row - 1) * columns + column;
let Some((_, data)) = read.fields.iter().find(|(a, _)| *a == start) else { continue };
let data = &data[..data.len().min(slot.size)];
if let Some(l) = slot.length_at() {
area[l..l + 2].copy_from_slice(&(data.len() as u16).to_be_bytes());
if data.is_empty() {
area[l + 2] = 0x80;
}
}
if data.is_empty() {
continue;
}
let fill = if field.fill_zero { 0xF0 } else { ebcdic::SPACE };
let target = &mut area[slot.data..slot.data + slot.size];
target.fill(fill);
let offset = if field.justify_right { slot.size - data.len() } else { 0 };
target[offset..offset + data.len()].copy_from_slice(data);
}
if has(block, "SET") {
let at = self.unit.push_temporary(&area);
self.store_pointer(block, "SET", Some(at))?;
} else if operand(block, "INTO").is_some() {
self.store_bytes(block, "INTO", &area)?;
} else {
let r = Ref { name: format!("{}I", map.name), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos: block.pos };
let loc = self.locate(&r)?;
self.assign(loc, Val::Bytes(area), None, block.pos)?;
}
self.cics_ok(block)
}
pub(super) fn receive_raw(&mut self, block: &ExecBlock) -> R<Flow> {
let Some(stream) = self.with_terminal(block, |t| t.receive())? else {
return Err(Abend::ironwork("EXEC CICS RECEIVE: the terminal has no more input", block.pos));
};
if let Some(&aid) = stream.first() {
self.eib_bytes(EIBAID, &[aid]);
}
self.deliver_record(block, &stream)
}
}