ironwork-exec 0.4.1

ironwork for COBOL: an interpreter over EBCDIC storage
Documentation
//! EXEC CICS in the walker: a block is bound into a `CicsCommand` (cics_bind.rs) and run by
//! `rt::cics`, which asks the walker what `CicsHost` names.

use super::*;
use super::cics_bind;
use crate::cics::{self, CicsHost};
use rt::bms::Mapset;
use rt::unit::Loader;
use std::rc::Rc;

pub(super) use crate::cics::Handlers;

fn flow(f: cics::Flow) -> Flow {
    match f {
        cics::Flow::Next => Flow::Next,
        cics::Flow::GoTo(p) => Flow::GoTo(p as usize),
        cics::Flow::End(e) => Flow::End(e),
    }
}

impl<'p> Machine<'p, '_, '_> {
    pub(super) fn cics(&mut self, block: &'p ExecBlock) -> R<Flow> {
        cics::in_task(self.unit, &block.command, block.pos)?;
        if self.unit.observed() {
            self.cics_sinks(block);
        }
        let command = self.bind_cics(block)?;
        cics::run(self, &command, block.pos).map(flow)
    }

    /// The operands of a command an input could steer (`cics_bind::sinks`), told to the observer
    /// before the command runs. An operand that cannot be read is left to the command to report,
    /// so tracing never changes how a run ends.
    fn cics_sinks(&mut self, block: &ExecBlock) {
        for (r, sink) in cics_bind::sinks(block) {
            if let Ok(loc) = self.locate(r) {
                let text = self.page.decode(store::bytes(&self.unit.mem, loc));
                self.sink(sink.kind(), block.pos, &text);
            }
        }
    }

    /// Runs this program as a logical level of the task: an abend that reaches it while its HANDLE
    /// ABEND exit is active goes to the exit, a LABEL taken as a GO TO at the HANDLE ABEND command
    /// with the PERFORMs the abend left still armed (C236), and a PROGRAM in place of the rest of
    /// this level (C142).
    pub(crate) fn run_level(&mut self) -> R<Ending> {
        self.run_taking_exits(None, true)
    }

    /// Runs a CALLed program, which is at its caller's logical level: an abend that reaches it
    /// takes the level's exit only when that is a LABEL (C238).
    pub(super) fn run_called(&mut self, at: Option<(usize, usize)>) -> R<Ending> {
        self.run_taking_exits(at, false)
    }

    fn run_taking_exits(&mut self, at: Option<(usize, usize)>, runs_level: bool) -> R<Ending> {
        let mut ending = self.run_from(at);
        loop {
            let abend = match ending {
                Err(abend) => abend,
                done => return done,
            };
            match cics::abend_exit(self.unit, &mut self.cics_handlers, &abend, self.serial, runs_level)? {
                None => return Err(abend),
                Some(cics::ExitTarget::Label { paragraph, at, .. }) => ending = self.go_to(paragraph as usize, at),
                Some(cics::ExitTarget::Program { name, commarea }) => {
                    let ending = cics::enter_exit_program(self, &name, commarea, abend.pos)?;
                    return Ok(if ending == Ending::StopRun { ending } else { Ending::Goback });
                }
            }
        }
    }

    /// Fills the EXEC interface block for the task's first program and binds DFHEIBLK and
    /// DFHCOMMAREA, the USING items the translator gave it.
    pub(crate) fn begin_task(&mut self, commarea: Option<usize>, length: usize) {
        cics::begin_task(self.unit, self.page, length);
        let eib = self.unit.eib;
        self.bind(&[Some(eib), commarea]);
    }

    /// A data item a name alone refers to, as the symbolic map's `mapI` and `mapO` are found.
    fn named(name: &str, pos: Pos) -> Ref {
        Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos }
    }
}

