wrps-demo-kit/03-plc/as-built/strucpp_runtime/include/iec_pointer.hpp
Clio Liu 55279ca78f docs(plc): as-built copy, versions, and a getting-started guide
Adds what the folder was missing for someone picking it up cold.

as-built/  the STruC++ output copied out of the live container - the C++
           the PLC is actually executing, plus program.st and the Modbus
           buffer config. The running image was made with docker commit
           and exists in no registry, so this is the only other copy of
           the compiled form.

           Verified while copying: the deployed program.st matches
           build/wrps.st generated from src/. Identical POU structure,
           ZERO differences in non-declaration lines. src/ is genuinely
           canonical and the running PLC agrees with it.

VERSIONS.md         every version read from the running system, not from
                    documentation: runtime v4.1.10, STruC++ 0.6.2, Editor
                    4.2.11, Debian 12, g++ 12.2, pymodbus 3.11.2, CI
                    Server R1.03. Plus which plugins are enabled -
                    ethercat is on for no reason - and a v3-vs-v4 table,
                    since most OpenPLC guidance online is for v3 and the
                    %MW HR1024 change silently produces wrong data.

GETTING-STARTED.md  three questions answered in a page: how to modify the
                    program, how to stand up a new PLC container from
                    scratch, how to move this one. Includes the compose
                    file, the Editor steps that are not generated and are
                    always missed, and a symptom-to-document table.
2026-09-02 16:48:30 +10:00

541 lines
14 KiB
C++

// 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<T>)
* - 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 <cstddef>
#include "iec_fault.hpp"
#if STRUCPP_HAS_EXCEPTIONS
#include <stdexcept>
#include <string>
#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<T> 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<typename T>
class IEC_REF_TO {
public:
using value_type = T;
using pointer_type = IECVar<T>*;
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<T>*;
* it must originate from REF_LINK()/ADR() of a matching variable.
*/
explicit IEC_REF_TO(std::uintptr_t addr) noexcept
: ptr_(reinterpret_cast<pointer_type>(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<T>& deref() {
if (ptr_ == nullptr) {
#if STRUCPP_HAS_EXCEPTIONS
throw NullReferenceException();
#else
iec_runtime_fault(IecFault::NullReference);
#endif
}
return *ptr_;
}
const IECVar<T>& 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<T>& 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<T>& 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<T>*.
*/
IEC_REF_TO& operator=(std::uintptr_t addr) noexcept {
set(reinterpret_cast<pointer_type>(addr));
return *this;
}
/**
* Dereference operator (*) - same as deref()
*/
IECVar<T>& operator*() {
return deref();
}
const IECVar<T>& 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<typename T>
class IEC_REFERENCE_TO {
public:
using value_type = T;
using pointer_type = IECVar<T>*;
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<T>& 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<T>& 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<T>& target() noexcept { return *ptr_; }
const IECVar<T>& 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<INT_t> myRef = REF(myVar);
*/
template<typename T>
inline IEC_REF_TO<T> REF(IECVar<T>& var) noexcept {
return IEC_REF_TO<T>(&var);
}
/**
* REF() for references (reference to reference)
* Allows creating a reference to a REF_TO variable.
*
* Usage:
* REF_TO<INT_t> ref1;
* REF_TO<REF_TO<INT_t>> ref2 = REF(ref1);
*/
template<typename T>
inline IEC_REF_TO<IEC_REF_TO<T>> REF(IEC_REF_TO<T>& ref) noexcept {
return IEC_REF_TO<IEC_REF_TO<T>>(&ref);
}
// Note: REF() for array elements and struct fields works automatically
// because they return IECVar<T>& from operator[] and member access.
// =============================================================================
// Type Aliases
// =============================================================================
/**
* Convenience alias for REF_TO types
* Usage: REF_TO<INT_t> myRef;
*/
template<typename T>
using REF_TO = IEC_REF_TO<T>;
/**
* Convenience alias for REFERENCE_TO types
* Usage: REFERENCE_TO<INT_t> myRef{target};
*/
template<typename T>
using REFERENCE_TO = IEC_REFERENCE_TO<T>;
/*
* 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<INT_t> 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<INT_t> 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<INT_t, 1, 10> arr;
* REF_TO<INT_t> elem_ref;
*
* elem_ref = REF(arr[5]); // arr[5] returns IECVar<INT_t>&
* elem_ref.deref().set(100);
*/
} // namespace strucpp