// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception // Copyright (C) 2025 Autonomy / OpenPLC Project // This file is part of the STruC++ Runtime Library and is covered by the // STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. // // ============================================================================ // WARNING: KEEP THIS HEADER C++14-CLEAN. // ---------------------------------------------------------------------------- // strucpp targets C++17 and its EMITTED code is compiled as C++17 — but this // header is part of the C/C++ Function Block include chain, which is NOT. // OpenPLC Editor's Arduino flow emits `c_blocks_code.cpp`, including // `iec_var.hpp` + `iec_string.hpp` (which transitively pull in `iec_traits.hpp` // and `iec_types.hpp`). That translation unit is compiled under whatever // `-std=` the Arduino core picks, and every mbed-based core — Nano RP2040 // Connect, Nano 33 BLE, Opta, GIGA, Portenta, Edge — hard-codes `-std=gnu++14`. // So any C++17/20 construct reachable from here breaks the user's C/C++ POU // build, even though the rest of strucpp is happily on C++17. // // In this header (and anything it includes) do NOT use C++17/20 features // unguarded. In particular: // * `std::trait_v` -> `std::trait::value` // * `if constexpr` -> SFINAE / tag dispatch // * inline variables / `inline constexpr` // * `auto` non-type template params -> typed NTTPs // * C++17/20 library headers (, , , // , ...) -> include ONLY behind `#if __cplusplus >= ...` // (see the guarded block in iec_types.hpp for the pattern). // // Boundary introduced in commit be85d8a. If you change which headers // `c_blocks_code.cpp` pulls in, update this set of warnings accordingly. // ============================================================================ /** * STruC++ Runtime - IEC Variable Wrapper * * This header defines the IECVar template class that wraps IEC types * with support for variable forcing (a key OpenPLC feature). * * Located variables (AT %IX0.0, etc.) use this same wrapper, and the * raw_ptr() method provides access to the underlying storage for * runtime binding to I/O image tables. */ #pragma once #include "iec_types.hpp" #include #include namespace strucpp { // Forward declaration for pointer-to-integer assignment template class IEC_Ptr; // ============================================================================= // IEC Variable Wrapper // ============================================================================= /** * Template wrapper for IEC variables with forcing support. * * This class wraps any IEC type and provides: * - Normal get/set operations * - Variable forcing (override value for debugging/testing) * - Implicit conversion for natural syntax * - Arithmetic operators for numeric types * * @tparam T The underlying C++ type (e.g., int16_t for INT) */ template class IECVar { public: using value_type = T; // ========================================================================= // Constructors // ========================================================================= /** Default constructor - initializes to zero/false */ IECVar() noexcept : value_{}, forced_{false}, forced_value_{} {} /** Construct with initial value (non-explicit to allow IEC_INT val = 10 syntax) */ IECVar(T v) noexcept : value_{v}, forced_{false}, forced_value_{} {} /** Cross-type converting constructor: IECVar → IECVar etc. * Enables implicit widening when struct fields (now IECVar-wrapped) are passed * to functions expecting a wider IECVar type. Without this, C++ would need * two user-defined conversions (IECVar→U→T→IECVar) which is disallowed. */ template::value && !std::is_same::value, int> = 0> IECVar(const IECVar& other) noexcept : value_{static_cast(other.get())}, forced_{false}, forced_value_{} {} /** Copy constructor — fresh IECVar starts unforced regardless of source. */ IECVar(const IECVar& other) noexcept : value_{other.get()}, forced_{false}, forced_value_{} {} /** Move constructor — same semantics as copy. */ IECVar(IECVar&& other) noexcept : value_{other.get()}, forced_{false}, forced_value_{} {} /** * Copy assignment. * * Assigning FROM another IECVar must go through `set()` so forcing * state is preserved on the destination. A memberwise copy would * clobber `forced_` / `forced_value_`, silently unforcing variables * that the debugger is holding — precisely what generated PLC code * does every scan cycle with `BLINK := TOF0.Q`. */ IECVar& operator=(const IECVar& other) noexcept { set(other.get()); return *this; } /** Move assignment — same semantics as copy. */ IECVar& operator=(IECVar&& other) noexcept { set(other.get()); return *this; } // ========================================================================= // Value Access // ========================================================================= /** * Get the current value. * Returns the forced value if forcing is active, otherwise the normal value. */ T get() const noexcept { return forced_ ? forced_value_ : value_; } /** * Set the value. * If forcing is active, the set is ignored to ensure drivers reading * the raw storage always see the forced value for output variables. */ void set(T v) noexcept { if (!forced_) { value_ = v; } } /** * Get the underlying value (ignoring forcing). * Useful for debugging to see what the program would have set. */ T get_underlying() const noexcept { return value_; } // ========================================================================= // Forcing Support // ========================================================================= /** * Force the variable to a specific value. * While forced, get() will return the forced value regardless of set() calls. * Also updates the raw storage so drivers reading via raw_ptr() see the forced value. */ void force(T v) noexcept { forced_ = true; forced_value_ = v; value_ = v; // Update raw value so external readers (plugins) see forced value } /** * Remove forcing and return to normal operation. */ void unforce() noexcept { forced_ = false; } /** * Check if the variable is currently forced. */ bool is_forced() const noexcept { return forced_; } /** * Get the forced value (only valid if is_forced() is true). */ T get_forced_value() const noexcept { return forced_value_; } // ========================================================================= // Raw Pointer Access (for Located Variables) // ========================================================================= /** * Get a pointer to the underlying raw storage. * Used by the runtime to bind located variables to I/O image tables. * Plugins and drivers read/write through this pointer. * * For inputs: drivers write to this pointer, get() returns forced value when forced * For outputs: force() updates this storage, so drivers always read the forced value */ T* raw_ptr() noexcept { return &value_; } /** * Get a const pointer to the underlying raw storage. */ const T* raw_ptr() const noexcept { return &value_; } // ========================================================================= // Implicit Conversions // ========================================================================= /** Implicit conversion to underlying type for natural syntax */ operator T() const noexcept { return get(); } /** Assignment from raw value */ IECVar& operator=(T v) noexcept { set(v); return *this; } /** Cross-type assignment: IECVar → IECVar etc. * Resolves ambiguity when assigning between different IECVar specializations * by providing a direct match (template is preferred over two indirect paths * that each require one user-defined conversion). */ template::value && !std::is_same::value, int> = 0> IECVar& operator=(const IECVar& other) noexcept { set(static_cast(other.get())); return *this; } /** Assignment from IEC_Ptr (CODESYS: DWORD_VAR := PT stores address as integer). * WARNING: On 64-bit platforms, assigning to types narrower than pointer width * (e.g., DWORD) truncates the address. Use ULINT, LWORD, or PTR_INT_t for * portable pointer-to-integer storage. */ template::value, int> = 0> IECVar& operator=(const IEC_Ptr& ptr) noexcept { set(static_cast(ptr.to_addr())); return *this; } /** Assignment from a raw pointer — stores the address as an integer. * Used by the ADR(x) lowering `_TMP : __XWORD := &(x)`. Integral targets * only; routed through uintptr_t so it is pointer-width-correct per * target (no truncation when T is __XWORD/XWORD_t). */ template::value, int> = 0> IECVar& operator=(U* p) noexcept { set(static_cast(reinterpret_cast(p))); return *this; } // ========================================================================= // Container Access Forwarding (for array/struct types) // ========================================================================= /** Forward operator-> to underlying type (struct/FB member access) */ template::value, int> = 0> T* operator->() noexcept { return &value_; } template::value, int> = 0> const T* operator->() const noexcept { return &value_; } /** Forward operator[] to underlying type (1D array access) */ template auto operator[](Index i) noexcept -> decltype(std::declval()[i]) { return value_[i]; } template auto operator[](Index i) const noexcept -> decltype(std::declval()[i]) { return value_[i]; } /** Forward operator() to underlying type (2D+ array access) */ template auto operator()(Args... args) noexcept -> decltype(std::declval()(args...)) { return value_(args...); } template auto operator()(Args... args) const noexcept -> decltype(std::declval()(args...)) { return value_(args...); } // ========================================================================= // Arithmetic Operators // ========================================================================= IECVar& operator+=(T v) noexcept { set(get() + v); return *this; } IECVar& operator-=(T v) noexcept { set(get() - v); return *this; } IECVar& operator*=(T v) noexcept { set(get() * v); return *this; } IECVar& operator/=(T v) noexcept { set(get() / v); return *this; } IECVar& operator%=(T v) noexcept { set(get() % v); return *this; } // Prefix increment IECVar& operator++() noexcept { set(get() + 1); return *this; } // Postfix increment IECVar operator++(int) noexcept { IECVar tmp = *this; ++(*this); return tmp; } // Prefix decrement IECVar& operator--() noexcept { set(get() - 1); return *this; } // Postfix decrement IECVar operator--(int) noexcept { IECVar tmp = *this; --(*this); return tmp; } // ========================================================================= // Bitwise Operators (for bit string types) // ========================================================================= IECVar& operator&=(T v) noexcept { set(get() & v); return *this; } IECVar& operator|=(T v) noexcept { set(get() | v); return *this; } IECVar& operator^=(T v) noexcept { set(get() ^ v); return *this; } private: T value_; ///< The actual value bool forced_; ///< Whether forcing is active T forced_value_; ///< The forced value (when forced_ is true) }; // ============================================================================= // Binary Operators // ============================================================================= template inline IECVar operator+(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() + b.get()); } template inline IECVar operator-(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() - b.get()); } template inline IECVar operator*(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() * b.get()); } template inline IECVar operator/(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() / b.get()); } template::value>> inline IECVar operator%(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() % b.get()); } // Mixed-type arithmetic operators (IECVar op T) and (T op IECVar) template inline IECVar operator+(const IECVar& a, T b) noexcept { return IECVar(a.get() + b); } template inline IECVar operator+(T a, const IECVar& b) noexcept { return IECVar(a + b.get()); } template inline IECVar operator-(const IECVar& a, T b) noexcept { return IECVar(a.get() - b); } template inline IECVar operator-(T a, const IECVar& b) noexcept { return IECVar(a - b.get()); } template inline IECVar operator*(const IECVar& a, T b) noexcept { return IECVar(a.get() * b); } template inline IECVar operator*(T a, const IECVar& b) noexcept { return IECVar(a * b.get()); } template inline IECVar operator/(const IECVar& a, T b) noexcept { return IECVar(a.get() / b); } template inline IECVar operator/(T a, const IECVar& b) noexcept { return IECVar(a / b.get()); } template::value>> inline IECVar operator%(const IECVar& a, T b) noexcept { return IECVar(a.get() % b); } template::value>> inline IECVar operator%(T a, const IECVar& b) noexcept { return IECVar(a % b.get()); } // ============================================================================= // Comparison Operators // ============================================================================= template inline bool operator==(const IECVar& a, const IECVar& b) noexcept { return a.get() == b.get(); } template inline bool operator!=(const IECVar& a, const IECVar& b) noexcept { return a.get() != b.get(); } template inline bool operator<(const IECVar& a, const IECVar& b) noexcept { return a.get() < b.get(); } template inline bool operator>(const IECVar& a, const IECVar& b) noexcept { return a.get() > b.get(); } template inline bool operator<=(const IECVar& a, const IECVar& b) noexcept { return a.get() <= b.get(); } template inline bool operator>=(const IECVar& a, const IECVar& b) noexcept { return a.get() >= b.get(); } // Mixed-type comparison operators template inline bool operator==(const IECVar& a, T b) noexcept { return a.get() == b; } template inline bool operator==(T a, const IECVar& b) noexcept { return a == b.get(); } template inline bool operator!=(const IECVar& a, T b) noexcept { return a.get() != b; } template inline bool operator!=(T a, const IECVar& b) noexcept { return a != b.get(); } template inline bool operator<(const IECVar& a, T b) noexcept { return a.get() < b; } template inline bool operator<(T a, const IECVar& b) noexcept { return a < b.get(); } template inline bool operator>(const IECVar& a, T b) noexcept { return a.get() > b; } template inline bool operator>(T a, const IECVar& b) noexcept { return a > b.get(); } template inline bool operator<=(const IECVar& a, T b) noexcept { return a.get() <= b; } template inline bool operator<=(T a, const IECVar& b) noexcept { return a <= b.get(); } template inline bool operator>=(const IECVar& a, T b) noexcept { return a.get() >= b; } template inline bool operator>=(T a, const IECVar& b) noexcept { return a >= b.get(); } // ============================================================================= // Bitwise Operators // ============================================================================= template inline IECVar operator&(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() & b.get()); } template inline IECVar operator|(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() | b.get()); } template inline IECVar operator^(const IECVar& a, const IECVar& b) noexcept { return IECVar(a.get() ^ b.get()); } // Mixed-type bitwise operators template inline IECVar operator&(const IECVar& a, T b) noexcept { return IECVar(a.get() & b); } template inline IECVar operator&(T a, const IECVar& b) noexcept { return IECVar(a & b.get()); } template inline IECVar operator|(const IECVar& a, T b) noexcept { return IECVar(a.get() | b); } template inline IECVar operator|(T a, const IECVar& b) noexcept { return IECVar(a | b.get()); } template inline IECVar operator^(const IECVar& a, T b) noexcept { return IECVar(a.get() ^ b); } template inline IECVar operator^(T a, const IECVar& b) noexcept { return IECVar(a ^ b.get()); } template inline IECVar operator~(const IECVar& a) noexcept { return IECVar(~a.get()); } // ============================================================================= // IEC Type Aliases with Forcing Support // ============================================================================= // Boolean using IEC_BOOL = IECVar; // Bit strings using IEC_BYTE = IECVar; using IEC_WORD = IECVar; using IEC_DWORD = IECVar; using IEC_LWORD = IECVar; // CODESYS __XWORD — pointer-width unsigned (see XWORD_t in iec_types.hpp). using IEC_XWORD = IECVar; // Signed integers using IEC_SINT = IECVar; using IEC_INT = IECVar; using IEC_DINT = IECVar; using IEC_LINT = IECVar; // Unsigned integers using IEC_USINT = IECVar; using IEC_UINT = IECVar; using IEC_UDINT = IECVar; using IEC_ULINT = IECVar; // Real numbers using IEC_REAL = IECVar; using IEC_LREAL = IECVar; // Time types using IEC_TIME = IECVar; using IEC_DATE = IECVar; using IEC_TOD = IECVar; using IEC_DT = IECVar; // IEC v3 Long time types using IEC_LTIME = IECVar; using IEC_LDATE = IECVar; using IEC_LTOD = IECVar; using IEC_LDT = IECVar; // Character types using IEC_CHAR = IECVar; using IEC_WCHAR = IECVar; // Aliases for compatibility using IEC_TIME_OF_DAY = IEC_TOD; using IEC_DATE_AND_TIME = IEC_DT; using IEC_LONG_TIME_OF_DAY = IEC_LTOD; using IEC_LONG_DATE_AND_TIME = IEC_LDT; } // namespace strucpp