Bläddra i källkod

library: ISO SysClock (DateTime/GetClock/IsValidDateTime)

The stdlib SysClock was a PIM-style stub with only Time/Clock, so the
corpus testclock/testclock2 failed on CanGetClock/DateTime/GetClock.

Add the ISO 10514-1 interface: maxSecondParts; Month, Day, Hour, Min,
Sec, Fraction, UTCDiff and DateTime; CanGetClock, CanSetClock,
IsValidDateTime, GetClock, SetClock.  GetClock converts the shim's
epoch seconds to a UTC civil date (Hinnant's algorithm; zone = 0, no
DST); SetClock is a no-op because CanSetClock is FALSE.  Time/Clock
remain as PIM-style extras.

Corpus +2: testclock, testclock2.  Regression TSysClock (session).
Suite 258/258, fixpoint OK (4,007,769 bytes), corpus 505/612,
0 crashes.
Eric Streit 1 dag sedan
förälder
incheckning
4d05afe62c

+ 2 - 2
README.md

@@ -47,8 +47,8 @@ docs/              STATUS.md, plan.md, plan-*.md, analysis_*.md, summary_*.md
 
 ## Status (2026-10-22)
 
-- Suite **257/257**; self-hosting **fixpoint OK** (byte-identical
-  stage2/stage3, 4,007,769 bytes); corpus **503/612 compile-OK,
+- Suite **258/258**; self-hosting **fixpoint OK** (byte-identical
+  stage2/stage3, 4,007,769 bytes); corpus **505/612 compile-OK,
   0 crashes** (`tools/v3-corpus`; the corpus is gm2's testsuite, so it
   mixes conforming Modula-2 with gm2 leniencies — see `docs/gaps/`).
 - Active branch **`master`**.

+ 1 - 0
compiler/run_tests.sh

@@ -308,6 +308,7 @@ expect_run_files DOpaqueProg 55 d_opaque.def d_opaque.mod d_opaque_prog.mod
 expect_run_files TString 42 d_string.def d_string.mod t_string.mod
 expect_run_files FioProg 42 ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod ../runtime/syslib/FileIO.def ../runtime/syslib/FileIO.mod fio_prog.mod
 expect_run_files StorageProg 42 ../stdlib/storage.def ../stdlib/storage.mod storage_prog.mod
+expect_run_files TSysClock 42 ../stdlib/sysclock.def ../stdlib/sysclock.mod t_sysclock.mod
 expect_run_files TAst 42 \
   ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod \
   ../runtime/syslib/FileIO.def ../runtime/syslib/FileIO.mod \

+ 25 - 0
compiler/tests/t_sysclock.mod

@@ -0,0 +1,25 @@
+MODULE TSysClock;
+// ISO SysClock: CanGetClock/CanSetClock, GetClock fills a valid UTC
+// DateTime, IsValidDateTime accepts it, SetClock is a no-op. Exit 42.
+IMPORT SysClock;
+VAR ExitCode : INTEGER;
+VAR dt : SysClock.DateTime;
+BEGIN
+  IF SysClock.CanGetClock() AND NOT SysClock.CanSetClock() THEN
+    SysClock.GetClock(dt);
+    SysClock.SetClock(dt);
+    IF SysClock.IsValidDateTime(dt)
+       AND (dt.year >= 2020) AND (dt.year <= 2100)
+       AND (dt.month >= 1) AND (dt.month <= 12)
+       AND (dt.day >= 1) AND (dt.day <= 31)
+       AND (dt.hour <= 23) AND (dt.minute <= 59) AND (dt.second <= 59)
+       AND (dt.fractions = 0) AND (dt.zone = 0)
+       AND NOT dt.summerTimeFlag THEN
+      ExitCode := 42
+    ELSE
+      ExitCode := 1
+    END
+  ELSE
+    ExitCode := 2
+  END
+END TSysClock.

+ 2 - 2
docs/INDEX.md

@@ -37,8 +37,8 @@ bisecting *why* a decision was made, not for reading end-to-end).
 - **`summary_session-2026-10-22.md`** — corpus triage: PIM/ISO
   multi-index arrays, typed CHAR/enum subranges, ordinal `FOR`, ISO
   empty string, typed libc prototypes, ISO SYSTEM sized types,
