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

1202 lines
41 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 String Types
*
* This header provides the IEC 61131-3 STRING type as a fixed-length string template.
* STRING[n] represents a string with maximum length n (default 254 per IEC standard).
* The implementation avoids dynamic memory allocation for real-time safety.
*/
#pragma once
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <type_traits>
#include "iec_types.hpp"
#include "iec_var.hpp"
#include "iec_traits.hpp"
namespace strucpp {
// Forward declaration for cross-type assignment
template<size_t MaxLen> class IECStringVar;
template<size_t MaxLen = 254>
class IECString {
public:
static constexpr size_t max_length = MaxLen;
using value_type = CHAR_t;
using size_type = size_t;
constexpr IECString() noexcept : length_(0) {
data_[0] = '\0';
}
IECString(const char* str) noexcept : length_(0) {
if (str) {
size_t len = std::strlen(str);
length_ = static_cast<uint16_t>(len < MaxLen ? len : MaxLen);
std::memcpy(data_, str, length_);
}
data_[length_] = '\0';
}
IECString(const char* str, size_t len) noexcept {
length_ = static_cast<uint16_t>(len < MaxLen ? len : MaxLen);
std::memcpy(data_, str, length_);
data_[length_] = '\0';
}
template<size_t OtherLen>
IECString(const IECString<OtherLen>& other) noexcept {
length_ = static_cast<uint16_t>(other.length() < MaxLen ? other.length() : MaxLen);
std::memcpy(data_, other.c_str(), length_);
data_[length_] = '\0';
}
IECString(const IECString&) = default;
IECString(IECString&&) = default;
IECString& operator=(const IECString&) = default;
IECString& operator=(IECString&&) = default;
IECString& operator=(const char* str) noexcept {
if (str) {
size_t len = std::strlen(str);
length_ = static_cast<uint16_t>(len < MaxLen ? len : MaxLen);
std::memcpy(data_, str, length_);
} else {
length_ = 0;
}
data_[length_] = '\0';
return *this;
}
template<size_t OtherLen>
IECString& operator=(const IECString<OtherLen>& other) noexcept {
length_ = static_cast<uint16_t>(other.length() < MaxLen ? other.length() : MaxLen);
std::memcpy(data_, other.c_str(), length_);
data_[length_] = '\0';
return *this;
}
// Cross-type assignment from IECStringVar (defined after IECStringVar class)
template<size_t OtherLen>
inline IECString& operator=(const IECStringVar<OtherLen>& other) noexcept;
constexpr size_t length() const noexcept { return length_; }
constexpr size_t size() const noexcept { return length_; }
constexpr size_t capacity() const noexcept { return MaxLen; }
constexpr bool empty() const noexcept { return length_ == 0; }
const char* c_str() const noexcept { return data_; }
const char* data() const noexcept { return data_; }
char* data() noexcept { return data_; }
char operator[](size_t index) const noexcept {
return index < length_ ? data_[index] : '\0';
}
char& operator[](size_t index) noexcept {
return data_[index < length_ ? index : length_];
}
char at(size_t index) const noexcept {
return index < length_ ? data_[index] : '\0';
}
void clear() noexcept {
length_ = 0;
data_[0] = '\0';
}
void resize(size_t new_len) noexcept {
if (new_len > MaxLen) new_len = MaxLen;
if (new_len > length_) {
std::memset(data_ + length_, ' ', new_len - length_);
}
length_ = static_cast<uint16_t>(new_len);
data_[length_] = '\0';
}
template<size_t OtherLen>
IECString& append(const IECString<OtherLen>& other) noexcept {
size_t copy_len = other.length();
if (length_ + copy_len > MaxLen) {
copy_len = MaxLen - length_;
}
std::memcpy(data_ + length_, other.c_str(), copy_len);
length_ += static_cast<uint16_t>(copy_len);
data_[length_] = '\0';
return *this;
}
IECString& append(const char* str) noexcept {
if (str) {
size_t str_len = std::strlen(str);
size_t copy_len = str_len;
if (length_ + copy_len > MaxLen) {
copy_len = MaxLen - length_;
}
std::memcpy(data_ + length_, str, copy_len);
length_ += static_cast<uint16_t>(copy_len);
data_[length_] = '\0';
}
return *this;
}
IECString& append(char c) noexcept {
if (length_ < MaxLen) {
data_[length_++] = c;
data_[length_] = '\0';
}
return *this;
}
template<size_t OtherLen>
IECString operator+(const IECString<OtherLen>& other) const noexcept {
IECString result(*this);
result.append(other);
return result;
}
IECString operator+(const char* str) const noexcept {
IECString result(*this);
result.append(str);
return result;
}
template<size_t OtherLen>
IECString& operator+=(const IECString<OtherLen>& other) noexcept {
return append(other);
}
IECString& operator+=(const char* str) noexcept {
return append(str);
}
IECString& operator+=(char c) noexcept {
return append(c);
}
template<size_t OtherLen>
bool operator==(const IECString<OtherLen>& other) const noexcept {
if (length_ != other.length()) return false;
return std::memcmp(data_, other.c_str(), length_) == 0;
}
bool operator==(const char* str) const noexcept {
if (!str) return length_ == 0;
return std::strcmp(data_, str) == 0;
}
template<size_t OtherLen>
bool operator!=(const IECString<OtherLen>& other) const noexcept {
return !(*this == other);
}
bool operator!=(const char* str) const noexcept {
return !(*this == str);
}
template<size_t OtherLen>
bool operator<(const IECString<OtherLen>& other) const noexcept {
return std::strcmp(data_, other.c_str()) < 0;
}
template<size_t OtherLen>
bool operator<=(const IECString<OtherLen>& other) const noexcept {
return std::strcmp(data_, other.c_str()) <= 0;
}
template<size_t OtherLen>
bool operator>(const IECString<OtherLen>& other) const noexcept {
return std::strcmp(data_, other.c_str()) > 0;
}
template<size_t OtherLen>
bool operator>=(const IECString<OtherLen>& other) const noexcept {
return std::strcmp(data_, other.c_str()) >= 0;
}
template<size_t OtherLen>
int compare(const IECString<OtherLen>& other) const noexcept {
return std::strcmp(data_, other.c_str());
}
int compare(const char* str) const noexcept {
return std::strcmp(data_, str ? str : "");
}
template<size_t OtherLen>
size_t find(const IECString<OtherLen>& substr, size_t pos = 0) const noexcept {
if (pos >= length_ || substr.length() == 0) return npos;
const char* found = std::strstr(data_ + pos, substr.c_str());
return found ? static_cast<size_t>(found - data_) : npos;
}
size_t find(const char* substr, size_t pos = 0) const noexcept {
if (pos >= length_ || !substr || !*substr) return npos;
const char* found = std::strstr(data_ + pos, substr);
return found ? static_cast<size_t>(found - data_) : npos;
}
size_t find(char c, size_t pos = 0) const noexcept {
for (size_t i = pos; i < length_; ++i) {
if (data_[i] == c) return i;
}
return npos;
}
IECString substr(size_t pos, size_t len = npos) const noexcept {
if (pos >= length_) return IECString();
if (len == npos || pos + len > length_) {
len = length_ - pos;
}
return IECString(data_ + pos, len);
}
void replace(size_t pos, size_t len, const char* str) noexcept {
if (pos >= length_) return;
if (pos + len > length_) len = length_ - pos;
size_t str_len = str ? std::strlen(str) : 0;
size_t new_len = length_ - len + str_len;
if (new_len > MaxLen) {
str_len = MaxLen - (length_ - len);
new_len = MaxLen;
}
if (str_len != len) {
std::memmove(data_ + pos + str_len, data_ + pos + len, length_ - pos - len);
}
if (str_len > 0 && str) {
std::memcpy(data_ + pos, str, str_len);
}
length_ = static_cast<uint16_t>(new_len);
data_[length_] = '\0';
}
void insert(size_t pos, const char* str) noexcept {
if (pos > length_) pos = length_;
if (!str) return;
size_t str_len = std::strlen(str);
if (length_ + str_len > MaxLen) {
str_len = MaxLen - length_;
}
std::memmove(data_ + pos + str_len, data_ + pos, length_ - pos);
std::memcpy(data_ + pos, str, str_len);
length_ += static_cast<uint16_t>(str_len);
data_[length_] = '\0';
}
void erase(size_t pos, size_t len = npos) noexcept {
if (pos >= length_) return;
if (len == npos || pos + len > length_) {
len = length_ - pos;
}
std::memmove(data_ + pos, data_ + pos + len, length_ - pos - len);
length_ -= static_cast<uint16_t>(len);
data_[length_] = '\0';
}
static constexpr size_t npos = static_cast<size_t>(-1);
private:
char data_[MaxLen + 1];
uint16_t length_;
};
using STRING = IECString<254>;
template<size_t MaxLen>
class IECStringVar {
public:
using value_type = IECString<MaxLen>;
IECStringVar() noexcept : value_{}, forced_{false}, forced_value_{} {}
IECStringVar(const value_type& v) noexcept : value_{v}, forced_{false}, forced_value_{} {}
IECStringVar(const char* str) noexcept : value_{str}, forced_{false}, forced_value_{} {}
IECStringVar(const IECStringVar&) = default;
IECStringVar(IECStringVar&&) = default;
IECStringVar& operator=(const IECStringVar&) = default;
IECStringVar& operator=(IECStringVar&&) = default;
// Cross-size converting constructor (IEC 61131-3: STRING types are interoperable)
template<size_t OtherLen, std::enable_if_t<OtherLen != MaxLen, int> = 0>
IECStringVar(const IECStringVar<OtherLen>& other) noexcept
: value_(other.get().c_str()), forced_{false}, forced_value_{} {}
// Converting constructor from IECString of any size
template<size_t OtherLen, std::enable_if_t<OtherLen != MaxLen, int> = 0>
IECStringVar(const IECString<OtherLen>& other) noexcept
: value_(other.c_str()), forced_{false}, forced_value_{} {}
// Converting constructor from IECVar<IECString<N>> (struct field access returns this type)
template<size_t OtherLen>
IECStringVar(const IECVar<IECString<OtherLen>>& other) noexcept
: value_(static_cast<IECString<OtherLen>>(other).c_str()), forced_{false}, forced_value_{} {}
// Cross-size assignment (IEC 61131-3: STRING types are interoperable, truncation on overflow)
template<size_t OtherLen>
IECStringVar& operator=(const IECStringVar<OtherLen>& other) noexcept {
value_ = IECString<MaxLen>(other.get().c_str());
return *this;
}
// Assignment from IECVar<IECString<N>> (struct field access)
template<size_t OtherLen>
IECStringVar& operator=(const IECVar<IECString<OtherLen>>& other) noexcept {
value_ = IECString<MaxLen>(static_cast<IECString<OtherLen>>(other).c_str());
return *this;
}
// Assignment from IECString of different size
template<size_t OtherLen, std::enable_if_t<OtherLen != MaxLen, int> = 0>
IECStringVar& operator=(const IECString<OtherLen>& other) noexcept {
value_ = IECString<MaxLen>(other.c_str());
return *this;
}
value_type get() const noexcept {
return forced_ ? forced_value_ : value_;
}
void set(const value_type& v) noexcept {
value_ = v;
}
void set(const char* str) noexcept {
value_ = str;
}
value_type get_underlying() const noexcept {
return value_;
}
void force(const value_type& v) noexcept {
forced_ = true;
forced_value_ = v;
}
void force(const char* str) noexcept {
forced_ = true;
forced_value_ = str;
}
void unforce() noexcept {
forced_ = false;
}
bool is_forced() const noexcept {
return forced_;
}
value_type get_forced_value() const noexcept {
return forced_value_;
}
operator value_type() const noexcept {
return get();
}
IECStringVar& operator=(const value_type& v) noexcept {
set(v);
return *this;
}
IECStringVar& operator=(const char* str) noexcept {
set(str);
return *this;
}
// Read-through proxies into the inner IECString. Without these,
// user code in C/C++ POU bodies has to write `name.get().c_str()`
// / `name.get().length()` for every read — `IECStringVar` would
// otherwise expose nothing but `get()` / `set()` / `operator=` to
// its consumers. Routed through the force-aware value (forced
// value when forcing is active, underlying value otherwise) so
// every read respects force semantics the same way `IECVar<T>`'s
// `operator T()` already does for numeric pins. Returning the
// pointer to the persistent member (not to `get()`'s temporary)
// keeps `c_str()`'s buffer stable for the lifetime of `*this`.
//
// NOTE: only methods that didn't previously exist on
// `IECStringVar` are added here. Comparison (`operator==` etc.)
// is intentionally NOT proxied — the free-function overloads at
// the bottom of this file (`operator==(IECStringVar, IECString)`
// and converse) already handle every cross-class compare path,
// and adding member operators would risk overload ambiguity at
// ST call sites that previously bound to the free functions.
constexpr size_t length() const noexcept {
return (forced_ ? forced_value_ : value_).length();
}
const char* c_str() const noexcept {
return (forced_ ? forced_value_ : value_).c_str();
}
char operator[](size_t index) const noexcept {
return (forced_ ? forced_value_ : value_)[index];
}
private:
value_type value_;
bool forced_;
value_type forced_value_;
};
using STRING_VAR = IECStringVar<254>;
// Deferred definition: IECString::operator=(const IECStringVar<OtherLen>&)
// Template deduction doesn't consider user-defined conversions, so we need
// this explicit assignment to handle: rawStringField = stringVar
template<size_t MaxLen>
template<size_t OtherLen>
inline IECString<MaxLen>& IECString<MaxLen>::operator=(const IECStringVar<OtherLen>& other) noexcept {
auto val = other.get();
length_ = static_cast<uint16_t>(val.length() < MaxLen ? val.length() : MaxLen);
std::memcpy(data_, val.c_str(), length_);
data_[length_] = '\0';
return *this;
}
// Comparison operators between IECString and IECStringVar
// (template deduction doesn't use implicit conversions)
template<size_t Len1, size_t Len2>
inline bool operator==(const IECString<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return a == b.get();
}
template<size_t Len1, size_t Len2>
inline bool operator==(const IECStringVar<Len1>& a, const IECString<Len2>& b) noexcept {
return a.get() == b;
}
template<size_t Len1, size_t Len2>
inline bool operator==(const IECStringVar<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return a.get() == b.get();
}
template<size_t Len1, size_t Len2>
inline bool operator!=(const IECString<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return !(a == b);
}
template<size_t Len1, size_t Len2>
inline bool operator!=(const IECStringVar<Len1>& a, const IECString<Len2>& b) noexcept {
return !(a == b);
}
template<size_t Len1, size_t Len2>
inline bool operator!=(const IECStringVar<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return !(a == b);
}
// Comparison operators for IECVar<IECString<N>> (struct field access returns this type)
// Template deduction won't chain through IECVar::operator T() + IECString comparison
template<size_t Len1, size_t Len2>
inline bool operator==(const IECVar<IECString<Len1>>& a, const IECStringVar<Len2>& b) noexcept {
return static_cast<IECString<Len1>>(a) == b.get();
}
template<size_t Len1, size_t Len2>
inline bool operator==(const IECStringVar<Len1>& a, const IECVar<IECString<Len2>>& b) noexcept {
return a.get() == static_cast<IECString<Len2>>(b);
}
template<size_t Len1, size_t Len2>
inline bool operator!=(const IECVar<IECString<Len1>>& a, const IECStringVar<Len2>& b) noexcept {
return !(a == b);
}
template<size_t Len1, size_t Len2>
inline bool operator!=(const IECStringVar<Len1>& a, const IECVar<IECString<Len2>>& b) noexcept {
return !(a == b);
}
// Comparison with const char*
template<size_t MaxLen>
inline bool operator==(const IECStringVar<MaxLen>& a, const char* b) noexcept {
return a.get() == b;
}
template<size_t MaxLen>
inline bool operator==(const char* a, const IECStringVar<MaxLen>& b) noexcept {
return b.get() == a;
}
template<size_t MaxLen>
inline bool operator!=(const IECStringVar<MaxLen>& a, const char* b) noexcept {
return !(a == b);
}
template<size_t MaxLen>
inline bool operator!=(const char* a, const IECStringVar<MaxLen>& b) noexcept {
return !(a == b);
}
// Ordering operators for IECStringVar
template<size_t Len1, size_t Len2>
inline bool operator<(const IECStringVar<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return a.get() < b.get();
}
template<size_t Len1, size_t Len2>
inline bool operator<(const IECString<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return a < b.get();
}
template<size_t Len1, size_t Len2>
inline bool operator<(const IECStringVar<Len1>& a, const IECString<Len2>& b) noexcept {
return a.get() < b;
}
template<size_t Len1, size_t Len2>
inline bool operator>(const IECStringVar<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return b < a;
}
template<size_t Len1, size_t Len2>
inline bool operator>(const IECString<Len1>& a, const IECStringVar<Len2>& b) noexcept {
return b < a;
}
template<size_t Len1, size_t Len2>
inline bool operator>(const IECStringVar<Len1>& a, const IECString<Len2>& b) noexcept {
return b < a;
}
// Non-template alias for codegen: IEC_STRING = IECStringVar<254>
// For parameterized STRING(N), codegen emits IECStringVar<N> directly
using IEC_STRING = IECStringVar<254>;
template<size_t MaxLen>
inline size_t LEN(const IECString<MaxLen>& s) noexcept {
return s.length();
}
// IECStringVar overload: template deduction doesn't go through implicit conversions
template<size_t MaxLen>
inline size_t LEN(const IECStringVar<MaxLen>& s) noexcept {
return s.get().length();
}
template<size_t MaxLen>
inline IECString<MaxLen> LEFT(const IECString<MaxLen>& s, size_t len) noexcept {
return s.substr(0, len);
}
template<size_t MaxLen>
inline IECString<MaxLen> RIGHT(const IECString<MaxLen>& s, size_t len) noexcept {
if (len >= s.length()) return s;
return s.substr(s.length() - len, len);
}
template<size_t MaxLen>
inline IECString<MaxLen> MID(const IECString<MaxLen>& s, size_t len, size_t pos) noexcept {
if (pos == 0) return IECString<MaxLen>();
return s.substr(pos - 1, len);
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
constexpr size_t ResultLen = MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2;
IECString<ResultLen> result(s1);
result.append(s2);
return result;
}
// Variadic CONCAT for 3+ arguments (IEC 61131-3 extensible function)
template<size_t MaxLen1, size_t MaxLen2, typename... Args>
inline auto
CONCAT(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2, const Args&... rest) noexcept {
return CONCAT(CONCAT(s1, s2), rest...);
}
template<size_t MaxLen>
inline IECString<MaxLen> INSERT(const IECString<MaxLen>& s1, const IECString<MaxLen>& s2, size_t pos) noexcept {
IECString<MaxLen> result(s1);
if (pos == 0) pos = 1;
result.insert(pos - 1, s2.c_str());
return result;
}
template<size_t MaxLen>
inline IECString<MaxLen> DELETE_STR(const IECString<MaxLen>& s, size_t len, size_t pos) noexcept {
IECString<MaxLen> result(s);
if (pos == 0) pos = 1;
result.erase(pos - 1, len);
return result;
}
template<size_t MaxLen>
inline IECString<MaxLen> REPLACE(const IECString<MaxLen>& s1, const IECString<MaxLen>& s2, size_t len, size_t pos) noexcept {
IECString<MaxLen> result(s1);
if (pos == 0) pos = 1;
result.replace(pos - 1, len, s2.c_str());
return result;
}
// const char* overloads for string functions (codegen may emit string literals)
template<size_t MaxLen>
inline IECString<MaxLen> REPLACE(const IECString<MaxLen>& s1, const char* s2, size_t len, size_t pos) noexcept {
return REPLACE(s1, IECString<MaxLen>(s2), len, pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> REPLACE(const IECStringVar<MaxLen>& s1, const char* s2, size_t len, size_t pos) noexcept {
return REPLACE(s1.get(), IECString<MaxLen>(s2), len, pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> INSERT(const IECString<MaxLen>& s1, const char* s2, size_t pos) noexcept {
return INSERT(s1, IECString<MaxLen>(s2), pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> INSERT(const IECStringVar<MaxLen>& s1, const char* s2, size_t pos) noexcept {
return INSERT(s1.get(), IECString<MaxLen>(s2), pos);
}
template<size_t MaxLen>
inline size_t FIND(const IECString<MaxLen>& s1, const char* s2) noexcept {
return FIND(s1, IECString<MaxLen>(s2));
}
template<size_t MaxLen>
inline size_t FIND(const IECStringVar<MaxLen>& s1, const char* s2) noexcept {
return FIND(s1.get(), IECString<MaxLen>(s2));
}
template<size_t MaxLen1, size_t MaxLen2>
inline size_t FIND(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
size_t pos = s1.find(s2);
return pos == IECString<MaxLen1>::npos ? 0 : pos + 1;
}
// =============================================================================
// IECStringVar overloads — template deduction doesn't use implicit conversions,
// so we need explicit overloads that forward to the IECString versions via .get()
// =============================================================================
template<size_t MaxLen>
inline IECString<MaxLen> LEFT(const IECStringVar<MaxLen>& s, size_t len) noexcept {
return LEFT(s.get(), len);
}
template<size_t MaxLen>
inline IECString<MaxLen> RIGHT(const IECStringVar<MaxLen>& s, size_t len) noexcept {
return RIGHT(s.get(), len);
}
template<size_t MaxLen>
inline IECString<MaxLen> MID(const IECStringVar<MaxLen>& s, size_t len, size_t pos) noexcept {
return MID(s.get(), len, pos);
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECStringVar<MaxLen1>& s1, const IECStringVar<MaxLen2>& s2) noexcept {
return CONCAT(s1.get(), s2.get());
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECStringVar<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return CONCAT(s1.get(), s2);
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECString<MaxLen1>& s1, const IECStringVar<MaxLen2>& s2) noexcept {
return CONCAT(s1, s2.get());
}
template<size_t MaxLen>
inline IECString<MaxLen> INSERT(const IECStringVar<MaxLen>& s1, const IECStringVar<MaxLen>& s2, size_t pos) noexcept {
return INSERT(s1.get(), s2.get(), pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> INSERT(const IECStringVar<MaxLen>& s1, const IECString<MaxLen>& s2, size_t pos) noexcept {
return INSERT(s1.get(), s2, pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> INSERT(const IECString<MaxLen>& s1, const IECStringVar<MaxLen>& s2, size_t pos) noexcept {
return INSERT(s1, s2.get(), pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> DELETE_STR(const IECStringVar<MaxLen>& s, size_t len, size_t pos) noexcept {
return DELETE_STR(s.get(), len, pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> REPLACE(const IECStringVar<MaxLen>& s1, const IECStringVar<MaxLen>& s2, size_t len, size_t pos) noexcept {
return REPLACE(s1.get(), s2.get(), len, pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> REPLACE(const IECStringVar<MaxLen>& s1, const IECString<MaxLen>& s2, size_t len, size_t pos) noexcept {
return REPLACE(s1.get(), s2, len, pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> REPLACE(const IECString<MaxLen>& s1, const IECStringVar<MaxLen>& s2, size_t len, size_t pos) noexcept {
return REPLACE(s1, s2.get(), len, pos);
}
template<size_t MaxLen1, size_t MaxLen2>
inline size_t FIND(const IECStringVar<MaxLen1>& s1, const IECStringVar<MaxLen2>& s2) noexcept {
return FIND(s1.get(), s2.get());
}
template<size_t MaxLen1, size_t MaxLen2>
inline size_t FIND(const IECStringVar<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return FIND(s1.get(), s2);
}
template<size_t MaxLen1, size_t MaxLen2>
inline size_t FIND(const IECString<MaxLen1>& s1, const IECStringVar<MaxLen2>& s2) noexcept {
return FIND(s1, s2.get());
}
// =============================================================================
// IECVar<IECString<N>> overloads — struct field access returns this type.
// Template deduction won't chain through operator T().
// =============================================================================
template<size_t MaxLen>
inline size_t LEN(const IECVar<IECString<MaxLen>>& s) noexcept {
return static_cast<IECString<MaxLen>>(s).length();
}
template<size_t MaxLen>
inline IECString<MaxLen> LEFT(const IECVar<IECString<MaxLen>>& s, size_t len) noexcept {
return LEFT(static_cast<IECString<MaxLen>>(s), len);
}
template<size_t MaxLen>
inline IECString<MaxLen> RIGHT(const IECVar<IECString<MaxLen>>& s, size_t len) noexcept {
return RIGHT(static_cast<IECString<MaxLen>>(s), len);
}
template<size_t MaxLen>
inline IECString<MaxLen> MID(const IECVar<IECString<MaxLen>>& s, size_t len, size_t pos) noexcept {
return MID(static_cast<IECString<MaxLen>>(s), len, pos);
}
template<size_t MaxLen>
inline IECString<MaxLen> DELETE_STR(const IECVar<IECString<MaxLen>>& s, size_t len, size_t pos) noexcept {
return DELETE_STR(static_cast<IECString<MaxLen>>(s), len, pos);
}
template<size_t MaxLen1, size_t MaxLen2>
inline size_t FIND(const IECVar<IECString<MaxLen1>>& s1, const IECStringVar<MaxLen2>& s2) noexcept {
return FIND(static_cast<IECString<MaxLen1>>(s1), s2.get());
}
template<size_t MaxLen1, size_t MaxLen2>
inline size_t FIND(const IECVar<IECString<MaxLen1>>& s1, const IECString<MaxLen2>& s2) noexcept {
return FIND(static_cast<IECString<MaxLen1>>(s1), s2);
}
template<size_t MaxLen1, size_t MaxLen2>
inline size_t FIND(const IECStringVar<MaxLen1>& s1, const IECVar<IECString<MaxLen2>>& s2) noexcept {
return FIND(s1.get(), static_cast<IECString<MaxLen2>>(s2));
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<MaxLen1> REPLACE(const IECVar<IECString<MaxLen1>>& s1, const IECStringVar<MaxLen2>& s2, size_t len, size_t pos) noexcept {
return REPLACE(static_cast<IECString<MaxLen1>>(s1), IECString<MaxLen1>(s2.get().c_str()), len, pos);
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<MaxLen1> INSERT(const IECVar<IECString<MaxLen1>>& s1, const IECStringVar<MaxLen2>& s2, size_t pos) noexcept {
return INSERT(static_cast<IECString<MaxLen1>>(s1), IECString<MaxLen1>(s2.get().c_str()), pos);
}
// Cross-size REPLACE/INSERT: arguments may have different string sizes
template<size_t MaxLen1, size_t MaxLen2, std::enable_if_t<MaxLen1 != MaxLen2, int> = 0>
inline IECString<MaxLen1> REPLACE(const IECStringVar<MaxLen1>& s1, const IECStringVar<MaxLen2>& s2, size_t len, size_t pos) noexcept {
return REPLACE(s1.get(), IECString<MaxLen1>(s2.get().c_str()), len, pos);
}
template<size_t MaxLen1, size_t MaxLen2, std::enable_if_t<MaxLen1 != MaxLen2, int> = 0>
inline IECString<MaxLen1> INSERT(const IECStringVar<MaxLen1>& s1, const IECStringVar<MaxLen2>& s2, size_t pos) noexcept {
return INSERT(s1.get(), IECString<MaxLen1>(s2.get().c_str()), pos);
}
// CONCAT overloads for IECVar<IECString<N>>
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECVar<IECString<MaxLen1>>& s1, const IECString<MaxLen2>& s2) noexcept {
return CONCAT(static_cast<IECString<MaxLen1>>(s1), s2);
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECString<MaxLen1>& s1, const IECVar<IECString<MaxLen2>>& s2) noexcept {
return CONCAT(s1, static_cast<IECString<MaxLen2>>(s2));
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECVar<IECString<MaxLen1>>& s1, const IECStringVar<MaxLen2>& s2) noexcept {
return CONCAT(static_cast<IECString<MaxLen1>>(s1), s2.get());
}
template<size_t MaxLen1, size_t MaxLen2>
inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)>
CONCAT(const IECStringVar<MaxLen1>& s1, const IECVar<IECString<MaxLen2>>& s2) noexcept {
return CONCAT(s1.get(), static_cast<IECString<MaxLen2>>(s2));
}
template<size_t MaxLen1, size_t MaxLen2>
inline bool GT_STRING(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return s1 > s2;
}
template<size_t MaxLen1, size_t MaxLen2>
inline bool GE_STRING(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return s1 >= s2;
}
template<size_t MaxLen1, size_t MaxLen2>
inline bool EQ_STRING(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return s1 == s2;
}
template<size_t MaxLen1, size_t MaxLen2>
inline bool LE_STRING(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return s1 <= s2;
}
template<size_t MaxLen1, size_t MaxLen2>
inline bool LT_STRING(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return s1 < s2;
}
template<size_t MaxLen1, size_t MaxLen2>
inline bool NE_STRING(const IECString<MaxLen1>& s1, const IECString<MaxLen2>& s2) noexcept {
return s1 != s2;
}
// =============================================================================
// TO_STRING Conversions
// =============================================================================
// Numeric → STRING (uses snprintf for real-time safety, no std::to_string)
//
// Split into two SFINAE'd overloads (signed vs unsigned) rather than a single
// function with `if constexpr`, because user C/C++ POU code in
// `c_blocks_code.cpp` compiles under whatever -std= the platform's Arduino
// core picked (gnu++14 on mbed cores). `if constexpr` is C++17-only and
// would make this header unusable from those compilation units. Tag
// dispatch via `enable_if_t` works back to C++11.
template<typename T,
std::enable_if_t<std::is_integral<decltype(iec_unwrap(std::declval<T>()))>::value
&& std::is_unsigned<decltype(iec_unwrap(std::declval<T>()))>::value, int> = 0>
inline IECString<254> TO_STRING(T v) noexcept {
char buf[32];
std::snprintf(buf, sizeof(buf), "%llu", static_cast<unsigned long long>(iec_unwrap(v)));
return IECString<254>(buf);
}
template<typename T,
std::enable_if_t<std::is_integral<decltype(iec_unwrap(std::declval<T>()))>::value
&& !std::is_unsigned<decltype(iec_unwrap(std::declval<T>()))>::value, int> = 0>
inline IECString<254> TO_STRING(T v) noexcept {
char buf[32];
std::snprintf(buf, sizeof(buf), "%lld", static_cast<long long>(iec_unwrap(v)));
return IECString<254>(buf);
}
template<typename T,
std::enable_if_t<std::is_floating_point<decltype(iec_unwrap(std::declval<T>()))>::value, int> = 0>
inline IECString<254> TO_STRING(T v) noexcept {
char buf[64];
std::snprintf(buf, sizeof(buf), "%g", static_cast<double>(iec_unwrap(v)));
return IECString<254>(buf);
}
// BOOL → STRING
inline IECString<254> TO_STRING(IECVar<bool> v) noexcept {
return IECString<254>(iec_unwrap(v) ? "TRUE" : "FALSE");
}
// IECString → STRING (identity / cross-size copy)
template<size_t N>
inline IECString<254> TO_STRING(const IECString<N>& v) noexcept {
return IECString<254>(v.c_str());
}
template<size_t N>
inline IECString<254> TO_STRING(const IECStringVar<N>& v) noexcept {
return IECString<254>(v.get().c_str());
}
// =============================================================================
// OSCAT-Compatible String Functions
// =============================================================================
// CODE - Return ASCII code of first character
template<size_t N>
inline int32_t CODE(const IECString<N>& s) noexcept {
return s.length() > 0 ? static_cast<int32_t>(static_cast<unsigned char>(s[0])) : 0;
}
template<size_t N>
inline int32_t CODE(const IECStringVar<N>& s) noexcept {
return CODE(s.get());
}
// CHR - Return string from ASCII code
inline IECString<254> CHR(int32_t code) noexcept {
char buf[2] = { static_cast<char>(code), '\0' };
return IECString<254>(buf);
}
// TRIM - Remove leading and trailing whitespace
template<size_t N>
inline IECString<N> TRIM(const IECString<N>& s) noexcept {
const char* start = s.c_str();
const char* end = start + s.length();
while (start < end && (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n')) ++start;
while (end > start && (*(end-1) == ' ' || *(end-1) == '\t' || *(end-1) == '\r' || *(end-1) == '\n')) --end;
return IECString<N>(start, static_cast<size_t>(end - start));
}
template<size_t N>
inline IECString<N> TRIM(const IECStringVar<N>& s) noexcept {
return TRIM(s.get());
}
// LOWERCASE / TOUPPER - Case conversion (OSCAT uses these names)
template<size_t N>
inline IECString<N> LOWERCASE(const IECString<N>& s) noexcept {
IECString<N> result(s);
for (size_t i = 0; i < result.length(); ++i) {
char c = result[i];
if (c >= 'A' && c <= 'Z') result[i] = c + ('a' - 'A');
}
return result;
}
template<size_t N>
inline IECString<N> LOWERCASE(const IECStringVar<N>& s) noexcept {
return LOWERCASE(s.get());
}
template<size_t N>
inline IECString<N> UPPERCASE(const IECString<N>& s) noexcept {
IECString<N> result(s);
for (size_t i = 0; i < result.length(); ++i) {
char c = result[i];
if (c >= 'a' && c <= 'z') result[i] = c - ('a' - 'A');
}
return result;
}
template<size_t N>
inline IECString<N> UPPERCASE(const IECStringVar<N>& s) noexcept {
return UPPERCASE(s.get());
}
// CONCAT with const char* overloads (codegen may mix string literals with IECString)
template<size_t MaxLen>
inline IECString<MaxLen> CONCAT(const IECString<MaxLen>& s1, const char* s2) noexcept {
IECString<MaxLen> result(s1);
result.append(s2);
return result;
}
template<size_t MaxLen>
inline IECString<MaxLen> CONCAT(const char* s1, const IECString<MaxLen>& s2) noexcept {
IECString<MaxLen> result(s1);
result.append(s2);
return result;
}
template<size_t MaxLen>
inline IECString<MaxLen> CONCAT(const IECStringVar<MaxLen>& s1, const char* s2) noexcept {
return CONCAT(s1.get(), s2);
}
template<size_t MaxLen>
inline IECString<MaxLen> CONCAT(const char* s1, const IECStringVar<MaxLen>& s2) noexcept {
return CONCAT(s1, s2.get());
}
// =============================================================================
// String-to-Numeric Conversions
// IEC 61131-3: STRING_TO_INT, STRING_TO_REAL, etc.
// Placed here because they need both IECString and IEC_INT/IEC_REAL types.
// =============================================================================
template<size_t N>
inline IEC_INT TO_INT(const IECString<N>& s) noexcept {
return IEC_INT(static_cast<INT_t>(std::strtol(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_INT TO_INT(const IECStringVar<N>& s) noexcept {
return TO_INT(s.get());
}
template<size_t N>
inline IEC_REAL TO_REAL(const IECString<N>& s) noexcept {
return IEC_REAL(static_cast<REAL_t>(std::strtod(s.c_str(), nullptr)));
}
template<size_t N>
inline IEC_REAL TO_REAL(const IECStringVar<N>& s) noexcept {
return TO_REAL(s.get());
}
template<size_t N>
inline IEC_LREAL TO_LREAL(const IECString<N>& s) noexcept {
return IEC_LREAL(static_cast<LREAL_t>(std::strtod(s.c_str(), nullptr)));
}
template<size_t N>
inline IEC_LREAL TO_LREAL(const IECStringVar<N>& s) noexcept {
return TO_LREAL(s.get());
}
template<size_t N>
inline IEC_DINT TO_DINT(const IECString<N>& s) noexcept {
return IEC_DINT(static_cast<DINT_t>(std::strtol(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_DINT TO_DINT(const IECStringVar<N>& s) noexcept {
return TO_DINT(s.get());
}
template<size_t N>
inline IEC_SINT TO_SINT(const IECString<N>& s) noexcept {
return IEC_SINT(static_cast<SINT_t>(std::strtol(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_SINT TO_SINT(const IECStringVar<N>& s) noexcept {
return TO_SINT(s.get());
}
template<size_t N>
inline IEC_LINT TO_LINT(const IECString<N>& s) noexcept {
return IEC_LINT(static_cast<LINT_t>(std::strtoll(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_LINT TO_LINT(const IECStringVar<N>& s) noexcept {
return TO_LINT(s.get());
}
template<size_t N>
inline IEC_USINT TO_USINT(const IECString<N>& s) noexcept {
return IEC_USINT(static_cast<USINT_t>(std::strtoul(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_USINT TO_USINT(const IECStringVar<N>& s) noexcept {
return TO_USINT(s.get());
}
template<size_t N>
inline IEC_UINT TO_UINT(const IECString<N>& s) noexcept {
return IEC_UINT(static_cast<UINT_t>(std::strtoul(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_UINT TO_UINT(const IECStringVar<N>& s) noexcept {
return TO_UINT(s.get());
}
template<size_t N>
inline IEC_UDINT TO_UDINT(const IECString<N>& s) noexcept {
return IEC_UDINT(static_cast<UDINT_t>(std::strtoul(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_UDINT TO_UDINT(const IECStringVar<N>& s) noexcept {
return TO_UDINT(s.get());
}
template<size_t N>
inline IEC_ULINT TO_ULINT(const IECString<N>& s) noexcept {
return IEC_ULINT(static_cast<ULINT_t>(std::strtoull(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_ULINT TO_ULINT(const IECStringVar<N>& s) noexcept {
return TO_ULINT(s.get());
}
template<size_t N>
inline IEC_BYTE TO_BYTE(const IECString<N>& s) noexcept {
return IEC_BYTE(static_cast<BYTE_t>(std::strtoul(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_BYTE TO_BYTE(const IECStringVar<N>& s) noexcept {
return TO_BYTE(s.get());
}
template<size_t N>
inline IEC_WORD TO_WORD(const IECString<N>& s) noexcept {
return IEC_WORD(static_cast<WORD_t>(std::strtoul(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_WORD TO_WORD(const IECStringVar<N>& s) noexcept {
return TO_WORD(s.get());
}
template<size_t N>
inline IEC_DWORD TO_DWORD(const IECString<N>& s) noexcept {
return IEC_DWORD(static_cast<DWORD_t>(std::strtoul(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_DWORD TO_DWORD(const IECStringVar<N>& s) noexcept {
return TO_DWORD(s.get());
}
template<size_t N>
inline IEC_LWORD TO_LWORD(const IECString<N>& s) noexcept {
return IEC_LWORD(static_cast<LWORD_t>(std::strtoull(s.c_str(), nullptr, 10)));
}
template<size_t N>
inline IEC_LWORD TO_LWORD(const IECStringVar<N>& s) noexcept {
return TO_LWORD(s.get());
}
template<size_t N>
inline IEC_BOOL TO_BOOL(const IECString<N>& s) noexcept {
// "TRUE" or "1" → true, everything else → false
return IEC_BOOL(s.length() > 0 && (s[0] == 'T' || s[0] == 't' || s[0] == '1'));
}
template<size_t N>
inline IEC_BOOL TO_BOOL(const IECStringVar<N>& s) noexcept {
return TO_BOOL(s.get());
}
} // namespace strucpp