/** * Marlin 3D Printer Firmware * Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #pragma once /** * Lightweight string class template providing operators for all common tasks * and conversion from F() and PSTR() strings into SRAM strings that reside * on the stack or persistently, with overflow prevention. * * Examples: * * MString<50> mystr(F("Hello "), intvar, " World"); // "Hello 3 World" * * mystr.append(" (", p_float_t(123.4, 2), ')'); // "Hello 3 World (123.40)" * * mystr.clear(); * * mystr.append(spaces_t(10), repchr_t('-', 5)); // Repeats are sometimes cheaper than strings * * mystr.appendf(F(" ... %i/%i"), count, total); // Works like printf, requires F string * */ #include "types.h" #include "utility.h" // AXIS_CHAR #include "../lcd/utf8.h" #ifndef DEFAULT_MSTRING_SIZE #define DEFAULT_MSTRING_SIZE 20 #endif //#define UNSAFE_MSTRING // Don't initialize the string and don't terminate strncpy //#define USE_SPRINTF // Use sprintf instead of snprintf //#define DJB2_HASH // 32-bit hash with Djb2 algorithm //#define MSTRING_DEBUG // Debug string operations to diagnose memory leaks //#define FASTER_APPEND // Append without using an intermediate buffer // Declare externs for serial debug output template extern void SERIAL_ECHO(T x); template extern void SERIAL_ECHOLN(T x); extern void SERIAL_ECHO(serial_char_t x); extern void SERIAL_CHAR(char c); #define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U) #if ENABLED(USE_SPRINTF) #define SNPRINTF(A, S, V...) sprintf(A, V) #define SNPRINTF_P(A, S, V...) sprintf_P(A, V) #else #define SNPRINTF(V...) snprintf(V) #define SNPRINTF_P(V...) snprintf_P(V) #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" /** * @brief MString class template * @details A class template providing convenient string operators, * very similar to the Arduino String class, as it turns out. * * @tparam SIZE The pre-allocated storage for the string buffer */ template class MString { protected: char str[SIZE+1]; public: MString() { safety(0); safety(SIZE); } template MString(const T v) { set(v); safety(SIZE); } static_assert(SIZE > 0, "Bad SIZE for MString!"); void debug(FSTR_P const f) { #if ENABLED(MSTRING_DEBUG) SERIAL_ECHO(FTOP(f)); SERIAL_CHAR(':'); SERIAL_ECHO(uintptr_t(str)); SERIAL_CHAR(' '); SERIAL_ECHO(length()); SERIAL_CHAR(' '); SERIAL_ECHOLN(str); #endif } void safety(const int n) { if (SAFE && n <= SIZE) str[n] = '\0'; } // Chainable String Setters MString& set() { str[0] = '\0'; debug(F("clear")); return *this; } MString& set(char *s) { strncpy(str, s, SIZE); debug(F("string")); return *this; } MString& set(const char *s) { return set(const_cast(s)); } MString& set_P(PGM_P const s) { strncpy_P(str, s, SIZE); debug(F("pstring")); return *this; } MString& set(FSTR_P const f) { return set_P(FTOP(f)); } MString& set(const bool &b) { return set(b ? F("true") : F("false")); } MString& set(const char c) { str[0] = c; if (1 < SIZE) str[1] = '\0'; debug(F("char")); return *this; } MString& set(const int8_t &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("int8_t")); return *this; } MString& set(const short &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("short")); return *this; } MString& set(const int &i) { SNPRINTF_P(str, SIZE, PSTR("%d"), i); debug(F("int")); return *this; } MString& set(const long &l) { SNPRINTF_P(str, SIZE, PSTR("%ld"), l); debug(F("long")); return *this; } MString& set(const unsigned char &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("uchar")); return *this; } MString& set(const unsigned short &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("ushort")); return *this; } MString& set(const unsigned int &i) { SNPRINTF_P(str, SIZE, PSTR("%u"), i); debug(F("uint")); return *this; } MString& set(const unsigned long &l) { SNPRINTF_P(str, SIZE, PSTR("%lu"), l); debug(F("ulong")); return *this; } MString& set(const float &f) { return set(p_float_t(f, SERIAL_FLOAT_PRECISION)); } MString& set(const p_float_t &pf) { return set(w_float_t(pf.value, 1, pf.prec)); } MString& set(const w_float_t &wf) { char f1[20]; return set(dtostrf(wf.value, wf.width, wf.prec, f1)); } MString& set(const serial_char_t &v) { return set(char(v.c)); } MString& set(const xyz_pos_t &v) { set(); return append(v); } MString& set(const xyze_pos_t &v) { set(); return append(v); } template MString& set(const MString &m) { strncpy(str, &m, SIZE); debug(F("MString")); return *this; } MString& setn(char *s, int len) { int c = _MIN(len, SIZE); strncpy(str, s, c); str[c] = '\0'; debug(F("string")); return *this; } MString& setn(const char *s, int len) { return setn(const_cast(s), len); } MString& setn_P(PGM_P const s, int len) { int c = _MIN(len, SIZE); strncpy_P(str, s, c); str[c] = '\0'; debug(F("pstring")); return *this; } MString& setn(FSTR_P const f, int len) { return setn_P(FTOP(f), len); } // set(repchr_t('-', 10)) MString& set(const repchr_t &s) { int c = _MIN(s.count, SIZE); memset(str, s.asc, c); str[c] = '\0'; debug(F("")); return *this; } // set(spaces_t(10)) MString& set(const spaces_t &s) { repchr_t r(' ', s.count); return set(r); } // Set with format string and arguments, like printf template MString& setf_P(PGM_P const fmt, Args... more) { SNPRINTF_P(str, SIZE, fmt, more...); debug(F("setf_P")); return *this; } template MString& setf(const char *fmt, Args... more) { SNPRINTF(str, SIZE, fmt, more...); debug(F("setf")); return *this; } template MString& setf(FSTR_P const fmt, Args... more) { return setf_P(FTOP(fmt), more...); } // Chainable String appenders MString& append() { debug(F("nil")); return *this; } // for macros that might emit no output MString& append(char *s) { int sz = length(); if (sz < SIZE) strncpy(str + sz, s, SIZE - sz); debug(F("string")); return *this; } MString& append(const char *s) { return append(const_cast(s)); } MString& append_P(PGM_P const s) { int sz = length(); if (sz < SIZE) strncpy_P(str + sz, s, SIZE - sz); debug(F("pstring")); return *this; } MString& append(FSTR_P const f) { return append_P(FTOP(f)); } MString& append(const bool &b) { return append(b ? F("true") : F("false")); } MString& append(const char c) { int sz = length(); if (sz < SIZE) { str[sz] = c; if (sz < SIZE - 1) str[sz + 1] = '\0'; } return *this; } #if ENABLED(FASTER_APPEND) MString& append(const int8_t &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } MString& append(const short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } MString& append(const int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%d", i); return *this; } MString& append(const long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%ld", l); return *this; } MString& append(const unsigned char &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } MString& append(const unsigned short &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } MString& append(const unsigned int &i) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%u", i); return *this; } MString& append(const unsigned long &l) { int sz = length(); SNPRINTF(&str[sz], SIZE - sz, "%lu", l); return *this; } #else MString& append(const int8_t &i) { char buf[ 5]; sprintf(buf, "%d", i); return append(buf); } MString& append(const short &i) { char buf[12]; sprintf(buf, "%d", i); return append(buf); } MString& append(const int &i) { char buf[12]; sprintf(buf, "%d", i); return append(buf); } MString& append(const long &l) { char buf[12]; sprintf(buf, "%ld", l); return append(buf); } MString& append(const unsigned char &i) { char buf[ 5]; sprintf(buf, "%u", i); return append(buf); } MString& append(const unsigned short &i) { char buf[11]; sprintf(buf, "%u", i); return append(buf); } MString& append(const unsigned int &i) { char buf[11]; sprintf(buf, "%u", i); return append(buf); } MString& append(const unsigned long &l) { char buf[11]; sprintf(buf, "%lu", l); return append(buf); } #endif MString& append(const float &f) { return append(p_float_t(f, SERIAL_FLOAT_PRECISION)); } MString& append(const p_float_t &pf) { return append(w_float_t(pf.value, 1, pf.prec)); } MString& append(const w_float_t &wf) { char f1[20]; return append(dtostrf(wf.value, wf.width, wf.prec, f1)); } MString& append(const serial_char_t &v) { return append(char(v.c)); } MString& append(const xyz_pos_t &v) { LOOP_NUM_AXES(i) { if (i) append(' '); append(AXIS_CHAR(i), v[i]); } debug(F("xyz")); return *this; } MString& append(const xyze_pos_t &v) { LOOP_LOGICAL_AXES(i) { if (i) append(' '); append(AXIS_CHAR(i), v[i]); } debug(F("xyze")); return *this; } template MString& append(const MString &m) { return append(&m); } // Append only if the given space is available MString& appendn(char *s, int len) { int sz = length(), c = _MIN(len, SIZE - sz); if (c > 0) { strncpy(str + sz, s, c); str[sz + c] = '\0'; } debug(F("string")); return *this; } MString& appendn(const char *s, int len) { return appendn(const_cast(s), len); } MString& appendn_P(PGM_P const s, int len) { int sz = length(), c = _MIN(len, SIZE - sz); if (c > 0) { strncpy_P(str + sz, s, c); str[sz + c] = '\0'; } debug(F("pstring")); return *this; } MString& appendn(FSTR_P const f, int len) { return appendn_P(FTOP(f), len); } // append(repchr_t('-', 10)) MString& append(const repchr_t &s) { const int sz = length(), c = _MIN(s.count, SIZE - sz); if (c > 0) { memset(str + sz, s.asc, c); safety(sz + c); } debug(F("repchr")); return *this; } // append(spaces_t(10)) MString& append(const spaces_t &s) { return append(repchr_t(' ', s.count)); } template MString& appendf_P(PGM_P const fmt, Args... more) { int sz = length(); if (sz < SIZE) SNPRINTF_P(str + sz, SIZE - sz, fmt, more...); debug(F("appendf_P")); return *this; } template MString& appendf(const char *fmt, Args... more) { const int sz = length(); if (sz < SIZE) SNPRINTF(str + sz, SIZE - sz, fmt, more...); debug(F("appendf")); return *this; } template MString& appendf(FSTR_P const fmt, Args... more) { return appendf_P(FTOP(fmt), more...); } // Instantiate with a list of things template MString(T arg1, Args... more) { set(arg1); append(more...); } // Catch unhandled types to prevent infinite recursion template MString& append(T) { return append(TERN(MSTRING_DEBUG, typeid(T).name(), '?')); } // Take a list of any number of arguments and append them to the string template MString& append(T arg1, Args... more) { return append(arg1).append(more...); } // Take a list of any number of arguments and set them in the string template MString& set(T arg1, Args... more) { return set(arg1).append(more...); } // Operator = as shorthand for set() template MString& operator=(const T &v) { return set(v); } // Operator += as shorthand for append() template MString& operator+=(const T &v) { return append(v); } // Operator + as shorthand for append-to-copy template MString operator+(const T &v) { return MString(str, v); } #ifndef __AVR__ MString(const double d) { set(d); } MString& set(const double &f) { return set(p_double_t(f, SERIAL_FLOAT_PRECISION)); } MString& set(const p_double_t &pf) { return set(w_double_t(pf.value, 1, pf.prec)); } MString& set(const w_double_t &wf) { char d1[20]; return set(dtostrf(wf.value, wf.width, wf.prec, d1)); } MString& append(const double &f) { return append(p_double_t(f, SERIAL_FLOAT_PRECISION)); } MString& append(const p_double_t &pf) { return append(w_double_t(pf.value, 1, pf.prec)); } MString& append(const w_double_t &wf) { char d1[20]; return append(dtostrf(wf.value, wf.width, wf.prec, d1)); } #endif // Get the character at a given index char operator[](const int i) const { return str[i]; } // Cast to char* (explicit?) operator char* () { return str; } // Use &mystring as shorthand for mystring.str char* operator&() { return str; } // Return the buffer address (same as &) char* buffer() { return str; } int length() const { return strlen(str); } int glyphs() { return utf8_strlen(str); } bool empty() { return !str[0]; } // Quick hash to detect change (e.g., to avoid expensive drawing) typedef IF::type hash_t; hash_t hash() const { #if ENABLED(DJB2_HASH) hash_t hval = 5381; char c; while ((c = *str++)) hval += (hval << 5) + c; // = hval * 33 + c #else const int sz = length(); hash_t hval = hash_t(sz); for (int i = 0; i < sz; i++) hval = ((hval << 1) | (hval >> 15)) ^ str[i]; // ROL, XOR #endif return hval; } void copyto(char * const dst) const { strcpy(dst, str); } void copyto(char * const dst, int len) const { strncpy(dst, str, len); } MString& clear() { return set(); } MString& eol() { return append('\n'); } MString& trunc(const int &i) { if (i <= SIZE) str[i] = '\0'; debug(F("trunc")); return *this; } // Truncate on a Unicode boundary MString& utrunc(const int &n=SIZE) { const int sz = length(); if (sz && n <= sz) for (int i = n; i >= 0; i--) if (START_OF_UTF8_CHAR(str[i])) { str[i] = '\0'; break; } debug(F("utrunc")); return *this; } }; #pragma GCC diagnostic pop #ifndef TS_SIZE #define TS_SIZE 63 #endif typedef MString TString; #define TS(V...) TString(V)