pub mod abend;
pub use rt::calendar;
pub use rt::cics;
pub use rt::digest;
pub use rt::evidence;
pub use compile::collating;
pub use compile::declaratives;
#[cfg(test)]
mod edit;
pub use rt::files;
pub use compile::layout;
pub mod le;
pub use rt::lir;
pub mod loader;
pub mod lower;
pub mod machine;
pub use rt::module;
pub mod oo;
pub use compile::picture;
pub mod printer;
pub mod report;
use compile::sort;
pub mod sql;
pub mod terminal;
#[cfg(test)]
mod testing;
pub use rt::tn3270;
pub mod unit;
pub mod vm;
pub use compile::{Compiled, compile, compile_at, compile_time, entry_points, read_lengths, variable_records};
pub(crate) use compile::{procedure, procedure_from, section_end};
pub use machine::{Abend, Ending};
use abend::AbendCode;
use unit::AddProgram;
use std::io::{BufRead, Write};
use syntax::Pos;
pub trait Execute {
fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend>;
fn execute<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, i16), Abend>;
#[allow(clippy::too_many_arguments)]
fn execute_with<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, i16), Abend>;
#[allow(clippy::too_many_arguments)]
fn execute_observed<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
) -> Result<(Ending, i16), Abend>;
#[allow(clippy::too_many_arguments)]
fn execute_main<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
parm: &str,
) -> Result<(Ending, i16), Abend>;
#[allow(clippy::too_many_arguments)]
fn execute_with_arguments<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
arguments: &[Option<Vec<u8>>],
) -> Result<(Ending, i16), Abend>;
#[allow(clippy::too_many_arguments)]
fn execute_kept<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
kept: &mut Option<unit::Remains>,
) -> Result<(Ending, i16), Abend>;
fn execute_cics<'w>(
&self,
library: unit::Library,
dds: files::Dds,
task: cics::Task,
clock: unit::Clock,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, cics::Task), Abend>;
#[allow(clippy::too_many_arguments)]
fn execute_cics_with<'w>(
&self,
library: unit::Library,
dds: files::Dds,
task: cics::Task,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, cics::Task), Abend>;
#[allow(clippy::too_many_arguments)]
fn execute_cics_observed<'w>(
&self,
library: unit::Library,
dds: files::Dds,
task: cics::Task,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
) -> Result<(Ending, cics::Task), Abend>;
}
impl Execute for Compiled {
fn run(&self, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
self.run_with(files::Dds::default(), out, err)
}
fn run_with(&self, dds: files::Dds, out: &mut dyn Write, err: &mut dyn Write) -> Result<Ending, Abend> {
self.execute(unit::Library::default(), dds, None, unit::Clock::System, out, err).map(|(ending, _)| ending)
}
fn execute<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, i16), Abend> {
self.execute_with(library, dds, sysin, clock, None, out, err)
}
fn execute_with<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, i16), Abend> {
self.execute_observed(library, dds, sysin, clock, database, out, err, None)
}
fn execute_observed<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
) -> Result<(Ending, i16), Abend> {
run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Nothing, &mut None)
}
fn execute_main<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
parm: &str,
) -> Result<(Ending, i16), Abend> {
run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Parm(parm), &mut None)
}
fn execute_with_arguments<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
arguments: &[Option<Vec<u8>>],
) -> Result<(Ending, i16), Abend> {
run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Arguments(arguments), &mut None)
}
fn execute_kept<'w>(
&self,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
kept: &mut Option<unit::Remains>,
) -> Result<(Ending, i16), Abend> {
run_main(self, library, dds, sysin, clock, database, out, err, observer, Passed::Nothing, kept)
}
fn execute_cics<'w>(
&self,
library: unit::Library,
dds: files::Dds,
task: cics::Task,
clock: unit::Clock,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, cics::Task), Abend> {
self.execute_cics_with(library, dds, task, clock, None, out, err)
}
fn execute_cics_with<'w>(
&self,
library: unit::Library,
dds: files::Dds,
task: cics::Task,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
) -> Result<(Ending, cics::Task), Abend> {
self.execute_cics_observed(library, dds, task, clock, database, out, err, None)
}
fn execute_cics_observed<'w>(
&self,
library: unit::Library,
dds: files::Dds,
task: cics::Task,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
) -> Result<(Ending, cics::Task), Abend> {
oo::refuse_to_run(&self.program)?;
let (ending, task) = execute_task(self, library, dds, task, clock, database, out, err, observer, &mut None);
ending.map(|e| (e, task))
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn execute_task<'w>(
compiled: &Compiled,
library: unit::Library,
dds: files::Dds,
task: cics::Task,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
kept: &mut Option<unit::Remains>,
) -> (Result<Ending, Abend>, cics::Task) {
let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
let limit = library.statement_limit;
let mut run_unit = unit::RunUnit::new(library, dds, None, clock, out, err);
run_unit.observer = observer;
run_unit.statements = statements;
run_unit.taint = taint;
run_unit.statement_limit = limit;
run_unit.sql = database.map(sql::Session::new);
let (ending, ended, task) = run_task(First::of(compiled), run_unit, task, kept, |unit, me, commarea, length| {
machine::Machine::activation(compiled, me, unit, true).and_then(|mut m| {
m.begin_task(commarea, length);
m.run_level()
})
});
(ending.map_err(asra).and_then(|e| ended.map(|()| e)), task)
}
pub(crate) fn run_task<'w, H: Clone, L: unit::Loader<H>, E>(
first: First<'_>,
mut run_unit: rt::unit::RunUnit<'w, H, L>,
mut task: cics::Task,
kept: &mut Option<unit::Remains>,
run: impl FnOnce(&mut rt::unit::RunUnit<'w, H, L>, usize, Option<usize>, usize) -> Result<Ending, E>,
) -> (Result<Ending, E>, Result<(), Abend>, cics::Task) {
let me = run_unit.add_named(None, first.name, first.files, first.size);
run_unit.programs[me].source = first.source;
run_unit.eib = run_unit.push_temporary(&[0; cics::EIB_LEN]);
let commarea = task.commarea.take();
let length = commarea.as_ref().map_or(0, Vec::len);
let commarea = commarea.map(|c| {
let at = run_unit.push_temporary(&c);
run_unit.mark_input(at, c.len(), true);
at
});
run_unit.cics = Some(task);
let ending = run(&mut run_unit, me, commarea, length);
let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.end_task(first.id, ending.is_ok()).map(drop));
let mut closed = run_unit.close_all(false);
for (name, f) in run_unit.cics_files.drain() {
if let Err(e) = f.close() {
closed = closed.and(Err(format!("closing CICS file {name}: {e}")));
}
}
*kept = Some(unit::Remains::of(&run_unit));
let mut task = run_unit.cics.take().unwrap_or_default();
if let Err(e) = task.flush_td(first.page) {
closed = closed.and(Err(format!("writing transient data: {e}")));
}
let settled = settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None });
let closed = closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None });
(ending, settled.and(closed), task)
}
pub(crate) struct First<'a> {
pub id: &'a str,
pub name: String,
pub files: usize,
pub size: usize,
pub page: &'static zarch::ebcdic::CodePage,
pub source: Option<std::path::PathBuf>,
}
impl<'a> First<'a> {
pub(crate) fn of(compiled: &'a Compiled) -> Self {
let p = &compiled.program;
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 }
}
}
pub(crate) fn asra(a: Abend) -> Abend {
match a.code {
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 },
_ => a,
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum Passed<'a> {
#[default]
Nothing,
Parm(&'a str),
Arguments(&'a [Option<Vec<u8>>]),
}
impl Passed<'_> {
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>> {
match self {
Passed::Nothing => Vec::new(),
Passed::Parm(parm) => vec![Some(push_parm(run_unit, page, parm))],
Passed::Arguments(arguments) => arguments.iter().map(|a| a.as_deref().map(|bytes| push_input(run_unit, bytes))).collect(),
}
}
pub(crate) fn main(self) -> bool {
!matches!(self, Passed::Arguments(_))
}
}
#[allow(clippy::too_many_arguments)]
fn run_main<'w>(
compiled: &Compiled,
library: unit::Library,
dds: files::Dds,
sysin: Option<Box<dyn BufRead + 'w>>,
clock: unit::Clock,
database: Option<&'w mut (dyn sql::Database + '_)>,
out: &'w mut dyn Write,
err: &'w mut dyn Write,
observer: Option<unit::Observer<'w>>,
passed: Passed<'_>,
kept: &mut Option<unit::Remains>,
) -> Result<(Ending, i16), Abend> {
oo::refuse_to_run(&compiled.program)?;
let (statements, taint) = (library.trace_statements.clone(), library.trace_input.then(rt::taint::Taint::default));
let limit = library.statement_limit;
let mut run_unit = unit::RunUnit::new(library, dds, sysin, clock, out, err);
run_unit.observer = observer;
run_unit.statements = statements;
run_unit.taint = taint;
run_unit.statement_limit = limit;
run_unit.sql = database.map(sql::Session::new);
let me = run_unit.add(None, &compiled.program, compiled.layout.size as usize);
let trap_off = matches!(passed, Passed::Parm(p) if rt::le::parm::trap_off(p));
let addresses = passed.addresses(&mut run_unit, compiled.options.code_page());
let ending = machine::Machine::activation(compiled, me, &mut run_unit, passed.main()).and_then(|mut m| {
if !addresses.is_empty() {
m.bind(&addresses);
}
m.run_procedure()
});
let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(&compiled.program.id, ending.is_ok()).map(drop));
let closed = run_unit.close_all(trap_off && ending.as_ref().is_err_and(|a| a.code.bypasses_trap_off()));
*kept = Some(unit::Remains::of(&run_unit));
let ending = ending?;
settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
Ok((ending, run_unit.return_code()))
}
pub(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 {
let area = rt::le::parm::parameter_area(rt::le::parm::program_arguments(parm), page);
push_input(run_unit, &area)
}
fn push_input<H: Clone, L: rt::unit::Loader<H>>(run_unit: &mut rt::unit::RunUnit<'_, H, L>, bytes: &[u8]) -> usize {
let at = run_unit.push_temporary(bytes);
run_unit.mark_input(at, bytes.len(), true);
at
}
#[cfg(test)]
mod tests;