-  procedure-type syntax, true 8/16-bit sized storage (corpus 467 →
-  503).
+  procedure-type syntax, true 8/16-bit sized storage, ISO `SysClock`
+  (corpus 467 → 505).
 - **`summary_session-2026-10-19.md`** — the whole 2026-10-19 session
   (P0–P5, gap14, robustness, library; corpus 405 → 467).
 - **2026-10-19 (P0–P4):** generics double-instantiation fixed (lower each

+ 9 - 5
docs/STATUS.md

@@ -4,10 +4,10 @@ This answers "where are we now?".
 
 ## Numbers
 
-- Suite **257/257** (`compiler/run_tests.sh`).
+- Suite **258/258** (`compiler/run_tests.sh`).
 - Self-hosting **FIXPOINT OK** — stage2 and stage3 images are
   byte-identical (4,007,769 bytes).
-- Corpus **503 / 612** sources compile with 0 errors, **0 crashes**
+- Corpus **505 / 612** sources compile with 0 errors, **0 crashes**
   (`tools/v3-corpus/corpus.sh` over the gm2 testsuite; the harness now
   resolves V3's own shipped libraries); the rest are genuine language
   gaps, **gm2 leniencies V3 deliberately rejects**, or unresolved
@@ -184,8 +184,12 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
    against the real `.def` instead of being mislabelled `LIB`.
    Building `StreamFile` also fixed a real bug: a **set CONST exported
    from a definition module** is now emitted under its global name
-   (`<unit>_<name>`), so a client's qualified reference binds.  Still
-   missing: gm2 runtime modules (`M2RTS`, `Builtins`, `Processes`, …).
+   (`<unit>_<name>`), so a client's qualified reference binds.
+   `SysClock` now implements the ISO 10514-1 API (`DateTime`,
+   `CanGetClock`, `GetClock`, `IsValidDateTime`, `CanSetClock`/
+   `SetClock` as an unsupported no-op) on top of the shim's time calls,
+   with `Time`/`Clock` kept as PIM-style extras.  Still missing: gm2
+   runtime modules (`M2RTS`, `Builtins`, `Processes`, …).
 5. **Housekeeping** — `docs/INDEX.md` is the entry point; the
    unreferenced historical records are under `docs/archive/` (2026-10-19).
    `resolve-check` was merged to `master`.
@@ -195,7 +199,7 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
 ```sh
 cd compiler
 ./build.sh          # M2.atg -> scanner/parser/driver -> ./M2
-./run_tests.sh      # 257/257
+./run_tests.sh      # 258/258
 cd ..
 ./bootstrap/fixpoint.sh   # FIXPOINT OK (byte-identical stage2/stage3)
 ```

+ 2 - 2
docs/comparison-PIM4-gm2-V3.md

@@ -1,7 +1,7 @@
 # PIM4 vs GNU Modula-2 vs V3 — feature comparison
 
-**Date:** 2026-10-22 · **V3 state:** `v3-word16` (suite 257/257,
-self-hosting fixpoint OK 4,007,769 bytes, corpus 503/612, 0 crashes).
+**Date:** 2026-10-22 · **V3 state:** `v3-sysclock` (suite 258/258,
+self-hosting fixpoint OK 4,007,769 bytes, corpus 505/612, 0 crashes).
 
 **Scope.** **PIM4** is Wirth's *Programming in Modula-2*, 4th edition
 (the language specification, not a compiler).  **GNU Modula-2 (gm2)** is

+ 2 - 1
docs/features.md

@@ -169,7 +169,8 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
   (Python-style), `Insert`, `Replace`, `Capitalize`), `CharClass`,
   `Files` (layered on `IOChan`),
   `Conversions` (ISO `ConvResults` statuses + BOOLEAN shortcuts),
-  `RealIO`, `ProgramArgs`, `SysClock`,
+  `RealIO`, `ProgramArgs`, `SysClock` (ISO `DateTime`/`GetClock` +
+  PIM `Time`/`Clock`),
   `IOChan` (streams + files; `Position`/`Seek`/`Rewind`), `Storage`.
 - ✅ gm2/ISO text + conversion library added: `ConvTypes`, `IOConsts`,
   `SIOResult`, `TERMINATION`, `StrIO`, `InOut`, `StdIO`, `WholeStr`,

+ 23 - 13
docs/summary_session-2026-10-22.md

@@ -1,4 +1,4 @@
-# Session 2026-10-22 — corpus triage, libc, SYSTEM, proc types, sized storage
+# Session 2026-10-22 — corpus triage, libc, SYSTEM, proc types, sized storage, SysClock
 
 Branch **`master`**.  Continues `summary_session-2026-10-19.md`; the
 only plan source was its "Remaining" list (corpus `LANG` triage).
@@ -7,16 +7,16 @@ only plan source was its "Remaining" list (corpus `LANG` triage).
 
 | gate | session start | end |
 | --- | --- | --- |
-| suite (`compiler/run_tests.sh`) | 249/249 | **257/257** |
+| suite (`compiler/run_tests.sh`) | 249/249 | **258/258** |
 | fixpoint (`bootstrap/fixpoint.sh`) | OK (3,882,337 B) | **OK (4,007,769 B)** |
-| corpus (`tools/v3-corpus/corpus.sh`) | 467/612 | **503/612, 0 crashes** |
+| corpus (`tools/v3-corpus/corpus.sh`) | 467/612 | **505/612, 0 crashes** |
 
 Reproduce:
 
 ```sh
-cd compiler && ./build.sh && ./run_tests.sh          # 257/257
+cd compiler && ./build.sh && ./run_tests.sh          # 258/258
 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
-tools/v3-corpus/corpus.sh                            # 503 OK, 0 crash
+tools/v3-corpus/corpus.sh                            # 505 OK, 0 crash
 ```
 
 ## Triage method
@@ -31,7 +31,7 @@ source line.  They fall into these buckets:
 | declaration order (type/const/var used before its declaration) | ~25 | correctly rejected (`docs/gaps/`); not a target |
 | gm2 extensions (`ASM`, `''` NUL char, `666B`, absolute `VAR [addr]`) | ~7 | not a target unless chosen |
 | ISO features actually missing | ~20 | `PACKEDSET`, exceptions/`UNIQUE`, `__FUNCTION__`, `PROC` as a result type; sized SYSTEM types **fixed below** |
-| **conforming** clusters: arrays, subranges, ordinal `FOR`, empty string, libc prototypes, SYSTEM sized types, procedure types | 36 | **fixed this session** |
+| **conforming** clusters: arrays, subranges, ordinal `FOR`, empty string, libc prototypes, SYSTEM sized types, procedure types, SysClock | 38 | **fixed this session** |
 
 ## What landed
 
@@ -146,6 +146,17 @@ bits).  `SET OF WORD16`/`SET OF INTEGER16` work (65536-element span,
 the documented set limit).  `WORD`/`WORD32` remain the 32-bit word.
 Regression `t_word16`.
 
