forked from MarlinFirmware/Marlin
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️ String helper class (MarlinFirmware#24390)
- Loading branch information
1 parent
ef7808f
commit 12c345b
Showing
36 changed files
with
949 additions
and
590 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,305 @@ | ||
/** | ||
* 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 <https://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
#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 <typename T> extern void SERIAL_ECHO(T x); | ||
template <typename T> 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 | ||
|
||
/** | ||
* @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 <int SIZE=DEFAULT_MSTRING_SIZE, bool SAFE=DISABLED(UNSAFE_MSTRING)> | ||
class MString { | ||
protected: | ||
char str[SIZE+1]; | ||
public: | ||
MString() { safety(0); safety(SIZE); } | ||
|
||
template<typename T> | ||
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<char*>(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 MString &s) { strncpy(str, s.str, SIZE); debug(F("MString")); return *this; } | ||
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 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); } | ||
|
||
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<char*>(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<typename... Args> | ||
MString& setf_P(PGM_P const fmt, Args... more) { SNPRINTF_P(str, SIZE, fmt, more...); debug(F("setf_P")); return *this; } | ||
|
||
template<typename... Args> | ||
MString& setf(const char *fmt, Args... more) { SNPRINTF(str, SIZE, fmt, more...); debug(F("setf")); return *this; } | ||
|
||
template<typename... Args> | ||
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<char *>(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 MString &s) { return append(s.str); } | ||
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 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 short &i) { char buf[20]; sprintf(buf, "%d", i); return append(buf); } | ||
MString& append(const int &i) { char buf[20]; sprintf(buf, "%d", i); return append(buf); } | ||
MString& append(const long &l) { char buf[20]; sprintf(buf, "%ld", l); return append(buf); } | ||
MString& append(const unsigned char &i) { char buf[20]; sprintf(buf, "%u", i); return append(buf); } | ||
MString& append(const unsigned short &i) { char buf[20]; sprintf(buf, "%u", i); return append(buf); } | ||
MString& append(const unsigned int &i) { char buf[20]; sprintf(buf, "%u", i); return append(buf); } | ||
MString& append(const unsigned long &l) { char buf[20]; 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; } | ||
|
||
// 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<char *>(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<typename... Args> | ||
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<typename... Args> | ||
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<typename... Args> | ||
MString& appendf(FSTR_P const fmt, Args... more) { return appendf_P(FTOP(fmt), more...); } | ||
|
||
// Instantiate with a list of things | ||
template <typename T, typename... Args> | ||
MString(T arg1, Args... more) { set(arg1); append(more...); } | ||
|
||
// Take a list of any number of arguments and append them to the string | ||
template<typename T, typename... Args> | ||
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<typename T, typename... Args> | ||
MString& set(T arg1, Args... more) { return set(arg1).append(more...); } | ||
|
||
// Operator = as shorthand for set() | ||
template<typename T> | ||
MString& operator=(const T &v) { return set(v); } | ||
|
||
// Operator += as shorthand for append() | ||
template<typename T> | ||
MString& operator+=(const T &v) { return append(v); } | ||
|
||
// Operator + as shorthand for append-to-copy | ||
template<typename T> | ||
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<ENABLED(DJB2_HASH), uint32_t, uint16_t>::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; | ||
} | ||
|
||
}; | ||
|
||
#ifndef TS_SIZE | ||
#define TS_SIZE 63 | ||
#endif | ||
#define TS(V...) MString<TS_SIZE>(V) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.