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    /// Fills the EXEC interface block for the task's first program and binds DFHEIBLK and
68    /// DFHCOMMAREA, the USING items the translator gave it.
69    pub(crate) fn begin_task(&mut self, commarea: Option<usize>, length: usize) {
70        cics::begin_task(self.unit, self.page, length);
71        let eib = self.unit.eib;
72        self.bind(&[Some(eib), commarea]);
73    }
74
75    /// A data item a name alone refers to, as the symbolic map's `mapI` and `mapO` are found.
76    fn named(name: &str, pos: Pos) -> Ref {
77        Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos }
78    }
79}
80
81impl<'a, 'w> CicsHost<'w, &'a Ref, &'a Operand, &'a str> for Machine<'_, '_, 'w> {
82    type Program = Rc<Compiled>;
83    type Loader = crate::unit::Library;
84
85    fn unit(&mut self) -> &mut RunUnit<'w> {
86        self.unit
87    }
88
89    fn handlers(&mut self) -> &mut Handlers {
90        &mut self.cics_handlers
91    }
92
93    fn content(&mut self, operand: &&'a Operand, pos: Pos) -> R<Vec<u8>> {
94        self.content_argument(operand, pos)
95    }
96
97    fn integer_of(&mut self, operand: &&'a Operand, pos: Pos) -> R<i64> {
98        Machine::integer(self, &Expr::Operand((*operand).clone()), pos)
99    }
100
101    fn text(&self, text: &&'a str) -> String {
102        (*text).to_owned()
103    }
104
105    fn main(&self) -> bool {
106        self.main
107    }
108
109    fn program_id(&self) -> String {
110        self.program.id.clone()
111    }
112
113    fn commarea(&self) -> Option<usize> {
114        let ordinal = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some("DFHCOMMAREA"));
115        ordinal.and_then(|o| self.linkage[o])
116    }
117
118    fn mapset(&mut self, name: &str) -> Option<Result<Mapset, String>> {
119        syntax::bms::find_mapset(&self.unit.library.copy, name).map(|found| found.map_err(|e| e.message))
120    }
121
122    fn item_named(&mut self, name: &str, pos: Pos) -> R<Option<Loc>> {
123        let r = Self::named(name, pos);
124        match self.resolve(&r) {
125            Ok(Resolved::Item(_)) => self.locate(&r).map(Some),
126            _ => Ok(None),
127        }
128    }
129
130    fn locate_named(&mut self, name: &str, pos: Pos) -> R<Loc> {
131        self.locate(&Self::named(name, pos))
132    }
133
134    fn run_program(&mut self, program: Rc<Compiled>, index: usize, commarea: Option<usize>, xctl: bool) -> R<Ending> {
135        let ending = Machine::activation(&program, index, &mut *self.unit, self.main && xctl).and_then(|mut callee| {
136            let eib = callee.unit.eib;
137            callee.bind(&[Some(eib), commarea]);
138            callee.run_procedure()
139        });
140        ending.map_err(|a| self.in_loaded(index, &program, a))
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use crate::cics::{EIBAID, EIBCALEN, EIBCPOSN, EIBDATE, EIBFN, EIBRESP, EIBRESP2, EIBRSRCE, EIBTASKN, EIBTIME, EIBTRMID, EIBTRNID};
147
148    #[test]
149    fn eib_offsets_match_the_dfheiblk_layout() {
150        let source = "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. EIBT.\n       PROCEDURE DIVISION.\n           EXEC CICS RETURN END-EXEC.\n";
151        let program = syntax::parse_all_with(source, &syntax::copy::Libraries::default()).unwrap().remove(0);
152        let compiled = crate::compile(program, &[]).unwrap_or_else(|e| panic!("{e:?}"));
153        let offset = |name: &str| compiled.layout.items.iter().find(|i| i.name.as_deref() == Some(name)).map(|i| i.offset as usize);
154        let fields = [
155            ("EIBTIME", EIBTIME),
156            ("EIBDATE", EIBDATE),
157            ("EIBTRNID", EIBTRNID),
158            ("EIBTASKN", EIBTASKN),
159            ("EIBTRMID", EIBTRMID),
160            ("EIBCPOSN", EIBCPOSN),
161            ("EIBCALEN", EIBCALEN),
162            ("EIBAID", EIBAID),
163            ("EIBFN", EIBFN),
164            ("EIBRSRCE", EIBRSRCE),
165            ("EIBRESP", EIBRESP),
166            ("EIBRESP2", EIBRESP2),
167        ];
168        for (name, at) in fields {
169            assert_eq!(offset(name), Some(at), "{name}");
170        }
171    }
172}