use super::*;
const EXTENDED: &[&str] = &["--compliance=extended"];
const EXTENDED_PROGRAM: &str = concat!(
"*> free form, read as Micro Focus and GnuCOBOL read it\n",
"IDENTIFICATION DIVISION.\n",
"PROGRAM-ID. EXT.\n",
"DATA DIVISION.\n",
"WORKING-STORAGE SECTION.\n",
"78 MAX-LEN VALUE 5.\n",
"01 GREETING CONSTANT AS \"HEL\" & \"LO\".\n",
"01 WS-NAME PIC X(MAX-LEN) VALUE GREETING.\n",
"01 WS-COUNT BINARY-LONG VALUE -7.\n",
"01 WS-SHORT USAGE BINARY-SHORT UNSIGNED.\n",
"01 WS-TABLE.\n",
" 05 WS-CELL OCCURS MAX-LEN TIMES PIC 9 VALUE ZERO.\n",
"PROCEDURE DIVISION.\n",
"MAIN-PARA.\n",
"* a comment line in column 1\n",
" DISPLAY WS-NAME \"|\" X\"C1\" & \"B\"\n",
" IF WS-COUNT <> MAX-LEN\n",
" ADD MAX-LEN TO WS-COUNT\n",
" END-IF\n",
" COMPUTE WS-SHORT = 65535 + 3\n",
" MOVE MAX-LEN TO WS-CELL(MAX-LEN)\n",
" PERFORM SHOW-PARA\n",
" GOBACK.\n",
"SHOW-PARA.\n",
" DISPLAY WS-COUNT \"|\" WS-SHORT \"|\" WS-TABLE.\n",
);
#[test]
fn an_extended_program_runs_alike_on_the_interpreter_and_the_vm() {
let walked = Harness::source(EXTENDED_PROGRAM).flags(EXTENDED).run(Executor::Interpreter);
assert_eq!((walked.out.as_str(), walked.ending.as_ref().ok()), ("HELLO|AB\n000000000K|00002|00005\n", Some(&Ending::Goback)), "{}", walked.err);
let vm = Harness::source(EXTENDED_PROGRAM).flags(EXTENDED).run(Executor::Vm);
assert_eq!((vm.out, vm.ending), (walked.out, walked.ending));
}
#[test]
fn each_extension_is_a_warning_naming_it_and_where_it_is() {
let parsed = syntax::parse_with(EXTENDED_PROGRAM, &syntax::copy::Libraries::default().with_compliance(numeric::Compliance::Extended)).unwrap();
let compiled = compile(parsed, &EXTENDED.iter().map(|f| f.to_string()).collect::<Vec<_>>()).unwrap_or_else(|e| panic!("{e:?}"));
let shown: Vec<(u32, u32, Option<&str>)> = compiled.diagnostics.iter().map(|m| (m.pos.line, m.pos.col, m.id)).collect();
assert_eq!(
shown,
[(1, 7, "IWX0001"), (6, 1, "IWX0002"), (7, 1, "IWX0002"), (7, 31, "IWX0004"), (9, 13, "IWX0005"), (10, 19, "IWX0005"), (16, 31, "IWX0004"), (17, 17, "IWX0003")].map(|(line, col, id)| (line, col, Some(id)))
);
assert!(compiled.diagnostics.iter().all(|m| m.severity == Severity::Warning));
assert_eq!(syntax::return_code(&compiled.diagnostics), 4);
assert_eq!(compiled.options.compliance, numeric::Compliance::Extended);
}
#[test]
fn strict_refuses_the_extended_program_as_before() {
assert!(syntax::parse(EXTENDED_PROGRAM).is_err_and(|e| !e.message.contains("IWX")));
let fixed = program("", " 78 N VALUE 1.\n", &line("GOBACK."));
assert_eq!(compile_errors(&fixed), "IWC0034-S level 78 is not a data level");
}
const RETURNING_OMITTED: &str = concat!(
" IDENTIFICATION DIVISION.\n",
" PROGRAM-ID. CALLER.\n",
" DATA DIVISION.\n",
" WORKING-STORAGE SECTION.\n",
" 01 R PIC 9(4) VALUE 99.\n",
" PROCEDURE DIVISION.\n",
" CALL 'VOIDSUB'\n",
" DISPLAY RETURN-CODE\n",
" CALL 'VOIDSUB' RETURNING R\n",
" DISPLAY R ' ' RETURN-CODE\n",
" GOBACK.\n",
" END PROGRAM CALLER.\n",
" IDENTIFICATION DIVISION.\n",
" PROGRAM-ID. VOIDSUB.\n",
" DATA DIVISION.\n",
" WORKING-STORAGE SECTION.\n",
" 01 N PIC 9 VALUE 0.\n",
" PROCEDURE DIVISION RETURNING OMITTED.\n",
" ADD 1 TO N\n",
" MOVE N TO RETURN-CODE\n",
" GOBACK.\n",
" END PROGRAM VOIDSUB.\n",
);
#[test]
fn a_program_returning_omitted_returns_its_return_code_and_no_item() {
let walked = Harness::source(RETURNING_OMITTED).flags(EXTENDED).run(Executor::Interpreter);
assert_eq!((walked.out.as_str(), walked.ending.as_ref().ok()), ("0001\n0099 0002\n", Some(&Ending::Goback)), "{}", walked.err);
let vm = Harness::source(RETURNING_OMITTED).flags(EXTENDED).run(Executor::Vm);
assert_eq!((vm.out, vm.ending), (walked.out, walked.ending));
let parsed = syntax::parse_all_with(RETURNING_OMITTED, &syntax::copy::Libraries::default().with_compliance(numeric::Compliance::Extended)).unwrap();
let compiled = compile(parsed[1].clone(), &EXTENDED.iter().map(|f| f.to_string()).collect::<Vec<_>>()).unwrap_or_else(|e| panic!("{e:?}"));
let shown: Vec<(u32, u32, Option<&str>, Severity)> = compiled.diagnostics.iter().map(|m| (m.pos.line, m.pos.col, m.id, m.severity)).collect();
assert_eq!(shown, [(18, 27, Some("IWX0009"), Severity::Warning)]);
let strict = compile(syntax::parse_all_with(RETURNING_OMITTED, &syntax::copy::Libraries::default()).unwrap().remove(1), &[]).err().unwrap();
assert_eq!(strict.iter().map(|e| e.message.as_str()).collect::<Vec<_>>(), ["PROCEDURE DIVISION RETURNING OMITTED: not an 01 or 77 item of the LINKAGE SECTION"]);
}
const COMMAND_LINE: &str = concat!(
" IDENTIFICATION DIVISION.\n",
" PROGRAM-ID. ARGS.\n",
" DATA DIVISION.\n",
" WORKING-STORAGE SECTION.\n",
" 01 CL PIC X(20).\n",
" 01 N PIC 9(3).\n",
" 01 V PIC X(6).\n",
" 01 K PIC 9 VALUE 3.\n",
" PROCEDURE DIVISION.\n",
" ACCEPT CL FROM COMMAND-LINE\n",
" ACCEPT N FROM ARGUMENT-NUMBER\n",
" DISPLAY '[' CL '] ' N\n",
" PERFORM 4 TIMES\n",
" MOVE ALL '*' TO V\n",
" ACCEPT V FROM ARGUMENT-VALUE\n",
" ON EXCEPTION DISPLAY 'NONE LEFT'\n",
" NOT ON EXCEPTION DISPLAY V\n",
" END-ACCEPT\n",
" END-PERFORM\n",
" DISPLAY K UPON ARGUMENT-NUMBER\n",
" ACCEPT V FROM ARGUMENT-VALUE\n",
" DISPLAY 'THIRD ' V\n",
" DISPLAY 9 UPON ARGUMENT-NUMBER\n",
" ACCEPT V FROM ARGUMENT-VALUE\n",
" DISPLAY 'LAST ' V\n",
" DISPLAY 0 UPON ARGUMENT-NUMBER\n",
" ACCEPT V FROM ARGUMENT-VALUE EXCEPTION DISPLAY 'NO WORD 0'\n",
" GOBACK.\n",
);
#[test]
fn extended_reads_the_command_line_and_arguments_from_the_parm_alike_on_both_executors() {
let run = |executor, parm: Option<&str>| {
let harness = Harness::source(COMMAND_LINE).flags(EXTENDED);
let o = match parm {
Some(p) => harness.parm(p),
None => harness,
}
.run(executor);
assert!(o.ending.is_ok(), "{:?}\n{}", o.ending, o.err);
o.out
};
let given = run(Executor::Interpreter, Some("alpha be 0042 zed/RPTOPTS(ON)"));
assert_eq!(given, "[alpha be 0042 zed ] 004\nalpha \nbe \n0042 \nzed \nTHIRD 0042 \nLAST zed \nNO WORD 0\n");
assert_eq!(run(Executor::Vm, Some("alpha be 0042 zed/RPTOPTS(ON)")), given);
let none = run(Executor::Interpreter, None);
assert_eq!(none, "[ ] 000\nNONE LEFT\nNONE LEFT\nNONE LEFT\nNONE LEFT\nTHIRD ******\nLAST ******\nNO WORD 0\n");
assert_eq!(run(Executor::Vm, None), none);
}
#[test]
fn the_command_line_is_a_warning_under_extended_and_refused_under_strict() {
let extended = syntax::parse_with(COMMAND_LINE, &syntax::copy::Libraries::default().with_compliance(numeric::Compliance::Extended)).unwrap();
let compiled = compile(extended, &EXTENDED.iter().map(|f| f.to_string()).collect::<Vec<_>>()).unwrap_or_else(|e| panic!("{e:?}"));
let ids: Vec<(u32, Option<&str>, Severity)> = compiled.diagnostics.iter().map(|m| (m.pos.line, m.id, m.severity)).collect();
assert_eq!(ids, [10, 11, 15, 20, 21, 23, 24, 26, 27].map(|line| (line, Some("IWX0010"), Severity::Warning)));
let strict = compile(syntax::parse(COMMAND_LINE).unwrap(), &[]).err().unwrap();
let refused: Vec<&str> = strict.iter().filter(|e| e.severity == Severity::Severe).map(|e| e.message.as_str()).collect();
assert_eq!(refused.len(), 9, "{refused:?}");
assert!(refused[0].starts_with("ACCEPT ... FROM COMMAND-LINE: Micro Focus's and GnuCOBOL's, not Enterprise COBOL's"), "{refused:?}");
assert!(refused.contains(&"DISPLAY UPON ARGUMENT-NUMBER: Micro Focus's and GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it"), "{refused:?}");
let misused = program("", " 01 V PIC X(6).\n", &[line("ACCEPT V FROM SYSIN ON EXCEPTION DISPLAY 'X' END-ACCEPT"), line("DISPLAY V UPON ARGUMENT-NUMBER"), line("GOBACK.")].concat());
let parsed = syntax::parse_with(&misused, &syntax::copy::Libraries::default().with_compliance(numeric::Compliance::Extended)).unwrap();
let errors = compile(parsed, &EXTENDED.iter().map(|f| f.to_string()).collect::<Vec<_>>()).err().unwrap();
let messages: Vec<&str> = errors.iter().map(|e| e.message.as_str()).collect();
assert!(messages.iter().any(|m| m.starts_with("ACCEPT ... ON EXCEPTION: of the ACCEPT statements, only ACCEPT ... FROM ARGUMENT-VALUE")), "{messages:?}");
assert!(messages.iter().any(|m| m.starts_with("DISPLAY UPON ARGUMENT-NUMBER: it shows one numeric item or literal")), "{messages:?}");
}
const COLONLESS_INTO: &str = concat!(
" IDENTIFICATION DIVISION.\n",
" PROGRAM-ID. COLONS.\n",
" DATA DIVISION.\n",
" WORKING-STORAGE SECTION.\n",
" 01 WS-A PIC X(4).\n",
" 01 WS-B PIC X(4).\n",
" EXEC SQL DECLARE C1 CURSOR FOR SELECT A, B FROM T END-EXEC\n",
" PROCEDURE DIVISION.\n",
" EXEC SQL FETCH C1 INTO :WS-A, WS-B END-EXEC\n",
" GOBACK.\n",
);
#[test]
fn an_into_name_without_its_colon_is_refused_under_strict_and_read_under_extended() {
let strict = compile(syntax::parse(COLONLESS_INTO).unwrap(), &[]).err().unwrap();
assert_eq!(strict.iter().map(|e| e.message.as_str()).collect::<Vec<_>>(), ["EXEC SQL FETCH: WS-B in the INTO list has no colon, which Db2 requires before every host variable"]);
let parsed = syntax::parse_with(COLONLESS_INTO, &syntax::copy::Libraries::default().with_compliance(numeric::Compliance::Extended)).unwrap();
let compiled = compile(parsed, &EXTENDED.iter().map(|f| f.to_string()).collect::<Vec<_>>()).unwrap_or_else(|e| panic!("{e:?}"));
let shown: Vec<(u32, Option<&str>, Severity)> = compiled.diagnostics.iter().map(|m| (m.pos.line, m.id, m.severity)).collect();
assert_eq!(shown, [(9, Some("IWX0011"), Severity::Warning)]);
}