Skip to main content

ironwork_exec/machine/
cics.rs

1//! EXEC CICS in the walker: a block is bound into a `CicsCommand` (cics_bind.rs) and run by
2//! `rt::cics`, which asks the walker what `CicsHost` names.
3
4use super::*;
5use super::cics_bind;
6use crate::cics::{self, CicsHost};
7use rt::bms::Mapset;
8use rt::unit::Loader;
9use std::rc::Rc;
10
11pub(super) use crate::cics::Handlers;
12
13fn flow(f: cics::Flow) -> Flow {
14    match f {
15        cics::Flow::Next => Flow::Next,
16        cics::Flow::GoTo(p) => Flow::GoTo(p as usize),
17        cics::Flow::End(e) => Flow::End(e),
18    }
19}
20
21impl<'p> Machine<'p, '_, '_> {
22    pub(super) fn cics(&mut self, block: &'p ExecBlock) -> R<Flow> {
23        cics::in_task(self.unit, &block.command, block.pos)?;
24        if self.unit.observed() {
25            self.cics_sinks(block);
26        }
27        let command = self.bind_cics(block)?;
28        cics::run(self, &command, block.pos).map(flow)
29    }
30
31    /// The operands of a command an input could steer (`cics_bind::sinks`), told to the observer
32    /// before the command runs. An operand that cannot be read is left to the command to report,
33    /// so tracing never changes how a run ends.
34    fn cics_sinks(&mut self, block: &ExecBlock) {
35        for (r, sink) in cics_bind::sinks(block) {
36            if let Ok(loc) = self.locate(r) {
37                let text = self.page.decode(store::bytes(&self.unit.mem, loc));
38                self.sink(sink.kind(), block.pos, &text);
39            }
40        }
41    }
42
43    /// Runs this program as a logical level of the task: an abend that reaches it while its HANDLE
44    /// ABEND exit is active goes to the exit, a LABEL taken as a GO TO at the HANDLE ABEND command
45    /// with the PERFORMs the abend left still armed (C236), and a PROGRAM in place of the rest of
46    /// this level (C142).
47    pub(crate) fn run_level(&mut self) -> R<Ending> {
48        self.run_taking_exits(None, true)
49    }
50
51    /// Runs a CALLed program, which is at its caller's logical level: an abend that reaches it
52    /// takes the level's exit only when that is a LABEL (C238).
53    pub(super) fn run_called(&mut self, at: Option<(usize, usize)>) -> R<Ending> {
54        self.run_taking_exits(at, false)
55    }
56
57    fn run_taking_exits(&mut self, at: Option<(usize, usize)>, runs_level: bool) -> R<Ending> {
58        let mut ending = self.run_from(at);
59        loop {
60            let abend = match ending {
61                Err(abend) => abend,
62                done => return done,
63            };
64            match cics::abend_exit(self.unit, &mut self.cics_handlers, &abend, self.serial, runs_level)? {
65                None => return Err(abend),
66                Some(cics::ExitTarget::Label { paragraph, at, .. }) => ending = self.go_to(paragraph as usize, at),
67                Some(cics::ExitTarget::Program { name, commarea }) => {
68                    let ending = cics::enter_exit_program(self, &name, commarea, abend.pos)?;
69                    return Ok(if ending == Ending::StopRun { ending } else { Ending::Goback });
70                }
71            }
72        }
73    }
74
75    /// Fills the EXEC interface block for the task's first program and binds DFHEIBLK and
76    /// DFHCOMMAREA, the USING items the translator gave it.
77    pub(crate) fn begin_task(&mut self, commarea: Option<usize>, length: usize) {
78        cics::begin_task(self.unit, self.page, length);
79        let eib = self.unit.eib;
80        self.bind(&[Some(eib), commarea]);
81    }
82
83    /// A data item a name alone refers to, as the symbolic map's `mapI` and `mapO` are found.
84    fn named(name: &str, pos: Pos) -> Ref {
85        Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos }
86    }
87}
88
89impl<'a, 'w> CicsHost<'w, &'a Ref, &'a Operand, &'a str> for Machine<'_, '_, 'w> {
90    fn handlers(&mut self) -> &mut Handlers {
91        &mut self.cics_handlers
92    }
93
94    fn content(&mut self, operand: &&'a Operand, pos: Pos) -> R<Vec<u8>> {
95        callee::content(self, &super::facts::chars(operand), pos)
96    }
97
98    fn integer_of(&mut self, operand: &&'a Operand, pos: Pos) -> R<i64> {
99        Machine::integer(self, &Expr::Operand((*operand).clone()), pos)
100    }
101
102    fn text(&self, text: &&'a str) -> String {
103        (*text).to_owned()
104    }
105
106    fn activation(&self) -> u64 {
107        self.serial
108    }
109
110    fn program_id(&self) -> String {
111        self.program.id.clone()
112    }
113
114    fn commarea(&self) -> Option<usize> {
115        let ordinal = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some("DFHCOMMAREA"));
116        ordinal.and_then(|o| self.linkage[o])
117    }
118
119    fn mapset(&mut self, name: &str) -> Option<Result<Mapset, String>> {
120        self.unit.library.mapset(name)
121    }
122
123    fn item_named(&mut self, name: &str, pos: Pos) -> R<Option<Loc>> {
124        let r = Self::named(name, pos);
125        match self.resolve(&r) {
126            Ok(Resolved::Item(_)) => self.locate(&r).map(Some),
127            _ => Ok(None),
128        }
129    }
130
131    fn locate_named(&mut self, name: &str, pos: Pos) -> R<Loc> {
132        self.locate(&Self::named(name, pos))
133    }
134
135    fn unaddressed(&mut self, place: &'a Ref) -> Option<usize> {
136        let Ok(Resolved::Item(i)) = self.resolve(place) else { return None };
137        let item = &self.layout.items[i];
138        item.linkage.filter(|&l| self.linkage[l as usize].is_none()).map(|_| item.size as usize)
139    }
140
141    fn run_program(&mut self, program: Option<Rc<Compiled>>, index: usize, commarea: Option<usize>, xctl: bool) -> R<Ending> {
142        let first = self.first;
143        let Some(compiled) = program.as_deref().or(first) else {
144            return Err(Abend::ironwork("the CICS task's first program cannot be LINKed or XCTLed to from a function or a method", Pos::default()));
145        };
146        let handlers = if xctl { self.cics_handlers.xctl() } else { Handlers::default() };
147        Machine::activation(compiled, index, &mut *self.unit, self.main && xctl).and_then(|mut callee| {
148            let eib = callee.unit.eib;
149            callee.bind(&[Some(eib), commarea]);
150            (callee.cics_handlers, callee.first) = (handlers, first);
151            callee.run_level()
152        })
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use crate::cics::{EIBAID, EIBCALEN, EIBCPOSN, EIBDATE, EIBFN, EIBRESP, EIBRESP2, EIBRSRCE, EIBTASKN, EIBTIME, EIBTRMID, EIBTRNID};
159
160    #[test]
161    fn eib_offsets_match_the_dfheiblk_layout() {
162        let source = "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. EIBT.\n       PROCEDURE DIVISION.\n           EXEC CICS RETURN END-EXEC.\n";
163        let program = syntax::parse_all_with(source, &syntax::copy::Libraries::default()).unwrap().remove(0);
164        let compiled = crate::compile(program, &[]).unwrap_or_else(|e| panic!("{e:?}"));
165        let offset = |name: &str| compiled.layout.items.iter().find(|i| i.name.as_deref() == Some(name)).map(|i| i.offset as usize);
166        let fields = [
167            ("EIBTIME", EIBTIME),
168            ("EIBDATE", EIBDATE),
169            ("EIBTRNID", EIBTRNID),
170            ("EIBTASKN", EIBTASKN),
171            ("EIBTRMID", EIBTRMID),
172            ("EIBCPOSN", EIBCPOSN),
173            ("EIBCALEN", EIBCALEN),
174            ("EIBAID", EIBAID),
175            ("EIBFN", EIBFN),
176            ("EIBRSRCE", EIBRSRCE),
177            ("EIBRESP", EIBRESP),
178            ("EIBRESP2", EIBRESP2),
179        ];
180        for (name, at) in fields {
181            assert_eq!(offset(name), Some(at), "{name}");
182        }
183    }
184}