diff --git a/package-lock.json b/package-lock.json index 1abc871..78ea141 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "strucpp", - "version": "0.4.18", + "version": "0.4.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "strucpp", - "version": "0.4.18", + "version": "0.4.19", "license": "GPL-3.0-or-later", "dependencies": { "chevrotain": "^11.0.0" diff --git a/package.json b/package.json index d17d301..fdfb86d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "strucpp", - "version": "0.4.18", + "version": "0.4.19", "description": "IEC 61131-3 Structured Text to C++ Compiler", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/runtime/include/iec_date.hpp b/src/runtime/include/iec_date.hpp index 9e3024d..76d23dc 100644 --- a/src/runtime/include/iec_date.hpp +++ b/src/runtime/include/iec_date.hpp @@ -3,303 +3,87 @@ // This file is part of the STruC++ Runtime Library and is covered by the // STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. /** - * STruC++ Runtime - IEC Date Types + * STruC++ Runtime - IEC DATE Standard Functions * - * This header provides value classes for IEC 61131-3 DATE and LDATE types. - * DATE represents calendar dates (stored as days since epoch 1970-01-01). - * LDATE is the IEC v3 long variant with the same precision. + * IEC 61131-3 standard functions on the DATE (and LDATE) calendar + * types. DATE / LDATE are stored as signed days since the Unix epoch + * (1970-01-01) in `IECVar` — the same generic per-variable + * wrapper used for every other elementary type. Codegen emits DATE + * variables as `IEC_DATE` (the `IECVar` alias) and the + * functions below take/return `IEC_DATE` so they're directly callable + * from generated POU code. + * + * Scope: only the standard arithmetic / comparison / round-trip + * functions. Calendar component accessors (YEAR, MONTH, DAY, + * DAY_OF_WEEK, DAY_OF_YEAR, …) are intentionally NOT here — those are + * OSCAT-style extensions and user libraries (OSCAT, codesys-v23 + * stdlib imports, …) ship their own implementations. Providing them + * here would create overload ambiguity when a project imports such a + * library. + * + * Historical note: an earlier `DateValue` + `IECDateVar` value- + * class design lived here. Codegen never adopted it (DATE variables + * were always declared as `IEC_DATE`), so the parallel API was dead + * from generated code's perspective. Removed in favour of a single + * IECVar-based surface. See `iec_time.hpp` for the matching note on + * the TIME family. */ #pragma once #include #include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_traits.hpp" namespace strucpp { -template -class DateValue { -public: - using storage_type = StorageType; - - constexpr DateValue() noexcept : days_since_epoch_(0) {} - constexpr explicit DateValue(StorageType days) noexcept : days_since_epoch_(days) {} - - static constexpr DateValue from_days(int64_t days) noexcept { - return DateValue(static_cast(days)); - } - - static constexpr DateValue from_ymd(int year, int month, int day) noexcept { - int a = (14 - month) / 12; - int y = year + 4800 - a; - int m = month + 12 * a - 3; - int jdn = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; - constexpr int UNIX_EPOCH_JDN = 2440588; - return DateValue(static_cast(jdn - UNIX_EPOCH_JDN)); - } - - constexpr StorageType to_days() const noexcept { - return days_since_epoch_; - } - - void to_ymd(int& year, int& month, int& day) const noexcept { - constexpr int UNIX_EPOCH_JDN = 2440588; - int jdn = static_cast(days_since_epoch_) + UNIX_EPOCH_JDN; - - int a = jdn + 32044; - int b = (4 * a + 3) / 146097; - int c = a - (146097 * b) / 4; - int d = (4 * c + 3) / 1461; - int e = c - (1461 * d) / 4; - int m = (5 * e + 2) / 153; - - day = e - (153 * m + 2) / 5 + 1; - month = m + 3 - 12 * (m / 10); - year = 100 * b + d - 4800 + m / 10; - } - - int year() const noexcept { - int y = 0, m = 0, d = 0; - to_ymd(y, m, d); - return y; - } - - int month() const noexcept { - int y = 0, m = 0, d = 0; - to_ymd(y, m, d); - return m; - } - - int day() const noexcept { - int y = 0, m = 0, d = 0; - to_ymd(y, m, d); - return d; - } - - constexpr int day_of_week() const noexcept { - return static_cast((days_since_epoch_ + 4) % 7); - } - - int day_of_year() const noexcept { - int y = 0, m = 0, d = 0; - to_ymd(y, m, d); - DateValue jan1 = from_ymd(y, 1, 1); - return static_cast(days_since_epoch_ - jan1.days_since_epoch_) + 1; - } - - constexpr operator StorageType() const noexcept { return days_since_epoch_; } - - constexpr DateValue operator+(int64_t days) const noexcept { - return DateValue(days_since_epoch_ + static_cast(days)); - } - - constexpr DateValue operator-(int64_t days) const noexcept { - return DateValue(days_since_epoch_ - static_cast(days)); - } - - constexpr int64_t operator-(const DateValue& other) const noexcept { - return days_since_epoch_ - other.days_since_epoch_; - } - - DateValue& operator+=(int64_t days) noexcept { - days_since_epoch_ += static_cast(days); - return *this; - } - - DateValue& operator-=(int64_t days) noexcept { - days_since_epoch_ -= static_cast(days); - return *this; - } - - constexpr bool operator==(const DateValue& other) const noexcept { - return days_since_epoch_ == other.days_since_epoch_; - } - - constexpr bool operator!=(const DateValue& other) const noexcept { - return days_since_epoch_ != other.days_since_epoch_; - } - - constexpr bool operator<(const DateValue& other) const noexcept { - return days_since_epoch_ < other.days_since_epoch_; - } - - constexpr bool operator<=(const DateValue& other) const noexcept { - return days_since_epoch_ <= other.days_since_epoch_; - } - - constexpr bool operator>(const DateValue& other) const noexcept { - return days_since_epoch_ > other.days_since_epoch_; - } - - constexpr bool operator>=(const DateValue& other) const noexcept { - return days_since_epoch_ >= other.days_since_epoch_; - } - -private: - StorageType days_since_epoch_; -}; - -using IEC_DATE_Value = DateValue; -using IEC_LDATE_Value = DateValue; - -template -class IECDateVar { -public: - using value_type = T; - - IECDateVar() noexcept : value_{}, forced_{false}, forced_value_{} {} - explicit IECDateVar(T v) noexcept : value_{v}, forced_{false}, forced_value_{} {} - IECDateVar(const IECDateVar&) = default; - IECDateVar(IECDateVar&&) = default; - IECDateVar& operator=(const IECDateVar&) = default; - IECDateVar& operator=(IECDateVar&&) = default; - - T get() const noexcept { - return forced_ ? forced_value_ : value_; - } - - void set(T v) noexcept { - value_ = v; - } - - T get_underlying() const noexcept { - return value_; - } - - void force(T v) noexcept { - forced_ = true; - forced_value_ = v; - } - - void unforce() noexcept { - forced_ = false; - } - - bool is_forced() const noexcept { - return forced_; - } - - T get_forced_value() const noexcept { - return forced_value_; - } - - operator T() const noexcept { - return get(); - } - - IECDateVar& operator=(T v) noexcept { - set(v); - return *this; - } - - IECDateVar& operator+=(int64_t days) noexcept { - set(get() + days); - return *this; - } - - IECDateVar& operator-=(int64_t days) noexcept { - set(get() - days); - return *this; - } - -private: - T value_; - bool forced_; - T forced_value_; -}; - -using IEC_DATE_Var = IECDateVar; -using IEC_LDATE_Var = IECDateVar; - -inline constexpr IEC_DATE_Value DATE_FROM_YMD(int year, int month, int day) noexcept { - return IEC_DATE_Value::from_ymd(year, month, day); +// --------------------------------------------------------------------------- +// Construction helpers +// --------------------------------------------------------------------------- +// `DATE_FROM_YMD(2024, 3, 15)` converts a Gregorian (year, month, day) +// triple into a DATE by way of the Julian Day Number. Branch-free +// integer arithmetic. +inline IEC_DATE DATE_FROM_YMD(int year, int month, int day) noexcept { + const int a = (14 - month) / 12; + const int y = year + 4800 - a; + const int m = month + 12 * a - 3; + const int jdn = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; + constexpr int UNIX_EPOCH_JDN = 2440588; + return IEC_DATE(static_cast(jdn - UNIX_EPOCH_JDN)); } -inline constexpr IEC_LDATE_Value LDATE_FROM_YMD(int year, int month, int day) noexcept { - return IEC_LDATE_Value::from_ymd(year, month, day); +inline IEC_DATE DATE_FROM_DAYS(int64_t days) noexcept { + return IEC_DATE(static_cast(days)); } -inline constexpr IEC_DATE_Value DATE_FROM_DAYS(int64_t days) noexcept { - return IEC_DATE_Value::from_days(days); +inline int64_t DATE_TO_DAYS(IEC_DATE d) noexcept { + return iec_unwrap(d); } -inline constexpr IEC_LDATE_Value LDATE_FROM_DAYS(int64_t days) noexcept { - return IEC_LDATE_Value::from_days(days); +// --------------------------------------------------------------------------- +// Arithmetic +// --------------------------------------------------------------------------- +inline IEC_DATE ADD_DATE(IEC_DATE d, int64_t days) noexcept { + return IEC_DATE(iec_unwrap(d) + static_cast(days)); } -template -inline constexpr int64_t DATE_TO_DAYS(const DateValue& d) noexcept { - return d.to_days(); +inline IEC_DATE SUB_DATE(IEC_DATE d, int64_t days) noexcept { + return IEC_DATE(iec_unwrap(d) - static_cast(days)); } -template -inline int YEAR(const DateValue& d) noexcept { - return d.year(); +inline int64_t DIFF_DATE(IEC_DATE a, IEC_DATE b) noexcept { + return iec_unwrap(a) - iec_unwrap(b); } -template -inline int MONTH(const DateValue& d) noexcept { - return d.month(); -} - -template -inline int DAY(const DateValue& d) noexcept { - return d.day(); -} - -template -inline constexpr int DAY_OF_WEEK(const DateValue& d) noexcept { - return d.day_of_week(); -} - -template -inline int DAY_OF_YEAR(const DateValue& d) noexcept { - return d.day_of_year(); -} - -template -inline constexpr DateValue ADD_DATE(const DateValue& d, int64_t days) noexcept { - return d + days; -} - -template -inline constexpr DateValue SUB_DATE(const DateValue& d, int64_t days) noexcept { - return d - days; -} - -template -inline constexpr int64_t DIFF_DATE(const DateValue& a, const DateValue& b) noexcept { - return a - b; -} - -template -inline constexpr bool GT_DATE(const DateValue& a, const DateValue& b) noexcept { - return a > b; -} - -template -inline constexpr bool GE_DATE(const DateValue& a, const DateValue& b) noexcept { - return a >= b; -} - -template -inline constexpr bool EQ_DATE(const DateValue& a, const DateValue& b) noexcept { - return a == b; -} - -template -inline constexpr bool LE_DATE(const DateValue& a, const DateValue& b) noexcept { - return a <= b; -} - -template -inline constexpr bool LT_DATE(const DateValue& a, const DateValue& b) noexcept { - return a < b; -} - -template -inline constexpr bool NE_DATE(const DateValue& a, const DateValue& b) noexcept { - return a != b; -} +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } } // namespace strucpp diff --git a/src/runtime/include/iec_dt.hpp b/src/runtime/include/iec_dt.hpp index 199c416..4125498 100644 --- a/src/runtime/include/iec_dt.hpp +++ b/src/runtime/include/iec_dt.hpp @@ -3,431 +3,140 @@ // This file is part of the STruC++ Runtime Library and is covered by the // STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. /** - * STruC++ Runtime - IEC Date and Time Types + * STruC++ Runtime - IEC DATE_AND_TIME Standard Functions * - * This header provides value classes for IEC 61131-3 DATE_AND_TIME (DT) and LDT types. - * DT represents combined date and time (stored as nanoseconds since epoch 1970-01-01 00:00:00). - * LDT is the IEC v3 long variant with nanosecond precision. + * IEC 61131-3 standard functions on the DATE_AND_TIME (DT) and LDT + * combined types. DT / LDT are stored as signed nanoseconds since + * the Unix epoch (1970-01-01 00:00:00) in `IECVar` — the same + * generic per-variable wrapper used everywhere. Codegen emits DT + * variables as `IEC_DT` (the `IECVar` alias) and the functions + * below take/return `IEC_DT` so they're directly callable from + * generated POU code. + * + * Scope: only the standard arithmetic / comparison / split-join + * functions. Calendar/clock component accessors (DT_YEAR, + * DT_MONTH, DT_DAY, DT_HOUR, …) are intentionally NOT here — those + * are OSCAT-style extensions and user libraries (OSCAT, codesys-v23 + * stdlib imports) ship their own implementations. Providing them + * here would create overload ambiguity when a project imports such + * a library. + * + * Historical note: an earlier `DateTimeValue` + `IECDtVar` + * value-class design lived here. Codegen never adopted it; the + * parallel API was dead from generated code's perspective. Removed + * in favour of a single IECVar-based surface. See `iec_time.hpp` + * for the matching note on the TIME family. */ #pragma once #include #include "iec_types.hpp" +#include "iec_var.hpp" #include "iec_date.hpp" #include "iec_tod.hpp" +#include "iec_traits.hpp" namespace strucpp { -template -class DateTimeValue { -public: - using storage_type = StorageType; - - static constexpr int64_t NS_PER_US = 1000LL; - static constexpr int64_t NS_PER_MS = 1000000LL; - static constexpr int64_t NS_PER_S = 1000000000LL; - static constexpr int64_t NS_PER_M = 60LL * NS_PER_S; - static constexpr int64_t NS_PER_H = 60LL * NS_PER_M; - static constexpr int64_t NS_PER_DAY = 24LL * NS_PER_H; - - constexpr DateTimeValue() noexcept : nanoseconds_(0) {} - constexpr explicit DateTimeValue(StorageType ns) noexcept : nanoseconds_(ns) {} - - static constexpr DateTimeValue from_nanoseconds(int64_t ns) noexcept { - return DateTimeValue(static_cast(ns)); - } - - static constexpr DateTimeValue from_milliseconds(int64_t ms) noexcept { - return DateTimeValue(static_cast(ms * NS_PER_MS)); - } - - static constexpr DateTimeValue from_seconds(int64_t s) noexcept { - return DateTimeValue(static_cast(s * NS_PER_S)); - } - - static constexpr DateTimeValue from_components( - int year, int month, int day, - int hour, int minute, int second, - int millisecond = 0, int microsecond = 0, int nanosecond = 0) noexcept { - - int a = (14 - month) / 12; - int y = year + 4800 - a; - int m = month + 12 * a - 3; - int jdn = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; - constexpr int UNIX_EPOCH_JDN = 2440588; - int64_t days = jdn - UNIX_EPOCH_JDN; - - int64_t ns = days * NS_PER_DAY + - hour * NS_PER_H + - minute * NS_PER_M + - second * NS_PER_S + - millisecond * NS_PER_MS + - microsecond * NS_PER_US + - nanosecond; - - return DateTimeValue(static_cast(ns)); - } - - template - static constexpr DateTimeValue from_date_and_tod( - const DateValue& date, - const TimeOfDayValue& tod) noexcept { - return DateTimeValue(static_cast( - date.to_days() * NS_PER_DAY + tod.to_nanoseconds())); - } - - constexpr StorageType to_nanoseconds() const noexcept { return nanoseconds_; } - constexpr int64_t to_milliseconds() const noexcept { return nanoseconds_ / NS_PER_MS; } - constexpr int64_t to_seconds() const noexcept { return nanoseconds_ / NS_PER_S; } - - constexpr int64_t days_since_epoch() const noexcept { - return nanoseconds_ / NS_PER_DAY; - } - - constexpr int64_t time_of_day_ns() const noexcept { - int64_t result = nanoseconds_ % NS_PER_DAY; - if (result < 0) result += NS_PER_DAY; - return result; - } - - void to_components(int& year, int& month, int& day, - int& hour, int& minute, int& second, - int& millisecond) const noexcept { - int64_t days = days_since_epoch(); - int64_t tod_ns = time_of_day_ns(); - - constexpr int UNIX_EPOCH_JDN = 2440588; - int jdn = static_cast(days) + UNIX_EPOCH_JDN; - - int a = jdn + 32044; - int b = (4 * a + 3) / 146097; - int c = a - (146097 * b) / 4; - int d = (4 * c + 3) / 1461; - int e = c - (1461 * d) / 4; - int m = (5 * e + 2) / 153; - - day = e - (153 * m + 2) / 5 + 1; - month = m + 3 - 12 * (m / 10); - year = 100 * b + d - 4800 + m / 10; - - hour = static_cast(tod_ns / NS_PER_H); - minute = static_cast((tod_ns % NS_PER_H) / NS_PER_M); - second = static_cast((tod_ns % NS_PER_M) / NS_PER_S); - millisecond = static_cast((tod_ns % NS_PER_S) / NS_PER_MS); - } - - int year() const noexcept { - int y = 0, m = 0, d = 0, h = 0, mi = 0, s = 0, ms = 0; - to_components(y, m, d, h, mi, s, ms); - return y; - } - - int month() const noexcept { - int y = 0, m = 0, d = 0, h = 0, mi = 0, s = 0, ms = 0; - to_components(y, m, d, h, mi, s, ms); - return m; - } - - int day() const noexcept { - int y = 0, m = 0, d = 0, h = 0, mi = 0, s = 0, ms = 0; - to_components(y, m, d, h, mi, s, ms); - return d; - } - - constexpr int hour() const noexcept { - return static_cast(time_of_day_ns() / NS_PER_H); - } - - constexpr int minute() const noexcept { - return static_cast((time_of_day_ns() % NS_PER_H) / NS_PER_M); - } - - constexpr int second() const noexcept { - return static_cast((time_of_day_ns() % NS_PER_M) / NS_PER_S); - } - - constexpr int millisecond() const noexcept { - return static_cast((time_of_day_ns() % NS_PER_S) / NS_PER_MS); - } - - constexpr int microsecond() const noexcept { - return static_cast((time_of_day_ns() % NS_PER_MS) / NS_PER_US); - } - - constexpr int nanosecond() const noexcept { - return static_cast(time_of_day_ns() % NS_PER_US); - } - - constexpr int day_of_week() const noexcept { - return static_cast((days_since_epoch() + 4) % 7); - } - - DateValue date() const noexcept { - return DateValue::from_days(days_since_epoch()); - } - - TimeOfDayValue time_of_day() const noexcept { - return TimeOfDayValue::from_nanoseconds(time_of_day_ns()); - } - - constexpr operator StorageType() const noexcept { return nanoseconds_; } - - constexpr DateTimeValue operator+(int64_t ns) const noexcept { - return DateTimeValue(nanoseconds_ + static_cast(ns)); - } - - constexpr DateTimeValue operator-(int64_t ns) const noexcept { - return DateTimeValue(nanoseconds_ - static_cast(ns)); - } - - constexpr int64_t operator-(const DateTimeValue& other) const noexcept { - return nanoseconds_ - other.nanoseconds_; - } - - DateTimeValue& operator+=(int64_t ns) noexcept { - nanoseconds_ += static_cast(ns); - return *this; - } - - DateTimeValue& operator-=(int64_t ns) noexcept { - nanoseconds_ -= static_cast(ns); - return *this; - } - - constexpr bool operator==(const DateTimeValue& other) const noexcept { - return nanoseconds_ == other.nanoseconds_; - } - - constexpr bool operator!=(const DateTimeValue& other) const noexcept { - return nanoseconds_ != other.nanoseconds_; - } - - constexpr bool operator<(const DateTimeValue& other) const noexcept { - return nanoseconds_ < other.nanoseconds_; - } - - constexpr bool operator<=(const DateTimeValue& other) const noexcept { - return nanoseconds_ <= other.nanoseconds_; - } - - constexpr bool operator>(const DateTimeValue& other) const noexcept { - return nanoseconds_ > other.nanoseconds_; - } - - constexpr bool operator>=(const DateTimeValue& other) const noexcept { - return nanoseconds_ >= other.nanoseconds_; - } - -private: - StorageType nanoseconds_; -}; - -using IEC_DT_Value = DateTimeValue; -using IEC_LDT_Value = DateTimeValue; - -template -class IECDtVar { -public: - using value_type = T; - - IECDtVar() noexcept : value_{}, forced_{false}, forced_value_{} {} - explicit IECDtVar(T v) noexcept : value_{v}, forced_{false}, forced_value_{} {} - IECDtVar(const IECDtVar&) = default; - IECDtVar(IECDtVar&&) = default; - IECDtVar& operator=(const IECDtVar&) = default; - IECDtVar& operator=(IECDtVar&&) = default; - - T get() const noexcept { - return forced_ ? forced_value_ : value_; - } - - void set(T v) noexcept { - value_ = v; - } - - T get_underlying() const noexcept { - return value_; - } - - void force(T v) noexcept { - forced_ = true; - forced_value_ = v; - } - - void unforce() noexcept { - forced_ = false; - } - - bool is_forced() const noexcept { - return forced_; - } - - T get_forced_value() const noexcept { - return forced_value_; - } - - operator T() const noexcept { - return get(); - } - - IECDtVar& operator=(T v) noexcept { - set(v); - return *this; - } - - IECDtVar& operator+=(int64_t ns) noexcept { - set(get() + ns); - return *this; - } - - IECDtVar& operator-=(int64_t ns) noexcept { - set(get() - ns); - return *this; - } - -private: - T value_; - bool forced_; - T forced_value_; -}; - -using IEC_DT_Var = IECDtVar; -using IEC_LDT_Var = IECDtVar; - -inline constexpr IEC_DT_Value DT_FROM_COMPONENTS( - int year, int month, int day, - int hour, int minute, int second, - int millisecond = 0) noexcept { - return IEC_DT_Value::from_components(year, month, day, hour, minute, second, millisecond); -} - -inline constexpr IEC_LDT_Value LDT_FROM_COMPONENTS( +// --------------------------------------------------------------------------- +// Nanosecond unit constant +// --------------------------------------------------------------------------- +// Duplicated from iec_time.hpp on purpose: clients sometimes include +// iec_dt.hpp without iec_time.hpp, and the `DT_FROM_*` helpers below +// need the unit factor. C++ tolerates redeclaration of inline +// constexpr at namespace scope as long as the value matches. +inline constexpr int64_t DT_NS_PER_DAY = 24LL * 60LL * 60LL * 1000000000LL; + +// --------------------------------------------------------------------------- +// Construction helpers +// --------------------------------------------------------------------------- +inline IEC_DT DT_FROM_COMPONENTS( int year, int month, int day, int hour, int minute, int second, int millisecond = 0, int microsecond = 0, int nanosecond = 0) noexcept { - return IEC_LDT_Value::from_components(year, month, day, hour, minute, second, - millisecond, microsecond, nanosecond); -} - -inline constexpr IEC_DT_Value DT_FROM_SECONDS(int64_t s) noexcept { - return IEC_DT_Value::from_seconds(s); -} - -inline constexpr IEC_LDT_Value LDT_FROM_NS(int64_t ns) noexcept { - return IEC_LDT_Value::from_nanoseconds(ns); -} + const int a = (14 - month) / 12; + const int y = year + 4800 - a; + const int m = month + 12 * a - 3; + const int jdn = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; + constexpr int UNIX_EPOCH_JDN = 2440588; + const int64_t days = jdn - UNIX_EPOCH_JDN; -template -inline constexpr IEC_DT_Value CONCAT_DATE_TOD( - const DateValue& date, - const TimeOfDayValue& tod) noexcept { - return IEC_DT_Value::from_date_and_tod(date, tod); + const int64_t ns = days * DT_NS_PER_DAY + + static_cast(hour) * 3600LL * 1000000000LL + + static_cast(minute) * 60LL * 1000000000LL + + static_cast(second) * 1000000000LL + + static_cast(millisecond) * 1000000LL + + static_cast(microsecond) * 1000LL + + nanosecond; + return IEC_DT(static_cast(ns)); } -template -inline constexpr int64_t DT_TO_SECONDS(const DateTimeValue& dt) noexcept { - return dt.to_seconds(); +inline IEC_DT DT_FROM_NS(int64_t ns) noexcept { + return IEC_DT(static_cast(ns)); } -template -inline constexpr int64_t DT_TO_NS(const DateTimeValue& dt) noexcept { - return dt.to_nanoseconds(); +inline IEC_DT DT_FROM_SECONDS(int64_t s) noexcept { + return IEC_DT(static_cast(s * 1000000000LL)); } -template -inline int DT_YEAR(const DateTimeValue& dt) noexcept { - return dt.year(); +// IEC 61131-3 `CONCAT_DATE_TOD`: combine a calendar date and a +// time-of-day into a single DT value. Stored as +// `date_days * NS_PER_DAY + tod_nanoseconds`. +inline IEC_DT CONCAT_DATE_TOD(IEC_DATE date, IEC_TOD tod) noexcept { + return IEC_DT(static_cast(iec_unwrap(date) * DT_NS_PER_DAY + iec_unwrap(tod))); } -template -inline int DT_MONTH(const DateTimeValue& dt) noexcept { - return dt.month(); +// IEC 61131-3 `DT_TO_DATE` and `DT_TO_TOD`: split a DT back into its +// date and time-of-day parts. Handles negative pre-epoch values by +// keeping `tod_ns` in the canonical [0, 24h) range and spilling the +// borrow into the day count. +inline IEC_DATE DATE_OF_DT(IEC_DT dt) noexcept { + const DT_t ns = iec_unwrap(dt); + DT_t days = ns / DT_NS_PER_DAY; + DT_t tod_ns = ns % DT_NS_PER_DAY; + if (tod_ns < 0) days -= 1; + return IEC_DATE(static_cast(days)); } -template -inline int DT_DAY(const DateTimeValue& dt) noexcept { - return dt.day(); +inline IEC_TOD TOD_OF_DT(IEC_DT dt) noexcept { + const DT_t ns = iec_unwrap(dt); + DT_t tod_ns = ns % DT_NS_PER_DAY; + if (tod_ns < 0) tod_ns += DT_NS_PER_DAY; + return IEC_TOD(static_cast(tod_ns)); } -template -inline constexpr int DT_HOUR(const DateTimeValue& dt) noexcept { - return dt.hour(); +inline int64_t DT_TO_NS(IEC_DT dt) noexcept { + return iec_unwrap(dt); } -template -inline constexpr int DT_MINUTE(const DateTimeValue& dt) noexcept { - return dt.minute(); +inline int64_t DT_TO_SECONDS(IEC_DT dt) noexcept { + return iec_unwrap(dt) / 1000000000LL; } -template -inline constexpr int DT_SECOND(const DateTimeValue& dt) noexcept { - return dt.second(); +// --------------------------------------------------------------------------- +// Arithmetic +// --------------------------------------------------------------------------- +inline IEC_DT ADD_DT(IEC_DT dt, int64_t ns) noexcept { + return IEC_DT(iec_unwrap(dt) + static_cast(ns)); } -template -inline constexpr int DT_MILLISECOND(const DateTimeValue& dt) noexcept { - return dt.millisecond(); +inline IEC_DT SUB_DT(IEC_DT dt, int64_t ns) noexcept { + return IEC_DT(iec_unwrap(dt) - static_cast(ns)); } -template -inline constexpr int DT_DAY_OF_WEEK(const DateTimeValue& dt) noexcept { - return dt.day_of_week(); +inline int64_t DIFF_DT(IEC_DT a, IEC_DT b) noexcept { + return iec_unwrap(a) - iec_unwrap(b); } -template -inline DateValue DATE_OF_DT(const DateTimeValue& dt) noexcept { - return dt.date(); -} - -template -inline TimeOfDayValue TOD_OF_DT(const DateTimeValue& dt) noexcept { - return dt.time_of_day(); -} - -template -inline constexpr DateTimeValue ADD_DT(const DateTimeValue& dt, int64_t ns) noexcept { - return dt + ns; -} - -template -inline constexpr DateTimeValue SUB_DT(const DateTimeValue& dt, int64_t ns) noexcept { - return dt - ns; -} - -template -inline constexpr int64_t DIFF_DT(const DateTimeValue& a, const DateTimeValue& b) noexcept { - return a - b; -} - -template -inline constexpr bool GT_DT(const DateTimeValue& a, const DateTimeValue& b) noexcept { - return a > b; -} - -template -inline constexpr bool GE_DT(const DateTimeValue& a, const DateTimeValue& b) noexcept { - return a >= b; -} - -template -inline constexpr bool EQ_DT(const DateTimeValue& a, const DateTimeValue& b) noexcept { - return a == b; -} - -template -inline constexpr bool LE_DT(const DateTimeValue& a, const DateTimeValue& b) noexcept { - return a <= b; -} - -template -inline constexpr bool LT_DT(const DateTimeValue& a, const DateTimeValue& b) noexcept { - return a < b; -} - -template -inline constexpr bool NE_DT(const DateTimeValue& a, const DateTimeValue& b) noexcept { - return a != b; -} +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } } // namespace strucpp diff --git a/src/runtime/include/iec_std_lib.hpp b/src/runtime/include/iec_std_lib.hpp index 6ee2205..7ec0e7b 100644 --- a/src/runtime/include/iec_std_lib.hpp +++ b/src/runtime/include/iec_std_lib.hpp @@ -26,6 +26,18 @@ #include "iec_ptr.hpp" #include "iec_string.hpp" #include "iec_wstring.hpp" +// IEC 61131-3 temporal types — pulled in here so the standard +// library entry point exposes every standard function (`ADD_TIME`, +// `ADD_DATE`, `ADD_DT`, `ADD_TOD`, `CONCAT_DATE_TOD`, etc.) without +// the caller having to chase the right per-type header. `codegen.ts` +// emits a single `#include "iec_std_lib.hpp"` into every +// generated.hpp, so the only way a generated POU using TIME or +// calendar arithmetic can resolve those symbols is through this +// transitive chain. Header guards make each include idempotent. +#include "iec_time.hpp" +#include "iec_date.hpp" +#include "iec_dt.hpp" +#include "iec_tod.hpp" #include #include #include @@ -33,6 +45,27 @@ #include #include +// Undefine AVR `` macros that collide with common IEC +// identifiers. `` above pulls in `` → ``, +// and AVR-libc's `time.h` defines a handful of all-caps duration / +// epoch constants that would silently rewrite a user's similarly- +// named variable into a numeric literal before the C++ parser sees +// it. The collisions surface as cryptic `expected unqualified-id +// before numeric constant` errors on lines like +// `IEC_TIME ONE_HOUR;`. +// +// Same pattern as the `#undef OVERFLOW` codegen emits into every +// `generated.hpp` to neutralise ``'s SVID error code. +// Header guards make each include idempotent, so doing the undef +// right after the time-family includes is fine: any later include +// of `` directly would re-define the macros, but no +// strucpp-side header does that. +#undef ONE_HOUR +#undef ONE_DEGREE +#undef ONE_DAY +#undef UNIX_OFFSET +#undef NTP_OFFSET + namespace strucpp { // ============================================================================= diff --git a/src/runtime/include/iec_struct.hpp b/src/runtime/include/iec_struct.hpp index c08a0ca..660a38a 100644 --- a/src/runtime/include/iec_struct.hpp +++ b/src/runtime/include/iec_struct.hpp @@ -32,23 +32,6 @@ class IEC_STRUCT_Base { virtual const char* type_name() const noexcept { return "STRUCT"; } }; -/** - * Macro for declaring struct fields with forcing support. - * Usage: IEC_STRUCT_FIELD(INT, counter) - * Expands to: IECVar counter - * - * This macro is provided for convenience but generated code may - * use the explicit IECVar syntax directly. - */ -#define IEC_STRUCT_FIELD(type, name) IECVar name - -/** - * Macro for declaring struct fields with value types (TIME, DATE, etc.) - * Usage: IEC_STRUCT_VALUE_FIELD(IEC_TIME_Value, timestamp) - * Expands to: IEC_TIME_Var timestamp - */ -#define IEC_STRUCT_VALUE_FIELD(value_type, name) IECVar name - /* * Example generated structure: * diff --git a/src/runtime/include/iec_time.hpp b/src/runtime/include/iec_time.hpp index a05ca50..fc3bfee 100644 --- a/src/runtime/include/iec_time.hpp +++ b/src/runtime/include/iec_time.hpp @@ -3,439 +3,120 @@ // This file is part of the STruC++ Runtime Library and is covered by the // STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. /** - * STruC++ Runtime - IEC Time Types + * STruC++ Runtime - IEC TIME Standard Functions * - * This header provides value classes for IEC 61131-3 TIME and LTIME types. - * TIME represents durations with millisecond precision (stored as nanoseconds). - * LTIME represents durations with nanosecond precision (IEC v3). + * IEC 61131-3 standard functions on the TIME (and LTIME) duration types. + * TIME / LTIME are stored as signed nanoseconds in `IECVar` (the + * generic per-variable wrapper). Codegen emits TIME variables as + * `IEC_TIME` (the `IECVar` alias) and time literals as raw + * nanosecond `int64_t` values that the IECVar `operator=(T)` assigns + * directly, so every function here takes/returns `IEC_TIME` for + * symmetry — no separate value class wraps the IEC variable form. + * + * Historical note: an earlier `TimeValue` + `IECTimeVar` value- + * class design lived here. Codegen never adopted it (TIME variables + * were always declared as `IEC_TIME`), so the parallel API was dead + * from generated code's perspective. Removed in favour of a single + * IECVar-based surface. */ #pragma once #include -#include #include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_traits.hpp" namespace strucpp { -template -class TimeValue { -public: - using storage_type = StorageType; - - static constexpr int64_t NS_PER_US = 1000LL; - static constexpr int64_t NS_PER_MS = 1000000LL; - static constexpr int64_t NS_PER_S = 1000000000LL; - static constexpr int64_t NS_PER_M = 60LL * NS_PER_S; - static constexpr int64_t NS_PER_H = 60LL * NS_PER_M; - static constexpr int64_t NS_PER_D = 24LL * NS_PER_H; - - constexpr TimeValue() noexcept : nanoseconds_(0) {} - constexpr explicit TimeValue(StorageType ns) noexcept : nanoseconds_(ns) {} - - static constexpr TimeValue from_nanoseconds(int64_t ns) noexcept { - return TimeValue(static_cast(ns)); - } - - static constexpr TimeValue from_microseconds(int64_t us) noexcept { - return TimeValue(static_cast(us * NS_PER_US)); - } - - static constexpr TimeValue from_milliseconds(int64_t ms) noexcept { - return TimeValue(static_cast(ms * NS_PER_MS)); - } - - static constexpr TimeValue from_seconds(int64_t s) noexcept { - return TimeValue(static_cast(s * NS_PER_S)); - } - - static constexpr TimeValue from_minutes(int64_t m) noexcept { - return TimeValue(static_cast(m * NS_PER_M)); - } - - static constexpr TimeValue from_hours(int64_t h) noexcept { - return TimeValue(static_cast(h * NS_PER_H)); - } - - static constexpr TimeValue from_days(int64_t d) noexcept { - return TimeValue(static_cast(d * NS_PER_D)); - } - - static constexpr TimeValue from_components( - int64_t days, int64_t hours, int64_t minutes, - int64_t seconds, int64_t milliseconds, - int64_t microseconds = 0, int64_t nanoseconds = 0) noexcept { - return TimeValue(static_cast( - days * NS_PER_D + - hours * NS_PER_H + - minutes * NS_PER_M + - seconds * NS_PER_S + - milliseconds * NS_PER_MS + - microseconds * NS_PER_US + - nanoseconds)); - } - - constexpr StorageType to_nanoseconds() const noexcept { return nanoseconds_; } - constexpr int64_t to_microseconds() const noexcept { return nanoseconds_ / NS_PER_US; } - constexpr int64_t to_milliseconds() const noexcept { return nanoseconds_ / NS_PER_MS; } - constexpr int64_t to_seconds() const noexcept { return nanoseconds_ / NS_PER_S; } - constexpr int64_t to_minutes() const noexcept { return nanoseconds_ / NS_PER_M; } - constexpr int64_t to_hours() const noexcept { return nanoseconds_ / NS_PER_H; } - constexpr int64_t to_days() const noexcept { return nanoseconds_ / NS_PER_D; } - - constexpr int64_t days_component() const noexcept { - return nanoseconds_ / NS_PER_D; - } - - constexpr int64_t hours_component() const noexcept { - return (nanoseconds_ % NS_PER_D) / NS_PER_H; - } - - constexpr int64_t minutes_component() const noexcept { - return (nanoseconds_ % NS_PER_H) / NS_PER_M; - } - - constexpr int64_t seconds_component() const noexcept { - return (nanoseconds_ % NS_PER_M) / NS_PER_S; - } - - constexpr int64_t milliseconds_component() const noexcept { - return (nanoseconds_ % NS_PER_S) / NS_PER_MS; - } - - constexpr int64_t microseconds_component() const noexcept { - return (nanoseconds_ % NS_PER_MS) / NS_PER_US; - } - - constexpr int64_t nanoseconds_component() const noexcept { - return nanoseconds_ % NS_PER_US; - } - - constexpr operator StorageType() const noexcept { return nanoseconds_; } - - constexpr TimeValue operator+(const TimeValue& other) const noexcept { - return TimeValue(nanoseconds_ + other.nanoseconds_); - } - - constexpr TimeValue operator-(const TimeValue& other) const noexcept { - return TimeValue(nanoseconds_ - other.nanoseconds_); - } - - constexpr TimeValue operator-() const noexcept { - return TimeValue(-nanoseconds_); - } - - template - constexpr TimeValue operator*(T scalar) const noexcept { - return TimeValue(static_cast(nanoseconds_ * scalar)); - } - - template - constexpr TimeValue operator/(T scalar) const noexcept { - return TimeValue(static_cast(nanoseconds_ / scalar)); - } - - constexpr int64_t operator/(const TimeValue& other) const noexcept { - return nanoseconds_ / other.nanoseconds_; - } - - constexpr TimeValue operator%(const TimeValue& other) const noexcept { - return TimeValue(nanoseconds_ % other.nanoseconds_); - } - - TimeValue& operator+=(const TimeValue& other) noexcept { - nanoseconds_ += other.nanoseconds_; - return *this; - } - - TimeValue& operator-=(const TimeValue& other) noexcept { - nanoseconds_ -= other.nanoseconds_; - return *this; - } - - template - TimeValue& operator*=(T scalar) noexcept { - nanoseconds_ = static_cast(nanoseconds_ * scalar); - return *this; - } - - template - TimeValue& operator/=(T scalar) noexcept { - nanoseconds_ = static_cast(nanoseconds_ / scalar); - return *this; - } - - constexpr bool operator==(const TimeValue& other) const noexcept { - return nanoseconds_ == other.nanoseconds_; - } - - constexpr bool operator!=(const TimeValue& other) const noexcept { - return nanoseconds_ != other.nanoseconds_; - } - - constexpr bool operator<(const TimeValue& other) const noexcept { - return nanoseconds_ < other.nanoseconds_; - } - - constexpr bool operator<=(const TimeValue& other) const noexcept { - return nanoseconds_ <= other.nanoseconds_; - } - - constexpr bool operator>(const TimeValue& other) const noexcept { - return nanoseconds_ > other.nanoseconds_; - } - - constexpr bool operator>=(const TimeValue& other) const noexcept { - return nanoseconds_ >= other.nanoseconds_; - } - - constexpr TimeValue abs() const noexcept { - return TimeValue(nanoseconds_ >= 0 ? nanoseconds_ : -nanoseconds_); - } - - constexpr bool is_negative() const noexcept { - return nanoseconds_ < 0; - } - - constexpr bool is_zero() const noexcept { - return nanoseconds_ == 0; - } - -private: - StorageType nanoseconds_; -}; - -template -constexpr TimeValue operator*(T scalar, const TimeValue& time) noexcept { - return time * scalar; -} - -using IEC_TIME_Value = TimeValue; -using IEC_LTIME_Value = TimeValue; - -template -class IECTimeVar { -public: - using value_type = T; - - IECTimeVar() noexcept : value_{}, forced_{false}, forced_value_{} {} - explicit IECTimeVar(T v) noexcept : value_{v}, forced_{false}, forced_value_{} {} - IECTimeVar(const IECTimeVar&) = default; - IECTimeVar(IECTimeVar&&) = default; - IECTimeVar& operator=(const IECTimeVar&) = default; - IECTimeVar& operator=(IECTimeVar&&) = default; - - T get() const noexcept { - return forced_ ? forced_value_ : value_; - } - - void set(T v) noexcept { - value_ = v; - } - - T get_underlying() const noexcept { - return value_; - } - - void force(T v) noexcept { - forced_ = true; - forced_value_ = v; - } - - void unforce() noexcept { - forced_ = false; - } - - bool is_forced() const noexcept { - return forced_; - } +// --------------------------------------------------------------------------- +// Nanosecond conversion constants +// --------------------------------------------------------------------------- +// Storage unit for both TIME and LTIME is nanoseconds. Conversion helpers +// below scale the underlying `int64_t` by these factors; user code can also +// import them for arithmetic that mixes literal scalars with TIME values +// (e.g. `MUL_TIME(t, 60)` for a 1-minute multiplier without recomputing the +// constant). +inline constexpr int64_t NS_PER_US = 1000LL; +inline constexpr int64_t NS_PER_MS = 1000000LL; +inline constexpr int64_t NS_PER_S = 1000000000LL; +inline constexpr int64_t NS_PER_M = 60LL * NS_PER_S; +inline constexpr int64_t NS_PER_H = 60LL * NS_PER_M; +inline constexpr int64_t NS_PER_D = 24LL * NS_PER_H; - T get_forced_value() const noexcept { - return forced_value_; - } - - operator T() const noexcept { - return get(); - } - - IECTimeVar& operator=(T v) noexcept { - set(v); - return *this; - } - - IECTimeVar& operator+=(const T& v) noexcept { - set(get() + v); - return *this; - } - - IECTimeVar& operator-=(const T& v) noexcept { - set(get() - v); - return *this; - } - - template - IECTimeVar& operator*=(U scalar) noexcept { - set(get() * scalar); - return *this; - } - - template - IECTimeVar& operator/=(U scalar) noexcept { - set(get() / scalar); - return *this; - } - -private: - T value_; - bool forced_; - T forced_value_; -}; - -using IEC_TIME_Var = IECTimeVar; -using IEC_LTIME_Var = IECTimeVar; - -inline constexpr IEC_TIME_Value MAKE_TIME_NS(int64_t ns) noexcept { - return IEC_TIME_Value::from_nanoseconds(ns); -} - -inline constexpr IEC_TIME_Value MAKE_TIME_US(int64_t us) noexcept { - return IEC_TIME_Value::from_microseconds(us); -} - -inline constexpr IEC_TIME_Value MAKE_TIME_MS(int64_t ms) noexcept { - return IEC_TIME_Value::from_milliseconds(ms); -} - -inline constexpr IEC_TIME_Value MAKE_TIME_S(int64_t s) noexcept { - return IEC_TIME_Value::from_seconds(s); -} - -inline constexpr IEC_TIME_Value MAKE_TIME_M(int64_t m) noexcept { - return IEC_TIME_Value::from_minutes(m); -} - -inline constexpr IEC_TIME_Value MAKE_TIME_H(int64_t h) noexcept { - return IEC_TIME_Value::from_hours(h); -} - -inline constexpr IEC_TIME_Value MAKE_TIME_D(int64_t d) noexcept { - return IEC_TIME_Value::from_days(d); -} - -inline constexpr IEC_LTIME_Value MAKE_LTIME_NS(int64_t ns) noexcept { - return IEC_LTIME_Value::from_nanoseconds(ns); -} - -inline constexpr IEC_LTIME_Value MAKE_LTIME_US(int64_t us) noexcept { - return IEC_LTIME_Value::from_microseconds(us); -} - -inline constexpr IEC_LTIME_Value MAKE_LTIME_MS(int64_t ms) noexcept { - return IEC_LTIME_Value::from_milliseconds(ms); -} - -inline constexpr IEC_LTIME_Value MAKE_LTIME_S(int64_t s) noexcept { - return IEC_LTIME_Value::from_seconds(s); -} - -inline constexpr IEC_LTIME_Value MAKE_LTIME_M(int64_t m) noexcept { - return IEC_LTIME_Value::from_minutes(m); -} - -inline constexpr IEC_LTIME_Value MAKE_LTIME_H(int64_t h) noexcept { - return IEC_LTIME_Value::from_hours(h); -} - -inline constexpr IEC_LTIME_Value MAKE_LTIME_D(int64_t d) noexcept { - return IEC_LTIME_Value::from_days(d); -} - -inline constexpr int64_t TIME_TO_NS(const IEC_TIME_Value& t) noexcept { - return t.to_nanoseconds(); -} - -inline constexpr int64_t TIME_TO_US(const IEC_TIME_Value& t) noexcept { - return t.to_microseconds(); +// --------------------------------------------------------------------------- +// Conversion: TIME → integer count of the requested unit (truncated) +// --------------------------------------------------------------------------- +// The millisecond / second variants live in `iec_std_lib.hpp` next to the +// generic numeric conversions. Other units stay here so the per-type +// surface is self-contained. +// +// Functions are inline but not constexpr: `IECVar`'s default constructor +// is non-constexpr (storage has a runtime `forced_` flag and forced-value +// slot the debugger flips through `force()`), so passing one by value to +// a constexpr context isn't allowed. Inline gives us identical codegen +// without the literal-type constraint. +inline int64_t TIME_TO_NS(IEC_TIME t) noexcept { + return iec_unwrap(t); } -inline constexpr int64_t TIME_TO_MS(const IEC_TIME_Value& t) noexcept { - return t.to_milliseconds(); +inline int64_t TIME_TO_US(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_US; } -inline constexpr int64_t TIME_TO_S(const IEC_TIME_Value& t) noexcept { - return t.to_seconds(); +inline int64_t TIME_TO_M(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_M; } -inline constexpr int64_t TIME_TO_M(const IEC_TIME_Value& t) noexcept { - return t.to_minutes(); +inline int64_t TIME_TO_H(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_H; } -inline constexpr int64_t TIME_TO_H(const IEC_TIME_Value& t) noexcept { - return t.to_hours(); +inline int64_t TIME_TO_D(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_D; } -inline constexpr int64_t TIME_TO_D(const IEC_TIME_Value& t) noexcept { - return t.to_days(); +// --------------------------------------------------------------------------- +// Arithmetic +// --------------------------------------------------------------------------- +inline IEC_TIME ABS_TIME(IEC_TIME t) noexcept { + const TIME_t ns = iec_unwrap(t); + return IEC_TIME(ns >= 0 ? ns : -ns); } -template -inline constexpr TimeValue ABS_TIME(const TimeValue& t) noexcept { - return t.abs(); +inline IEC_TIME ADD_TIME(IEC_TIME a, IEC_TIME b) noexcept { + return IEC_TIME(iec_unwrap(a) + iec_unwrap(b)); } -template -inline constexpr TimeValue ADD_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a + b; +inline IEC_TIME SUB_TIME(IEC_TIME a, IEC_TIME b) noexcept { + return IEC_TIME(iec_unwrap(a) - iec_unwrap(b)); } -template -inline constexpr TimeValue SUB_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a - b; +template +inline IEC_TIME MUL_TIME(IEC_TIME t, S scalar) noexcept { + return IEC_TIME(static_cast(iec_unwrap(t) * scalar)); } -template -inline constexpr TimeValue MUL_TIME(const TimeValue& t, S scalar) noexcept { - return t * scalar; +template +inline IEC_TIME DIV_TIME(IEC_TIME t, S scalar) noexcept { + return IEC_TIME(static_cast(iec_unwrap(t) / scalar)); } -template -inline constexpr TimeValue DIV_TIME(const TimeValue& t, S scalar) noexcept { - return t / scalar; +// DIVTIME(a, b) returns the integer count of `b`-durations that fit in `a` +// (i.e. `floor(a / b)`). Different return type from `DIV_TIME(t, scalar)` +// because the operands carry units that cancel — the result is unitless. +inline int64_t DIVTIME(IEC_TIME a, IEC_TIME b) noexcept { + return iec_unwrap(a) / iec_unwrap(b); } -template -inline constexpr int64_t DIVTIME(const TimeValue& a, const TimeValue& b) noexcept { - return a / b; -} - -template -inline constexpr bool GT_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a > b; -} - -template -inline constexpr bool GE_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a >= b; -} - -template -inline constexpr bool EQ_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a == b; -} - -template -inline constexpr bool LE_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a <= b; -} - -template -inline constexpr bool LT_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a < b; -} - -template -inline constexpr bool NE_TIME(const TimeValue& a, const TimeValue& b) noexcept { - return a != b; -} +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } } // namespace strucpp diff --git a/src/runtime/include/iec_tod.hpp b/src/runtime/include/iec_tod.hpp index f5d6456..1372111 100644 --- a/src/runtime/include/iec_tod.hpp +++ b/src/runtime/include/iec_tod.hpp @@ -3,316 +3,114 @@ // This file is part of the STruC++ Runtime Library and is covered by the // STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. /** - * STruC++ Runtime - IEC Time of Day Types + * STruC++ Runtime - IEC TIME_OF_DAY Standard Functions * - * This header provides value classes for IEC 61131-3 TIME_OF_DAY (TOD) and LTOD types. - * TOD represents time within a day (stored as nanoseconds since midnight). - * LTOD is the IEC v3 long variant with nanosecond precision. + * IEC 61131-3 standard functions on the TIME_OF_DAY (TOD) and LTOD + * types. TOD / LTOD are stored as signed nanoseconds since midnight + * in `IECVar`, normalised into the [0, 24h) range by the + * `TOD_NORMALIZE` helper used by the construction + arithmetic + * functions below. Codegen emits TOD variables as `IEC_TOD` (the + * `IECVar` alias) and the functions take/return `IEC_TOD` so + * they're directly callable from generated POU code. + * + * Scope: only the standard arithmetic / comparison / round-trip + * functions. Calendar/clock component accessors (HOUR, MINUTE, + * SECOND, …) are intentionally NOT here — those are OSCAT-style + * extensions and user libraries (OSCAT, codesys-v23 stdlib imports) + * ship their own implementations. Providing them here would create + * overload ambiguity when a project imports such a library. + * + * Historical note: an earlier `TimeOfDayValue` + `IECTodVar` + * value-class design lived here. Codegen never adopted it; the + * parallel API was dead from generated code's perspective. Removed + * in favour of a single IECVar-based surface. See `iec_time.hpp` + * for the matching note on the TIME family. */ #pragma once #include #include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_traits.hpp" namespace strucpp { -template -class TimeOfDayValue { -public: - using storage_type = StorageType; - - static constexpr int64_t NS_PER_US = 1000LL; - static constexpr int64_t NS_PER_MS = 1000000LL; - static constexpr int64_t NS_PER_S = 1000000000LL; - static constexpr int64_t NS_PER_M = 60LL * NS_PER_S; - static constexpr int64_t NS_PER_H = 60LL * NS_PER_M; - static constexpr int64_t NS_PER_DAY = 24LL * NS_PER_H; - - constexpr TimeOfDayValue() noexcept : nanoseconds_(0) {} - constexpr explicit TimeOfDayValue(StorageType ns) noexcept : nanoseconds_(normalize(ns)) {} - - static constexpr TimeOfDayValue from_nanoseconds(int64_t ns) noexcept { - return TimeOfDayValue(static_cast(ns)); - } - - static constexpr TimeOfDayValue from_microseconds(int64_t us) noexcept { - return TimeOfDayValue(static_cast(us * NS_PER_US)); - } - - static constexpr TimeOfDayValue from_milliseconds(int64_t ms) noexcept { - return TimeOfDayValue(static_cast(ms * NS_PER_MS)); - } - - static constexpr TimeOfDayValue from_seconds(int64_t s) noexcept { - return TimeOfDayValue(static_cast(s * NS_PER_S)); - } - - static constexpr TimeOfDayValue from_hms(int hour, int minute, int second, - int millisecond = 0, int microsecond = 0, - int nanosecond = 0) noexcept { - return TimeOfDayValue(static_cast( - hour * NS_PER_H + - minute * NS_PER_M + - second * NS_PER_S + - millisecond * NS_PER_MS + - microsecond * NS_PER_US + - nanosecond)); - } - - constexpr StorageType to_nanoseconds() const noexcept { return nanoseconds_; } - constexpr int64_t to_microseconds() const noexcept { return nanoseconds_ / NS_PER_US; } - constexpr int64_t to_milliseconds() const noexcept { return nanoseconds_ / NS_PER_MS; } - constexpr int64_t to_seconds() const noexcept { return nanoseconds_ / NS_PER_S; } - - constexpr int hour() const noexcept { - return static_cast(nanoseconds_ / NS_PER_H); - } - - constexpr int minute() const noexcept { - return static_cast((nanoseconds_ % NS_PER_H) / NS_PER_M); - } - - constexpr int second() const noexcept { - return static_cast((nanoseconds_ % NS_PER_M) / NS_PER_S); - } - - constexpr int millisecond() const noexcept { - return static_cast((nanoseconds_ % NS_PER_S) / NS_PER_MS); - } - - constexpr int microsecond() const noexcept { - return static_cast((nanoseconds_ % NS_PER_MS) / NS_PER_US); - } - - constexpr int nanosecond() const noexcept { - return static_cast(nanoseconds_ % NS_PER_US); - } - - constexpr operator StorageType() const noexcept { return nanoseconds_; } - - constexpr TimeOfDayValue operator+(int64_t ns) const noexcept { - return TimeOfDayValue(nanoseconds_ + static_cast(ns)); - } - - constexpr TimeOfDayValue operator-(int64_t ns) const noexcept { - return TimeOfDayValue(nanoseconds_ - static_cast(ns)); - } - - constexpr int64_t operator-(const TimeOfDayValue& other) const noexcept { - return nanoseconds_ - other.nanoseconds_; - } - - TimeOfDayValue& operator+=(int64_t ns) noexcept { - nanoseconds_ = normalize(nanoseconds_ + static_cast(ns)); - return *this; - } - - TimeOfDayValue& operator-=(int64_t ns) noexcept { - nanoseconds_ = normalize(nanoseconds_ - static_cast(ns)); - return *this; - } - - constexpr bool operator==(const TimeOfDayValue& other) const noexcept { - return nanoseconds_ == other.nanoseconds_; - } - - constexpr bool operator!=(const TimeOfDayValue& other) const noexcept { - return nanoseconds_ != other.nanoseconds_; - } - - constexpr bool operator<(const TimeOfDayValue& other) const noexcept { - return nanoseconds_ < other.nanoseconds_; - } - - constexpr bool operator<=(const TimeOfDayValue& other) const noexcept { - return nanoseconds_ <= other.nanoseconds_; - } - - constexpr bool operator>(const TimeOfDayValue& other) const noexcept { - return nanoseconds_ > other.nanoseconds_; - } - - constexpr bool operator>=(const TimeOfDayValue& other) const noexcept { - return nanoseconds_ >= other.nanoseconds_; - } - -private: - static constexpr StorageType normalize(StorageType ns) noexcept { - StorageType result = ns % static_cast(NS_PER_DAY); - if (result < 0) { - result += static_cast(NS_PER_DAY); - } - return result; - } - - StorageType nanoseconds_; -}; - -using IEC_TOD_Value = TimeOfDayValue; -using IEC_LTOD_Value = TimeOfDayValue; - -template -class IECTodVar { -public: - using value_type = T; - - IECTodVar() noexcept : value_{}, forced_{false}, forced_value_{} {} - explicit IECTodVar(T v) noexcept : value_{v}, forced_{false}, forced_value_{} {} - IECTodVar(const IECTodVar&) = default; - IECTodVar(IECTodVar&&) = default; - IECTodVar& operator=(const IECTodVar&) = default; - IECTodVar& operator=(IECTodVar&&) = default; +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- +// Mirror of iec_time.hpp's NS_PER_X but scoped here too so iec_tod.hpp is +// self-contained. Duplicate inline-constexpr declarations at namespace +// scope are legal as long as the value matches; clients including both +// headers see one definition. +inline constexpr int64_t TOD_NS_PER_DAY = 24LL * 60LL * 60LL * 1000000000LL; - T get() const noexcept { - return forced_ ? forced_value_ : value_; - } - - void set(T v) noexcept { - value_ = v; - } - - T get_underlying() const noexcept { - return value_; - } - - void force(T v) noexcept { - forced_ = true; - forced_value_ = v; - } - - void unforce() noexcept { - forced_ = false; - } - - bool is_forced() const noexcept { - return forced_; - } - - T get_forced_value() const noexcept { - return forced_value_; - } - - operator T() const noexcept { - return get(); - } - - IECTodVar& operator=(T v) noexcept { - set(v); - return *this; - } - - IECTodVar& operator+=(int64_t ns) noexcept { - set(get() + ns); - return *this; - } - - IECTodVar& operator-=(int64_t ns) noexcept { - set(get() - ns); - return *this; - } - -private: - T value_; - bool forced_; - T forced_value_; -}; - -using IEC_TOD_Var = IECTodVar; -using IEC_LTOD_Var = IECTodVar; - -inline constexpr IEC_TOD_Value TOD_FROM_HMS(int hour, int minute, int second, - int millisecond = 0) noexcept { - return IEC_TOD_Value::from_hms(hour, minute, second, millisecond); -} - -inline constexpr IEC_LTOD_Value LTOD_FROM_HMS(int hour, int minute, int second, - int millisecond = 0, int microsecond = 0, - int nanosecond = 0) noexcept { - return IEC_LTOD_Value::from_hms(hour, minute, second, millisecond, microsecond, nanosecond); -} - -inline constexpr IEC_TOD_Value TOD_FROM_MS(int64_t ms) noexcept { - return IEC_TOD_Value::from_milliseconds(ms); -} - -inline constexpr IEC_LTOD_Value LTOD_FROM_NS(int64_t ns) noexcept { - return IEC_LTOD_Value::from_nanoseconds(ns); -} - -template -inline constexpr int64_t TOD_TO_MS(const TimeOfDayValue& tod) noexcept { - return tod.to_milliseconds(); -} - -template -inline constexpr int64_t TOD_TO_NS(const TimeOfDayValue& tod) noexcept { - return tod.to_nanoseconds(); -} - -template -inline constexpr int HOUR(const TimeOfDayValue& tod) noexcept { - return tod.hour(); -} - -template -inline constexpr int MINUTE(const TimeOfDayValue& tod) noexcept { - return tod.minute(); -} - -template -inline constexpr int SECOND(const TimeOfDayValue& tod) noexcept { - return tod.second(); -} - -template -inline constexpr int MILLISECOND(const TimeOfDayValue& tod) noexcept { - return tod.millisecond(); +// Normalise a signed nanosecond count into the canonical [0, 24h) TOD +// range. Negative inputs roll over from "before midnight today" to +// "before midnight yesterday"; values ≥ 24h wrap to the next day. +inline TOD_t TOD_NORMALIZE(int64_t ns) noexcept { + TOD_t result = static_cast(ns % TOD_NS_PER_DAY); + if (result < 0) result += static_cast(TOD_NS_PER_DAY); + return result; } -template -inline constexpr TimeOfDayValue ADD_TOD(const TimeOfDayValue& tod, int64_t ns) noexcept { - return tod + ns; +// --------------------------------------------------------------------------- +// Construction helpers +// --------------------------------------------------------------------------- +inline IEC_TOD TOD_FROM_HMS(int hour, int minute, int second, + int millisecond = 0, int microsecond = 0, + int nanosecond = 0) noexcept { + const int64_t ns = static_cast(hour) * 3600LL * 1000000000LL + + static_cast(minute) * 60LL * 1000000000LL + + static_cast(second) * 1000000000LL + + static_cast(millisecond) * 1000000LL + + static_cast(microsecond) * 1000LL + + nanosecond; + return IEC_TOD(TOD_NORMALIZE(ns)); } -template -inline constexpr TimeOfDayValue SUB_TOD(const TimeOfDayValue& tod, int64_t ns) noexcept { - return tod - ns; +inline IEC_TOD TOD_FROM_NS(int64_t ns) noexcept { + return IEC_TOD(TOD_NORMALIZE(ns)); } -template -inline constexpr int64_t DIFF_TOD(const TimeOfDayValue& a, const TimeOfDayValue& b) noexcept { - return a - b; +inline IEC_TOD TOD_FROM_MS(int64_t ms) noexcept { + return IEC_TOD(TOD_NORMALIZE(ms * 1000000LL)); } -template -inline constexpr bool GT_TOD(const TimeOfDayValue& a, const TimeOfDayValue& b) noexcept { - return a > b; +inline int64_t TOD_TO_NS(IEC_TOD tod) noexcept { + return iec_unwrap(tod); } -template -inline constexpr bool GE_TOD(const TimeOfDayValue& a, const TimeOfDayValue& b) noexcept { - return a >= b; +inline int64_t TOD_TO_MS(IEC_TOD tod) noexcept { + return iec_unwrap(tod) / 1000000LL; } -template -inline constexpr bool EQ_TOD(const TimeOfDayValue& a, const TimeOfDayValue& b) noexcept { - return a == b; +// --------------------------------------------------------------------------- +// Arithmetic — results normalised back into the [0, 24h) range. +// `DIFF_TOD` returns a raw (signed) difference in (-24h, +24h); +// callers decide whether to treat negatives as "tomorrow" or +// "yesterday". +// --------------------------------------------------------------------------- +inline IEC_TOD ADD_TOD(IEC_TOD tod, int64_t ns) noexcept { + return IEC_TOD(TOD_NORMALIZE(iec_unwrap(tod) + ns)); } -template -inline constexpr bool LE_TOD(const TimeOfDayValue& a, const TimeOfDayValue& b) noexcept { - return a <= b; +inline IEC_TOD SUB_TOD(IEC_TOD tod, int64_t ns) noexcept { + return IEC_TOD(TOD_NORMALIZE(iec_unwrap(tod) - ns)); } -template -inline constexpr bool LT_TOD(const TimeOfDayValue& a, const TimeOfDayValue& b) noexcept { - return a < b; +inline int64_t DIFF_TOD(IEC_TOD a, IEC_TOD b) noexcept { + return iec_unwrap(a) - iec_unwrap(b); } -template -inline constexpr bool NE_TOD(const TimeOfDayValue& a, const TimeOfDayValue& b) noexcept { - return a != b; -} +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } } // namespace strucpp diff --git a/src/runtime/tests/test_std_lib.cpp b/src/runtime/tests/test_std_lib.cpp index b0b9274..0fd7be6 100644 --- a/src/runtime/tests/test_std_lib.cpp +++ b/src/runtime/tests/test_std_lib.cpp @@ -135,104 +135,127 @@ TEST(StdLibTest, TypeConversions) { EXPECT_DOUBLE_EQ(TO_LREAL(val42).get(), 42.0); } -TEST(TimeValueTest, Construction) { - IEC_TIME_Value t1; - EXPECT_EQ(t1.to_nanoseconds(), 0); - - IEC_TIME_Value t2 = MAKE_TIME_MS(1000); - EXPECT_EQ(t2.to_milliseconds(), 1000); - - IEC_TIME_Value t3 = MAKE_TIME_S(5); - EXPECT_EQ(t3.to_seconds(), 5); +// ============================================================================= +// IEC TIME / DATE / TOD / DT standard-library tests +// ============================================================================= +// +// These exercise the IECVar-based surface (the only one codegen emits), so +// passing here means generated POU code that calls `ADD_TIME` etc. will +// link. Time literals in IEC code lower to raw `int64_t` nanoseconds; we +// mirror that by constructing IEC_TIME directly from a literal `LL` count +// (no separate value-class helper). + +TEST(TimeTest, Arithmetic) { + IEC_TIME t1(10LL * NS_PER_S); + IEC_TIME t2(5LL * NS_PER_S); + + EXPECT_EQ(TIME_TO_S(ADD_TIME(t1, t2)), 15); + EXPECT_EQ(TIME_TO_S(SUB_TIME(t1, t2)), 5); + EXPECT_EQ(TIME_TO_S(MUL_TIME(t1, 2)), 20); + EXPECT_EQ(TIME_TO_S(DIV_TIME(t1, 2)), 5); + EXPECT_EQ(DIVTIME(t1, t2), 2); + EXPECT_EQ(TIME_TO_S(ABS_TIME(IEC_TIME(-5LL * NS_PER_S))), 5); } -TEST(TimeValueTest, Components) { - auto t = IEC_TIME_Value::from_components(1, 2, 30, 45, 500); - EXPECT_EQ(t.days_component(), 1); - EXPECT_EQ(t.hours_component(), 2); - EXPECT_EQ(t.minutes_component(), 30); - EXPECT_EQ(t.seconds_component(), 45); - EXPECT_EQ(t.milliseconds_component(), 500); +TEST(TimeTest, Comparison) { + IEC_TIME t1(10LL * NS_PER_S); + IEC_TIME t2(5LL * NS_PER_S); + IEC_TIME t3(10LL * NS_PER_S); + + EXPECT_TRUE(GT_TIME(t1, t2)); + EXPECT_TRUE(LT_TIME(t2, t1)); + EXPECT_TRUE(EQ_TIME(t1, t3)); + EXPECT_TRUE(GE_TIME(t1, t3)); + EXPECT_TRUE(LE_TIME(t1, t3)); + EXPECT_TRUE(NE_TIME(t1, t2)); } -TEST(TimeValueTest, Arithmetic) { - auto t1 = MAKE_TIME_S(10); - auto t2 = MAKE_TIME_S(5); - - EXPECT_EQ((t1 + t2).to_seconds(), 15); - EXPECT_EQ((t1 - t2).to_seconds(), 5); - EXPECT_EQ((t1 * 2).to_seconds(), 20); - EXPECT_EQ((t1 / 2).to_seconds(), 5); +TEST(TimeTest, UnitConversion) { + IEC_TIME t(static_cast(NS_PER_D + 2 * NS_PER_H + 30 * NS_PER_M + 45 * NS_PER_S + 500 * NS_PER_MS)); + EXPECT_EQ(TIME_TO_D(t), 1); + EXPECT_EQ(TIME_TO_H(t), 26); + EXPECT_EQ(TIME_TO_M(t), 26 * 60 + 30); + EXPECT_EQ(TIME_TO_S(t), 26LL * 3600LL + 30 * 60 + 45); } -TEST(TimeValueTest, Comparison) { - auto t1 = MAKE_TIME_S(10); - auto t2 = MAKE_TIME_S(5); - auto t3 = MAKE_TIME_S(10); - - EXPECT_TRUE(t1 > t2); - EXPECT_TRUE(t2 < t1); - EXPECT_TRUE(t1 == t3); - EXPECT_TRUE(t1 >= t3); - EXPECT_TRUE(t1 <= t3); - EXPECT_TRUE(t1 != t2); +TEST(DateTest, Construction) { + IEC_DATE d = DATE_FROM_YMD(2024, 6, 15); + // 2024-06-15 is 19888 days since 1970-01-01. + EXPECT_EQ(DATE_TO_DAYS(d), 19888); } -TEST(DateValueTest, Construction) { - auto d = DATE_FROM_YMD(2024, 6, 15); - EXPECT_EQ(d.year(), 2024); - EXPECT_EQ(d.month(), 6); - EXPECT_EQ(d.day(), 15); +TEST(DateTest, Arithmetic) { + IEC_DATE d1 = DATE_FROM_YMD(2024, 6, 15); + IEC_DATE d2 = ADD_DATE(d1, 10); + EXPECT_EQ(DATE_TO_DAYS(d2), DATE_TO_DAYS(d1) + 10); + EXPECT_EQ(DATE_TO_DAYS(SUB_DATE(d1, 1)), DATE_TO_DAYS(d1) - 1); + + IEC_DATE d3 = DATE_FROM_YMD(2024, 6, 20); + EXPECT_EQ(DIFF_DATE(d3, d1), 5); } -TEST(DateValueTest, Arithmetic) { - auto d1 = DATE_FROM_YMD(2024, 6, 15); - auto d2 = d1 + static_cast(10); - EXPECT_EQ(d2.day(), 25); - - auto d3 = DATE_FROM_YMD(2024, 6, 20); - EXPECT_EQ(d3 - d1, 5); +TEST(DateTest, Comparison) { + IEC_DATE early = DATE_FROM_YMD(2024, 1, 1); + IEC_DATE late = DATE_FROM_YMD(2024, 12, 31); + + EXPECT_TRUE(LT_DATE(early, late)); + EXPECT_TRUE(GT_DATE(late, early)); + EXPECT_TRUE(EQ_DATE(early, DATE_FROM_YMD(2024, 1, 1))); + EXPECT_TRUE(NE_DATE(early, late)); } -TEST(DateValueTest, DayOfWeek) { - auto d = DATE_FROM_YMD(2024, 1, 1); - int dow = d.day_of_week(); - EXPECT_GE(dow, 0); - EXPECT_LE(dow, 6); +TEST(TodTest, Construction) { + // 14:30:45 → 14*3600 + 30*60 + 45 = 52245 seconds = 52245 * 10^9 ns. + IEC_TOD tod = TOD_FROM_HMS(14, 30, 45); + EXPECT_EQ(TOD_TO_NS(tod), 52245LL * 1000000000LL); } -TEST(TodValueTest, Construction) { - auto tod = TOD_FROM_HMS(14, 30, 45); - EXPECT_EQ(tod.hour(), 14); - EXPECT_EQ(tod.minute(), 30); - EXPECT_EQ(tod.second(), 45); +TEST(TodTest, ArithmeticNormalises) { + IEC_TOD ten_am = TOD_FROM_HMS(10, 0, 0); + // +5h crosses noon but not midnight. + IEC_TOD plus5h = ADD_TOD(ten_am, 5LL * 3600LL * 1000000000LL); + EXPECT_EQ(TOD_TO_NS(plus5h), 15LL * 3600LL * 1000000000LL); + + // +20h wraps past midnight: 10am + 20h = 6am next day → normalised to 6am. + IEC_TOD next_morning = ADD_TOD(ten_am, 20LL * 3600LL * 1000000000LL); + EXPECT_EQ(TOD_TO_NS(next_morning), 6LL * 3600LL * 1000000000LL); } -TEST(TodValueTest, Comparison) { - auto tod1 = TOD_FROM_HMS(10, 0, 0); - auto tod2 = TOD_FROM_HMS(14, 0, 0); - - EXPECT_TRUE(tod1 < tod2); - EXPECT_TRUE(tod2 > tod1); +TEST(TodTest, Comparison) { + IEC_TOD tod1 = TOD_FROM_HMS(10, 0, 0); + IEC_TOD tod2 = TOD_FROM_HMS(14, 0, 0); + + EXPECT_TRUE(LT_TOD(tod1, tod2)); + EXPECT_TRUE(GT_TOD(tod2, tod1)); } -TEST(DtValueTest, Construction) { - auto dt = DT_FROM_COMPONENTS(2024, 6, 15, 14, 30, 45); - EXPECT_EQ(dt.year(), 2024); - EXPECT_EQ(dt.month(), 6); - EXPECT_EQ(dt.day(), 15); - EXPECT_EQ(dt.hour(), 14); - EXPECT_EQ(dt.minute(), 30); - EXPECT_EQ(dt.second(), 45); +TEST(DtTest, Construction) { + IEC_DT dt = DT_FROM_COMPONENTS(2024, 6, 15, 14, 30, 45); + // 2024-06-15 14:30:45 UTC = days_to_epoch * 86400 + 14*3600 + 30*60 + 45 seconds. + const int64_t expected_seconds = 19888LL * 86400LL + 14LL * 3600LL + 30LL * 60LL + 45LL; + EXPECT_EQ(DT_TO_SECONDS(dt), expected_seconds); + EXPECT_EQ(DT_TO_NS(dt), expected_seconds * 1000000000LL); } -TEST(DtValueTest, DateAndTod) { - auto dt = DT_FROM_COMPONENTS(2024, 6, 15, 14, 30, 45); - auto date = dt.date(); - auto tod = dt.time_of_day(); - - EXPECT_EQ(date.year(), 2024); - EXPECT_EQ(tod.hour(), 14); +TEST(DtTest, DateAndTodRoundTrip) { + IEC_DT dt = DT_FROM_COMPONENTS(2024, 6, 15, 14, 30, 45); + IEC_DATE date = DATE_OF_DT(dt); + IEC_TOD tod = TOD_OF_DT(dt); + + EXPECT_TRUE(EQ_DATE(date, DATE_FROM_YMD(2024, 6, 15))); + EXPECT_EQ(TOD_TO_NS(tod), TOD_TO_NS(TOD_FROM_HMS(14, 30, 45))); + + // CONCAT_DATE_TOD rebuilds the original DT from the split parts. + IEC_DT rebuilt = CONCAT_DATE_TOD(date, tod); + EXPECT_TRUE(EQ_DT(rebuilt, dt)); +} + +TEST(DtTest, Arithmetic) { + IEC_DT base = DT_FROM_COMPONENTS(2024, 6, 15, 14, 30, 45); + const int64_t one_minute_ns = 60LL * 1000000000LL; + IEC_DT later = ADD_DT(base, one_minute_ns); + EXPECT_EQ(DIFF_DT(later, base), one_minute_ns); + EXPECT_TRUE(EQ_DT(SUB_DT(later, one_minute_ns), base)); } TEST(StringTest, Construction) { @@ -347,19 +370,22 @@ TEST(CharTest, Conversions) { } TEST(TimeVarTest, Forcing) { - IEC_TIME_Var tv(MAKE_TIME_S(10)); - EXPECT_EQ(tv.get().to_seconds(), 10); - - tv.force(MAKE_TIME_S(99)); + // IEC_TIME is just `IECVar`, so debugger forcing works the + // same way it does for any other elementary type — no per-type + // wrapper. This pins that the IECVar surface is enough. + IEC_TIME tv(10LL * NS_PER_S); + EXPECT_EQ(TIME_TO_S(tv), 10); + + tv.force(99LL * NS_PER_S); EXPECT_TRUE(tv.is_forced()); - EXPECT_EQ(tv.get().to_seconds(), 99); - - tv.set(MAKE_TIME_S(20)); - EXPECT_EQ(tv.get().to_seconds(), 99); - EXPECT_EQ(tv.get_underlying().to_seconds(), 20); - + EXPECT_EQ(TIME_TO_S(tv), 99); + + tv.set(20LL * NS_PER_S); + EXPECT_EQ(TIME_TO_S(tv), 99); // forced value still wins + EXPECT_EQ(tv.get_underlying() / NS_PER_S, 20); + tv.unforce(); - EXPECT_EQ(tv.get().to_seconds(), 20); + EXPECT_EQ(TIME_TO_S(tv), 20); } TEST(StringVarTest, Forcing) { diff --git a/src/version-build.ts b/src/version-build.ts index 1655a51..25f6e99 100644 --- a/src/version-build.ts +++ b/src/version-build.ts @@ -2,4 +2,4 @@ // Copyright (C) 2025 Autonomy / OpenPLC Project // AUTO-GENERATED by scripts/rebuild-libs.mjs from package.json. Do not edit by hand — // any changes are overwritten on the next build. -export const STRUCPP_VERSION_BUILD = "0.4.18"; +export const STRUCPP_VERSION_BUILD = "0.4.19"; diff --git a/tests/integration/cpp-compile.test.ts b/tests/integration/cpp-compile.test.ts index c20e5e8..ab1ad67 100644 --- a/tests/integration/cpp-compile.test.ts +++ b/tests/integration/cpp-compile.test.ts @@ -119,6 +119,81 @@ describeIfGpp('C++ Compilation Tests', () => { expect(cppResult.success).toBe(true); }); + /** + * Regression: ADD_TIME (and the rest of the IEC TIME / DATE / DT / TOD + * standard functions) used to fail at the C++ compile step with + * `'ADD_TIME' was not declared in this scope` — `iec_std_lib.hpp` + * didn't transitively pull in `iec_time.hpp`, so the symbol was + * never reachable from the generated `pou_*.cpp` TUs that did + * `#include "generated.hpp"`. A deeper bug followed: the time- + * family functions were templated on `TimeValue` (a dead value + * class) instead of `IECVar` (the wrapper codegen actually emits + * for TIME variables), so even with the include in place the + * overload couldn't bind. Both branches are fixed; this test pins + * the end-to-end behaviour on the canonical IECVar surface. + * + * See https://github.com/Autonomy-Logic/STruCpp/pull/ for the + * full diagnosis. + */ + it('compiles IEC TIME / DATE / DT / TOD standard arithmetic (regression)', () => { + const source = ` + FUNCTION_BLOCK Scheduler + VAR_INPUT + enable : BOOL; + END_VAR + VAR_OUTPUT + elapsed : TIME; + next_day : DATE; + next_moment : DATE_AND_TIME; + next_tod : TIME_OF_DAY; + duration_s : LINT; + END_VAR + VAR + base_time : TIME := T#5s; + one_hour : TIME := T#1h; + today : DATE; + moment : DATE_AND_TIME; + now : TIME_OF_DAY; + END_VAR + IF enable THEN + elapsed := ADD_TIME(base_time, one_hour); + next_day := ADD_DATE(today, 1); + next_moment := ADD_DT(moment, T#1h); + next_tod := ADD_TOD(now, T#30m); + duration_s := TIME_TO_S(elapsed); + END_IF; + END_FUNCTION_BLOCK + + PROGRAM main + VAR sched : Scheduler; END_VAR + sched(enable := TRUE); + END_PROGRAM + + CONFIGURATION cfg + RESOURCE res ON cpu + TASK fast(INTERVAL := T#10ms, PRIORITY := 0); + PROGRAM mainInst WITH fast : main; + END_RESOURCE + END_CONFIGURATION + `; + const result = compile(source); + expect(result.success).toBe(true); + + // The fix lives in `iec_std_lib.hpp` (it now transitively includes + // the time-family headers). Generated header still emits the same + // single include, so the regression guard is that the resulting + // C++ compiles — not that the include list changed shape. + expect(result.headerCode).toContain('#include "iec_std_lib.hpp"'); + + const cppResult = compileWithGpp(result.headerCode, result.cppCode, 'iec_time_date_dt_tod_arith'); + expect(cppResult.success).toBe(true); + if (!cppResult.success) { + // Surface the g++ diagnostic so the failure mode is obvious if + // this test ever regresses. + console.error(cppResult.error); + } + }); + // UDT syntax-only tests (struct, enum, array, multi-dim) removed — // covered by st-validation/data_types/ behavioral tests.