Skip to main content

ironwork_exec/
lib.rs

1//! ironwork for COBOL: an interpreter that runs a program `ironwork-compile` has checked and laid
2//! out, in EBCDIC with the numeric model of `ironwork-numeric`.
3
4pub mod abend;
5pub use rt::calendar;
6pub use rt::cics;
7pub use rt::codec;
8pub use rt::digest;
9pub use rt::evidence;
10pub use compile::collating;
11pub use compile::declaratives;
12pub mod edit;
13pub use rt::files;
14pub use compile::layout;
15pub mod le;
16pub use rt::lir;
17pub use compile::linage;
18pub mod loader;
19pub mod lower;
20pub mod machine;
21pub use rt::module;
22pub mod oo;
23pub use compile::picture;
24pub mod printer;
25pub mod report;
26use compile::sort;
27pub mod sql;
28pub use rt::strings;
29pub mod terminal;
30#[cfg(test)]
31mod testing;
32pub use rt::tn3270;
33pub mod unit;
34
35pub use compile::{Compiled, EntryPoint, compile, compile_at, compile_time, entry_points};
36pub(crate) use compile::{procedure, section_end};
37pub use machine::{Abend, Ending};
38
39use abend::AbendCode;
40use unit::AddProgram;
41use std::io::{BufRead, Write};
42use syntax::Pos;
43
44/// Running a compiled program, in a run unit of its own or as a CICS task.
45pub trait Execute {
46    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
47
48    /// Runs with the DDs that ASSIGN names map to.
49    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
50
51    /// Runs as the first program of a run unit: CALL finds other programs in `library`, ACCEPT
52    /// reads `sysin`. Returns how the run ended and RETURN-CODE.
53    fn execute<'w>(
54        &self,
55        library: unit::Library,
56        dds: files::Dds,
57        sysin: Option<Box<dyn BufRead + 'w>>,
58        clock: unit::Clock,
59        out: &'w mut dyn Write,
60        err: &'w mut dyn Write,
61    ) -> Result<(Ending, i16), Abend>;
62
63    /// Runs as [`Execute::execute`] does, with EXEC SQL answered by `database`.
64    #[allow(clippy::too_many_arguments)]
65    fn execute_with<'w>(
66        &self,
67        library: unit::Library,
68        dds: files::Dds,
69        sysin: Option<Box<dyn BufRead + 'w>>,
70        clock: unit::Clock,
71        database: Option<&'w mut (dyn sql::Database + '_)>,
72        out: &'w mut dyn Write,
73        err: &'w mut dyn Write,
74    ) -> Result<(Ending, i16), Abend>;
75
76    /// Runs as [`Execute::execute_with`] does, telling `observer` what the run opens, closes and
77    /// loads.
78    #[allow(clippy::too_many_arguments)]
79    fn execute_observed<'w>(
80        &self,
81        library: unit::Library,
82        dds: files::Dds,
83        sysin: Option<Box<dyn BufRead + 'w>>,
84        clock: unit::Clock,
85        database: Option<&'w mut (dyn sql::Database + '_)>,
86        out: &'w mut dyn Write,
87        err: &'w mut dyn Write,
88        observer: Option<unit::Observer<'w>>,
89    ) -> Result<(Ending, i16), Abend>;
90
91    /// Runs as the first program of a CICS task. `task` says who started it, what COMMAREA it
92    /// starts with, and what files and queues it has. Returns how the run ended and the task, with
93    /// RETURN TRANSID and COMMAREA if it ended that way.
94    fn execute_cics<'w>(
95        &self,
96        library: unit::Library,
97        dds: files::Dds,
98        task: cics::Task,
99        clock: unit::Clock,
100        out: &'w mut dyn Write,
101        err: &'w mut dyn Write,
102    ) -> Result<(Ending, cics::Task), Abend>;
103
104    /// A CICS task with a database: SYNCPOINT commits, and the end of the task commits, or rolls
105    /// back after an abend, and closes every cursor. The database outlives the task, so a region's
106    /// tasks can share one.
107    #[allow(clippy::too_many_arguments)]
108    fn execute_cics_with<'w>(
109        &self,
110        library: unit::Library,
111        dds: files::Dds,
112        task: cics::Task,
113        clock: unit::Clock,
114        database: Option<&'w mut (dyn sql::Database + '_)>,
115        out: &'w mut dyn Write,
116        err: &'w mut dyn Write,
117    ) -> Result<(Ending, cics::Task), Abend>;
118
119    /// Runs as [`Execute::execute_cics_with`] does, telling `observer` what the task opens, loads
120    /// and passes to an operation an input could steer.
121    #[allow(clippy::too_many_arguments)]
122    fn execute_cics_observed<'w>(
123        &self,
124        library: unit::Library,
125        dds: files::Dds,
126        task: cics::Task,
127        clock: unit::Clock,
128        database: Option<&'w mut (dyn sql::Database + '_)>,
129        out: &'w mut dyn Write,
130        err: &'w mut dyn Write,
131        observer: Option<unit::Observer<'w>>,
132    ) -> Result<(Ending, cics::Task), Abend>;
133}
134
135impl Execute for Compiled {
136    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
137        self.run_with(files::Dds::default(), out, err)
138    }
139
140    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
141        self.execute(unit::Library::default(), dds, None, unit::Clock::System, out, err).map(|(ending, _)| ending)
142    }
143
144    fn execute<'w>(
145        &self,
146        library: unit::Library,
147        dds: files::Dds,
148        sysin: Option<Box<dyn BufRead + 'w>>,
149        clock: unit::Clock,
150        out: &'w mut dyn Write,
151        err: &'w mut dyn Write,
152    ) -> Result<(Ending, i16), Abend> {
153        self.execute_with(library, dds, sysin, clock, None, out, err)
154    }
155
156    fn execute_with<'w>(
157        &self,
158        library: unit::Library,
159        dds: files::Dds,
160        sysin: Option<Box<dyn BufRead + 'w>>,
161        clock: unit::Clock,
162        database: Option<&'w mut (dyn sql::Database + '_)>,
163        out: &'w mut dyn Write,
164        err: &'w mut dyn Write,
165    ) -> Result<(Ending, i16), Abend> {
166        self.execute_observed(library, dds, sysin, clock, database, out, err, None)
167    }
168
169    fn execute_observed<'w>(
170        &self,
171        library: unit::Library,
172        dds: files::Dds,
173        sysin: Option<Box<dyn BufRead + 'w>>,
174        clock: unit::Clock,
175        database: Option<&'w mut (dyn sql::Database + '_)>,
176        out: &'w mut dyn Write,
177        err: &'w mut dyn Write,
178        observer: Option<unit::Observer<'w>>,
179    ) -> Result<(Ending, i16), Abend> {
180        oo::refuse_to_run(&self.program)?;
181        let mut run_unit = unit::RunUnit::new(library, dds, sysin, clock, out, err);
182        run_unit.observer = observer;
183        run_unit.sql = database.map(sql::Session::new);
184        let me = run_unit.add(None, &self.program, self.layout.size as usize);
185        let ending = machine::Machine::activation(self, me, &mut run_unit, true).and_then(|mut m| m.run_procedure());
186        let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(&self.program.id, ending.is_ok()).map(drop));
187        let closed = run_unit.close_all();
188        let ending = ending?;
189        settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
190        closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
191        Ok((ending, run_unit.return_code()))
192    }
193
194    fn execute_cics<'w>(
195        &self,
196        library: unit::Library,
197        dds: files::Dds,
198        task: cics::Task,
199        clock: unit::Clock,
200        out: &'w mut dyn Write,
201        err: &'w mut dyn Write,
202    ) -> Result<(Ending, cics::Task), Abend> {
203        self.execute_cics_with(library, dds, task, clock, None, out, err)
204    }
205
206    fn execute_cics_with<'w>(
207        &self,
208        library: unit::Library,
209        dds: files::Dds,
210        task: cics::Task,
211        clock: unit::Clock,
212        database: Option<&'w mut (dyn sql::Database + '_)>,
213        out: &'w mut dyn Write,
214        err: &'w mut dyn Write,
215    ) -> Result<(Ending, cics::Task), Abend> {
216        self.execute_cics_observed(library, dds, task, clock, database, out, err, None)
217    }
218
219    fn execute_cics_observed<'w>(
220        &self,
221        library: unit::Library,
222        dds: files::Dds,
223        mut task: cics::Task,
224        clock: unit::Clock,
225        database: Option<&'w mut (dyn sql::Database + '_)>,
226        out: &'w mut dyn Write,
227        err: &'w mut dyn Write,
228        observer: Option<unit::Observer<'w>>,
229    ) -> Result<(Ending, cics::Task), Abend> {
230        oo::refuse_to_run(&self.program)?;
231        let mut run_unit = unit::RunUnit::new(library, dds, None, clock, out, err);
232        run_unit.observer = observer;
233        run_unit.sql = database.map(sql::Session::new);
234        let me = run_unit.add(None, &self.program, self.layout.size as usize);
235        run_unit.eib = run_unit.push_temporary(&[0; cics::EIB_LEN]);
236        let commarea = task.commarea.take();
237        let length = commarea.as_ref().map_or(0, Vec::len);
238        let commarea = commarea.map(|c| run_unit.push_temporary(&c));
239        run_unit.cics = Some(task);
240        let ending = machine::Machine::activation(self, me, &mut run_unit, true).and_then(|mut m| {
241            m.begin_task(commarea, length);
242            m.run_procedure()
243        });
244        let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.end_task(&self.program.id, ending.is_ok()).map(drop));
245        let mut closed = run_unit.close_all();
246        for (name, f) in run_unit.cics_files.drain() {
247            if let Err(e) = f.close() {
248                closed = closed.and(Err(format!("closing CICS file {name}: {e}")));
249            }
250        }
251        let mut task = run_unit.cics.take().unwrap_or_default();
252        if let Err(e) = task.flush_td(self.options.code_page()) {
253            closed = closed.and(Err(format!("writing transient data: {e}")));
254        }
255        let ending = ending.map_err(|a| match a.code {
256            AbendCode::Check(_) | AbendCode::Protection => Abend { message: format!("{} ({}, which CICS reports as ASRA)", a.message, a.code), code: AbendCode::Cics("ASRA".into()), pos: a.pos, file: a.file },
257            _ => a,
258        })?;
259        settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
260        closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
261        Ok((ending, task))
262    }
263}
264
265#[cfg(test)]
266mod tests;