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::operand;
6use crate::cics::{self, CicsHost};
7use rt::bms::Mapset;
8use std::rc::Rc;
9
10use super::cics_bind::has;
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, told to the observer before the command
32    /// runs, so a command ironwork does not carry out yet is still traced. Only a data item is: a
33    /// literal operand is the program's own choice. An operand that cannot be read is left to the
34    /// command to report, so tracing never changes how a run ends.
35    fn cics_sinks(&mut self, block: &ExecBlock) {
36        let command = block.command.as_str();
37        let queue = matches!(command, "WRITEQ" | "READQ" | "DELETEQ") || command.starts_with("WRITEQ ") || command.starts_with("READQ ") || command.starts_with("DELETEQ ");
38        let mut sinks: Vec<(&str, &'static str)> = Vec::new();
39        match command {
40            "LINK" | "XCTL" => sinks.push(("PROGRAM", "cics-dynamic-transfer")),
41            "START" | "START TRANSID" => sinks.push(("TRANSID", "cics-dynamic-transfer")),
42            "READ" | "STARTBR" | "RESETBR" => sinks.push(("RIDFLD", "record-key")),
43            "DELETE" => sinks.push(("RIDFLD", "record-update")),
44            "WRITEQ TD" => sinks.push(("FROM", "log")),
45            "WRITE" if has(block, "OPERATOR") => sinks.push(("TEXT", "log")),
46            "WRITE" if has(block, "JOURNALNAME") || has(block, "JOURNALNUM") => sinks.push(("FROM", "log")),
47            "SEND TEXT" | "SEND MAP" | "SEND" => sinks.push(("FROM", "screen")),
48            "WEB SEND" => sinks.push(("FROM", "web-response")),
49            "WEB WRITE" => sinks.push(("VALUE", "http-header")),
50            "WEB OPEN" => sinks.extend([("HOST", "outbound-host"), ("URL", "outbound-host")]),
51            "WEB CONVERSE" => sinks.extend([("PATH", "outbound-host"), ("FROM", "outbound-http")]),
52            _ => {}
53        }
54        if queue {
55            sinks.extend([("QUEUE", "queue-name"), ("QNAME", "queue-name")]);
56        }
57        sinks.push(("SYSID", "cics-sysid"));
58        for (option, kind) in sinks {
59            let Some(Operand::Ref(r)) = operand(block, option) else { continue };
60            if let Ok(loc) = self.locate(r) {
61                let text = self.page.decode(self.bytes(loc));
62                self.sink(kind, block.pos, &text);
63            }
64        }
65    }
66
67    /// Runs this program as a logical level of the task: an abend that reaches it while its HANDLE
68    /// ABEND exit is active goes to the exit, a LABEL taken as a GO TO from the procedure's start
69    /// and a PROGRAM in place of the rest of this level (C142).
70    pub(crate) fn run_level(&mut self) -> R<Ending> {
71        let mut start = None;
72        loop {
73            let abend = match self.run_from(start) {
74                Err(abend) => abend,
75                done => return done,
76            };
77            match cics::abend_exit(self.unit, &mut self.cics_handlers, &abend) {
78                None => return Err(abend),
79                Some(cics::ExitTarget::Label(p)) => start = Some((p as usize, 0)),
80                Some(cics::ExitTarget::Program(name)) => {
81                    let ending = cics::enter_exit_program(self, &name, abend.pos)?;
82                    return Ok(if ending == Ending::StopRun { ending } else { Ending::Goback });
83                }
84            }
85        }
86    }
87
88    /// Fills the EXEC interface block for the task's first program and binds DFHEIBLK and
89    /// DFHCOMMAREA, the USING items the translator gave it.
90    pub(crate) fn begin_task(&mut self, commarea: Option<usize>, length: usize) {
91        cics::begin_task(self.unit, self.page, length);
92        let eib = self.unit.eib;
93        self.bind(&[Some(eib), commarea]);
94    }
95
96    /// A data item a name alone refers to, as the symbolic map's `mapI` and `mapO` are found.
97    fn named(name: &str, pos: Pos) -> Ref {
98        Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos }
99    }
100}
101
102impl<'a, 'w> CicsHost<'w, &'a Ref, &'a Operand, &'a str> for Machine<'_, '_, 'w> {
103    type Program = Rc<Compiled>;
104    type Loader = crate::unit::Library;
105
106    fn unit(&mut self) -> &mut RunUnit<'w> {
107        self.unit
108    }
109
110    fn handlers(&mut self) -> &mut Handlers {
111        &mut self.cics_handlers
112    }
113
114    fn content(&mut self, operand: &&'a Operand, pos: Pos) -> R<Vec<u8>> {
115        self.content_argument(operand, pos)
116    }
117
118    fn integer_of(&mut self, operand: &&'a Operand, pos: Pos) -> R<i64> {
119        Machine::integer(self, &Expr::Operand((*operand).clone()), pos)
120    }
121
122    fn text(&self, text: &&'a str) -> String {
123        (*text).to_owned()
124    }
125
126    fn main(&self) -> bool {
127        self.main
128    }
129
130    fn program_id(&self) -> String {
131        self.program.id.clone()
132    }
133
134    fn commarea(&self) -> Option<usize> {
135        let ordinal = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some("DFHCOMMAREA"));
136        ordinal.and_then(|o| self.linkage[o])
137    }
138
139    fn mapset(&mut self, name: &str) -> Option<Result<Mapset, String>> {
140        syntax::bms::find_mapset(&self.unit.library.copy, name).map(|found| found.map_err(|e| e.message))
141    }
142
143    fn item_named(&mut self, name: &str, pos: Pos) -> R<Option<Loc>> {
144        let r = Self::named(name, pos);
145        match self.resolve(&r) {
146            Ok(Resolved::Item(_)) => self.locate(&r).map(Some),
147            _ => Ok(None),
148        }
149    }
150
151    fn locate_named(&mut self, name: &str, pos: Pos) -> R<Loc> {
152        self.locate(&Self::named(name, pos))
153    }
154
155    fn run_program(&mut self, program: Rc<Compiled>, index: usize, commarea: Option<usize>, xctl: bool) -> R<Ending> {
156        let ending = Machine::activation(&program, index, &mut *self.unit, self.main && xctl).and_then(|mut callee| {
157            let eib = callee.unit.eib;
158            callee.bind(&[Some(eib), commarea]);
159            callee.run_level()
160        });
161        ending.map_err(|a| self.in_loaded(index, &program, a))
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use crate::cics::{EIBAID, EIBCALEN, EIBCPOSN, EIBDATE, EIBFN, EIBRESP, EIBRESP2, EIBRSRCE, EIBTASKN, EIBTIME, EIBTRMID, EIBTRNID};
168
169    #[test]
170    fn eib_offsets_match_the_dfheiblk_layout() {
171        let source = "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. EIBT.\n       PROCEDURE DIVISION.\n           EXEC CICS RETURN END-EXEC.\n";
172        let program = syntax::parse_all_with(source, &syntax::copy::Libraries::default()).unwrap().remove(0);
173        let compiled = crate::compile(program, &[]).unwrap_or_else(|e| panic!("{e:?}"));
174        let offset = |name: &str| compiled.layout.items.iter().find(|i| i.name.as_deref() == Some(name)).map(|i| i.offset as usize);
175        let fields = [
176            ("EIBTIME", EIBTIME),
177            ("EIBDATE", EIBDATE),
178            ("EIBTRNID", EIBTRNID),
179            ("EIBTASKN", EIBTASKN),
180            ("EIBTRMID", EIBTRMID),
181            ("EIBCPOSN", EIBCPOSN),
182            ("EIBCALEN", EIBCALEN),
183            ("EIBAID", EIBAID),
184            ("EIBFN", EIBFN),
185            ("EIBRSRCE", EIBRSRCE),
186            ("EIBRESP", EIBRESP),
187            ("EIBRESP2", EIBRESP2),
188        ];
189        for (name, at) in fields {
190            assert_eq!(offset(name), Some(at), "{name}");
191        }
192    }
193}