Skip to content

release: v0.4.20 — TIME→numeric ms scaling + WSTRING parsers#137

Merged
thiagoralves merged 4 commits into
mainfrom
development
Jun 1, 2026
Merged

release: v0.4.20 — TIME→numeric ms scaling + WSTRING parsers#137
thiagoralves merged 4 commits into
mainfrom
development

Conversation

@thiagoralves
Copy link
Copy Markdown
Contributor

Promotes v0.4.20 to production.

Headline

TO_<int>(TIME) family now returns milliseconds (was: low 16 / 32 / 64 bits of the raw nanosecond storage). Fixes the openplc-web user report where dragging a TO_UINT block fed by T#5s produced 61952 instead of 5000.

Commits

Scope (audit thread, not just the headline)

  • TIME / LTIME / TOD / LTOD / DT / LDT → numeric: scale ns → ms in codegen (the runtime types are all IECVar<int64_t> aliases, so the fix can't live in C++ overloading)
  • DATE / LDATE → numeric: pass-through (days since epoch — the natural unit; iec_date.hpp already stores DATE as days)
  • DATE literal lowering: was emitting nanoseconds, runtime expects days — renamed parseDateLiteralToNsparseDateLiteralToDays and updated the two call sites
  • PROGRAM-level VAR initialisers for DATE / TOD / DT: previously emitted verbatim into the constructor init list (invalid C++); now lowered the same way the FB init path already did
  • WSTRING → numeric: missing surface added, routes through WSTRING_TO_STRING so parse rules stay byte-identical with the STRING set

Tests

1985 passed / 7 skipped / 0 failed. New coverage: 9 TS-level codegen-string tests + 21 ST-validation end-to-end tests (compile ST → emit C++ → g++ → run → assert numeric output) across TIME / TOD / DT / DATE / STRING / WSTRING.

Post-merge

Tagging v0.4.20 triggers the release workflow (on: push: tags: v* in release.yml) which publishes the tarball to GitHub Releases. openplc-web + openplc-editor get version-bumped to consume it.

🤖 Generated with Claude Code

thiagoralves and others added 4 commits May 31, 2026 11:13
… fix DATE literal unit

User report from openplc-web: a TO_UINT block fed by a T#5s constant
produced 61952 in the simulator instead of 5000.  61952 = 0xF200 =
low 16 bits of 5e9.  Strucpp's `TO_UINT<T>(T)` runtime template was
just `static_cast`ing the raw int64_t nanosecond storage straight to
`uint16_t` — losing every bit of millisecond semantics.

This is structurally hard to fix at the C++ runtime alone because
`IEC_TIME`, `IEC_LTIME`, `IEC_TOD`, `IEC_LTOD`, `IEC_DT`, `IEC_LDT`,
`IEC_DATE`, `IEC_LDATE` are all `using ... = IECVar<int64_t>` aliases
in `iec_var.hpp`.  After preprocessing they collapse to the same C++
type, so a `TO_UINT(IEC_TIME)` overload is indistinguishable from
`TO_UINT(IEC_DATE)`.  The IEC type label only survives at the
language layer — the fix has to live in codegen.

## Fix 1 — temporal → numeric scaling (the user's reported bug)

`wrapTemporalArgForNumericConversion` in `codegen.ts` wraps the
argument with the matching `*_TO_MS` helper before emitting the
conversion call.  Applied at both the `*_TO_*` conversion branch
(explicit `TIME_TO_UINT` etc.) and the std-lookup branch (bare
`TO_UINT(time_var)`):

  - TIME / LTIME            → `TIME_TO_MS` (ns → ms)
  - TOD / LTOD / TIME_OF_DAY / LTIME_OF_DAY → `TOD_TO_MS`
  - DT / LDT / DATE_AND_TIME / LDATE_AND_TIME → `DT_TO_MS`
  - DATE / LDATE: NOT wrapped — DATE is stored as days, which is
    already the natural integer answer.

Matches `TO_TIME(integer)`'s existing "integer means milliseconds"
convention.  Explicit `TIME_TO_S`, `TIME_TO_US`, etc. still work
unchanged for callers that want a different unit.

Added `DT_TO_MS` to `iec_dt.hpp` to complete the helper trio
(`TIME_TO_MS` and `TOD_TO_MS` already shipped).

## Fix 2 — DATE literal storage unit (pre-existing, found by audit)

`parseDateLiteralToNs` was lowering `DATE#1970-01-15` to nanoseconds,
but `iec_date.hpp` stores DATE as days (see `DT_FROM_DATE_AND_TOD`'s
`iec_unwrap(date) * DT_NS_PER_DAY` math — that only works if DATE is
days).  Result: every `DATE_TO_DAYS`, `DT_FROM_DATE_AND_TOD`, and
`TO_<int>(DATE)` call returned garbage.  Renamed to
`parseDateLiteralToDays`; codegen + type-codegen consumers updated.

## Fix 3 — PROGRAM-level VAR initialisers for DATE / TOD / DT

`getDefaultValue` (the PROGRAM init path) only lowered TIME literals.
DATE / TOD / DT initialisers fell through verbatim, emitting invalid
C++ like `D(DATE#1970-01-15)`.  Added the matching branches so the
PROGRAM and FUNCTION_BLOCK init paths agree.

## Fix 4 — WSTRING → numeric (missing surface)

`iec_string.hpp` had a full `TO_<int>(STRING)` / `TO_REAL(STRING)`
set built on `strtoul` / `strtol` / `strtod` (which already parses
`'3.14'` to 3 for integer targets and 3.14 for REAL — verified by
new tests).  The WSTRING side had no parsers — `TO_UINT(WSTRING)`
fell through the generic `CONVERT` template and didn't compile.
Added the symmetric WSTRING set that routes through the existing
`WSTRING_TO_STRING` transcoder before parsing, so the parse rules
stay byte-identical between STRING and WSTRING.

## Tests

- `tests/backend/codegen-functions.test.ts`: 9 new TS-level tests
  covering every TIME / TOD / DT / DATE source path plus the
  pass-through cases (TO_TIME, plain numeric source).
- `tests/st-validation/data_types/time_types.st` +
  `test_time_types.st`: rewrote to assert ms semantics across
  TIME / TOD / DT / DATE, plus the explicit `TIME_TO_US` /
  `TIME_TO_S` helpers.  Old `test_time_types.st` asserted the
  broken `1_500_000_000` (raw ns) value — it was validating the
  bug.  Updated to assert `1500` (the correct ms count).
- `tests/st-validation/data_types/string_conversions.st` +
  `test_string_conversions.st`: 13 new end-to-end tests covering
  STRING and WSTRING parsing into every integer width, REAL,
  LREAL, BOOL, and the strtoul "stop at first non-digit" edge
  case (`'12abc'` → 12).

Full suite: 1985 passed, 7 skipped, 0 failed.

Refs: openplc-web user report from the cmpoevbbv00zc07n9urou381a
blink-demo-multilanguage project on production.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(runtime,codegen): TO_<int>(TIME) returns ms; add WSTRING parsers; fix DATE literal unit
@thiagoralves thiagoralves merged commit c853481 into main Jun 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant