use super::*;
use numeric::Dialect;
fn under(source: &str, dialect: Dialect) -> String {
let flags = [dialect.flag()];
let walker = Harness::source(source).flags(&flags).run(Executor::Interpreter);
assert!(walker.ending.is_ok(), "{:?}\n{}", walker.ending, walker.err);
let vm = Harness::source(source).flags(&flags).run(Executor::Vm);
assert_eq!(vm.out, walker.out, "the VM under {}", dialect.name());
walker.out
}
#[test]
fn a_rounded_receiver_s_extra_place_reaches_intermediate_results_under_ibm_alone() {
let source = program(
"",
" 01 A PIC 9(5)V99 VALUE 1.\n 01 B PIC 9(5)V99 VALUE 3.\n 01 C PIC 9(3) VALUE 100.\n 01 D PIC S9(5)V99 VALUE 1.\n 01 E PIC S9(5)V99 VALUE 12.35.\n 01 DIV2 PIC 99V9 VALUE 44.1.\n 01 DIV3 PIC 9(4)V9 VALUE 1661.7.\n 01 X PIC 9(5)V99.\n 01 Y PIC 9(5)V99.\n 01 S PIC 99V9.\n 01 Z PIC S99V9.\n",
&[
line("COMPUTE D ROUNDED = D + E / 3"),
line("COMPUTE Y ROUNDED = A / B * C"),
line("COMPUTE X ROUNDED = (A / B) + (A / B)"),
line("DISPLAY 'INNER ' D ' ' Y ' ' X"),
line("COMPUTE S ROUNDED = 1 + 1661.7 / DIV2"),
line("COMPUTE Z ROUNDED = - (DIV3 / DIV2)"),
line("DISPLAY 'INNER ' S ' ' Z"),
line("COMPUTE S ROUNDED = 1661.7 / DIV2"),
line("DIVIDE DIV2 INTO DIV3 ROUNDED"),
line("DIVIDE 3 INTO 2 GIVING X ROUNDED REMAINDER Y"),
line("DISPLAY 'LAST ' S ' ' DIV3 ' ' X ' ' Y"),
line("GOBACK."),
]
.concat(),
);
let last = "LAST 377 00377 0000067 0000002\n";
assert_eq!(run(&source), format!("INNER 000051B 0003330 0000067\nINNER 387 37P\n{last}"));
assert_eq!(under(&source, Dialect::Ibm), run(&source));
assert_eq!(under(&source, Dialect::Gnucobol), format!("INNER 000051A 0003300 0000066\nINNER 386 37O\n{last}"));
}
#[test]
fn the_packed_benchmark_gives_cobc_s_totals_under_gnucobol() {
let source = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/../../bench/packed.cbl")).unwrap().replace("VALUE 500000", "VALUE 300");
assert_eq!(under(&source, Dialect::Ibm), "PACKED ACC= 0000059890595.94\nPACKED D= 0000000391030.02\n");
assert_eq!(under(&source, Dialect::Gnucobol), "PACKED ACC= 0000059890449.97\nPACKED D= 0000000391029.04\n");
}
#[test]
fn display_shows_packed_and_binary_items_as_cobc_does_under_gnucobol() {
let source = program(
"",
" 01 P1 PIC S9(5)V99 COMP-3 VALUE -123.45.\n 01 P2 PIC S9(5)V99 COMP-3 VALUE 123.45.\n 01 P3 PIC 9(5)V99 COMP-3 VALUE 123.45.\n 01 B1 PIC S9(4) COMP VALUE -12.\n 01 B2 PIC 9(4) COMP VALUE 12.\n 01 B3 PIC S9(9) COMP VALUE -123456.\n 01 B4 PIC S9(18) COMP VALUE -5.\n 01 B5 PIC S9(3)V99 COMP VALUE -1.25.\n 01 C5 PIC S9(4) COMP-5 VALUE -3.\n 01 Z1 PIC S9(3) VALUE -12.\n",
&[line("DISPLAY P1 ' ' P2 ' ' P3"), line("DISPLAY B1 ' ' B2 ' ' B3 ' ' B4 ' ' B5 ' ' C5 ' ' Z1"), line("GOBACK.")].concat(),
);
assert_eq!(under(&source, Dialect::Ibm), "001234N 0012345 0012345\n001K 0012 00012345O 00000000000000000N 0012N 0000L 01K\n");
assert_eq!(under(&source, Dialect::Gnucobol), "-0012345 +0012345 0012345\n-00012 00012 -0000123456 -00000000000000000005 -0000000125 -00003 01K\n");
let separate = source.replacen(" IDENTIFICATION", " CBL DISPSIGN(SEP)\n IDENTIFICATION", 1);
assert_eq!(under(&separate, Dialect::Gnucobol), under(&source, Dialect::Gnucobol).replace("01K", "-012"));
}
#[test]
fn display_writes_a_numeric_literal_without_its_decimal_point_under_gnucobol() {
let source = program("", "", &[line("DISPLAY 1.5 ' ' -1.50 ' ' .5 ' ' +0.25 ' ' 0.0 ' ' 007"), line("GOBACK.")].concat());
assert_eq!(under(&source, Dialect::Ibm), "1.5 -1.50 .5 +0.25 0.0 007\n");
assert_eq!(under(&source, Dialect::Gnucobol), "15 -150 5 +025 00 007\n");
let comma = source
.replace("DATA DIVISION.", "ENVIRONMENT DIVISION.\n CONFIGURATION SECTION.\n SPECIAL-NAMES.\n DECIMAL-POINT IS COMMA.\n DATA DIVISION.")
.replace("1.5 ' ' -1.50 ' ' .5 ' ' +0.25 ' ' 0.0", "1,5 ' ' -1,50 ' ' ,5 ' ' +0,25 ' ' 0,0");
assert_eq!(under(&comma, Dialect::Ibm), "1,5 -1,50 ,5 +0,25 0,0 007\n");
assert_eq!(under(&comma, Dialect::Gnucobol), "15 -150 5 +025 00 007\n");
}
#[test]
fn accept_at_the_end_of_sysin_moves_a_space_under_gnucobol() {
let source = program(
"",
" 01 N PIC 9(3) VALUE 7.\n 01 P PIC S9(3) COMP-3 VALUE 7.\n 01 B PIC S9(4) COMP VALUE 7.\n 01 X PIC X(4) VALUE 'QQQQ'.\n 01 E PIC ZZ9 VALUE 5.\n",
&[line("ACCEPT X"), line("ACCEPT N"), line("ACCEPT P"), line("ACCEPT B"), line("ACCEPT X"), line("ACCEPT E"), line("DISPLAY '[' N '][' P '][' B '][' X '][' E ']'"), line("GOBACK.")].concat(),
);
let run = |dialect: Dialect| {
let flags = [dialect.flag()];
let walker = Harness::source(&source).flags(&flags).sysin("AB\n").run(Executor::Interpreter);
let vm = Harness::source(&source).flags(&flags).sysin("AB\n").run(Executor::Vm);
assert_eq!((&vm.out, &vm.err), (&walker.out, &walker.err));
(walker.out, walker.err.lines().count())
};
assert_eq!(run(Dialect::Ibm), ("[007][007][0007][AB ][ 5]\n".to_owned(), 5));
assert_eq!(run(Dialect::Gnucobol), ("[000][+000][+00000][ ][ 0]\n".to_owned(), 5));
}
fn cobol(lines: &[&str]) -> String {
lines.iter().map(|l| format!(" {l}\n")).collect()
}
#[test]
fn a_dynamic_call_of_an_entry_name_shares_the_programs_storage_under_gnucobol() {
let source = cobol(&[
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. CALLER.",
"DATA DIVISION.",
"WORKING-STORAGE SECTION.",
"01 REC PIC X(5) VALUE 'HELLO'.",
"01 PGM PIC X(8) VALUE 'PAYMASTR'.",
"01 PGM2 PIC X(8) VALUE 'SUBPROG'.",
"PROCEDURE DIVISION.",
" CALL 'SUBPROG' USING REC",
" CALL PGM USING REC",
" CALL PGM USING REC",
" CANCEL PGM",
" CALL PGM USING REC",
" CALL 'SUBPROG' USING REC",
" CANCEL PGM2",
" CALL PGM USING REC",
" GOBACK.",
"END PROGRAM CALLER.",
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. SUBPROG.",
"DATA DIVISION.",
"WORKING-STORAGE SECTION.",
"01 COUNTER PIC 9 VALUE 0.",
"LINKAGE SECTION.",
"01 PAYREC PIC X(5).",
"PROCEDURE DIVISION USING PAYREC.",
" ADD 1 TO COUNTER",
" DISPLAY 'SUBPROG ' COUNTER",
" GOBACK.",
"ENTRY 'PAYMASTR' USING PAYREC.",
" ADD 1 TO COUNTER",
" DISPLAY 'PAYMASTR ' COUNTER",
" GOBACK.",
"END PROGRAM SUBPROG.",
]);
assert_eq!(under(&source, Dialect::Ibm), "SUBPROG 1\nPAYMASTR 1\nPAYMASTR 2\nPAYMASTR 1\nSUBPROG 2\nPAYMASTR 2\n");
assert_eq!(under(&source, Dialect::Gnucobol), "SUBPROG 1\nPAYMASTR 2\nPAYMASTR 3\nPAYMASTR 4\nSUBPROG 5\nPAYMASTR 1\n");
}
#[test]
fn a_shorter_external_record_shares_the_storage_under_gnucobol() {
let source = cobol(&[
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. MAIN.",
"DATA DIVISION.",
"WORKING-STORAGE SECTION.",
"01 SHARED PIC X(9) EXTERNAL.",
"PROCEDURE DIVISION.",
" MOVE 'ALPHA0010' TO SHARED",
" CALL 'SUB'",
" DISPLAY 'MAIN AFTER [' SHARED ']'",
" GOBACK.",
"END PROGRAM MAIN.",
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. SUB.",
"DATA DIVISION.",
"WORKING-STORAGE SECTION.",
"01 SHARED IS EXTERNAL.",
" 05 S-NAME PIC X(5).",
" 05 S-COUNT PIC 9(3).",
"PROCEDURE DIVISION.",
" DISPLAY 'SUB SEES [' SHARED ']'",
" MOVE 'OMEGA' TO S-NAME",
" ADD 1 TO S-COUNT",
" GOBACK.",
"END PROGRAM SUB.",
]);
let ibm = Harness::source(&source).run(Executor::Interpreter);
assert!(ibm.ending.unwrap_err().message.contains("EXTERNAL record SHARED has 9 bytes in the run unit, and this program describes 8"));
assert_eq!(under(&source, Dialect::Gnucobol), "SUB SEES [ALPHA001]\nMAIN AFTER [OMEGA0020]\n");
let longer = source.replace("PIC X(9) EXTERNAL", "PIC X(7) EXTERNAL").replace("'ALPHA0010'", "'ALPHA00'");
let flags = [Dialect::Gnucobol.flag()];
let refused = Harness::source(&longer).flags(&flags).run(Executor::Interpreter);
assert!(refused.ending.unwrap_err().message.contains("EXTERNAL record SHARED has 7 bytes in the run unit, and this program describes 8"));
}
#[test]
fn unsigned_zoned_items_of_one_length_compare_by_their_bytes_under_gnucobol() {
let source = program(
"",
" 01 VALUE0 PIC X(4) VALUE '00 0'.\n 01 VALUE1 REDEFINES VALUE0 PIC 9(4).\n 01 W PIC 9(4) VALUE 0.\n",
&[
line("IF VALUE1 = ZERO DISPLAY 'ZERO' ELSE DISPLAY 'ZONES' END-IF"),
line("IF VALUE1 = W DISPLAY 'W' ELSE DISPLAY 'NOT W' END-IF"),
line("GOBACK."),
]
.concat(),
);
assert_eq!(under(&source, Dialect::Ibm), "ZERO\nW\n");
assert_eq!(under(&source, Dialect::Gnucobol), "ZERO\nNOT W\n");
let optimized = [Dialect::Gnucobol.flag(), "--optimize=2"];
assert_eq!(Harness::source(&source).flags(&optimized).run(Executor::Interpreter).out, "ZERO\nNOT W\n");
assert_eq!(Harness::source(&source).flags(&["--optimize=2"]).run(Executor::Interpreter).out, "ZONES\nNOT W\n");
}