impl<'a, 'w> CicsHost<'w, &'a Ref, &'a Operand, &'a str> for Machine<'_, '_, 'w> {
    fn handlers(&mut self) -> &mut Handlers {
        &mut self.cics_handlers
    }

    fn content(&mut self, operand: &&'a Operand, pos: Pos) -> R<Vec<u8>> {
        callee::content(self, &super::facts::chars(operand), pos)
    }

    fn integer_of(&mut self, operand: &&'a Operand, pos: Pos) -> R<i64> {
        Machine::integer(self, &Expr::Operand((*operand).clone()), pos)
    }

    fn text(&self, text: &&'a str) -> String {
        (*text).to_owned()
    }

    fn activation(&self) -> u64 {
        self.serial
    }

    fn program_id(&self) -> String {
        self.program.id.clone()
    }

    fn commarea(&self) -> Option<usize> {
        let ordinal = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some("DFHCOMMAREA"));
        ordinal.and_then(|o| self.linkage[o])
    }

    fn mapset(&mut self, name: &str) -> Option<Result<Mapset, String>> {
        self.unit.library.mapset(name)
    }

    fn item_named(&mut self, name: &str, pos: Pos) -> R<Option<Loc>> {
        let r = Self::named(name, pos);
        match self.resolve(&r) {
            Ok(Resolved::Item(_)) => self.locate(&r).map(Some),
            _ => Ok(None),
        }
    }

    fn locate_named(&mut self, name: &str, pos: Pos) -> R<Loc> {
        self.locate(&Self::named(name, pos))
    }

    fn unaddressed(&mut self, place: &'a Ref) -> Option<usize> {
        let Ok(Resolved::Item(i)) = self.resolve(place) else { return None };
        let item = &self.layout.items[i];
        item.linkage.filter(|&l| self.linkage[l as usize].is_none()).map(|_| item.size as usize)
    }

    fn run_program(&mut self, program: Option<Rc<Compiled>>, index: usize, commarea: Option<usize>, xctl: bool) -> R<Ending> {
        let first = self.first;
        let Some(compiled) = program.as_deref().or(first) else {
            return Err(Abend::ironwork("the CICS task's first program cannot be LINKed or XCTLed to from a function or a method", Pos::default()));
        };
        let handlers = if xctl { self.cics_handlers.xctl() } else { Handlers::default() };
        Machine::activation(compiled, index, &mut *self.unit, self.main && xctl).and_then(|mut callee| {
            let eib = callee.unit.eib;
            callee.bind(&[Some(eib), commarea]);
            (callee.cics_handlers, callee.first) = (handlers, first);
            callee.run_level()
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::cics::{EIBAID, EIBCALEN, EIBCPOSN, EIBDATE, EIBFN, EIBRESP, EIBRESP2, EIBRSRCE, EIBTASKN, EIBTIME, EIBTRMID, EIBTRNID};

    #[test]
    fn eib_offsets_match_the_dfheiblk_layout() {
        let source = "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. EIBT.\n       PROCEDURE DIVISION.\n           EXEC CICS RETURN END-EXEC.\n";
        let program = syntax::parse_all_with(source, &syntax::copy::Libraries::default()).unwrap().remove(0);
        let compiled = crate::compile(program, &[]).unwrap_or_else(|e| panic!("{e:?}"));
        let offset = |name: &str| compiled.layout.items.iter().find(|i| i.name.as_deref() == Some(name)).map(|i| i.offset as usize);
        let fields = [
            ("EIBTIME", EIBTIME),
            ("EIBDATE", EIBDATE),
            ("EIBTRNID", EIBTRNID),
            ("EIBTASKN", EIBTASKN),
            ("EIBTRMID", EIBTRMID),
            ("EIBCPOSN", EIBCPOSN),
            ("EIBCALEN", EIBCALEN),
            ("EIBAID", EIBAID),
            ("EIBFN", EIBFN),
            ("EIBRSRCE", EIBRSRCE),
            ("EIBRESP", EIBRESP),
            ("EIBRESP2", EIBRESP2),
        ];
        for (name, at) in fields {
            assert_eq!(offset(name), Some(at), "{name}");
        }
    }
}