// 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. /** * STruC++ Runtime - IEC Reference Types (REF_TO, REFERENCE_TO) * * This header provides IEC 61131-3 reference types: * - REF_TO: Explicit-dereference reference (nullable pointer) * - REFERENCE_TO: Implicit-dereference reference (CODESYS compatibility) * - REF() operator to get reference to a variable * - NULL value for uninitialized REF_TO * - NullReferenceException for null dereference errors * * Design decisions: * - References are NOT forceable (unlike IECVar) * - Writes through references respect target's forcing state * - Null dereference throws NullReferenceException * - REFERENCE_TO cannot be NULL (must always be bound) * * Example ST code (REF_TO): * VAR * V1, V2 : INT; * rV : REF_TO INT; * END_VAR * * rV := REF(V2); // Get reference to V2 * rV^ := 12; // Assign 12 to V2 via reference * V1 := rV^; // Read V2 via reference * * IF rV <> NULL THEN // Null check * rV^ := 42; * END_IF; * * Example ST code (REFERENCE_TO): * VAR * target : INT := 10; * ref : REFERENCE_TO INT := target; // Must be initialized * END_VAR * * ref := 42; // Implicit write to target * x := ref; // Implicit read from target * ref REF= other; // Rebind to different variable */ #pragma once #include #include "iec_fault.hpp" #if STRUCPP_HAS_EXCEPTIONS #include #include #endif #include "iec_var.hpp" namespace strucpp { // ============================================================================= // Null Reference Exception // ============================================================================= #if STRUCPP_HAS_EXCEPTIONS /** * Exception thrown when dereferencing a NULL reference. * The runtime catches this and stops execution of the affected POU. */ class NullReferenceException : public std::runtime_error { public: NullReferenceException() : std::runtime_error("Null reference dereference") {} explicit NullReferenceException(const char* context) : std::runtime_error(std::string("Null reference dereference in ") + context) {} explicit NullReferenceException(const std::string& context) : std::runtime_error("Null reference dereference in " + context) {} }; #endif // ============================================================================= // IEC NULL Constant // ============================================================================= /** * IEC NULL pointer constant. * Used to indicate no address / uninitialized pointer. */ constexpr std::nullptr_t IEC_NULL = nullptr; // ============================================================================= // REF_TO Type (Explicit Dereference) // ============================================================================= /** * REF_TO pointer type for IEC 61131-3. * Wraps a pointer to an IECVar with null checking. * * Note: References themselves are NOT forceable. * Writes through references respect the target's forcing state. * * @tparam T The underlying value type (e.g., INT_t, REAL_t) */ template class IEC_REF_TO { public: using value_type = T; using pointer_type = IECVar*; private: pointer_type ptr_; public: /** * Default constructor - initializes to NULL */ IEC_REF_TO() noexcept : ptr_(nullptr) {} /** * Constructor from pointer to IECVar */ explicit IEC_REF_TO(pointer_type p) noexcept : ptr_(p) {} /** * Constructor from nullptr (NULL) */ IEC_REF_TO(std::nullptr_t) noexcept : ptr_(nullptr) {} /** * Constructor from an integer address (e.g. a __XWORD temp produced by * REF_LINK()). The address is reinterpreted as the wrapped IECVar*; * it must originate from REF_LINK()/ADR() of a matching variable. */ explicit IEC_REF_TO(std::uintptr_t addr) noexcept : ptr_(reinterpret_cast(addr)) {} // Copy and move constructors/assignment - default is fine IEC_REF_TO(const IEC_REF_TO&) = default; IEC_REF_TO(IEC_REF_TO&&) = default; IEC_REF_TO& operator=(const IEC_REF_TO&) = default; IEC_REF_TO& operator=(IEC_REF_TO&&) = default; /** * Get the pointer (for internal use) */ pointer_type get() const noexcept { return ptr_; } /** * Set the pointer */ void set(pointer_type p) noexcept { ptr_ = p; } /** * Check if pointer is NULL */ bool is_null() const noexcept { return ptr_ == nullptr; } /** * Dereference - throws NullReferenceException if NULL * Used by generated code for ^ operator and DREF() function */ IECVar& deref() { if (ptr_ == nullptr) { #if STRUCPP_HAS_EXCEPTIONS throw NullReferenceException(); #else iec_runtime_fault(IecFault::NullReference); #endif } return *ptr_; } const IECVar& deref() const { if (ptr_ == nullptr) { #if STRUCPP_HAS_EXCEPTIONS throw NullReferenceException(); #else iec_runtime_fault(IecFault::NullReference); #endif } return *ptr_; } /** * Dereference with context for better error messages */ IECVar& deref(const char* context) { if (ptr_ == nullptr) { #if STRUCPP_HAS_EXCEPTIONS throw NullReferenceException(context); #else iec_runtime_fault(IecFault::NullReference, context); #endif } return *ptr_; } const IECVar& deref(const char* context) const { if (ptr_ == nullptr) { #if STRUCPP_HAS_EXCEPTIONS throw NullReferenceException(context); #else iec_runtime_fault(IecFault::NullReference, context); #endif } return *ptr_; } // ========================================================================= // Operators // ========================================================================= /** * Assignment from pointer */ IEC_REF_TO& operator=(pointer_type p) noexcept { set(p); return *this; } /** * Assignment from nullptr (NULL) */ IEC_REF_TO& operator=(std::nullptr_t) noexcept { set(nullptr); return *this; } /** * Assignment from an integer address (e.g. `ref := _TMP` where the temp is * a __XWORD produced by REF_LINK()). The address is reinterpreted as the * wrapped IECVar*. */ IEC_REF_TO& operator=(std::uintptr_t addr) noexcept { set(reinterpret_cast(addr)); return *this; } /** * Dereference operator (*) - same as deref() */ IECVar& operator*() { return deref(); } const IECVar& operator*() const { return deref(); } /** * Arrow operator for accessing IECVar methods * Also throws NullReferenceException if NULL */ pointer_type operator->() { if (ptr_ == nullptr) { #if STRUCPP_HAS_EXCEPTIONS throw NullReferenceException(); #else iec_runtime_fault(IecFault::NullReference); #endif } return ptr_; } const pointer_type operator->() const { if (ptr_ == nullptr) { #if STRUCPP_HAS_EXCEPTIONS throw NullReferenceException(); #else iec_runtime_fault(IecFault::NullReference); #endif } return ptr_; } /** * Comparison with nullptr (NULL) */ bool operator==(std::nullptr_t) const noexcept { return is_null(); } bool operator!=(std::nullptr_t) const noexcept { return !is_null(); } /** * Comparison with another pointer */ bool operator==(const IEC_REF_TO& other) const noexcept { return ptr_ == other.ptr_; } bool operator!=(const IEC_REF_TO& other) const noexcept { return ptr_ != other.ptr_; } /** * Implicit conversion to bool (for null checks) * Returns true if pointer is not NULL */ explicit operator bool() const noexcept { return !is_null(); } }; // ============================================================================= // REFERENCE_TO Type (Implicit Dereference - CODESYS Compatibility) // ============================================================================= /** * REFERENCE_TO type with implicit dereferencing (CODESYS compatibility). * Unlike REF_TO, this type automatically dereferences on value access. * * Conceptually always bound; default-constructs unbound (nullptr) only so * the variable can be declared before being bound. Must be bound (via the * REF= operator / bind()) before any read or write — accessing it unbound * is undefined behaviour, mirroring CODESYS. * * @tparam T The underlying value type (e.g., INT_t, REAL_t) */ template class IEC_REFERENCE_TO { public: using value_type = T; using pointer_type = IECVar*; private: pointer_type ptr_; public: /** * Default constructor - unbound (ptr_ == nullptr). * * IEC 61131-3 REFERENCE TO is conceptually "always bound", but a * variable of this type must still be *declarable* before it is bound * (e.g. an uninitialized FB member or local that is bound later via the * REF= operator inside the POU body). The generated code default- * constructs such members, so a usable default ctor is required. * * As with CODESYS, reading or writing an unbound REFERENCE TO is * undefined behaviour — generated code is expected to bind it (REF=) * before first access. (Use REF_TO if you need explicit NULL checks.) */ IEC_REFERENCE_TO() noexcept : ptr_(nullptr) {} /** * Constructor - initialize bound to a variable (REFERENCE TO X := target) */ explicit IEC_REFERENCE_TO(IECVar& var) noexcept : ptr_(&var) {} // Copy/move - default is fine IEC_REFERENCE_TO(const IEC_REFERENCE_TO&) = default; IEC_REFERENCE_TO(IEC_REFERENCE_TO&&) = default; IEC_REFERENCE_TO& operator=(const IEC_REFERENCE_TO&) = default; IEC_REFERENCE_TO& operator=(IEC_REFERENCE_TO&&) = default; /** * Bind to a new variable (REF= operator) */ void bind(IECVar& var) noexcept { ptr_ = &var; } /** * Implicit value access (get) - reads from target */ T get() const noexcept { return ptr_->get(); } /** * Implicit value assignment (set) - writes to target * Respects target's forcing state */ void set(const T& value) noexcept { ptr_->set(value); } /** * Get underlying IECVar reference */ IECVar& target() noexcept { return *ptr_; } const IECVar& target() const noexcept { return *ptr_; } // ========================================================================= // Operators // ========================================================================= /** * Assignment operator writes through to target */ IEC_REFERENCE_TO& operator=(const T& value) noexcept { set(value); return *this; } /** * Implicit conversion to value type for reading */ operator T() const noexcept { return get(); } // Arithmetic compound assignment operators (write through to target) IEC_REFERENCE_TO& operator+=(const T& v) noexcept { set(get() + v); return *this; } IEC_REFERENCE_TO& operator-=(const T& v) noexcept { set(get() - v); return *this; } IEC_REFERENCE_TO& operator*=(const T& v) noexcept { set(get() * v); return *this; } IEC_REFERENCE_TO& operator/=(const T& v) noexcept { set(get() / v); return *this; } }; // ============================================================================= // REF() Function - Get Reference to Variable // ============================================================================= /** * REF() operator - Get reference to an IECVar * Returns a REF_TO that can be assigned to a reference variable. * * Usage: * IEC_INT myVar; * REF_TO myRef = REF(myVar); */ template inline IEC_REF_TO REF(IECVar& var) noexcept { return IEC_REF_TO(&var); } /** * REF() for references (reference to reference) * Allows creating a reference to a REF_TO variable. * * Usage: * REF_TO ref1; * REF_TO> ref2 = REF(ref1); */ template inline IEC_REF_TO> REF(IEC_REF_TO& ref) noexcept { return IEC_REF_TO>(&ref); } // Note: REF() for array elements and struct fields works automatically // because they return IECVar& from operator[] and member access. // ============================================================================= // Type Aliases // ============================================================================= /** * Convenience alias for REF_TO types * Usage: REF_TO myRef; */ template using REF_TO = IEC_REF_TO; /** * Convenience alias for REFERENCE_TO types * Usage: REFERENCE_TO myRef{target}; */ template using REFERENCE_TO = IEC_REFERENCE_TO; /* * Example usage (generated code for REF_TO): * * ST Source: * VAR * V1, V2 : INT; * rV : REF_TO INT; * END_VAR * * rV := REF(V2); * rV^ := 12; * V1 := rV^; * * IF rV <> NULL THEN * rV^ := 42; * END_IF; * * Generated C++: * IEC_INT V1, V2; * REF_TO rV; * * rV = REF(V2); * rV.deref().set(12); * V1 = rV.deref().get(); * * if (rV != IEC_NULL) { * rV.deref().set(42); * } */ /* * Example usage (generated code for REFERENCE_TO): * * ST Source: * VAR * target : INT := 10; * ref : REFERENCE_TO INT := target; * END_VAR * * ref := 42; // Implicit write * x := ref; // Implicit read * ref REF= other; // Rebind * * Generated C++: * IEC_INT target{10}; * REFERENCE_TO ref{target}; * * ref.set(42); * x = ref.get(); * ref.bind(other); */ /* * Example usage (reference to array element): * * ST Source: * VAR * arr : ARRAY[1..10] OF INT; * elem_ref : REF_TO INT; * END_VAR * * elem_ref := REF(arr[5]); * elem_ref^ := 100; * * Generated C++: * Array1D arr; * REF_TO elem_ref; * * elem_ref = REF(arr[5]); // arr[5] returns IECVar& * elem_ref.deref().set(100); */ } // namespace strucpp