Skip to main content

ironwork_rt/le/
parm.rs

1//! The parameter Language Environment gives a COBOL main program that EXEC PGM=,PARM= starts.
2
3use zarch::ebcdic::CodePage;
4
5/// Language Environment's runtime options, as the CEEXOPT sample in the Programming Guide
6/// (SA38-0682) spells them, with the NO forms it gives.
7const RUNTIME_OPTIONS: &[&str] = &[
8    "ABPERC", "ABTERMENC", "AIXBLD", "ALL31", "ANYHEAP", "BELOWHEAP", "CBLOPTS", "CBLPSHPOP", "CBLQDA", "CEEDUMP", "CHECK", "COUNTRY", "DEBUG", "DEPTHCONDLMT", "DYNDUMP", "ENVAR", "ERRCOUNT", "ERRUNIT", "FILEHIST", "FILETAG", "HEAP", "HEAPCHK",
9    "HEAPPOOLS", "HEAPZONES", "INFOMSGFILTER", "INQPCOPN", "INTERRUPT", "LIBSTACK", "MSGFILE", "MSGQ", "NATLANG", "NOTEST", "NOUSRHDLR", "OCSTATUS", "PAGEFRAMESIZE", "PC", "PLITASKCOUNT", "POSIX", "PROFILE", "PRTUNIT", "PUNUNIT", "RDRUNIT",
10    "RECPAD", "RPTOPTS", "RPTSTG", "RTEREUS", "SIMVRD", "STACK", "STORAGE", "TERMTHDACT", "TEST", "THREADHEAP", "THREADSTACK", "TRACE", "TRAP", "UPSI", "USRHDLR", "VCTRSAVE", "XPLINK", "XUFLOW",
11];
12
13/// The longest PARM= value JCL allows, in characters.
14pub const PARM_LIMIT: usize = 100;
15
16/// The program arguments in `parm` under CBLOPTS(ON), Language Environment's default outside
17/// CICS: what precedes the last slash, unless no word after it is a runtime option, when the
18/// whole string is (assumption C250).
19pub fn program_arguments(parm: &str) -> &str {
20    match parm.rfind('/') {
21        Some(at) if names_runtime_options(&parm[at + 1..]) => &parm[..at],
22        _ => parm,
23    }
24}
25
26/// Whether the runtime options in `parm` turn TRAP off, the last TRAP among them deciding
27/// ([`numeric::assumptions::TRAP_OFF_LEAVES_FILES_OPEN`]).
28pub fn trap_off(parm: &str) -> bool {
29    let options = match parm.rfind('/') {
30        Some(at) if names_runtime_options(&parm[at + 1..]) => &parm[at + 1..],
31        _ => "",
32    };
33    let trap = words(options).into_iter().rev().find_map(|w| {
34        let (name, value) = w.split_once('(')?;
35        name.eq_ignore_ascii_case("TRAP").then(|| value.trim_end_matches(')').split(',').next().unwrap_or("").trim().to_ascii_uppercase())
36    });
37    trap.as_deref() == Some("OFF")
38}
39
40/// Nothing at all, or at least one runtime option among the words, each NAME or NAME(...),
41/// separated by commas or blanks.
42fn names_runtime_options(text: &str) -> bool {
43    let words = words(text);
44    words.is_empty() || words.iter().any(|w| RUNTIME_OPTIONS.iter().any(|o| o.eq_ignore_ascii_case(w.split('(').next().unwrap_or(w))))
45}
46
47/// The words of runtime options, each NAME or NAME(...), separated by commas or blanks.
48fn words(text: &str) -> Vec<String> {
49    let (mut words, mut word, mut depth) = (Vec::new(), String::new(), 0i32);
50    for c in text.chars() {
51        match c {
52            '(' => depth += 1,
53            ')' => depth -= 1,
54            ',' | ' ' if depth <= 0 => {
55                words.push(std::mem::take(&mut word));
56                continue;
57            }
58            _ => {}
59        }
60        word.push(c);
61    }
62    words.push(word);
63    words.retain(|w| !w.is_empty());
64    words
65}
66
67/// The storage the main program's first USING item addresses: a halfword length and the
68/// arguments in EBCDIC, then X'00' up to the longest PARM (assumption C251).
69pub fn parameter_area(arguments: &str, page: &CodePage) -> Vec<u8> {
70    let text = page.encode_lossy(arguments);
71    let mut area = (text.len() as u16).to_be_bytes().to_vec();
72    area.extend(&text);
73    area.resize(2 + PARM_LIMIT.max(text.len()), 0);
74    area
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn the_last_slash_ends_the_arguments_when_runtime_options_follow_it() {
83        assert_eq!(program_arguments("RUN=1,MODE=X"), "RUN=1,MODE=X");
84        assert_eq!(program_arguments("A/B/RPTOPTS(ON)"), "A/B");
85        assert_eq!(program_arguments("ABC/RPTOPTS(ON),MSGFILE(SYSOUT,FBA,121,0)"), "ABC");
86        assert_eq!(program_arguments("ABC/ rptstg(on) NOSUCH"), "ABC");
87        assert_eq!(program_arguments("ABC/"), "ABC");
88        assert_eq!(program_arguments("/TRAP(OFF)"), "");
89        assert_eq!(program_arguments("11/16/1967"), "11/16/1967", "the manual's example: 1967 is no runtime option");
90    }
91
92    #[test]
93    fn trap_is_off_when_the_last_trap_among_the_runtime_options_says_so() {
94        assert!(trap_off("/TRAP(OFF)"));
95        assert!(trap_off("ARGS/RPTOPTS(ON) trap(off,nospie)"));
96        assert!(!trap_off("/TRAP(OFF),TRAP(ON)"));
97        assert!(!trap_off("/TRAP(,NOSPIE)"));
98        assert!(!trap_off("TRAP(OFF)"), "with no slash the PARM is all program arguments");
99        assert!(!trap_off(""));
100    }
101
102    #[test]
103    fn the_area_holds_a_halfword_length_and_the_arguments_in_ebcdic() {
104        let page = numeric::options::Options::default().code_page();
105        let area = parameter_area("AB 1", page);
106        assert_eq!(&area[..6], [0, 4, 0xC1, 0xC2, 0x40, 0xF1]);
107        assert_eq!(area.len(), 2 + PARM_LIMIT);
108        assert!(area[6..].iter().all(|&b| b == 0));
109    }
110}