+### ISO `SysClock` — `stdlib/sysclock.def` / `.mod`
+The stdlib stub only had PIM-style `Time`/`Clock`, so `testclock` and
+`testclock2` failed on `CanGetClock`/`DateTime`/`GetClock`.  `SysClock`
+now implements the ISO 10514-1 interface: `maxSecondParts`, `Month`/
+`Day`/`Hour`/`Min`/`Sec`/`Fraction`/`UTCDiff`, `DateTime`,
+`CanGetClock`, `CanSetClock`, `IsValidDateTime`, `GetClock`,
+`SetClock`.  `GetClock` converts the shim's epoch seconds to a UTC
+civil date (Hinnant's algorithm; `zone = 0`, no DST); `SetClock` is a
+no-op with `CanSetClock = FALSE`.  `Time`/`Clock` remain as extras.
+Corpus **+2**: `testclock`, `testclock2`.  Regression `TSysClock`.
+
 ## Commits
 
 Three tagged states, each green on all gates:
@@ -159,6 +170,7 @@ Three tagged states, each green on all gates:
 | `f236600` | `v3-proc-types` | ISO procedure-type syntax (+4) |
 | `7621f16` | `v3-byte` | true 8-bit `BYTE`/`SBYTE` storage |
 | `a5d0f16` | `v3-word16` | true 16-bit `WORD16`/`CARDINAL16`/`INTEGER16` |
+| `pending` | `v3-sysclock` | ISO `SysClock` API |
 
 ## Lessons
 
@@ -195,25 +207,23 @@ word model, so the narrow integer/word names share `INTEGER`/
 1. **A real `libc.def`** — the built-in prototype table covers the
    common functions; a definition module (or `DEFINITION MODULE FOR
    "C"`) would let users extend it.  Not needed for the corpus now.
-2. **ISO `SysClock`** — the stdlib stub lacks `DateTime`,
-   `CanGetClock`, `GetClock`, `SetClock` (`testclock`, `testclock2`).
-3. **`PACKEDSET`**, ISO exceptions/`UNIQUE`, `__FUNCTION__` — each
+2. **`PACKEDSET`**, ISO exceptions/`UNIQUE`, `__FUNCTION__` — each
    one or two files; larger or gm2-flavoured.
-4. **Runtime subrange range checks** — the remaining conformance
+3. **Runtime subrange range checks** — the remaining conformance
    difference from this session's subrange work.
-5. `STATUS.md` "what remains" is the live list.
+4. `STATUS.md` "what remains" is the live list.
 
 ## Publication
 
 - Branch `master`; feature tags `v3-corpus-triage`, `v3-empty-string`,
   `v3-libc-prototypes`, `v3-system-types`, `v3-proc-types`, `v3-byte`,
-  `v3-word16`.
+  `v3-word16`, `v3-sysclock`.
 - `v3-session-2026-10-22` is a **moving** session tag: it was advanced
   after publication, so the remote rejects updating it without
   `--force`.  The feature tags are immutable checkpoints.
 
 ```sh
-git push origin master v3-word16
+git push origin master v3-sysclock
 # only if you want the moving tag advanced on the remote too:
 git push --force origin refs/tags/v3-session-2026-10-22
 ```

+ 44 - 8
stdlib/sysclock.def

@@ -1,16 +1,52 @@
 DEFINITION MODULE SysClock;
-(* Wall-clock and processor time, bound to the runtime shim (libc
-   time/clock). *)
+(* ISO 10514-1 clock interface plus PIM-style Time/Clock helpers.
+   GetClock returns UTC (zone = 0, no summer time); SetClock is not
+   supported (CanSetClock = FALSE). *)
 
+CONST maxSecondParts = 1000000;
 CONST ClocksPerSec = 1000000;
-(* Processor-time ticks per second on the supported host (libc
-   CLOCKS_PER_SEC is 1,000,000). *)
+(* Processor-time ticks per second (libc CLOCKS_PER_SEC). *)
 
-PROCEDURE Time() : LONGINT;
+TYPE
+  Month    = [1 .. 12];
+  Day      = [1 .. 31];
+  Hour     = [0 .. 23];
+  Min      = [0 .. 59];
+  Sec      = [0 .. 59];
+  Fraction = [0 .. maxSecondParts];
+  UTCDiff  = [-780 .. 720];
+  DateTime =
+    RECORD
+      year:           CARDINAL;
+      month:          Month;
+      day:            Day;
+      hour:           Hour;
+      minute:         Min;
+      second:         Sec;
+      fractions:      Fraction;   (* parts of a second *)
+      zone:           UTCDiff;    (* minutes to add to local time for UTC *)
+      summerTimeFlag: BOOLEAN;
+    END;
+
+PROCEDURE CanGetClock (): BOOLEAN;
+(* TRUE: the system clock can be read. *)
+
+PROCEDURE CanSetClock (): BOOLEAN;
+(* FALSE: V3 does not set the system clock. *)
+
+PROCEDURE IsValidDateTime (userData: DateTime): BOOLEAN;
+(* Range-check a date/time value. *)
+
+PROCEDURE GetClock (VAR userData: DateTime);
+(* The current UTC date and time. *)
+
+PROCEDURE SetClock (userData: DateTime);
+(* Unsupported: CanSetClock is FALSE, so this is a no-op. *)
+
+PROCEDURE Time () : LONGINT;
 (* Seconds since the Unix epoch (1 January 1970, 00:00 UTC). *)
 
-PROCEDURE Clock() : LONGINT;
-(* Processor time used by the program so far, in ticks of
-   ClocksPerSec per second. *)
+PROCEDURE Clock () : LONGINT;
+(* Processor time used so far, in ticks of ClocksPerSec per second. *)
 
 END SysClock.

+ 71 - 1
stdlib/sysclock.mod

@@ -1,5 +1,6 @@
 IMPLEMENTATION MODULE SysClock;
-(* Binds the shim time routines directly. *)
+(* ISO 10514-1 clock interface, bound to the shim's time/clock calls.
+   GetClock computes the UTC civil date from the Unix epoch seconds. *)
 
 PROCEDURE m2time() : LONGINT;
   EXTERNAL;
@@ -17,4 +18,73 @@ BEGIN
   RETURN m2clock()
 END Clock;
 
+PROCEDURE DaysToDate (days: INTEGER; VAR y, m, d: INTEGER);
+(* proleptic Gregorian civil date from days since 1970-01-01
+   (Hinnant's algorithm; INTEGER is wide enough for the epoch range). *)
+  VAR z, era, doe, yoe, doy, mp: INTEGER;
+BEGIN
+  z := days + 719468;
+  era := z DIV 146097;
+  doe := z - era * 146097;
+  yoe := (doe - doe DIV 1460 + doe DIV 36524 - doe DIV 146096) DIV 365;
+  y := yoe + era * 400;
+  doy := doe - (365 * yoe + yoe DIV 4 - yoe DIV 100);
+  mp := (5 * doy + 2) DIV 153;
+  d := doy - (153 * mp + 2) DIV 5 + 1;
+  IF mp < 10 THEN m := mp + 3 ELSE m := mp - 9 END;
+  IF m <= 2 THEN INC(y) END
+END DaysToDate;
+
+PROCEDURE CanGetClock () : BOOLEAN;
+BEGIN
+  RETURN TRUE
+END CanGetClock;
+
+PROCEDURE CanSetClock () : BOOLEAN;
+BEGIN
+  RETURN FALSE
+END CanSetClock;
+
+PROCEDURE IsValidDateTime (userData: DateTime) : BOOLEAN;
+BEGIN
+  RETURN (userData.year <= 9999)
+     AND (userData.month >= 1) AND (userData.month <= 12)
+     AND (userData.day >= 1) AND (userData.day <= 31)
+     AND (userData.hour <= 23)
+     AND (userData.minute <= 59)
+     AND (userData.second <= 59)
+     AND (userData.fractions <= maxSecondParts)
+     AND (userData.zone >= -780) AND (userData.zone <= 720)
+END IsValidDateTime;
+
+PROCEDURE GetClock (VAR userData: DateTime);
+  VAR secs, days, sod, y, m, d, hh, mm, ss: INTEGER;
+BEGIN
+  secs := INTEGER(Time());
+  days := secs DIV 86400;
+  sod := secs MOD 86400;
+  IF sod < 0 THEN
+    (* floor division for times before 1970 *)
+    DEC(days); INC(sod, 86400)
+  END;
+  DaysToDate(days, y, m, d);
+  hh := sod DIV 3600;
+  mm := (sod MOD 3600) DIV 60;
+  ss := sod MOD 60;
+  userData.year := y;
+  userData.month := m;
+  userData.day := d;
+  userData.hour := hh;
+  userData.minute := mm;
+  userData.second := ss;
+  userData.fractions := 0;
+  userData.zone := 0;
+  userData.summerTimeFlag := FALSE
+END GetClock;
+
+PROCEDURE SetClock (userData: DateTime);
+BEGIN
+  (* unsupported: CanSetClock is FALSE *)
+END SetClock;
+
 END SysClock.