Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/language_status/cobolrt_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -1982,10 +1982,12 @@
{
"id": "RT-FUNC-DATE-INTEGER",
"family": "FUNC",
"title": "FUNCTION INTEGER-OF-DATE / DATE-OF-INTEGER: day 1 is 1 January 1601 (INTDATE(ANSI))",
"title": "FUNCTION INTEGER-OF-DATE / DATE-OF-INTEGER: day 1 is 1 January 1601 (INTDATE(ANSI)) or 15 October 1582 (INTDATE(LILIAN))",
"methods": [
"integerOfDate",
"dateOfInteger"
"dateOfInteger",
"integerOfDateLilian",
"dateOfIntegerLilian"
],
"ibm": {
"doc": "cobol-lr",
Expand Down
21 changes: 12 additions & 9 deletions docs/language_status/register/C5.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
id: "C5"
family: "C"
status: "MATCHED (NUMPROC) / MODELLED (TRUNC(OPT) and ARITH(EXTEND), det ports; a stop by name) / REFUSED (the rest)"
title: "Compiler options: NUMPROC MATCHED where IBM pins it (#4271); TRUNC(OPT) and ARITH(EXTEND) MODELLED (#4706); INTDATE(LILIAN) REFUSED"
status: "MATCHED (NUMPROC) / MODELLED (TRUNC(OPT) and ARITH(EXTEND), det ports, a stop by name; INTDATE(LILIAN), det port, no oracle) / REFUSED (the rest)"
title: "Compiler options: NUMPROC MATCHED where IBM pins it (#4271); TRUNC(OPT) and ARITH(EXTEND) MODELLED (#4706); INTDATE(LILIAN) MODELLED in the det port, refused by the oracle"
area: "compiler"
summary: "`NUMPROC(MIG)` as Enterprise COBOL 5+ compiles it (NOPFD), `NUMPROC(PFD)` with preferred signs (#4271); `TRUNC(OPT)` with conforming binary values, `ARITH(EXTEND)` with intermediates within 30 digits (#4706); `INTDATE(LILIAN)`"
reached: "NUMPROC: no; TRUNC(OPT): CBSA (det ports); ARITH(EXTEND): DBB EPSCSMRD (det ports)"
Expand Down Expand Up @@ -51,12 +51,15 @@ reached: "NUMPROC: no; TRUNC(OPT): CBSA (det ports); ARITH(EXTEND): DBB EPSCSMRD
COMPAT and EXTEND agree or is not judged. A floating-point expression under EXTEND is still refused at translation
(`Untranslatable`). A port without the stop (model, generated) is proven as COMPAT, a declared difference in its
evidence record (`option_differences`). Among the burned estates only DBB's EPSCSMRD carries `PROCESS ARITH(EXTEND)`.
- **REFUSED:** `INTDATE(LILIAN)`, from a CBL/PROCESS card or a case's `compiler_options`, stops the run
(`UnsupportedOption`).
- `INTDATE(LILIAN)` is pinned by IBM (day 1 is 15 October 1582 instead of 1 January 1601), and could be modelled
by the documented offset of the integer-date functions. Dates before 1601 cannot run on GnuCOBOL and would be
refused. No case needs it yet: no burned estate's program carries `INTDATE(LILIAN)` on a CBL/PROCESS card
(#4271 status check), so it stays refused by name rather than modelled.
- **INTDATE(LILIAN): MODELLED in the det port (#4706), REFUSED by the oracle.** IBM pins it (day 1 is 15 October 1582
instead of 1 January 1601: the same date is 6653 higher). The translator reads the option through the one resolver
(`program.intdate_lilian`: CBL / PROCESS cards over the compile step's PARM over the default, ANSI) and emits
`Funcs.integerOfDateLilian` / `dateOfIntegerLilian` for INTEGER-OF-DATE / DATE-OF-INTEGER (rule
`RT-FUNC-DATE-INTEGER`); it is fixed at translation, with no run-time switch. GnuCOBOL has no INTDATE, so
`COBC_OPTIONS` still refuses it (`UnsupportedOption`): a LILIAN program is not compared with cobc, and the port is
checked against IBM's documented day numbers (`test_intdate_lilian.py`), residual risk stated. Dates before day 1 of
the count are not defined by IBM and are not judged. No burned estate's program carries `INTDATE(LILIAN)` (#4271
status check, re-confirmed by #4706's slice 2), so no estate port changed.

### Compile options: the table (#4706)
One row per option that can change a result: (1) does it change results; (2) can cobc honour it; (3) what the det
Expand All @@ -69,7 +72,7 @@ instrument). The rows below are rendered by one table in code (`compiler_options
| `TRUNC(BIN)` | yes: a binary receiver keeps its bytes | `-fnotrunc` | `Cobol.swapTruncBinary(false)` | -- | C1 |
| `TRUNC(OPT)` | only for a binary value past its PICTURE, where IBM's result is unpredictable; conforming values compute as STD | `-fbinary-truncate` (STD: equal for conforming values) | STD, plus `Cobol.swapTruncOpt(true)`: a value past a COMP / COMP-4 / BINARY receiver's PICTURE stops by name ("TRUNC(OPT): value exceeds PICTURE; IBM result unpredictable") | a port without the stop (model, generated) is proven as STD: a declared difference (`option_differences`) | C1, C5 |
| `ARITH(COMPAT)` / `ARITH(EXTEND)` | yes: EXTEND widens intermediates to 31 digits and floats to extended precision | COMPAT flags (no cap on either side, C2) | COMPAT, plus `Cobol.swapArithExtend(true)`: an intermediate past 30 digits stops by name ("ARITH(EXTEND): an intermediate result of N digits"); a float is refused | a port without the stop is proven as COMPAT: a declared difference (`option_differences`) | C2, C5 |
| `INTDATE(ANSI)` / `INTDATE(LILIAN)` | yes: day 1 of INTEGER-OF-DATE moves from 1601-01-01 to 1582-10-15 | none for LILIAN | none | `INTDATE(LILIAN)` refused by name (`COBC_OPTIONS`) | C5 |
| `INTDATE(ANSI)` / `INTDATE(LILIAN)` | yes: day 1 of INTEGER-OF-DATE moves from 1601-01-01 to 1582-10-15 | none for LILIAN | `Funcs.integerOfDateLilian` / `dateOfIntegerLilian`, picked at translation | the oracle refuses `INTDATE(LILIAN)` by name (`COBC_OPTIONS`); the port is checked against IBM's day numbers, not cobc | C5 |
| `NUMPROC(NOPFD\|PFD\|MIG)` | only for a sign that is not preferred | none needed | PFD refuses a non-preferred sign (`Codec.numprocPfd`) | MIG compiles as NOPFD from Enterprise COBOL 5 (`numproc_mig`) | C5 |
| `ZONEDATA(PFD)` (IBM default) | no for valid zone bits (an invalid one is refused by C11) | none needed | refuses a non-digit zoned operand | -- | C11 |
| `ZONEDATA(NOPFD\|MIG)` | yes: defines what invalid zone bits / digits do in MOVE, compare and arithmetic (APAR PI57769) | none | none | refused by name | C5 |
Expand Down
17 changes: 17 additions & 0 deletions gitgalaxy/tools/cobol_to_java/det/cobolrt/Funcs.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,23 @@ public static BigDecimal dateOfInteger(BigDecimal days) {
return BigDecimal.valueOf(d.getYear() * 10000L + d.getMonthValue() * 100L + d.getDayOfMonth());
}

private static final java.time.LocalDate LILIAN_DAY_ZERO = java.time.LocalDate.of(1582, 10, 14);

/** INTEGER-OF-DATE under INTDATE(LILIAN) (#4706): day 1 is 15 October 1582, so the same date is 6653 higher than
* under ANSI. */
// [rule RT-FUNC-DATE-INTEGER]
public static BigDecimal integerOfDateLilian(BigDecimal yyyymmdd) {
int v = yyyymmdd.intValue();
java.time.LocalDate d = java.time.LocalDate.of(v / 10000, v / 100 % 100, v % 100);
return BigDecimal.valueOf(java.time.temporal.ChronoUnit.DAYS.between(LILIAN_DAY_ZERO, d));
}

/** DATE-OF-INTEGER under INTDATE(LILIAN) (#4706): days since 14 October 1582 -> YYYYMMDD. */
public static BigDecimal dateOfIntegerLilian(BigDecimal days) {
java.time.LocalDate d = LILIAN_DAY_ZERO.plusDays(days.longValue());
return BigDecimal.valueOf(d.getYear() * 10000L + d.getMonthValue() * 100L + d.getDayOfMonth());
}

/** FUNCTION TEST-NUMVAL: 0 when the argument is valid for NUMVAL, else the (1-based) position of the first
* character that makes it invalid -- the argument's length + 1 when it ends before a digit (GnuCOBOL,
* checked: one sign, leading or trailing; + - CR DB in upper case; nothing but spaces after a trailing one). */
Expand Down
20 changes: 20 additions & 0 deletions gitgalaxy/tools/cobol_to_java/det/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,24 @@ def arith_extend(program: Path, options: list[str] | None = None, declared: str
return str(eff.get("ARITH") or "").upper() == "EXTEND"


def intdate_lilian(program: Path, options: list[str] | None = None, declared: str | None = None) -> bool:
"""Whether the program is compiled with INTDATE(LILIAN) (#4706): its CBL / PROCESS cards over `options` (the compile
step's PARM), else IBM's default, ANSI -- the same resolver as TRUNC and ARITH (#4704)."""
from gitgalaxy.core.compiler_options import effective_with_defaults
from gitgalaxy.core.source_text import read_source

eff = effective_with_defaults(options, read_source(program, declared=declared).text)
return str(eff.get("INTDATE") or "").upper() == "LILIAN"


def with_intdate_lilian(java: str) -> str:
"""INTDATE(LILIAN) (#4706): the integer-date functions count from 1582-10-15, `Funcs.*Lilian`. Fixed at translation
(no run-time switch): the option is the program's own, so a LINK or CALL into another program leaves it alone."""
return java.replace("Funcs.integerOfDate(", "Funcs.integerOfDateLilian(").replace(
"Funcs.dateOfInteger(", "Funcs.dateOfIntegerLilian("
)


def numproc_pfd(program: Path, options: list[str] | None = None, declared: str | None = None) -> bool:
"""Whether the program runs under NUMPROC(PFD) (#4271): the NUMPROC option in effect -- its CBL / PROCESS cards over
`options` (the compile step's PARM), else IBM's default, NOPFD. NUMPROC(MIG) is NOPFD: Enterprise COBOL 5 and 6 no
Expand Down Expand Up @@ -1317,6 +1335,8 @@ def callee_types(cls: str) -> list[str]:
numproc_pfd(program, options, page),
arith_extend(program, options, page),
)
if intdate_lilian(program, options, page):
java = with_intdate_lilian(java)
return Result(java, service, stats)


Expand Down
119 changes: 119 additions & 0 deletions tests/cobol_mainframe/test_intdate_lilian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""#4706: INTDATE(LILIAN) in the det port (oracle_assumptions.md C5).

IBM is the reference. INTDATE(LILIAN) moves day 1 of FUNCTION INTEGER-OF-DATE / DATE-OF-INTEGER from 1 January 1601
(ANSI, the default) to 15 October 1582, so a date is 6653 higher (Enterprise COBOL for z/OS 6.x Language Reference,
"INTEGER-OF-DATE"; Programming Guide, "INTDATE"). GnuCOBOL has no INTDATE, so the oracle still refuses the option by
name and a LILIAN program is not compared with cobc: the det port is checked here against IBM's documented day
numbers. The translator reads the option through the one resolver (CBL / PROCESS cards over the PARM over the default)
and picks `Funcs.*Lilian` at translation.

The runtime tests need a JDK 17 (JAVA_HOME / JDK_17)."""

from __future__ import annotations

import sys
from pathlib import Path

import pytest

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "tests" / "tools"))
sys.path.insert(0, str(Path(__file__).resolve().parent))

# the epochs, a century year that is a leap year (1600) and one that is not (1700), a modern leap day, today, and the
# 1601 boundary where the two counts differ by exactly 6653
DATES = [15821015, 16000229, 16010101, 17000228, 20000229, 20261018]


def _expected():
import datetime

out = []
for ymd in DATES:
d = datetime.date(ymd // 10000, ymd // 100 % 100, ymd % 100)
ansi = (d - datetime.date(1600, 12, 31)).days
lilian = (d - datetime.date(1582, 10, 14)).days
out.append((ymd, ansi, lilian))
return out


def test_the_documented_anchors():
rows = {ymd: (a, lil) for ymd, a, lil in _expected()}
assert rows[15821015][1] == 1 # day 1 of LILIAN is 15 October 1582
assert rows[16010101] == (1, 6654) # day 1 of ANSI
assert all(lil - a == 6653 for ymd, (a, lil) in rows.items() if ymd >= 16010101)


def _service(java: str) -> str:
pytest.importorskip("tree_sitter_language_pack")
from gitgalaxy.tools.cobol_to_java.det import program as P

return P.with_intdate_lilian(java)


def test_the_translator_swaps_both_functions_and_only_them():
java = "a = Funcs.integerOfDate(x); b = Funcs.dateOfInteger(y); c = Funcs.integerPart(z);\n"
out = _service(java)
assert out == "a = Funcs.integerOfDateLilian(x); b = Funcs.dateOfIntegerLilian(y); c = Funcs.integerPart(z);\n"
assert _service(out) == out # idempotent: a swapped call is not swapped again


@pytest.mark.parametrize(
("options", "card", "lilian"),
[
([], "", False), # IBM's default
(["INTDATE(LILIAN)"], "", True), # the compile step's PARM
([], " CBL INTDATE(LILIAN)", True), # the program's own card
(["INTDATE(LILIAN)"], " CBL INTDATE(ANSI)", False), # the card wins over the PARM
([], " PROCESS INTDATE(LILIAN),TRUNC(BIN)", True),
],
)
def test_the_option_comes_through_the_one_resolver(options, card, lilian, tmp_path):
pytest.importorskip("tree_sitter_language_pack")
from gitgalaxy.tools.cobol_to_java.det import program as P

src = tmp_path / "p.cbl"
src.write_text((card + "\n" if card else "") + " IDENTIFICATION DIVISION.\n PROGRAM-ID. P.\n")
assert P.intdate_lilian(src, options) is lilian


def _jdk_or_skip():
pytest.importorskip("tree_sitter_language_pack")
import test_det_programs as T

if T._java() is None:
pytest.skip("needs a JDK 17 (JAVA_HOME / JDK_17)")
return T


def _program(name: str, dates: list[int], card: str) -> str:
proc = []
for ymd in dates:
proc += [
f"COMPUTE N = FUNCTION INTEGER-OF-DATE({ymd})",
f"DISPLAY 'I {ymd} ' N",
"COMPUTE D = FUNCTION DATE-OF-INTEGER(N)",
f"DISPLAY 'D {ymd} ' D",
]
src = [*([card] if card else []), " IDENTIFICATION DIVISION.", f" PROGRAM-ID. {name}.",
" DATA DIVISION.", " WORKING-STORAGE SECTION.", " 01 N PIC 9(8).", " 01 D PIC 9(8).",
" PROCEDURE DIVISION.", *(f" {x}" for x in proc), " GOBACK."] # fmt: skip
assert all(len(x) <= 72 for x in src)
return "\n".join(src) + "\n"


@pytest.mark.parametrize("typed", [False, True])
def test_the_port_counts_from_the_option_in_effect(typed, tmp_path):
T = _jdk_or_skip()
rows = _expected()
dates = DATES
for sub, card, col in (("lil", " CBL INTDATE(LILIAN)", 2), ("ans", "", 1)):
(tmp_path / sub).mkdir()
out = T._java_run("intd", _program("INTD", dates, card), tmp_path / sub, typed)
for row in rows:
ymd, day = row[0], row[col]
if day < 1: # before day 1 of the count: IBM defines no integer for it (ANSI: before 1601)
continue
assert f"I {ymd} {day:08d}" in out, (sub, ymd, out)
assert f"D {ymd} {ymd:08d}" in out, (sub, ymd, out) # DATE-OF-INTEGER inverts it under the same option
Loading