Skip to content

Commit

Permalink
♻️ String helper class (MarlinFirmware#24390)
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkyhead authored and Andy-Big committed Jul 17, 2023
1 parent ef7808f commit 12c345b
Show file tree
Hide file tree
Showing 36 changed files with 949 additions and 590 deletions.
2 changes: 1 addition & 1 deletion Marlin/src/MarlinCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ void setup() {
#if ENABLED(MARLIN_DEV_MODE)
auto log_current_ms = [&](PGM_P const msg) {
SERIAL_ECHO_START();
SERIAL_CHAR('['); SERIAL_ECHO(millis()); SERIAL_ECHOPGM("] ");
TSS('[', millis(), F("] ")).echo();
SERIAL_ECHOLNPGM_P(msg);
};
#define SETUP_LOG(M) log_current_ms(PSTR(M))
Expand Down
305 changes: 305 additions & 0 deletions Marlin/src/core/mstring.h
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)
49 changes: 49 additions & 0 deletions Marlin/src/core/serial.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,55 @@ inline void print_pos(const xyze_pos_t &xyze, FSTR_P const prefix=nullptr, FSTR_
#define SERIAL_POS(SUFFIX,VAR) do { print_pos(VAR, F(" " STRINGIFY(VAR) "="), F(" : " SUFFIX "\n")); }while(0)
#define SERIAL_XYZ(PREFIX,V...) do { print_pos(V, F(PREFIX)); }while(0)

/**
* Extended string that can echo itself to serial
*/
template <int SIZE=DEFAULT_MSTRING_SIZE>
class SString : public MString<SIZE> {
public:
typedef MString<SIZE> super;
using super::str;
using super::debug;

SString() : super() {}

template <typename T, typename... Args>
SString(T arg1, Args... more) : super(arg1, more...) {}

SString& set() { super::set(); return *this; }

template<typename... Args>
SString& setf_P(PGM_P const fmt, Args... more) { snprintf_P(str, SIZE, fmt, more...); debug(F("setf_P")); return *this; }

template<typename... Args>
SString& setf(const char *fmt, Args... more) { snprintf(str, SIZE, fmt, more...); debug(F("setf")); return *this; }

template<typename... Args>
SString& setf(FSTR_P const fmt, Args... more) { return setf_P(FTOP(fmt), more...); }

template <typename T>
SString& set(const T &v) { super::set(v); return *this; }

template <typename T>
SString& append(const T &v) { super::append(v); return *this; }

template<typename T, typename... Args>
SString& set(T arg1, Args... more) { set(arg1).append(more...); return *this; }

template<typename T, typename... Args>
SString& append(T arg1, Args... more) { append(arg1).append(more...); return *this; }

SString& clear() { set(); return *this; }
SString& eol() { append('\n'); return *this; }
SString& trunc(const int &i) { super::trunc(i); return *this; }

// Extended with methods to print to serial
SString& echo() { SERIAL_ECHO(str); return *this; }
SString& echoln() { SERIAL_ECHOLN(str); return *this; }
};

#define TSS(V...) SString<>(V)

//
// Commonly-used strings in serial output
//
Expand Down
2 changes: 1 addition & 1 deletion Marlin/src/feature/cancel_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ void CancelObject::set_active_object(const int8_t obj) {

#if ALL(HAS_STATUS_MESSAGE, CANCEL_OBJECTS_REPORTING)
if (active_object >= 0)
ui.status_printf(0, F(S_FMT " %i"), GET_TEXT(MSG_PRINTING_OBJECT), int(active_object));
ui.set_status(MString<30>(GET_TEXT_F(MSG_PRINTING_OBJECT), ' ', active_object));
else
ui.reset_status();
#endif
Expand Down
Loading

0 comments on commit 12c345b

Please sign in to comment.