wrps-demo-kit/03-plc/as-built/strucpp_runtime/include/iec_array.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

379 lines
13 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 Array Types
*
* This header provides IEC 61131-3 array types as C++ templates.
* Arrays use 1-based indexing (IEC convention) and support element-level forcing.
* Unlike MatIEC, individual array elements can be forced for debugging.
*/
#pragma once
#include <array>
#include <cstdint>
#include <initializer_list>
#include "iec_fault.hpp"
#if STRUCPP_HAS_EXCEPTIONS
#include <stdexcept>
#endif
#include "iec_var.hpp"
namespace strucpp {
// Array bounds specification
template<int64_t Lower, int64_t Upper>
struct ArrayBounds {
static constexpr int64_t lower = Lower;
static constexpr int64_t upper = Upper;
static constexpr size_t size = static_cast<size_t>(Upper - Lower + 1);
static constexpr bool in_bounds(int64_t index) noexcept {
return index >= Lower && index <= Upper;
}
};
// Single-dimensional array
// T is stored directly — caller controls wrapping (IECVar<INT_t> for elementary,
// bare StructName for composites whose fields already contain IECVar leaves).
template<typename T, typename Bounds>
class IEC_ARRAY_1D {
public:
using element_type = T;
using bounds_type = Bounds;
using var_type = T;
static constexpr size_t size = Bounds::size;
private:
std::array<T, size> data_;
// Convert IEC 1-based index to 0-based internal index
static constexpr size_t to_internal_index(int64_t index) noexcept {
return static_cast<size_t>(index - Bounds::lower);
}
public:
// Default constructor - initializes all elements to default
IEC_ARRAY_1D() noexcept : data_{} {}
// Initializer list constructor
template<typename U>
IEC_ARRAY_1D(std::initializer_list<U> init) noexcept : data_{} {
size_t i = 0;
for (const auto& val : init) {
if (i >= size) break;
data_[i] = val;
++i;
}
}
// Element access (1-based IEC indexing) - no bounds checking.
// constexpr so &arr[i] is a constant expression — required by AVR
// PROGMEM placement of the generated debug-pointer table.
// generated_debug.cpp emits entries like
// { (void*)&g_config.foo.MY_ARRAY[i], TAG_DINT, 0 }
// which would otherwise need dynamic initialization and avr-gcc
// rejects `dynamic initialization put into program memory area`.
constexpr var_type& operator[](int64_t index) noexcept {
return data_[to_internal_index(index)];
}
constexpr const var_type& operator[](int64_t index) const noexcept {
return data_[to_internal_index(index)];
}
// Bounds-checked access - throws std::out_of_range on invalid index
var_type& at(int64_t index) {
if (!Bounds::in_bounds(index)) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[to_internal_index(index)];
}
const var_type& at(int64_t index) const {
if (!Bounds::in_bounds(index)) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[to_internal_index(index)];
}
// Iterators for range-based for loops
auto begin() noexcept { return data_.begin(); }
auto end() noexcept { return data_.end(); }
auto begin() const noexcept { return data_.begin(); }
auto end() const noexcept { return data_.end(); }
auto cbegin() const noexcept { return data_.cbegin(); }
auto cend() const noexcept { return data_.cend(); }
// Size information
static constexpr size_t length() noexcept { return size; }
static constexpr int64_t lower_bound(int = 1) noexcept { return Bounds::lower; }
static constexpr int64_t upper_bound(int = 1) noexcept { return Bounds::upper; }
// Raw data access (for interop)
var_type* data() noexcept { return data_.data(); }
const var_type* data() const noexcept { return data_.data(); }
};
// Multi-dimensional array (2D)
template<typename T, typename Bounds1, typename Bounds2>
class IEC_ARRAY_2D {
public:
using element_type = T;
using var_type = T;
static constexpr size_t rows = Bounds1::size;
static constexpr size_t cols = Bounds2::size;
static constexpr size_t total_size = rows * cols;
private:
std::array<T, total_size> data_;
// Convert 2D IEC indices to linear internal index
static constexpr size_t to_linear_index(int64_t i, int64_t j) noexcept {
return static_cast<size_t>((i - Bounds1::lower) * cols + (j - Bounds2::lower));
}
public:
IEC_ARRAY_2D() noexcept : data_{} {}
// Flat (row-major) initializer-list constructor — mirrors IEC_ARRAY_1D.
// ST aggregate inits for a 2D array (e.g. `ARRAY[1..20,0..1] OF REAL := [..]`)
// codegen to a flat brace list; fill row-major, ignoring any overflow.
template<typename U>
IEC_ARRAY_2D(std::initializer_list<U> init) noexcept : data_{} {
size_t i = 0;
for (const auto& val : init) {
if (i >= total_size) break;
data_[i] = val;
++i;
}
}
// Element access (1-based IEC indexing) - no bounds checking.
// constexpr so &arr(i, j) is a constant expression — see the
// matching note on IEC_ARRAY_1D::operator[] above.
constexpr var_type& operator()(int64_t i, int64_t j) noexcept {
return data_[to_linear_index(i, j)];
}
constexpr const var_type& operator()(int64_t i, int64_t j) const noexcept {
return data_[to_linear_index(i, j)];
}
// Bounds-checked access - throws std::out_of_range on invalid index
var_type& at(int64_t i, int64_t j) {
if (!Bounds1::in_bounds(i) || !Bounds2::in_bounds(j)) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[to_linear_index(i, j)];
}
const var_type& at(int64_t i, int64_t j) const {
if (!Bounds1::in_bounds(i) || !Bounds2::in_bounds(j)) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[to_linear_index(i, j)];
}
// Size information
static constexpr size_t dim1_size() noexcept { return rows; }
static constexpr size_t dim2_size() noexcept { return cols; }
static constexpr int64_t dim1_lower() noexcept { return Bounds1::lower; }
static constexpr int64_t dim1_upper() noexcept { return Bounds1::upper; }
static constexpr int64_t dim2_lower() noexcept { return Bounds2::lower; }
static constexpr int64_t dim2_upper() noexcept { return Bounds2::upper; }
// Raw data access
var_type* data() noexcept { return data_.data(); }
const var_type* data() const noexcept { return data_.data(); }
// Iterators (linear traversal)
auto begin() noexcept { return data_.begin(); }
auto end() noexcept { return data_.end(); }
auto begin() const noexcept { return data_.begin(); }
auto end() const noexcept { return data_.end(); }
};
// Multi-dimensional array (3D)
template<typename T, typename Bounds1, typename Bounds2, typename Bounds3>
class IEC_ARRAY_3D {
public:
using element_type = T;
using var_type = T;
static constexpr size_t dim1 = Bounds1::size;
static constexpr size_t dim2 = Bounds2::size;
static constexpr size_t dim3 = Bounds3::size;
static constexpr size_t total_size = dim1 * dim2 * dim3;
private:
std::array<T, total_size> data_;
static constexpr size_t to_linear_index(int64_t i, int64_t j, int64_t k) noexcept {
return static_cast<size_t>(
(i - Bounds1::lower) * dim2 * dim3 +
(j - Bounds2::lower) * dim3 +
(k - Bounds3::lower)
);
}
public:
IEC_ARRAY_3D() noexcept : data_{} {}
// constexpr so &arr(i, j, k) is a constant expression — see the
// matching note on IEC_ARRAY_1D::operator[] above.
constexpr var_type& operator()(int64_t i, int64_t j, int64_t k) noexcept {
return data_[to_linear_index(i, j, k)];
}
constexpr const var_type& operator()(int64_t i, int64_t j, int64_t k) const noexcept {
return data_[to_linear_index(i, j, k)];
}
static constexpr size_t size1() noexcept { return dim1; }
static constexpr size_t size2() noexcept { return dim2; }
static constexpr size_t size3() noexcept { return dim3; }
var_type* data() noexcept { return data_.data(); }
const var_type* data() const noexcept { return data_.data(); }
};
// Convenience type aliases
// Array1D<T, Lower, Upper> - e.g., Array1D<INT_t, 1, 10> for ARRAY[1..10] OF INT
template<typename T, int64_t Lower, int64_t Upper>
using Array1D = IEC_ARRAY_1D<T, ArrayBounds<Lower, Upper>>;
// Array2D<T, L1, U1, L2, U2> - e.g., Array2D<REAL_t, 1, 3, 1, 4> for ARRAY[1..3, 1..4] OF REAL
template<typename T, int64_t L1, int64_t U1, int64_t L2, int64_t U2>
using Array2D = IEC_ARRAY_2D<T, ArrayBounds<L1, U1>, ArrayBounds<L2, U2>>;
// Array3D<T, L1, U1, L2, U2, L3, U3>
template<typename T, int64_t L1, int64_t U1, int64_t L2, int64_t U2, int64_t L3, int64_t U3>
using Array3D = IEC_ARRAY_3D<T, ArrayBounds<L1, U1>, ArrayBounds<L2, U2>, ArrayBounds<L3, U3>>;
// =============================================================================
// Variable-Length Array Views (Phase 3.4)
// Type-erased array views for ARRAY[*] parameters in VAR_IN_OUT
// =============================================================================
// 1D ArrayView - type-erased wrapper for ARRAY[*] OF T
template<typename T>
class ArrayView1D {
T* data_;
int64_t lower_;
int64_t upper_;
public:
// Construct from any IEC_ARRAY_1D with matching element type
template<typename Bounds>
ArrayView1D(IEC_ARRAY_1D<T, Bounds>& arr)
: data_(&arr[Bounds::lower])
, lower_(Bounds::lower)
, upper_(Bounds::upper) {}
T& operator[](int64_t index) noexcept {
return data_[index - lower_];
}
const T& operator[](int64_t index) const noexcept {
return data_[index - lower_];
}
// Bounds-checked access — the codegen emits .at() for all subscripts so an
// out-of-range index raises a clean fault instead of corrupting memory.
T& at(int64_t index) {
if (index < lower_ || index > upper_) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[index - lower_];
}
const T& at(int64_t index) const {
if (index < lower_ || index > upper_) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[index - lower_];
}
int64_t lower_bound(int = 1) const noexcept { return lower_; }
int64_t upper_bound(int = 1) const noexcept { return upper_; }
int64_t length() const noexcept { return upper_ - lower_ + 1; }
};
// 2D ArrayView - type-erased wrapper for ARRAY[*, *] OF T
template<typename T>
class ArrayView2D {
T* data_;
int64_t lower1_, upper1_;
int64_t lower2_, upper2_;
int64_t dim2_;
public:
template<typename Bounds1, typename Bounds2>
ArrayView2D(IEC_ARRAY_2D<T, Bounds1, Bounds2>& arr)
: data_(arr.data())
, lower1_(Bounds1::lower), upper1_(Bounds1::upper)
, lower2_(Bounds2::lower), upper2_(Bounds2::upper)
, dim2_(Bounds2::upper - Bounds2::lower + 1) {}
T& operator()(int64_t i, int64_t j) noexcept {
return data_[(i - lower1_) * dim2_ + (j - lower2_)];
}
const T& operator()(int64_t i, int64_t j) const noexcept {
return data_[(i - lower1_) * dim2_ + (j - lower2_)];
}
// Bounds-checked access — codegen emits .at(i, j) for 2D subscripts.
T& at(int64_t i, int64_t j) {
if (i < lower1_ || i > upper1_ || j < lower2_ || j > upper2_) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[(i - lower1_) * dim2_ + (j - lower2_)];
}
const T& at(int64_t i, int64_t j) const {
if (i < lower1_ || i > upper1_ || j < lower2_ || j > upper2_) {
#if STRUCPP_HAS_EXCEPTIONS
throw std::out_of_range("Array index out of bounds");
#else
iec_runtime_fault(IecFault::ArrayBounds);
#endif
}
return data_[(i - lower1_) * dim2_ + (j - lower2_)];
}
int64_t lower_bound(int dim) const noexcept { return dim == 1 ? lower1_ : lower2_; }
int64_t upper_bound(int dim) const noexcept { return dim == 1 ? upper1_ : upper2_; }
};
} // namespace strucpp