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::digest;
8pub use rt::evidence;
9pub use compile::collating;
10pub use compile::constructs;
11pub use compile::declaratives;
12#[cfg(test)]
13mod edit;
14pub use rt::files;
15pub use compile::layout;
16pub mod le;
17pub use rt::lir;
18pub mod loader;
19pub use compile::lower;
20pub mod machine;
21pub use rt::module;
22pub use rt::refusal;
23pub mod oo;
24pub use compile::picture;
25pub mod printer;
26pub mod report;
27use compile::sort;
28pub mod sql;
29pub mod terminal;
30#[cfg(test)]
31mod testing;
32pub use rt::tn3270;
33pub mod unit;
34pub mod vm;
35
36pub use compile::{Compiled, compile, compile_at, compile_time, entry_points, read_lengths, variable_records};
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, as a subprogram whose caller passed `arguments`,
110    /// one per PROCEDURE DIVISION USING item: the bytes of the item passed, or None for OMITTED,
111    /// which passes a null address. Each argument is input to the run, and EXIT PROGRAM returns.
112    /// `kept` takes what the run left in its run unit, with each argument as the caller sees it
113    /// after the CALL.
114    #[allow(clippy::too_many_arguments)]
115    fn execute_with_arguments<'w>(
116        &self,
117        library: unit::Library,
118        dds: files::Dds,
119        sysin: Option<Box<dyn BufRead + 'w>>,
120        clock: unit::Clock,
121        database: Option<&'w mut (dyn sql::Database + '_)>,
122        out: &'w mut dyn Write,
123        err: &'w mut dyn Write,
124        observer: Option<unit::Observer<'w>>,
125        arguments: &[Option<Vec<u8>>],
126        kept: &mut Option<unit::Remains>,
127    ) -> Result<(Ending, i16), Abend>;
128
129    /// Runs as [`Execute::execute_observed`] does, and puts what the run left in its run unit in
130    /// `kept`.
131    #[allow(clippy::too_many_arguments)]
132    fn execute_kept<'w>(
133        &self,
134        library: unit::Library,
135        dds: files::Dds,
136        sysin: Option<Box<dyn BufRead + 'w>>,
137        clock: unit::Clock,
138        database: Option<&'w mut (dyn sql::Database + '_)>,
139        out: &'w mut dyn Write,
140        err: &'w mut dyn Write,
141        observer: Option<unit::Observer<'w>>,
142        kept: &mut Option<unit::Remains>,
143    ) -> Result<(Ending, i16), Abend>;
144
145    /// Runs as the first program of a CICS task. `task` says who started it, what COMMAREA it
146    /// starts with, and what files and queues it has. Returns how the run ended and the task, with
147    /// RETURN TRANSID and COMMAREA if it ended that way.
148    fn execute_cics<'w>(
149        &self,
150        library: unit::Library,
151        dds: files::Dds,
152        task: cics::Task,
153        clock: unit::Clock,
154        out: &'w mut dyn Write,
155        err: &'w mut dyn Write,
156    ) -> Result<(Ending, cics::Task), Abend>;
157
158    /// A CICS task with a database: SYNCPOINT commits, and the end of the task commits, or rolls
159    /// back after an abend, and closes every cursor. The database outlives the task, so a region's
160    /// tasks can share one.
161    #[allow(clippy::too_many_arguments)]
162    fn execute_cics_with<'w>(
163        &self,
164        library: unit::Library,
165        dds: files::Dds,
166        task: cics::Task,
167        clock: unit::Clock,
168        database: Option<&'w mut (dyn sql::Database + '_)>,
169        out: &'w mut dyn Write,
170        err: &'w mut dyn Write,
171    ) -> Result<(Ending, cics::Task), Abend>;
172
173    /// Runs as [`Execute::execute_cics_with`] does, telling `observer` what the task opens, loads
174    /// and passes to an operation an input could steer.
175    #[allow(clippy::too_many_arguments)]
176    fn execute_cics_observed<'w>(
177        &self,
178        library: unit::Library,
179        dds: files::Dds,
180        task: cics::Task,
181        clock: unit::Clock,
182        database: Option<&'w mut (dyn sql::Database + '_)>,
183        out: &'w mut dyn Write,
184        err: &'w mut dyn Write,
185        observer: Option<unit::Observer<'w>>,
186    ) -> Result<(Ending, cics::Task), Abend>;
187}
188
189impl Execute for Compiled {
190    fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
191        self.run_with(files::Dds::default(), out, err)
192    }
193
194    fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
195        self.execute(unit::Library::default(), dds, None, unit::Clock::System, out, err).map(|(ending, _)| ending)
196    }
197
198    fn execute<'w>(
199        &self,
200        library: unit::Library,
201        dds: files::Dds,
202        sysin: Option<Box<dyn BufRead + 'w>>,
203        clock: unit::Clock,
204        out: &'w mut dyn Write,
205        err: &'w mut dyn Write,
206    ) -> Result<(Ending, i16), Abend> {
207        self.execute_with(library, dds, sysin, clock, None, out, err)
208    }
209
210    fn execute_with<'w>(
211        &self,
212        library: unit::Library,
213        dds: files::Dds,
214        sysin: Option<Box<dyn BufRead + 'w>>,
215        clock: unit::Clock,
216        database: Option<&'w mut (dyn sql::Database + '_)>,
217        out: &'w mut dyn Write,
218        err: &'w mut dyn Write,
219    ) -> Result<(Ending, i16), Abend> {
220        self.execute_observed(library, dds, sysin, clock, database, out, err, None)
221    }
222
223    fn execute_observed<'w>(
224        &self,
225        library: unit::Library,
226        dds: files::Dds,
227        sysin: Option<Box<dyn BufRead + 'w>>,
228        clock: unit::Clock,
229        database: Option<&'w mut (dyn sql::Database + '_)>,
230        out: &'w mut dyn Write,
231        err: &'w mut dyn Write,
232        observer: Option<unit::Observer<'w>>,
233    ) -> Result<(Ending, i16), Abend> {
234        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Nothing, &mut None)
235    }
236
237    fn execute_main<'w>(
238        &self,
239        library: unit::Library,
240        dds: files::Dds,
241        sysin: Option<Box<dyn BufRead + 'w>>,
242        clock: unit::Clock,
243        database: Option<&'w mut (dyn sql::Database + '_)>,
244        out: &'w mut dyn Write,
245        err: &'w mut dyn Write,
246        observer: Option<unit::Observer<'w>>,
247        parm: &str,
248    ) -> Result<(Ending, i16), Abend> {
249        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Parm(parm), &mut None)
250    }
251
252    fn execute_with_arguments<'w>(
253        &self,
254        library: unit::Library,
255        dds: files::Dds,
256        sysin: Option<Box<dyn BufRead + 'w>>,
257        clock: unit::Clock,
258        database: Option<&'w mut (dyn sql::Database + '_)>,
259        out: &'w mut dyn Write,
260        err: &'w mut dyn Write,
261        observer: Option<unit::Observer<'w>>,
262        arguments: &[Option<Vec<u8>>],
263        kept: &mut Option<unit::Remains>,
264    ) -> Result<(Ending, i16), Abend> {
265        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Arguments(arguments), kept)
266    }
267
268    fn execute_kept<'w>(
269        &self,
270        library: unit::Library,
271        dds: files::Dds,
272        sysin: Option<Box<dyn BufRead + 'w>>,
273        clock: unit::Clock,
274        database: Option<&'w mut (dyn sql::Database + '_)>,
275        out: &'w mut dyn Write,
276        err: &'w mut dyn Write,
277        observer: Option<unit::Observer<'w>>,
278        kept: &mut Option<unit::Remains>,
279    ) -> Result<(Ending, i16), Abend> {
280        run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Nothing, kept)
281    }
282
283    fn execute_cics<'w>(
284        &self,
285        library: unit::Library,
286        dds: files::Dds,
287        task: cics::Task,
288        clock: unit::Clock,
289        out: &'w mut dyn Write,
290        err: &'w mut dyn Write,
291    ) -> Result<(Ending, cics::Task), Abend> {
292        self.execute_cics_with(library, dds, task, clock, None, out, err)
293    }
294
295    fn execute_cics_with<'w>(
296        &self,
297        library: unit::Library,
298        dds: files::Dds,
299        task: cics::Task,
300        clock: unit::Clock,
301        database: Option<&'w mut (dyn sql::Database + '_)>,
302        out: &'w mut dyn Write,
303        err: &'w mut dyn Write,
304    ) -> Result<(Ending, cics::Task), Abend> {
305        self.execute_cics_observed(library, dds, task, clock, database, out, err, None)
306    }
307
308    fn execute_cics_observed<'w>(
309        &self,
310        library: unit::Library,
311        dds: files::Dds,
312        task: cics::Task,
313        clock: unit::Clock,
314        database: Option<&'w mut (dyn sql::Database + '_)>,
315        out: &'w mut dyn Write,
316        err: &'w mut dyn Write,
317        observer: Option<unit::Observer<'w>>,
318    ) -> Result<(Ending, cics::Task), Abend> {
319        oo::refuse_to_run(&self.program)?;
320        let (ending, task) = execute_task(self, library, dds, task, clock, database, out, err, observer, &mut None);
321        ending.map(|e| (e, task))
322    }
323}
324
325/// Runs `compiled` on the interpreter as the first program of a CICS task, as
326/// [`Execute::execute_cics_observed`] does; `kept` takes what the run left in its run unit, and the
327/// task comes back however the run ended.
328#[allow(clippy::too_many_arguments)]
329pub(crate) fn execute_task<'w>(
330    compiled: &Compiled,
331    library: unit::Library,
332    dds: files::Dds,
333    task: cics::Task,
334    clock: unit::Clock,
335    database: Option<&'w mut (dyn sql::Database + '_)>,
336    out: &'w mut dyn Write,
337    err: &'w mut dyn Write,
338    observer: Option<unit::Observer<'w>>,
339    kept: &mut Option<unit::Remains>,
340) -> (Result<Ending, Abend>, cics::Task) {
341    let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
342    let limits = (library.statement_limit, library.time_limit, library.storage_limit);
343    let mut run_unit = unit::RunUnit::new(library, dds, None, clock, out, err);
344    run_unit.observer = observer;
345    run_unit.statements = statements;
346    run_unit.taint = taint;
347    run_unit.limit(limits.0, limits.1, limits.2);
348    run_unit.sql = database.map(sql::Session::new);
349    let (ending, ended, task) = run_task(First::of(compiled), run_unit, task, kept, |unit, me, commarea, length| {
350        machine::Machine::activation(compiled, me, unit, true).and_then(|mut m| {
351            m.begin_task(commarea, length);
352            m.run_level()
353        })
354    });
355    (ending.map_err(asra).and_then(|e| ended.map(|()| e)), task)
356}
357
358/// A CICS task's run unit given the task, its EXEC interface block and its COMMAREA, and the task's
359/// first program added as `me` and run by `run` with the COMMAREA's address and length; then the
360/// task's unit of work ended, its files closed and its transient data written. Returns how the run
361/// ended, how ending the task went, and the task; `kept` takes what the run left in its run unit.
362pub(crate) fn run_task<'w, H: Clone, L: unit::Loader<H>, E>(
363    first: First<'_>,
364    mut run_unit: rt::unit::RunUnit<'w, H, L>,
365    mut task: cics::Task,
366    kept: &mut Option<unit::Remains>,
367    run: impl FnOnce(&mut rt::unit::RunUnit<'w, H, L>, usize, Option<usize>, usize) -> Result<Ending, E>,
368) -> (Result<Ending, E>, Result<(), Abend>, cics::Task) {
369    let me = run_unit.add_named(None, first.name, first.files, first.size);
370    run_unit.programs[me].source = first.source;
371    run_unit.eib = run_unit.push_temporary(&[0; cics::EIB_LEN]);
372    let commarea = task.commarea.take();
373    let length = commarea.as_ref().map_or(0, Vec::len);
374    let commarea = commarea.map(|c| {
375        let at = run_unit.push_temporary(&c);
376        run_unit.mark_input(at, c.len(), true);
377        at
378    });
379    run_unit.cics = Some(task);
380    let ending = run(&mut run_unit, me, commarea, length);
381    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.end_task(first.id, ending.is_ok()).map(drop));
382    let mut closed = run_unit.close_all(false);
383    for (name, f) in run_unit.cics_files.drain() {
384        if let Err(e) = f.close() {
385            closed = closed.and(Err(format!("closing CICS file {name}: {e}")));
386        }
387    }
388    *kept = Some(unit::Remains::of(&run_unit));
389    let mut task = run_unit.cics.take().unwrap_or_default();
390    if let Err(e) = task.flush_td(first.page) {
391        closed = closed.and(Err(format!("writing transient data: {e}")));
392    }
393    let settled = settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None });
394    let closed = closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None });
395    (ending, settled.and(closed), task)
396}
397
398/// What a CICS task's run unit needs of its first program besides its code: the PROGRAM-ID, which
399/// names the task's unit of work, the name the run unit holds it by, its shape, its code page, and
400/// the file a program library supplied it from.
401pub(crate) struct First<'a> {
402    pub id: &'a str,
403    pub name: String,
404    pub files: usize,
405    pub size: usize,
406    pub page: &'static zarch::ebcdic::CodePage,
407    pub source: Option<std::path::PathBuf>,
408}
409
410impl<'a> First<'a> {
411    pub(crate) fn of(compiled: &'a Compiled) -> Self {
412        let p = &compiled.program;
413        Self { id: &p.id, name: p.id.to_ascii_uppercase(), files: p.files.len(), size: compiled.layout.size as usize, page: compiled.options.code_page(), source: None }
414    }
415}
416
417/// How a CICS task reports its ending: a program check as ASRA, and a Language Environment
418/// condition nothing handled, U4038 in batch, as transaction abend 4038 (assumption C454).
419pub(crate) fn asra(a: Abend) -> Abend {
420    match a.code {
421        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 },
422        ref u if *u == "U4038" => Abend { message: format!("{} (U4038, which CICS reports as transaction abend 4038)", a.message), code: AbendCode::Cics("4038".into()), pos: a.pos, file: a.file },
423        _ => a,
424    }
425}
426
427/// What the first program of a batch run unit is given for its PROCEDURE DIVISION USING items.
428#[derive(Clone, Copy, Debug, Default)]
429pub enum Passed<'a> {
430    /// Nothing: a main program no one passes anything.
431    #[default]
432    Nothing,
433    /// A job step's PARM, as Language Environment builds its parameter list.
434    Parm(&'a str),
435    /// What a caller passes a subprogram, one per USING item: the bytes of the item passed, or
436    /// None for OMITTED.
437    Arguments(&'a [Option<Vec<u8>>]),
438}
439
440impl Passed<'_> {
441    /// The addresses the USING items are bound to, each argument pushed as input.
442    pub(crate) fn addresses<H: Clone, L: rt::unit::Loader<H>>(self, run_unit: &mut rt::unit::RunUnit<'_, H, L>, page: &zarch::ebcdic::CodePage) -> Vec<Option<usize>> {
443        match self {
444            Passed::Nothing => Vec::new(),
445            Passed::Parm(parm) => vec![Some(push_parm(run_unit, page, parm))],
446            Passed::Arguments(arguments) => arguments.iter().map(|a| a.as_deref().map(|bytes| push_input(run_unit, bytes))).collect(),
447        }
448    }
449
450    /// Whether the program runs as a run unit's main program, where EXIT PROGRAM does nothing,
451    /// rather than as one a caller passed arguments to.
452    pub(crate) fn main(self) -> bool {
453        !matches!(self, Passed::Arguments(_))
454    }
455
456    /// Each argument's bytes at `addresses` in `mem` as the run left them, None for OMITTED; none
457    /// for a run given no arguments.
458    pub(crate) fn returned(self, addresses: &[Option<usize>], mem: &[u8]) -> Vec<Option<Vec<u8>>> {
459        let Passed::Arguments(arguments) = self else { return Vec::new() };
460        arguments
461            .iter()
462            .zip(addresses)
463            .map(|(a, at)| match (a, at) {
464                (Some(a), Some(at)) => mem.get(*at..at + a.len()).map(<[u8]>::to_vec),
465                _ => None,
466            })
467            .collect()
468    }
469
470    /// Gives the run unit what a job step's PARM sets: the program arguments, and the UPSI switches
471    /// its runtime options give, which are off otherwise; a malformed UPSI is named on standard
472    /// error.
473    pub(crate) fn apply_parm<H: Clone, L: rt::unit::Loader<H>>(self, run_unit: &mut rt::unit::RunUnit<'_, H, L>) {
474        let Passed::Parm(parm) = self else { return };
475        run_unit.arguments = rt::le::parm::Arguments::of(parm);
476        match rt::le::parm::upsi(parm) {
477            Some(Ok(on)) => run_unit.set_switches(on),
478            Some(Err(m)) => {
479                let _ = writeln!(run_unit.err, "ironwork: {m}");
480            }
481            None => {}
482        }
483    }
484}
485
486/// Runs `compiled` as the first program of a batch run unit, given `passed`: a main program, a
487/// job step's main program, or a subprogram as its caller would run it.
488#[allow(clippy::too_many_arguments)]
489fn run_main<'w>(
490    compiled: &Compiled,
491    library: unit::Library,
492    dds: files::Dds,
493    sysin: Option<Box<dyn BufRead + 'w>>,
494    clock: unit::Clock,
495    database: Option<&'w mut (dyn sql::Database + '_)>,
496    out: &'w mut dyn Write,
497    err: &'w mut dyn Write,
498    observer: Option<unit::Observer<'w>>,
499    passed: Passed<'_>,
500    kept: &mut Option<unit::Remains>,
501) -> Result<(Ending, i16), Abend> {
502    oo::refuse_to_run(&compiled.program)?;
503    let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
504    let (limits, screen, environment) = ((library.statement_limit, library.time_limit, library.storage_limit), library.screen.clone(), library.environment.clone());
505    let mut run_unit = unit::RunUnit::new(library, dds, sysin, clock, out, err);
506    run_unit.crt = screen;
507    run_unit.environment = rt::environment::Environment::of(environment);
508    run_unit.observer = observer;
509    run_unit.statements = statements;
510    run_unit.taint = taint;
511    run_unit.limit(limits.0, limits.1, limits.2);
512    run_unit.sql = database.map(sql::Session::new);
513    let me = run_unit.add(None, &compiled.program, compiled.layout.size as usize);
514    let trap_off = matches!(passed, Passed::Parm(p) if rt::le::parm::trap_off(p));
515    passed.apply_parm(&mut run_unit);
516    let addresses = passed.addresses(&mut run_unit, compiled.options.code_page());
517    let ending = machine::Machine::activation(compiled, me, &mut run_unit, passed.main()).and_then(|mut m| {
518        if !addresses.is_empty() {
519            m.bind(&addresses);
520        }
521        m.run_procedure()
522    });
523    let last_paragraph = compiled.program.paragraphs.last().map(|p| p.pos);
524    let ending = ending.and_then(|e| past_the_end(e, passed.main(), &compiled.program.id, last_paragraph));
525    let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(&compiled.program.id, ending.is_ok()).map(drop));
526    let closed = run_unit.close_all(ending.as_ref().is_err_and(|a| a.code.skips_termination(trap_off)));
527    *kept = Some(unit::Remains { arguments: passed.returned(&addresses, &run_unit.mem), ..unit::Remains::of(&run_unit) });
528    let ending = ending?;
529    settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
530    closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
531    Ok((ending, run_unit.return_code()))
532}
533
534/// A main program whose control ran past its last statement: IGZ0037S, a severity-3 condition that
535/// ends the run U4038 (assumption C456), placed at the last paragraph, the one control ran out of. A
536/// program a caller passed arguments to returns there, as an implicit EXIT PROGRAM does.
537pub(crate) fn past_the_end(ending: Ending, main: bool, program: &str, last_paragraph: Option<Pos>) -> Result<Ending, Abend> {
538    if main && ending == Ending::EndOfProgram {
539        let message = format!("IGZ0037S The flow of control in program {} proceeded beyond the last line of the program.", program.to_ascii_uppercase());
540        return Err(Abend { code: AbendCode::user(4038), message, pos: last_paragraph.unwrap_or_default(), file: None });
541    }
542    Ok(ending)
543}
544
545/// A job step's PARM as Language Environment passes it, at the end of memory: input.
546pub(crate) fn push_parm<H: Clone, L: rt::unit::Loader<H>>(run_unit: &mut rt::unit::RunUnit<'_, H, L>, page: &zarch::ebcdic::CodePage, parm: &str) -> usize {
547    let area = rt::le::parm::parameter_area(rt::le::parm::program_arguments(parm), page);
548    push_input(run_unit, &area)
549}
550
551/// `bytes` at the end of memory, marked as input.
552fn push_input<H: Clone, L: rt::unit::Loader<H>>(run_unit: &mut rt::unit::RunUnit<'_, H, L>, bytes: &[u8]) -> usize {
553    let at = run_unit.push_temporary(bytes);
554    run_unit.mark_input(at, bytes.len(), true);
555    at
556}
557
558#[cfg(test)]
559mod tests;