release: v0.4.20 — TIME→numeric ms scaling + WSTRING parsers#137
Merged
Conversation
… 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
chore(release): v0.4.20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 byT#5sproduced 61952 instead of 5000.Commits
fix(runtime,codegen): TO_<int>(TIME) returns ms; add WSTRING parsers; fix DATE literal unitchore(release): v0.4.20Scope (audit thread, not just the headline)
IECVar<int64_t>aliases, so the fix can't live in C++ overloading)iec_date.hppalready stores DATE as days)parseDateLiteralToNs→parseDateLiteralToDaysand updated the two call sitesWSTRING_TO_STRINGso parse rules stay byte-identical with the STRING setTests
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.20triggers the release workflow (on: push: tags: v*inrelease.yml) which publishes the tarball to GitHub Releases. openplc-web + openplc-editor get version-bumped to consume it.🤖 Generated with Claude Code