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

616 lines
23 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.
//
// ============================================================================
// 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<T>` -> `std::trait<T>::value`
// * `if constexpr` -> SFINAE / tag dispatch
// * inline variables / `inline constexpr`
// * `auto` non-type template params -> typed NTTPs
// * C++17/20 library headers (<optional>, <variant>, <string_view>,
// <concepts>, ...) -> include ONLY behind `#if __cplusplus >= ...`
// (see the guarded <concepts> 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 Type Traits
*
* This header provides C++ type traits and concepts for IEC 61131-3 type categories.
* These traits enable compile-time type checking and generic programming for
* standard library functions.
*/
#pragma once
#include <type_traits>
#include <cstdint>
#include <cstddef>
#include "iec_types.hpp"
namespace strucpp {
// Forward declarations
template<typename T> class IECVar;
// Forward declarations for composite types
template<int64_t Lower, int64_t Upper> struct ArrayBounds;
template<typename T, typename Bounds> class IEC_ARRAY_1D;
template<typename T, typename Bounds1, typename Bounds2> class IEC_ARRAY_2D;
template<typename T, typename Bounds1, typename Bounds2, typename Bounds3> class IEC_ARRAY_3D;
class IEC_STRUCT_Base;
template<typename EnumType> class IEC_ENUM_Value;
template<typename EnumType> class IEC_ENUM_Var;
// Forward declarations. `auto` template parameters are C++17; we declare
// these subrange templates with `BaseType`-typed bounds so they remain
// compilable under the platform's gnu++14 default (mbed Arduino cores).
// Numeric bounds in IEC subrange types are always the same type as the
// base, so requiring `Lower`/`Upper` to be `BaseType` is no semantic loss.
template<typename BaseType, BaseType Lower, BaseType Upper> class IEC_SUBRANGE_Value;
template<typename BaseType, BaseType Lower, BaseType Upper> class IEC_SUBRANGE_Var;
template<typename T> class IEC_REF_TO;
// =============================================================================
// Primary Type Traits (default to false)
// =============================================================================
/** Check if T is an IEC type */
template<typename T> struct is_iec_type : std::false_type {};
/** Check if T is ANY_BOOL (just BOOL) */
template<typename T> struct is_any_bool : std::false_type {};
/** Check if T is ANY_SINT (signed integers) */
template<typename T> struct is_any_sint : std::false_type {};
/** Check if T is ANY_UINT (unsigned integers) */
template<typename T> struct is_any_uint : std::false_type {};
/** Check if T is ANY_INT (all integers) */
template<typename T> struct is_any_int : std::false_type {};
/** Check if T is ANY_REAL (floating point) */
template<typename T> struct is_any_real : std::false_type {};
/** Check if T is ANY_NUM (numeric: integers + reals) */
template<typename T> struct is_any_num : std::false_type {};
/** Check if T is ANY_BIT (bit strings including BOOL) */
template<typename T> struct is_any_bit : std::false_type {};
/** Check if T is ANY_STRING (strings and characters) */
template<typename T> struct is_any_string : std::false_type {};
/** Check if T is ANY_DATE (date/time types) */
template<typename T> struct is_any_date : std::false_type {};
/** Check if T is ANY_TIME (duration types: TIME, LTIME) */
template<typename T> struct is_any_time : std::false_type {};
/** Check if T is ANY_MAGNITUDE (numeric + time) */
template<typename T> struct is_any_magnitude : std::false_type {};
/** Check if T is ANY_ELEMENTARY (all elementary types) */
template<typename T> struct is_any_elementary : std::false_type {};
// =============================================================================
// Specializations for Raw C++ Types
// =============================================================================
// Note: IEC types share underlying C++ types, so we specialize on the actual
// C++ types. The categorization reflects the primary IEC use case.
// Boolean (BOOL_t = bool)
template<> struct is_iec_type<bool> : std::true_type {};
template<> struct is_any_bool<bool> : std::true_type {};
template<> struct is_any_bit<bool> : std::true_type {};
template<> struct is_any_elementary<bool> : std::true_type {};
// 8-bit unsigned (BYTE_t/USINT_t = uint8_t) - categorized as bit string
template<> struct is_iec_type<uint8_t> : std::true_type {};
template<> struct is_any_bit<uint8_t> : std::true_type {};
template<> struct is_any_uint<uint8_t> : std::true_type {};
template<> struct is_any_int<uint8_t> : std::true_type {};
template<> struct is_any_num<uint8_t> : std::true_type {};
template<> struct is_any_magnitude<uint8_t> : std::true_type {};
template<> struct is_any_elementary<uint8_t> : std::true_type {};
// 8-bit signed (SINT_t = int8_t)
template<> struct is_iec_type<int8_t> : std::true_type {};
template<> struct is_any_sint<int8_t> : std::true_type {};
template<> struct is_any_int<int8_t> : std::true_type {};
template<> struct is_any_num<int8_t> : std::true_type {};
template<> struct is_any_magnitude<int8_t> : std::true_type {};
template<> struct is_any_elementary<int8_t> : std::true_type {};
// 16-bit unsigned (WORD_t/UINT_t = uint16_t) - categorized as bit string
template<> struct is_iec_type<uint16_t> : std::true_type {};
template<> struct is_any_bit<uint16_t> : std::true_type {};
template<> struct is_any_uint<uint16_t> : std::true_type {};
template<> struct is_any_int<uint16_t> : std::true_type {};
template<> struct is_any_num<uint16_t> : std::true_type {};
template<> struct is_any_magnitude<uint16_t> : std::true_type {};
template<> struct is_any_elementary<uint16_t> : std::true_type {};
// 16-bit signed (INT_t = int16_t)
template<> struct is_iec_type<int16_t> : std::true_type {};
template<> struct is_any_sint<int16_t> : std::true_type {};
template<> struct is_any_int<int16_t> : std::true_type {};
template<> struct is_any_num<int16_t> : std::true_type {};
template<> struct is_any_magnitude<int16_t> : std::true_type {};
template<> struct is_any_elementary<int16_t> : std::true_type {};
// 32-bit unsigned (DWORD_t/UDINT_t = uint32_t) - categorized as bit string
template<> struct is_iec_type<uint32_t> : std::true_type {};
template<> struct is_any_bit<uint32_t> : std::true_type {};
template<> struct is_any_uint<uint32_t> : std::true_type {};
template<> struct is_any_int<uint32_t> : std::true_type {};
template<> struct is_any_num<uint32_t> : std::true_type {};
template<> struct is_any_magnitude<uint32_t> : std::true_type {};
template<> struct is_any_elementary<uint32_t> : std::true_type {};
// 32-bit signed (DINT_t = int32_t)
template<> struct is_iec_type<int32_t> : std::true_type {};
template<> struct is_any_sint<int32_t> : std::true_type {};
template<> struct is_any_int<int32_t> : std::true_type {};
template<> struct is_any_num<int32_t> : std::true_type {};
template<> struct is_any_magnitude<int32_t> : std::true_type {};
template<> struct is_any_elementary<int32_t> : std::true_type {};
// 64-bit unsigned (LWORD_t/ULINT_t = uint64_t) - categorized as bit string
template<> struct is_iec_type<uint64_t> : std::true_type {};
template<> struct is_any_bit<uint64_t> : std::true_type {};
template<> struct is_any_uint<uint64_t> : std::true_type {};
template<> struct is_any_int<uint64_t> : std::true_type {};
template<> struct is_any_num<uint64_t> : std::true_type {};
template<> struct is_any_magnitude<uint64_t> : std::true_type {};
template<> struct is_any_elementary<uint64_t> : std::true_type {};
// 64-bit signed (LINT_t/TIME_t/DATE_t/TOD_t/DT_t/etc = int64_t)
// Note: This type is used for both integers and date/time types
template<> struct is_iec_type<int64_t> : std::true_type {};
template<> struct is_any_sint<int64_t> : std::true_type {};
template<> struct is_any_int<int64_t> : std::true_type {};
template<> struct is_any_num<int64_t> : std::true_type {};
template<> struct is_any_time<int64_t> : std::true_type {};
template<> struct is_any_date<int64_t> : std::true_type {};
template<> struct is_any_magnitude<int64_t> : std::true_type {};
template<> struct is_any_elementary<int64_t> : std::true_type {};
// Single precision float (REAL_t = float)
template<> struct is_iec_type<float> : std::true_type {};
template<> struct is_any_real<float> : std::true_type {};
template<> struct is_any_num<float> : std::true_type {};
template<> struct is_any_magnitude<float> : std::true_type {};
template<> struct is_any_elementary<float> : std::true_type {};
// Double precision float (LREAL_t = double)
template<> struct is_iec_type<double> : std::true_type {};
template<> struct is_any_real<double> : std::true_type {};
template<> struct is_any_num<double> : std::true_type {};
template<> struct is_any_magnitude<double> : std::true_type {};
template<> struct is_any_elementary<double> : std::true_type {};
// Character (CHAR_t = char)
template<> struct is_iec_type<char> : std::true_type {};
template<> struct is_any_string<char> : std::true_type {};
template<> struct is_any_elementary<char> : std::true_type {};
// Wide character (WCHAR_t = char16_t)
template<> struct is_iec_type<char16_t> : std::true_type {};
template<> struct is_any_string<char16_t> : std::true_type {};
template<> struct is_any_elementary<char16_t> : std::true_type {};
// =============================================================================
// Specializations for IECVar<T> Wrapped Types
// =============================================================================
template<typename T>
struct is_iec_type<IECVar<T>> : is_iec_type<T> {};
template<typename T>
struct is_any_bool<IECVar<T>> : is_any_bool<T> {};
template<typename T>
struct is_any_sint<IECVar<T>> : is_any_sint<T> {};
template<typename T>
struct is_any_uint<IECVar<T>> : is_any_uint<T> {};
template<typename T>
struct is_any_int<IECVar<T>> : is_any_int<T> {};
template<typename T>
struct is_any_real<IECVar<T>> : is_any_real<T> {};
template<typename T>
struct is_any_num<IECVar<T>> : is_any_num<T> {};
template<typename T>
struct is_any_bit<IECVar<T>> : is_any_bit<T> {};
template<typename T>
struct is_any_string<IECVar<T>> : is_any_string<T> {};
template<typename T>
struct is_any_date<IECVar<T>> : is_any_date<T> {};
template<typename T>
struct is_any_time<IECVar<T>> : is_any_time<T> {};
template<typename T>
struct is_any_magnitude<IECVar<T>> : is_any_magnitude<T> {};
template<typename T>
struct is_any_elementary<IECVar<T>> : is_any_elementary<T> {};
// =============================================================================
// Helper Variable Templates (C++17)
// =============================================================================
template<typename T>
constexpr bool is_iec_type_v = is_iec_type<T>::value;
template<typename T>
constexpr bool is_any_bool_v = is_any_bool<T>::value;
template<typename T>
constexpr bool is_any_sint_v = is_any_sint<T>::value;
template<typename T>
constexpr bool is_any_uint_v = is_any_uint<T>::value;
template<typename T>
constexpr bool is_any_int_v = is_any_int<T>::value;
template<typename T>
constexpr bool is_any_real_v = is_any_real<T>::value;
template<typename T>
constexpr bool is_any_num_v = is_any_num<T>::value;
template<typename T>
constexpr bool is_any_bit_v = is_any_bit<T>::value;
template<typename T>
constexpr bool is_any_string_v = is_any_string<T>::value;
template<typename T>
constexpr bool is_any_date_v = is_any_date<T>::value;
template<typename T>
constexpr bool is_any_time_v = is_any_time<T>::value;
template<typename T>
constexpr bool is_any_magnitude_v = is_any_magnitude<T>::value;
template<typename T>
constexpr bool is_any_elementary_v = is_any_elementary<T>::value;
// =============================================================================
// Type Size Traits
// =============================================================================
/** Get the size in bits for an IEC type */
template<typename T> struct iec_bit_size;
// Note: Using actual C++ types to avoid duplicate specializations
template<> struct iec_bit_size<bool> : std::integral_constant<size_t, 1> {};
template<> struct iec_bit_size<uint8_t> : std::integral_constant<size_t, 8> {};
template<> struct iec_bit_size<int8_t> : std::integral_constant<size_t, 8> {};
template<> struct iec_bit_size<uint16_t> : std::integral_constant<size_t, 16> {};
template<> struct iec_bit_size<int16_t> : std::integral_constant<size_t, 16> {};
template<> struct iec_bit_size<uint32_t> : std::integral_constant<size_t, 32> {};
template<> struct iec_bit_size<int32_t> : std::integral_constant<size_t, 32> {};
template<> struct iec_bit_size<uint64_t> : std::integral_constant<size_t, 64> {};
template<> struct iec_bit_size<int64_t> : std::integral_constant<size_t, 64> {};
template<> struct iec_bit_size<float> : std::integral_constant<size_t, 32> {};
template<> struct iec_bit_size<double> : std::integral_constant<size_t, 64> {};
template<> struct iec_bit_size<char> : std::integral_constant<size_t, 8> {};
template<> struct iec_bit_size<char16_t> : std::integral_constant<size_t, 16> {};
// IECVar wrapper
template<typename T>
struct iec_bit_size<IECVar<T>> : iec_bit_size<T> {};
template<typename T>
constexpr size_t iec_bit_size_v = iec_bit_size<T>::value;
// =============================================================================
// Underlying Type Traits
// =============================================================================
/** Get the underlying C++ type for an IEC type */
template<typename T> struct iec_underlying_type { using type = T; };
// IECVar wrapper - extract the underlying type
template<typename T>
struct iec_underlying_type<IECVar<T>> { using type = T; };
template<typename T>
using iec_underlying_type_t = typename iec_underlying_type<T>::type;
/** Extract the raw value from an IECVar or pass through a raw value unchanged */
template<typename T>
constexpr T iec_unwrap(T v) noexcept { return v; }
template<typename T>
constexpr T iec_unwrap(const IECVar<T>& v) noexcept { return v.get(); }
// =============================================================================
// Type Limits
// =============================================================================
/** Get the minimum and maximum values for an IEC type */
template<typename T> struct iec_limits;
// Note: Using actual C++ types to avoid duplicate specializations
template<> struct iec_limits<int8_t> {
static constexpr int8_t min() { return -128; }
static constexpr int8_t max() { return 127; }
};
template<> struct iec_limits<int16_t> {
static constexpr int16_t min() { return -32768; }
static constexpr int16_t max() { return 32767; }
};
template<> struct iec_limits<int32_t> {
static constexpr int32_t min() { return -2147483648; }
static constexpr int32_t max() { return 2147483647; }
};
template<> struct iec_limits<int64_t> {
static constexpr int64_t min() { return INT64_MIN; }
static constexpr int64_t max() { return INT64_MAX; }
};
template<> struct iec_limits<uint8_t> {
static constexpr uint8_t min() { return 0; }
static constexpr uint8_t max() { return 255; }
};
template<> struct iec_limits<uint16_t> {
static constexpr uint16_t min() { return 0; }
static constexpr uint16_t max() { return 65535; }
};
template<> struct iec_limits<uint32_t> {
static constexpr uint32_t min() { return 0; }
static constexpr uint32_t max() { return 4294967295U; }
};
template<> struct iec_limits<uint64_t> {
static constexpr uint64_t min() { return 0; }
static constexpr uint64_t max() { return UINT64_MAX; }
};
// IECVar wrapper
template<typename T>
struct iec_limits<IECVar<T>> : iec_limits<T> {};
// =============================================================================
// Composite Type Traits
// =============================================================================
/** Check if T is an IEC array type */
template<typename T> struct is_iec_array : std::false_type {};
template<typename T, typename B>
struct is_iec_array<IEC_ARRAY_1D<T, B>> : std::true_type {};
template<typename T, typename Bnd1, typename Bnd2>
struct is_iec_array<IEC_ARRAY_2D<T, Bnd1, Bnd2>> : std::true_type {};
template<typename T, typename Bnd1, typename Bnd2, typename Bnd3>
struct is_iec_array<IEC_ARRAY_3D<T, Bnd1, Bnd2, Bnd3>> : std::true_type {};
template<typename T>
constexpr bool is_iec_array_v = is_iec_array<T>::value;
/** Check if T is an IEC struct type */
// Uses std::is_base_of to detect types derived from IEC_STRUCT_Base
// Note: Generated structs inherit from IEC_STRUCT_Base
template<typename T>
struct is_iec_struct : std::integral_constant<bool,std::is_base_of<IEC_STRUCT_Base, T>::value> {};
template<typename T>
constexpr bool is_iec_struct_v = is_iec_struct<T>::value;
/** Check if T is an IEC enumeration type */
template<typename T> struct is_iec_enum : std::false_type {};
template<typename E>
struct is_iec_enum<IEC_ENUM_Value<E>> : std::true_type {};
template<typename E>
struct is_iec_enum<IEC_ENUM_Var<E>> : std::true_type {};
template<typename T>
constexpr bool is_iec_enum_v = is_iec_enum<T>::value;
/** Check if T is an IEC subrange type */
template<typename T> struct is_iec_subrange : std::false_type {};
template<typename B, B L, B U>
struct is_iec_subrange<IEC_SUBRANGE_Value<B, L, U>> : std::true_type {};
template<typename B, B L, B U>
struct is_iec_subrange<IEC_SUBRANGE_Var<B, L, U>> : std::true_type {};
template<typename T>
constexpr bool is_iec_subrange_v = is_iec_subrange<T>::value;
/** Check if T is an IEC pointer type (REF_TO) */
template<typename T> struct is_iec_pointer : std::false_type {};
template<typename T>
struct is_iec_pointer<IEC_REF_TO<T>> : std::true_type {};
template<typename T>
constexpr bool is_iec_pointer_v = is_iec_pointer<T>::value;
/** Check if T is ANY_DERIVED (composite types: arrays, structs, enums, subranges, pointers) */
template<typename T>
struct is_any_derived : std::integral_constant<bool,
is_iec_array<T>::value ||
is_iec_struct<T>::value ||
is_iec_enum<T>::value ||
is_iec_subrange<T>::value ||
is_iec_pointer<T>::value
> {};
template<typename T>
constexpr bool is_any_derived_v = is_any_derived<T>::value;
// =============================================================================
// C++17 SFINAE Helpers
// =============================================================================
template<typename T>
using enable_if_any_int = std::enable_if_t<is_any_int_v<T>, int>;
template<typename T>
using enable_if_any_real = std::enable_if_t<is_any_real_v<T>, int>;
template<typename T>
using enable_if_any_num = std::enable_if_t<is_any_num_v<T>, int>;
template<typename T>
using enable_if_any_bit = std::enable_if_t<is_any_bit_v<T>, int>;
template<typename T>
using enable_if_any_string = std::enable_if_t<is_any_string_v<T>, int>;
template<typename T>
using enable_if_any_date = std::enable_if_t<is_any_date_v<T>, int>;
template<typename T>
using enable_if_any_time = std::enable_if_t<is_any_time_v<T>, int>;
template<typename T>
using enable_if_any_magnitude = std::enable_if_t<is_any_magnitude_v<T>, int>;
template<typename T>
using enable_if_any_elementary = std::enable_if_t<is_any_elementary_v<T>, int>;
template<typename T>
using enable_if_iec_array = std::enable_if_t<is_iec_array_v<T>, int>;
template<typename T>
using enable_if_iec_struct = std::enable_if_t<is_iec_struct_v<T>, int>;
template<typename T>
using enable_if_iec_enum = std::enable_if_t<is_iec_enum_v<T>, int>;
template<typename T>
using enable_if_iec_subrange = std::enable_if_t<is_iec_subrange_v<T>, int>;
template<typename T>
using enable_if_any_derived = std::enable_if_t<is_any_derived_v<T>, int>;
template<typename T>
using enable_if_iec_pointer = std::enable_if_t<is_iec_pointer_v<T>, int>;
// =============================================================================
// C++20 Concepts (when available)
// =============================================================================
#if __cplusplus >= 202002L
#include <concepts>
/** Concept for IEC types */
template<typename T>
concept IECType = is_iec_type_v<T>;
/** Concept for ANY_BOOL types */
template<typename T>
concept AnyBool = is_any_bool_v<T>;
/** Concept for ANY_SINT types (signed integers) */
template<typename T>
concept AnySInt = is_any_sint_v<T>;
/** Concept for ANY_UINT types (unsigned integers) */
template<typename T>
concept AnyUInt = is_any_uint_v<T>;
/** Concept for ANY_INT types (all integers) */
template<typename T>
concept AnyInt = is_any_int_v<T>;
/** Concept for ANY_REAL types */
template<typename T>
concept AnyReal = is_any_real_v<T>;
/** Concept for ANY_NUM types */
template<typename T>
concept AnyNum = is_any_num_v<T>;
/** Concept for ANY_BIT types */
template<typename T>
concept AnyBit = is_any_bit_v<T>;
/** Concept for ANY_STRING types */
template<typename T>
concept AnyString = is_any_string_v<T>;
/** Concept for ANY_DATE types */
template<typename T>
concept AnyDate = is_any_date_v<T>;
/** Concept for ANY_TIME types (durations) */
template<typename T>
concept AnyTime = is_any_time_v<T>;
/** Concept for ANY_MAGNITUDE types */
template<typename T>
concept AnyMagnitude = is_any_magnitude_v<T>;
/** Concept for ANY_ELEMENTARY types */
template<typename T>
concept AnyElementary = is_any_elementary_v<T>;
/** Concept for IEC array types */
template<typename T>
concept IECArray = is_iec_array_v<T>;
/** Concept for IEC struct types */
template<typename T>
concept IECStruct = is_iec_struct_v<T>;
/** Concept for IEC enum types */
template<typename T>
concept IECEnum = is_iec_enum_v<T>;
/** Concept for IEC subrange types */
template<typename T>
concept IECSubrange = is_iec_subrange_v<T>;
/** Concept for ANY_DERIVED types (composite types) */
template<typename T>
concept AnyDerived = is_any_derived_v<T>;
/** Concept for IEC pointer types (REF_TO) */
template<typename T>
concept IECPointer = is_iec_pointer_v<T>;
#endif // C++20
} // namespace strucpp