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;
34pub mod vm;
35
36pub use compile::{Compiled, EntryPoint, compile, compile_at, compile_time, entry_points};
37pub(crate) use compile::{procedure, procedure_from, section_end};
38pub use machine::{Abend, Ending};
39
40use abend::AbendCode;
41use unit::AddProgram;
42use std::io::{BufRead, Write};
43use syntax::Pos;
44
45/// Running a compiled program, in a run unit of its own or as a CICS task.
46pub trait Execute {
47    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
48
49    /// Runs with the DDs that ASSIGN names map to.
50    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
51
52    /// Runs as the first program of a run unit: CALL finds other programs in `library`, ACCEPT
53    /// reads `sysin`. Returns how the run ended and RETURN-CODE.
54    fn execute<'w>(
55        &self,
56        library: unit::Library,
57        dds: files::Dds,
58        sysin: Option<Box<dyn BufRead + 'w>>,
59        clock: unit::Clock,
60        out: &'w mut dyn Write,
61        err: &'w mut dyn Write,
62    ) -> Result<(Ending, i16), Abend>;
63
64    /// Runs as [`Execute::execute`] does, with EXEC SQL answered by `database`.
65    #[allow(clippy::too_many_arguments)]
66    fn execute_with<'w>(
67        &self,
68        library: unit::Library,
69        dds: files::Dds,
70        sysin: Option<Box<dyn BufRead + 'w>>,
71        clock: unit::Clock,
72        database: Option<&'w mut (dyn sql::Database + '_)>,
73        out: &'w mut dyn Write,
74        err: &'w mut dyn Write,
75    ) -> Result<(Ending, i16), Abend>;
76
77    /// Runs as [`Execute::execute_with`] does, telling `observer` what the run opens, closes and
78    /// loads.
79    #[allow(clippy::too_many_arguments)]
80    fn execute_observed<'w>(
81        &self,
82        library: unit::Library,
83        dds: files::Dds,
84        sysin: Option<Box<dyn BufRead + 'w>>,
85        clock: unit::Clock,
86        database: Option<&'w mut (dyn sql::Database + '_)>,
87        out: &'w mut dyn Write,
88        err: &'w mut dyn Write,
89        observer: Option<unit::Observer<'w>>,
90    ) -> Result<(Ending, i16), Abend>;
91
92    /// Runs as [`Execute::execute_observed`] does, as the main program of a job step that EXEC
93    /// PGM= started with `parm`: the first PROCEDURE DIVISION USING item addresses a halfword
94    /// length and the program arguments Language Environment finds in it.
95    #[allow(clippy::too_many_arguments)]
96    fn execute_main<'w>(
97        &self,
98        library: unit::Library,
99        dds: files::Dds,
100        sysin: Option<Box<dyn BufRead + 'w>>,
101        clock: unit::Clock,
102        database: Option<&'w mut (dyn sql::Database + '_)>,
103        out: &'w mut dyn Write,
104        err: &'w mut dyn Write,
105        observer: Option<unit::Observer<'w>>,
106        parm: &str,
107    ) -> Result<(Ending, i16), Abend>;
108
109    /// Runs as [`Execute::execute_observed`] does, and puts what the run left in its run unit in
110    /// `kept`.
111    #[allow(clippy::too_many_arguments)]
112    fn execute_kept<'w>(
113        &self,
114        library: unit::Library,
115        dds: files::Dds,
116        sysin: Option<Box<dyn BufRead + 'w>>,
117        clock: unit::Clock,
118        database: Option<&'w mut (dyn sql::Database + '_)>,
119        out: &'w mut dyn Write,
120        err: &'w mut dyn Write,
121        observer: Option<unit::Observer<'w>>,
122        kept: &mut Option<unit::Remains>,
123    ) -> Result<(Ending, i16), Abend>;
124
125    /// Runs as the first program of a CICS task. `task` says who started it, what COMMAREA it
126    /// starts with, and what files and queues it has. Returns how the run ended and the task, with
127    /// RETURN TRANSID and COMMAREA if it ended that way.
128    fn execute_cics<'w>(
129        &self,
130        library: unit::Library,
131        dds: files::Dds,
132        task: cics::Task,
133        clock: unit::Clock,
134        out: &'w mut dyn Write,
135        err: &'w mut dyn Write,
136    ) -> Result<(Ending, cics::Task), Abend>;
137
138    /// A CICS task with a database: SYNCPOINT commits, and the end of the task commits, or rolls
139    /// back after an abend, and closes every cursor. The database outlives the task, so a region's
140    /// tasks can share one.
141    #[allow(clippy::too_many_arguments)]
142    fn execute_cics_with<'w>(
143        &self,
144        library: unit::Library,
145        dds: files::Dds,
146        task: cics::Task,
147        clock: unit::Clock,
148        database: Option<&'w mut (dyn sql::Database + '_)>,
149        out: &'w mut dyn Write,
150        err: &'w mut dyn Write,
151    ) -> Result<(Ending, cics::Task), Abend>;
152
153    /// Runs as [`Execute::execute_cics_with`] does, telling `observer` what the task opens, loads
154    /// and passes to an operation an input could steer.
155    #[allow(clippy::too_many_arguments)]
156    fn execute_cics_observed<'w>(
157        &self,
158        library: unit::Library,
159        dds: files::Dds,
160        task: cics::Task,
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        observer: Option<unit::Observer<'w>>,
166    ) -> Result<(Ending, cics::Task), Abend>;
167}
168
169impl Execute for Compiled {
170    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
171        self.run_with(files::Dds::default(), out, err)
172    }
173
174    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
175        self.execute(unit::Library::default(), dds, None, unit::Clock::System, out, err).map(|(ending, _)| ending)
176    }
177
178    fn execute<'w>(
179        &self,
180        library: unit::Library,
181        dds: files::Dds,
182        sysin: Option<Box<dyn BufRead + 'w>>,
183        clock: unit::Clock,
184        out: &'w mut dyn Write,
185        err: &'w mut dyn Write,
186    ) -> Result<(Ending, i16), Abend> {
187        self.execute_with(library, dds, sysin, clock, None, out, err)
188    }
189
190    fn execute_with<'w>(
191        &self,
192        library: unit::Library,
193        dds: files::Dds,
194        sysin: Option<Box<dyn BufRead + 'w>>,
195        clock: unit::Clock,
196        database: Option<&'w mut (dyn sql::Database + '_)>,
197        out: &'w mut dyn Write,
198        err: &'w mut dyn Write,
199    ) -> Result<(Ending, i16), Abend> {
200        self.execute_observed(library, dds, sysin, clock, database, out, err, None)
201    }
202
203    fn execute_observed<'w>(
204        &self,
205        library: unit::Library,
206        dds: files::Dds,
207        sysin: Option<Box<dyn BufRead + 'w>>,
208        clock: unit::Clock,
209        database: Option<&'w mut (dyn sql::Database + '_)>,
210        out: &'w mut dyn Write,
211        err: &'w mut dyn Write,
212        observer: Option<unit::Observer<'w>>,
213    ) -> Result<(Ending, i16), Abend> {
214        run_main(self, library, dds, sysin, clock, database, out, err, observer, None, &mut None)
215    }
216
217    fn execute_main<'w>(
218        &self,
219        library: unit::Library,
220        dds: files::Dds,
221        sysin: Option<Box<dyn BufRead + 'w>>,
222        clock: unit::Clock,
223        database: Option<&'w mut (dyn sql::Database + '_)>,
224        out: &'w mut dyn Write,
225        err: &'w mut dyn Write,
226        observer: Option<unit::Observer<'w>>,
227        parm: &str,
228    ) -> Result<(Ending, i16), Abend> {
229        run_main(self, library, dds, sysin, clock, database, out, err, observer, Some(parm), &mut None)
230    }
231
232    fn execute_kept<'w>(
233        &self,
234        library: unit::Library,
235        dds: files::Dds,
236        sysin: Option<Box<dyn BufRead + 'w>>,
237        clock: unit::Clock,
238        database: Option<&'w mut (dyn sql::Database + '_)>,
239        out: &'w mut dyn Write,
240        err: &'w mut dyn Write,
241        observer: Option<unit::Observer<'w>>,
242        kept: &mut Option<unit::Remains>,
243    ) -> Result<(Ending, i16), Abend> {
244        run_main(self, library, dds, sysin, clock, database, out, err, observer, None, kept)
245    }
246
247    fn execute_cics<'w>(
248        &self,
249        library: unit::Library,
250        dds: files::Dds,
251        task: cics::Task,
252        clock: unit::Clock,
253        out: &'w mut dyn Write,
254        err: &'w mut dyn Write,
255    ) -> Result<(Ending, cics::Task), Abend> {
256        self.execute_cics_with(library, dds, task, clock, None, out, err)
257    }
258
259    fn execute_cics_with<'w>(
260        &self,
261        library: unit::Library,
262        dds: files::Dds,
263        task: cics::Task,
264        clock: unit::Clock,
265        database: Option<&'w mut (dyn sql::Database + '_)>,
266        out: &'w mut dyn Write,
267        err: &'w mut dyn Write,
268    ) -> Result<(Ending, cics::Task), Abend> {
269        self.execute_cics_observed(library, dds, task, clock, database, out, err, None)
270    }
271
272    fn execute_cics_observed<'w>(
273        &self,
274        library: unit::Library,
275        dds: files::Dds,
276        mut task: cics::Task,
277        clock: unit::Clock,
278        database: Option<&'w mut (dyn sql::Database + '_)>,
279        out: &'w mut dyn Write,
280        err: &'w mut dyn Write,
281        observer: Option<unit::Observer<'w>>,
282    ) -> Result<(Ending, cics::Task), Abend> {
283        oo::refuse_to_run(&self.program)?;
284        let mut run_unit = unit::RunUnit::new(library, dds, None, clock, out, err);
285        run_unit.observer = observer;
286        run_unit.sql = database.map(sql::Session::new);
287        let me = run_unit.add(None, &self.program, self.layout.size as usize);
288        run_unit.eib = run_unit.push_temporary(&[0; cics::EIB_LEN]);
289        let commarea = task.commarea.take();
290        let length = commarea.as_ref().map_or(0, Vec::len);
291        let commarea = commarea.map(|c| run_unit.push_temporary(&c));
292        run_unit.cics = Some(task);
293        let ending = machine::Machine::activation(self, me, &mut run_unit, true).and_then(|mut m| {
294            m.begin_task(commarea, length);
295            m.run_level()
296        });
297        let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.end_task(&self.program.id, ending.is_ok()).map(drop));
298        let mut closed = run_unit.close_all();
299        for (name, f) in run_unit.cics_files.drain() {
300            if let Err(e) = f.close() {
301                closed = closed.and(Err(format!("closing CICS file {name}: {e}")));
302            }
303        }
304        let mut task = run_unit.cics.take().unwrap_or_default();
305        if let Err(e) = task.flush_td(self.options.code_page()) {
306            closed = closed.and(Err(format!("writing transient data: {e}")));
307        }
308        let ending = ending.map_err(|a| match a.code {
309            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 },
310            _ => a,
311        })?;
312        settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
313        closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
314        Ok((ending, task))
315    }
316}
317
318/// Runs `compiled` as the first program of a batch run unit; with `parm`, as a job step's main
319/// program.
320#[allow(clippy::too_many_arguments)]
321fn run_main<'w>(
322    compiled: &Compiled,
323    library: unit::Library,
324    dds: files::Dds,
325    sysin: Option<Box<dyn BufRead + 'w>>,
326    clock: unit::Clock,
327    database: Option<&'w mut (dyn sql::Database + '_)>,
328    out: &'w mut dyn Write,
329    err: &'w mut dyn Write,
330    observer: Option<unit::Observer<'w>>,
331    parm: Option<&str>,
332    kept: &mut Option<unit::Remains>,
333) -> Result<(Ending, i16), Abend> {
334    oo::refuse_to_run(&compiled.program)?;
335    let statements = library.trace_statements.clone();
336    let mut run_unit = unit::RunUnit::new(library, dds, sysin, clock, out, err);
337    run_unit.observer = observer;
338    run_unit.statements = statements;
339    run_unit.sql = database.map(sql::Session::new);
340    let me = run_unit.add(None, &compiled.program, compiled.layout.size as usize);
341    let parm = parm.map(|p| run_unit.push_temporary(&rt::le::parm::parameter_area(rt::le::parm::program_arguments(p), compiled.options.code_page())));
342    let ending = machine::Machine::activation(compiled, me, &mut run_unit, true).and_then(|mut m| {
343        if parm.is_some() {
344            m.bind(&[parm]);
345        }
346        m.run_procedure()
347    });
348    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(&compiled.program.id, ending.is_ok()).map(drop));
349    let closed = run_unit.close_all();
350    *kept = Some(unit::Remains::of(&run_unit));
351    let ending = ending?;
352    settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
353    closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
354    Ok((ending, run_unit.return_code()))
355}
356
357#[cfg(test)]
358mod tests;