// 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 #include #include #include "iec_fault.hpp" #if STRUCPP_HAS_EXCEPTIONS #include #endif #include "iec_var.hpp" namespace strucpp { // Array bounds specification template struct ArrayBounds { static constexpr int64_t lower = Lower; static constexpr int64_t upper = Upper; static constexpr size_t size = static_cast(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 for elementary, // bare StructName for composites whose fields already contain IECVar leaves). template 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 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(index - Bounds::lower); } public: // Default constructor - initializes all elements to default IEC_ARRAY_1D() noexcept : data_{} {} // Initializer list constructor template IEC_ARRAY_1D(std::initializer_list 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 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 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((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 IEC_ARRAY_2D(std::initializer_list 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 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 data_; static constexpr size_t to_linear_index(int64_t i, int64_t j, int64_t k) noexcept { return static_cast( (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 - e.g., Array1D for ARRAY[1..10] OF INT template using Array1D = IEC_ARRAY_1D>; // Array2D - e.g., Array2D for ARRAY[1..3, 1..4] OF REAL template using Array2D = IEC_ARRAY_2D, ArrayBounds>; // Array3D template using Array3D = IEC_ARRAY_3D, ArrayBounds, ArrayBounds>; // ============================================================================= // 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 class ArrayView1D { T* data_; int64_t lower_; int64_t upper_; public: // Construct from any IEC_ARRAY_1D with matching element type template ArrayView1D(IEC_ARRAY_1D& 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 class ArrayView2D { T* data_; int64_t lower1_, upper1_; int64_t lower2_, upper2_; int64_t dim2_; public: template ArrayView2D(IEC_ARRAY_2D& 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