1use super::command::{Cics, CicsCommand, Datum, Record, Resp};
5use super::{Condition, Task, file_control, maps, program, services};
6use crate::abend::{Abend, AbendCode, Ending};
7use crate::bms::Mapset;
8use crate::host::Host;
9use crate::lir::{ParaId, Step};
10use crate::storage::{Loc, Val};
11use crate::store::{self, ProgramFacts};
12use crate::unit::{ADDRESS_BASE, Loader, RunUnit, UnitHost};
13use crate::vocab::Pos;
14use numeric::precision::{Fixed, Places};
15use std::collections::HashMap;
16use zarch::decimal::{self, Decimal};
17use zarch::ebcdic::{self, CodePage};
18
19pub(super) type R<T> = Result<T, Abend>;
20
21pub const EIBTIME: usize = 0x00;
22pub const EIBDATE: usize = 0x04;
23pub const EIBTRNID: usize = 0x08;
24pub const EIBTASKN: usize = 0x0C;
25pub const EIBTRMID: usize = 0x10;
26pub const EIBCPOSN: usize = 0x16;
27pub const EIBCALEN: usize = 0x18;
28pub const EIBAID: usize = 0x1A;
29pub const EIBFN: usize = 0x1B;
30pub const EIBRSRCE: usize = 0x33;
31pub const EIBRESP: usize = 0x4C;
32pub const EIBRESP2: usize = 0x50;
33
34pub trait CicsHost<'w, P: Copy, O, S>: Host<P> + UnitHost<'w> {
38 fn handlers(&mut self) -> &mut Handlers;
39 fn content(&mut self, operand: &O, pos: Pos) -> R<Vec<u8>>;
41 fn integer_of(&mut self, operand: &O, pos: Pos) -> R<i64>;
43 fn text(&self, text: &S) -> String;
44 fn activation(&self) -> u64;
46 fn program_id(&self) -> String;
47 fn commarea(&self) -> Option<usize>;
49 fn mapset(&mut self, name: &str) -> Option<Result<Mapset, String>>;
51 fn item_named(&mut self, name: &str, pos: Pos) -> R<Option<Loc>>;
53 fn locate_named(&mut self, name: &str, pos: Pos) -> R<Loc>;
55 fn unaddressed(&mut self, place: P) -> Option<usize>;
58 fn run_program(&mut self, program: Option<Self::Program>, index: usize, commarea: Option<usize>, xctl: bool) -> R<Ending>;
63}
64
65#[derive(Clone, Debug, Default)]
68pub struct Handlers {
69 pub conditions: HashMap<Condition, Handler>,
70 pub stack: Vec<(HashMap<Condition, Handler>, Option<AbendExit>)>,
72 pub abend: Option<AbendExit>,
73}
74
75impl Handlers {
76 pub fn push(&mut self) {
78 let suspended = (std::mem::take(&mut self.conditions), self.abend.take());
79 self.stack.push(suspended);
80 }
81
82 pub fn pop(&mut self) -> bool {
84 let Some((conditions, abend)) = self.stack.pop() else { return false };
85 self.conditions = conditions;
86 self.abend = abend;
87 true
88 }
89
90 pub fn lend(&mut self, pushes: bool) -> Handlers {
93 let mut level = std::mem::take(self);
94 if pushes {
95 level.push();
96 }
97 level
98 }
99
100 pub fn xctl(&mut self) -> Handlers {
104 let stack = std::mem::take(&mut self.stack).into_iter().map(|(_, abend)| (HashMap::new(), abend)).collect();
105 Handlers { conditions: HashMap::new(), stack, abend: self.abend.take() }
106 }
107
108 pub fn take_back(&mut self, callee: &mut Handlers, pops: bool) {
112 *self = std::mem::take(callee);
113 if pops {
114 self.pop();
115 }
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct AbendExit {
123 pub target: ExitTarget,
124 pub active: bool,
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
128pub enum ExitTarget {
129 Label { paragraph: ParaId, owner: u64, at: Pos },
132 Program { name: String, commarea: Option<(usize, i16)> },
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Eq)]
138pub enum Handler {
139 Label { paragraph: ParaId, owner: u64 },
141 Ignore,
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub enum Flow {
147 Next,
148 GoTo(ParaId),
150 End(Ending),
153}
154
155impl From<Flow> for Step {
156 fn from(flow: Flow) -> Self {
157 match flow {
158 Flow::Next => Step::Next,
159 Flow::GoTo(p) => Step::GoTo(p),
160 Flow::End(e) => Step::End(e),
161 }
162 }
163}
164
165pub struct At<'c, P, O, S> {
167 pub name: &'c str,
168 pub resp: &'c Resp<P, O, S>,
169 pub pos: Pos,
170}
171
172pub fn in_task<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>, name: &str, pos: Pos) -> R<()> {
174 match unit.cics {
175 Some(_) => Ok(()),
176 None => Err(Abend::ironwork(format!("EXEC CICS {name} was reached outside a CICS task: run the program with `ironwork cics`"), pos)),
177 }
178}
179
180pub fn begin_command<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>) {
182 eib_fullword(unit, EIBRESP, 0);
183 eib_fullword(unit, EIBRESP2, 0);
184}
185
186pub fn run<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, command: &CicsCommand<P, O, S>, pos: Pos) -> R<Flow> {
188 if let Cics::Refused(why) = &command.command {
189 return Err(Abend::ironwork(x.text(why), pos));
190 }
191 let name = x.text(&command.name);
192 in_task(x.unit(), &name, pos)?;
193 begin_command(x.unit());
194 let at = At { name: &name, resp: &command.resp, pos };
195 match &command.command {
196 Cics::File { verb, file, options } => file_control::run(x, &at, *verb, file.as_ref(), options),
197 Cics::Return { transid, commarea, length, channel, immediate } => program::cics_return(x, &at, transid.as_ref(), commarea.as_ref(), length.as_ref(), channel.is_some() || *immediate),
198 Cics::Link(t) => program::link(x, &at, t, false),
199 Cics::Xctl(t) => program::link(x, &at, t, true),
200 Cics::Abend { abcode, cancel } => program::abend(x, &at, abcode.as_ref(), *cancel),
201 Cics::HandleCondition(labels) => program::handle_condition(x, &at, labels),
202 Cics::IgnoreCondition(conditions) => program::ignore_condition(x, &at, conditions),
203 Cics::PushHandle => program::push_handle(x, &at),
204 Cics::PopHandle => program::pop_handle(x, &at),
205 Cics::HandleAbend { program, label, reset } => program::handle_abend(x, &at, program.as_ref(), *label, *reset),
206 Cics::HandleAid | Cics::Freemain | Cics::Enq | Cics::Deq | Cics::Delay => ok(x, &at),
207 Cics::SendMap { map, mapset, from, maponly, dataonly, cursor, control } => {
208 maps::send_map(x, &at, maps::MapNames { map: map.as_ref(), mapset: mapset.as_ref() }, from.as_ref(), (*maponly, *dataonly), cursor.as_ref(), *control)
209 }
210 Cics::ReceiveMap { map, mapset, into, set } => maps::receive_map(x, &at, maps::MapNames { map: map.as_ref(), mapset: mapset.as_ref() }, into.as_ref(), set.as_ref()),
211 Cics::SendControl { cursor, control } => maps::send_control(x, &at, cursor.as_ref(), *control),
212 Cics::Receive(record) => maps::receive_raw(x, &at, record),
213 Cics::Asktime { abstime } => services::asktime(x, &at, abstime.as_ref()),
214 Cics::Formattime { abstime, datesep, timesep, outputs } => services::formattime(x, &at, abstime.as_ref(), datesep.as_ref(), timesep.as_ref(), outputs),
215 Cics::Assign(assign) => services::assign(x, &at, assign),
216 Cics::Getmain { flength, length, initimg, set } => services::getmain(x, &at, flength.as_ref(), length.as_ref(), initimg.as_ref(), set.as_ref()),
217 Cics::Syncpoint { rollback } => services::syncpoint(x, &at, *rollback),
218 Cics::Address { eib, commarea, cwa, twa } => services::address(x, &at, eib.as_ref(), commarea.as_ref(), cwa.as_ref(), twa.as_ref()),
219 Cics::SendText { from, length } => services::send_text(x, &at, from.as_ref(), length.as_ref()),
220 Cics::WriteOperator { text, textlength } => services::write_operator(x, &at, text.as_ref(), textlength.as_ref()),
221 Cics::WriteqTs { queue, from, length, rewrite, item, numitems } => {
222 services::writeq_ts(x, &at, queue.as_ref(), (from.as_ref(), length.as_ref()), *rewrite, item.as_ref(), numitems.as_ref())
223 }
224 Cics::ReadqTs { queue, next, item, numitems, record } => services::readq_ts(x, &at, queue.as_ref(), *next, item.as_ref(), numitems.as_ref(), record),
225 Cics::DeleteqTs { queue } => services::deleteq_ts(x, &at, queue.as_ref()),
226 Cics::WriteqTd { queue, from, length } => services::writeq_td(x, &at, queue.as_ref(), from.as_ref(), length.as_ref()),
227 Cics::ReadqTd { queue, record } => services::readq_td(x, &at, queue.as_ref(), record),
228 Cics::DeleteqTd { queue } => services::deleteq_td(x, &at, queue.as_ref()),
229 Cics::Unsupported => Err(Abend::ironwork(unsupported(&name), pos)),
230 Cics::Refused(_) => unreachable!("a refused command abends before it runs"),
231 }
232}
233
234pub fn unsupported(name: &str) -> String {
236 format!("EXEC CICS {name} is not supported yet")
237}
238
239pub(super) fn task<'a, 'w: 'a, P: Copy + 'a, O: 'a, S: 'a>(x: &'a mut impl CicsHost<'w, P, O, S>) -> &'a mut Task {
241 x.unit().cics.as_mut().expect("EXEC CICS runs only in a task")
242}
243
244pub(super) fn page<'w, P: Copy, O, S>(x: &impl CicsHost<'w, P, O, S>) -> &'static CodePage {
245 x.facts().page()
246}
247
248pub(super) fn encoded(page: &CodePage, text: &str) -> Vec<u8> {
250 text.chars().map(|c| page.encode_char(c).unwrap_or(ebcdic::SPACE)).collect()
251}
252
253pub fn bytes<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<Vec<u8>>> {
255 Ok(match d {
256 Some(Datum::Place(p)) => {
257 let loc = x.locate(*p, false)?;
258 Some(store::bytes(x.mem(), loc).to_vec())
259 }
260 Some(Datum::Value(o)) => Some(x.content(o, pos)?),
261 _ => None,
262 })
263}
264
265pub(super) fn bytes_cut<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, data: Option<&Datum<P, O, S>>, length: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<Vec<u8>>> {
267 let Some(mut bytes) = bytes(x, data, pos)? else { return Ok(None) };
268 if let Some(n) = int(x, length, pos)? {
269 bytes.truncate(n.max(0) as usize);
270 }
271 Ok(Some(bytes))
272}
273
274pub(super) fn sent<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, data: Option<&Datum<P, O, S>>, length: Option<&Datum<P, O, S>>, from: &str) -> R<Vec<u8>> {
276 bytes_cut(x, data, length, at.pos)?.ok_or_else(|| Abend::ironwork(format!("EXEC CICS {} needs {from}", at.name), at.pos))
277}
278
279pub(super) fn int<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<i64>> {
280 match d {
281 Some(Datum::Place(p)) => x.integer(*p, pos).map(Some),
282 Some(Datum::Value(o)) => x.integer_of(o, pos).map(Some),
283 _ => Ok(None),
284 }
285}
286
287pub(super) fn text<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, pos: Pos) -> R<Option<String>> {
290 if let Some(Datum::Text(t)) = d {
291 return Ok(Some(x.text(t).trim().trim_matches(|c| c == '\'' || c == '"').to_owned()));
292 }
293 let page = page(x);
294 Ok(bytes(x, d, pos)?.map(|b| page.decode(&b).trim_end().to_owned()))
295}
296
297fn receiver<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, p: P) -> R<Loc> {
299 let was = x.unit().writing(true);
300 let loc = x.locate(p, false);
301 x.unit().writing(was);
302 loc
303}
304
305pub(super) fn store_bytes<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, d: Option<&Datum<P, O, S>>, name: &str, bytes: &[u8]) -> R<()> {
308 match d {
309 Some(Datum::Place(p)) => {
310 let loc = receiver(x, *p)?;
311 x.assign(loc, Val::Bytes(bytes.to_vec()), None, at.pos)
312 }
313 Some(Datum::Value(_)) => Err(Abend::ironwork(format!("EXEC CICS {}: {name} must name a data item", at.name), at.pos)),
314 _ => Ok(()),
315 }
316}
317
318pub(super) fn store_int<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, value: i64, pos: Pos) -> R<()> {
319 match d {
320 Some(Datum::Place(p)) => {
321 let dest = receiver(x, *p)?;
322 x.store_fixed(dest, &Fixed::new(i128::from(value), Places::new(19, 0)), pos)
323 }
324 _ => Ok(()),
325 }
326}
327
328pub(super) fn store_pointer<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, d: Option<&Datum<P, O, S>>, offset: Option<usize>, pos: Pos) -> R<()> {
330 if let Some(Datum::Place(p)) = d {
331 let loc = receiver(x, *p)?;
332 let address = offset.map_or(0, |o| ADDRESS_BASE + o as u32);
333 x.assign(loc, Val::Address(address), None, pos)?;
334 }
335 Ok(())
336}
337
338pub(super) fn eib_bytes<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, bytes: &[u8]) {
339 let at = unit.eib + offset;
340 unit.write(at, bytes);
341}
342
343pub(super) fn eib_halfword<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, value: i16) {
344 eib_bytes(unit, offset, &value.to_be_bytes());
345}
346
347pub(super) fn eib_fullword<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, value: i32) {
348 eib_bytes(unit, offset, &value.to_be_bytes());
349}
350
351pub(super) fn eib_packed<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, offset: usize, value: i64) {
353 let mut field = [0u8; 4];
354 let _ = decimal::encode(&mut field, Decimal { negative: value < 0, magnitude: value.unsigned_abs() as u128 });
355 eib_bytes(unit, offset, &field);
356}
357
358pub(super) fn eib_text<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, page: &CodePage, offset: usize, len: usize, text: &str) {
360 let mut bytes = encoded(page, text);
361 bytes.resize(len, ebcdic::SPACE);
362 eib_bytes(unit, offset, &bytes);
363}
364
365pub(super) fn eib_calen<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>) -> i16 {
366 let at = unit.eib + EIBCALEN;
367 i16::from_be_bytes([unit.mem[at], unit.mem[at + 1]])
368}
369
370pub(super) fn kept_calen<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>) -> (i16, bool) {
373 (eib_calen(unit), unit.holds_input(unit.eib + EIBCALEN, 2))
374}
375
376pub(super) fn restore_calen<H, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, (value, input): (i16, bool)) {
377 eib_halfword(unit, EIBCALEN, value);
378 unit.mark_input(unit.eib + EIBCALEN, 2, input);
379}
380
381pub fn begin_task<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, page: &CodePage, length: usize) {
384 let (seconds, hundredths) = unit.now();
385 let abstime = super::abstime(seconds, hundredths);
386 let (transid, termid, number) = unit.cics.as_ref().map(|t| (t.transid.clone(), t.termid.clone(), t.number)).unwrap_or_default();
387 eib_packed(unit, EIBTIME, super::eib_time(abstime));
388 eib_packed(unit, EIBDATE, super::eib_date(abstime));
389 eib_text(unit, page, EIBTRNID, 4, &transid);
390 eib_packed(unit, EIBTASKN, i64::from(number));
391 eib_text(unit, page, EIBTRMID, 4, &termid);
392 eib_halfword(unit, EIBCALEN, length as i16);
393 unit.mark_input(unit.eib + EIBCALEN, 2, length > 0);
395 if let Some(aid) = unit.cics.as_ref().and_then(|t| t.initial_aid) {
396 eib_bytes(unit, EIBAID, &[aid]);
397 unit.mark_input(unit.eib + EIBAID, 1, true);
398 }
399}
400
401pub(super) fn deliver<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, record: &Record<P, O, S>, data: &[u8]) -> R<Flow> {
404 let limit = match int(x, record.length.as_ref(), at.pos)? {
405 Some(n) => n.max(0) as usize,
406 None => match &record.into {
407 Some(Datum::Place(p)) => x.locate(*p, false)?.len,
408 _ => data.len(),
409 },
410 };
411 if record.set.is_some() {
412 let area = x.unit().push_temporary(data);
413 store_pointer(x, record.set.as_ref(), Some(area), at.pos)?;
414 } else {
415 store_bytes(x, at, record.into.as_ref(), "INTO", &data[..data.len().min(limit)])?;
416 }
417 store_int(x, record.length.as_ref(), data.len() as i64, at.pos)?;
418 if data.len() > limit && record.set.is_none() {
419 return raise(x, at, Condition::LENGERR, 0);
420 }
421 ok(x, at)
422}
423
424pub fn ok<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>) -> R<Flow> {
426 store_int(x, at.resp.resp.as_ref(), 0, at.pos)?;
427 store_int(x, at.resp.resp2.as_ref(), 0, at.pos)?;
428 Ok(Flow::Next)
429}
430
431pub(super) fn normal<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, resp2: i32) -> R<Flow> {
434 eib_fullword(x.unit(), EIBRESP, 0);
435 eib_fullword(x.unit(), EIBRESP2, resp2);
436 store_int(x, at.resp.resp.as_ref(), 0, at.pos)?;
437 store_int(x, at.resp.resp2.as_ref(), i64::from(resp2), at.pos)?;
438 Ok(Flow::Next)
439}
440
441pub fn raise<'w, P: Copy, O, S>(x: &mut impl CicsHost<'w, P, O, S>, at: &At<P, O, S>, condition: Condition, resp2: i32) -> R<Flow> {
446 let resp = condition.resp();
447 eib_fullword(x.unit(), EIBRESP, resp);
448 eib_fullword(x.unit(), EIBRESP2, resp2);
449 if at.resp.resp.is_some() {
450 store_int(x, at.resp.resp.as_ref(), i64::from(resp), at.pos)?;
451 store_int(x, at.resp.resp2.as_ref(), i64::from(resp2), at.pos)?;
452 return Ok(Flow::Next);
453 }
454 if at.resp.nohandle {
455 return Ok(Flow::Next);
456 }
457 let me = x.activation();
458 let handlers = x.handlers();
459 match handlers.conditions.get(&condition).or_else(|| handlers.conditions.get(&Condition::ERROR)).copied() {
460 Some(Handler::Ignore) => Ok(Flow::Next),
461 Some(Handler::Label { paragraph, owner }) if owner == me => Ok(Flow::GoTo(paragraph)),
462 Some(Handler::Label { .. }) => Err(Abend {
463 code: AbendCode::Cics("APC2".into()),
464 message: format!("EXEC CICS {}: {} was raised; its HANDLE CONDITION label is in a program that is not running there, which CICS cannot branch to", at.name, condition.name()),
465 pos: at.pos,
466 file: None,
467 }),
468 None => Err(Abend {
469 code: AbendCode::Cics(condition.default_abend().into()),
470 message: format!("EXEC CICS {}: {} was raised with no RESP, HANDLE CONDITION or IGNORE CONDITION", at.name, condition.name()),
471 pos: at.pos,
472 file: None,
473 }),
474 }
475}