[![latest](https://img.shields.io/github/v/release/GyverLibs/StringUtils.svg?color=brightgreen)](https://github.com/GyverLibs/StringUtils/releases/latest/download/StringUtils.zip) [![Foo](https://img.shields.io/badge/Website-AlexGyver.ru-blue.svg?style=flat-square)](https://alexgyver.ru/) [![Foo](https://img.shields.io/badge/%E2%82%BD$%E2%82%AC%20%D0%9D%D0%B0%20%D0%BF%D0%B8%D0%B2%D0%BE-%D1%81%20%D1%80%D1%8B%D0%B1%D0%BA%D0%BE%D0%B9-orange.svg?style=flat-square)](https://alexgyver.ru/support_alex/) [![Foo](https://img.shields.io/badge/README-ENGLISH-blueviolet.svg?style=flat-square)](https://github-com.translate.goog/GyverLibs/StringUtils?_x_tr_sl=ru&_x_tr_tl=en) [![Foo](https://img.shields.io/badge/ПОДПИСАТЬСЯ-НА%20ОБНОВЛЕНИЯ-brightgreen.svg?style=social&logo=telegram&color=blue)](https://t.me/GyverLibs) # StringUtils Набор инструментов для работы со строками - Быстрые функции конвертации - Парсинг, разбивание по разделителям - Несколько классов-конвертеров данных в строку и обратно для использования в других библиотеках - Кодирование и раскодирование base64, url, unicode, йцукен/qwerty-раскладки ### Совместимость Совместима со всеми Arduino платформами (используются Arduino-функции) ## Содержание - [Документация](#docs) - [Версии](#versions) - [Установка](#install) - [Баги и обратная связь](#feedback) ## Документация ### su::Text Класс-обёртка для всех типов строк. Может быть создана в конструкторе из: - `"const char"` - строки - `char[]` - строки - `F("f-строки")` - `PROGMEM` - строки - `String` - строки Особенности: - Хранит тип и длину строки - Позволяет **печататься**, **конвертироваться** в любой целочисленный формат и **сравниваться** с переменными всех стандартных типов, а также сравниваться с любыми другими строками - Вывод в подстроки разными способами, поиск и разделение - **Не может изменять исходную строку**, все операции только "для чтения" - **Не создаёт копию строки** и работает с оригинальной строкой, т.е. *оригинальная строка должна быть в памяти на время существования Text* - Если создана из String строки, то оригинальная String строка не должна меняться в процессе работы экземпляра Text ```cpp // ====== КОНСТРУКТОР ====== su::Text(String& str); su::Text(const String& str); su::Text(const __FlashStringHelper* str, int16_t len = 0); su::Text(const char* str, int16_t len = 0, bool pgm = 0); // ======== СИСТЕМА ======== bool valid(); // Статус строки, существует или нет bool pgm(); // Строка из Flash памяти uint16_t length(); // Длина строки uint16_t lengthUnicode();// Длина строки с учётом unicode символов uint16_t readLen(); // посчитать и вернуть длину строки (const) void calcLen(); // пересчитать и запомнить длину строки (non-const) Type type(); // Тип строки const char* str(); // Получить указатель на строку. Всегда вернёт ненулевой указатель const char* end(); // указатель на конец строки. Всегда вернёт ненулевой указатель bool terminated(); // строка валидна и оканчивается \0 // ======== ХЭШ ======== size_t hash(); // хэш строки size_t uint32_t hash32(); // хэш строки 32 бит // ======== PRINT ======== size_t printTo(Print& p); // Напечатать в Print (c учётом длины) // ======== СРАВНЕНИЕ И ПОИСК ======== // сравнивается со всеми типами строк через == // Сравнить со строкой, начиная с индекса bool compare(Text s, uint16_t from = 0); // Сравнить со строкой, начиная с индекса, с указанием количества символов bool compareN(Text s, uint16_t amount, uint16_t from = 0); // Найти позицию символа в строке, начиная с индекса int16_t indexOf(char sym, uint16_t from = 0); // Найти позицию строки в строке int16_t indexOf(Text txt, uint16_t from = 0); // Найти позицию символа в строке с конца int16_t lastIndexOf(char sym); // Найти позицию строки в строке с конца int16_t lastIndexOf(Text txt); // найти символ и получить указатель на первое вхождение const char* find(char sym, uint16_t from = 0); // начинается со строки bool startsWith(const Text& txt); // заканчивается строкой bool endsWith(const Text& txt); // ======== РАЗДЕЛЕНИЕ И ПАРСИНГ ======== // вернёт новую строку с убранными пробельными символами с начала и конца Text trim(); // Посчитать количество подстрок, разделённых символом (количество разделителей +1) uint16_t count(char sym); // Посчитать количество подстрок, разделённых строками (количество разделителей +1) uint16_t count(Text txt); // Разделить по символу-разделителю в массив любого типа uint16_t split(T* arr, uint16_t len, char div); // Получить подстроку из списка по индексу Text getSub(uint16_t idx, char div); // Получить подстроку из списка по индексу Text getSub(uint16_t idx, Text div); // выделить подстроку (начало, конец не включая). Отрицательные индексы работают с конца строки Text substring(int16_t start, int16_t end = 0); // Получить символ по индексу char charAt(uint16_t idx); // ======== ВЫВОД. СТРОКИ ======== // Получить как String строку String toString(bool decodeUnicode = false); // Вывести в String строку. Вернёт false при неудаче bool toString(String& s, bool decodeUnicode = false); // Добавить к String строке. Вернёт false при неудаче bool addString(String& s, bool decodeUnicode = false); // Вывести в char массив. Вернёт длину строки. terminate - завершить строку нулём uint16_t toStr(char* buf, int16_t bufsize = -1, bool terminate = true); // ======== ВЫВОД. B64 ======== // размер данных (байт), если они b64 size_t sizeB64(); // вывести в переменную из b64 bool decodeB64(void* var, size_t size); // ======== ВЫВОД. ЧИСЛА ======== bool toBool(); // получить значение как bool int16_t toInt16(); // получить значение как int16 int32_t toInt32(); // получить значение как int32 int64_t toInt64(); // получить значение как int64 uint32_t toInt32HEX(); // получить значение как uint 32 из HEX строки float toFloat(); // получить значение как float // также автоматически конвертируется и сравнивается с bool char + unsigned short + unsigned int + unsigned long + unsigned long long + unsigned float double String // для ручного управления строкой const char* _str; // указатель на строку uint16_t _len; // длина ``` #### Пример ```cpp // конструктор su::Text v0("-123456"); su::Text v1 = "-123456"; v1 = F("-123456"); String s("abcd"); su::Text v2(s); v2 = s; // сравнение v2 == v1; v2 == F("text"); v1 == -123456; // авто конвертация int v = v0; String s2 = v2; // вывод в массив char buf[20]; v1.toStr(buf); // парсинг и разделение su::Text list("abc/123/def"); Serial.println(list.getSub(0, '/')); // abc Serial.println(list.getSub(2, '/')); // def Serial.println(list.substring(4, 6)); // 123 su::Text arr[3]; list.split(arr, 3, '/'); Serial.println(arr[0]); Serial.println(arr[1]); Serial.println(arr[2]); // парсить можно в любой тип int arr2[3]; // float, byte... list.split(arr2, 3, '/'); Serial.println(arr2[0]); Serial.println(arr2[1]); Serial.println(arr2[2]); // так делать НЕЛЬЗЯ Text t1(String("123")); // строка будет выгружена из памяти! // t1.... программа сломается String s; Text t1(s); s += String("123"); // адрес строки изменится! // t1.... программа сломается // в то же время вот так - можно void foo(const Text& text) { // String существует тут print(text); } foo(String("123")); ``` Встроенный разделитель и хэш-функции позволяют очень просто и эффективно разбирать различные текстовые протоколы. Например пакет вида `key=value`, где `key` может отсылать к переменной в коде. Пакет можно разделить, ключ хешировать и опросить через switch для присвоения н ужной переменной: ```cpp Text txt("key1=1234"); int val = txt.getSub(1, '='); // значение в int switch (txt.getSub(0, '=').hash()) { // хэш ключа case su::SH("key1"): var1 = val; break; case su::SH("key2"): var2 = val; break; case su::SH("key3"): var2 = val; break; } ``` или протокол вида `name/index/value`, где `name` - текстовый ключ, `index` - порядковый номер: ```cpp Text txt("key/3/1234"); int val = txt.getSub(2, '/'); switch (txt.getSub(0, '/').hash()) { case su::SH("key"): switch(txt.getSub(1, '/').toInt16()) { case 0: break; case 1: break; case 2: break; //..... } break; case su::SH("keykey"): //... break; case su::SH("anotherKey"): //... break; } ``` ### su::Value Добавка к `Text`, поддерживает все остальные стандартные типы данных. Имеет буфер 22 байта, при создании конвертирует число в него: ```cpp su::Value(bool value); su::Value(char + unsigned value, uint8_t base = DEC); su::Value(short + unsigned value, uint8_t base = DEC); su::Value(int + unsigned value, uint8_t base = DEC); su::Value(long + unsigned value, uint8_t base = DEC); su::Value(long long + unsigned value, uint8_t base = DEC); su::Value(double value, uint8_t dec = 2); // аналогично с ручным размером буфера su::ValueT<размер буфера>(); ``` #### Пример ```cpp su::Value v0("-123456"); // все строки также можно su::Value v1(123); su::Value v2(3.14); su::Value v3((uint64_t)12345678987654321); // конвертируется из числа в текст v1 = 10; v1 = 3.14; v1 = 12345654321ull; Serial.println(v0); // печатается в Serial Serial.println(v1 == v2); // сравнивается // сравнивается с любыми строками su::Text s("123"); String ss = "123"; Serial.println(s == "123"); Serial.println(s == F("123")); Serial.println(s == ss); // конвертируется в любой тип float f = v2; // f == 3.14 int i = v1; // i = 123 // выводится в String String S; v0.toString(s); // выводится в char[] char buf[v1.length() + 1]; // +1 для '\0' v1.toStr(buf); ``` #### Использование в библиотеках На базе `Text` построены следующие библиотеки: - [GSON](https://github.com/GyverLibs/GSON) - [GyverHub](https://github.com/GyverLibs/GyverHub) - [Pairs](https://github.com/GyverLibs/Pairs) - [FastBot2](https://github.com/GyverLibs/FastBot2) ##### Передача текста в функцию - Строки любого типа - Без аллокаций, что чрезвычайно критично при сборке String - Без создания десятка перегруженных функций Например нужна функция, принимающая строку в любом виде. В ванильном фреймворке Arduino можно сделать так: ```cpp void setText(const String& str) { // и например прибавить к строке s += str; } ``` Такая функция сможет принимать любые строки: ```cpp setText("const literal"); setText(F("F-string")); char str[] = "buffer string"; setText(str); String s = "Arduino String"; setText(s); ``` Но эта строка будет *продублирована* в конструкторе `String`, и самое страшное - в динамической памяти! Таким образом при прибавлении к условно-глобальной String в этой области определения случится переаллокация и фрагментирование памяти. `Text` позволяет полностью этого избежать: ```cpp void setText(const Text& str) { // и например прибавить к строке str.addString(s); } ``` Теперь эта функция так же умеет принимать строки в любом формате, но **не создаёт их копии**, и например прибавление к строке становится быстрым и безопасным. ##### Вывод текста Также Text удобен для вывода, например в классе, который хранит буфер и сам наполняет его данными и знает их длину: ```cpp class MyClass { public: su::Text get() { return su::Text(buffer, len); } private: char buffer[20]; int len; }; MyClass s; Serial.println(s.get()); ``` Вариант с наследованием: ```cpp class MyClass : public su::Text { public: void foo() { su::Text::_str = buffer; su::Text::_len = somelen; } private: char buffer[20]; }; MyClass s; Serial.println(s); ``` Если вместо `Text` использовать `Value` - функция сможет принимать также любые численные данные. ### su::TextList Разделитель `Text` списков на `Text` подстроки. #### Статический ```cpp TextListT(Text list, char div); TextListT(Text list, Text div); // количество построк uint16_t length(); // размер буфера uint16_t capacity(); // получить подстроку под индексом const Text& get(uint16_t idx); const Text& operator[](int idx); ``` #### Динамический ```cpp TextList(Text list, char div); TextList(Text list, Text div); // количество построк uint16_t length(); // получить подстроку под индексом const Text& get(uint16_t idx); const Text& operator[](int idx); ``` ### Устаревшие парсеры
Развернуть ### su::Parser Разделение строки на подстроки по разделителю в цикле. **Изменяет** исходную строку, но после завершения возвращает разделители на место. ```cpp su::Parser p(String& str, char div = ';'); su::Parser p(const char* str, char div = ';'); bool next(); // парсить следующую подстроку. Вернёт false, если парсинг закончен uint8_t index(); // индекс текущей подстроки const char* str(); // получить подстроку Text get(); // получить подстроку как Text ``` #### Пример ```cpp char buf[] = "123;456;abc"; su::Parser p(buf); while (p.next()) { Serial.print(p.index()); Serial.print(": "); Serial.println(p.get()); } ``` ### su::Splitter Разделение строки на подстроки по разделителю в цикле. **Изменяет** исходную строку! После удаления объекта строка восстанавливается, либо вручную вызвать `restore()` ```cpp su::SplitterT<макс. подстрок> spl(String& str, char div = ';'); su::SplitterT<макс. подстрок> spl(const char* str, char div = ';'); su::Splitter spl(String& str, char div = ';'); // авто-размер (выделяется в heap) su::Splitter spl(const char* str, char div = ';'); // авто-размер (выделяется в heap) void setDiv(char div); // установить разделитель void restore(); // восстановить строку (вернуть разделители) uint8_t length(); // количество подстрок const char* str(uint16_t idx); // получить подстроку по индексу Text get(uint16_t idx); // получить подстроку по индексу как Text ``` #### Пример ```cpp char buf[] = "123;456;abc"; su::Splitter spl(buf); for (uint8_t i = 0; i < spl.length(); i++) { Serial.print(i); Serial.print(": "); Serial.println(spl[i]); } spl.restore(); ``` ### su::list функции ```cpp // Получить количество подстрок в списке uint16_t su::list::length(Text list, char div = ';'); // Получить индекс подстроки в списке int16_t su::list::indexOf(Text list, Text str, char div = ';'); // Проверка содержит ли список подстроку bool su::list::includes(Text list, Text str, char div = ';'); // Получить подстроку из списка по индексу Text su::list::get(Text list, uint16_t idx, char div = ';'); // распарсить в массив указанного типа и размера. Вернёт количество записанных подстрок template uint16_t su::list::parse(Text list, T* buf, uint16_t len, char div = ';'); ``` #### Пример ```cpp Serial.println(su::list::length("123;456;333")); // 3 Serial.println(su::list::includes("123;456;333", "456")); // true Serial.println(su::list::indexOf("123;456;333", "333")); // 2 Serial.println(su::list::get("123;456;333", 1)); // 456 // распарсить в массив float arr[3]; su::list::parse(F("3.14;2.54;15.15"), arr, 3); ``` ### su::List класс Получение подстрок по разделителям **без модификации исходной строки**, работает также с PROGMEM строками. ```cpp List(Text); // установить разделитель void setDiv(char div); // получить размер списка uint16_t length(); // получить индекс подстроки в списке или -1 если её нет int16_t indexOf(Text str); // проверить наличие подстроки в списке bool includes(Text str); // получить подстроку под индексом Text get(uint16_t idx); // распарсить в массив указанного типа и размера. Вернёт количество записанных подстрок template uint16_t parse(T* buf, uint16_t len); ``` #### Пример ```cpp su::List list(F("123;456;333")); Serial.print("len: "); Serial.println(list.length()); // 3 Serial.print("index 2: "); Serial.println(list[2]); // 333 Serial.print("index of '456':"); Serial.println(list.indexOf(F("456"))); // 1 Serial.print("index of '789':"); Serial.println(list.indexOf("789")); // -1 // переписать в массив int arr[3]; list.parse(arr, 3); ```
### su::PrintString ```cpp // строка, в которую можно делать print/println su::PrintString prs; prs += "как обычный String"; prs.print(10); prs.println("hello"); prs.print(F("print!")); Serial.println(prs); ``` ### QWERTY ```cpp // Изменить раскладку (RU в QWERTY) - String String su::toQwerty(const String& ru); // Изменить раскладку (RU в QWERTY) - char* (qw длина как ru + 1, функция добавит '\0') char* su::toQwerty(const char* ru, char* qw); ``` ### Base64 ```cpp // размер закодированных данных по размеру исходных size_t su::b64::encodedLen(size_t len); // будущий размер декодированных данных по строке b64 и её длине size_t su::b64::decodedLen(const char* b64, size_t len); // закодировать данные в String void su::b64::encode(String* b64, uint8_t* data, size_t len, bool pgm = false); // закодировать данные в char[] (библиотека не добавляет '\0' в конец) void su::b64::encode(char* b64, uint8_t* data, size_t len, bool pgm = false); // раскодировать данные из строки b64 в буфер data void su::b64::decode(uint8_t* data, const char* b64, size_t len); void su::b64::decode(uint8_t* data, const String& b64); ``` ### Unicode Декодер строки, содержащей unicode символы вида `\u0abc`. Также делает unescape символов `\t\r\n`! ```cpp // декодировать строку.Зарезервировать строку на длину len. Иначе - по длине строки String su::unicode::decode(const char* str, uint16_t len = 0); // декодировать строку String su::unicode::decode(const String& str); // кодировать unicode символ по его коду. В массиве должно быть 5 ячеек void su::unicode::encode(char* str, uint32_t c); // кодировать unicode символ по его коду String su::unicode::encode(uint32_t code); ``` ### URL ```cpp // символ должен быть urlencoded bool su::url::needsEncode(char c); // закодировать в url void su::url::encode(const char* src, uint16_t len, String& dest); void su::url::encode(const String& src, String& dest); String su::url::encode(const String& src); // раскодировать url void su::url::decode(const char* src, uint16_t len, String& dest); void su::url::decode(const String& src, String& dest); String su::url::decode(const String& src); ``` ### Hash Вместо сравнения строк можно сравнивать хэш этих строк, что делает программу компактнее, легче и в большинстве случаев быстрее. Функции, указанные ниже как "считается компилятором" - считаются компилятором, то есть **строка даже не попадает в код программы** - вместо неё подставляется хэш-число: ```cpp // считается компилятором constexpr su::size_t su::SH(const char* str); // (String Hash) размер size_t constexpr su::size_t SH32(const char* str); // (String Hash) размер 32 бит // считается в рантайме size_t su::hash(const char* str, int16_t len = -1); // Размер зависит от платформы и соответствует size_t uint32_t su::hash32(const char* str, int16_t len = -1); // Размер 32 бит size_t su::hash_P(PGM_P str, int16_t len = -1); // PROGMEM строка, размер size_t uint32_t su::hash32_P(PGM_P str, int16_t len = -1); // PROGMEM строка, размер 32 бит ``` > На ESP-платах `SH`, `hash` и `hash_P` по умолчанию являются 32-битными! По проведённому тесту 32-битная версия хэша имеет 7 коллизий из 234450 английских слов, 16-битная версия - 170723 коллизий (что есть 73% - чисто статистическое количество коллизий из расчёта 16 бит - 65536 значений) #### Пример Определить, какая строка была "получена". Классический способ со сравнением строк из PROGMEM: ```cpp char buf[] = "some_text"; if (!strcmp_P(buf, PSTR("abcdef"))) Serial.println(0); else if (!strcmp_P(buf, PSTR("12345"))) Serial.println(1); else if (!strcmp_P(buf, PSTR("wrong text"))) Serial.println(2); else if (!strcmp_P(buf, PSTR("some text"))) Serial.println(3); else if (!strcmp_P(buf, PSTR("hello"))) Serial.println(4); else if (!strcmp_P(buf, PSTR("some_text"))) Serial.println(5); ``` Способ с хэшем строки: ```cpp using su::SH; using su::hash; char buf[] = "some_text"; switch (hash(buf)) { case su::SH("abcdef"): Serial.println(0); break; case su::SH("12345"): Serial.println(1); break; case su::SH("wrong text"): Serial.println(2); break; case su::SH("some text"): Serial.println(3); break; case su::SH("hello"): Serial.println(4); break; case su::SH("some_text"): Serial.println(5); break; } ``` > Один расчёт хэша занимает чуть большее время, чем сравнение со строкой. Но итоговая конструкция из примера выполняется в 2 раза быстрее (на ESP). > `SH("строки")` в данном примере вообще не попадают в код программы - вместо них подставляется их хэш ### Прочие утилиты ```cpp // Длина строки с русскими символами uint16_t su::strlenRu(const char* str); // Получить длину целого числа uint8_t su::intLen(int32_t val); // Получить длину float числа uint8_t su::floatLen(double val, uint8_t dec); // Преобразовать строку в целое число template T su::strToInt(const char* str, uint8_t len = 0); // Преобразовать PROGMEM строку в целое число template T su::strToInt_P(const char* str, uint8_t len = 0); // Преобразовать float в строку с указанием кол-ва знаков после точки uint8_t su::floatToStr(double val, char* buf, uint8_t dec = 2); // Преобразовать HEX строку в целое число. Опционально длина uint32_t su::strToIntHex(const char* str, int8_t len = -1); // Длина символа в количестве байт uint8_t su::charSize(char sym); // Конвертация числа в char* массив. Пишет от начала массива, добавляет '\0', вернёт длину строки // для int64 макс. длина буфера - 22 символа, для int32 - 12 uint8_t su::uintToStr(uint32_t n, char* buf, uint8_t base = DEC); uint8_t su::intToStr(int32_t n, char* buf, uint8_t base = DEC); uint8_t su::uint64ToStr(uint64_t n, char* buf, uint8_t base = DEC); uint8_t su::int64ToStr(int64_t n, char* buf, uint8_t base = DEC); // конвертация из строки во float float su::strToFloat(const char* s); // конвертация из PROGEMEM строки во float float su::strToFloat_P(PGM_P s); // быстрый целочисленный логарифм 10 (длина числа в кол-ве символов) uint8_t su::getLog10(uint32_t value); uint8_t su::getLog10(int32_t value); // быстрое возведение 10 в степень uint32_t su::getPow10(uint8_t value); ``` ## Версии - v1.0 - v1.1.0 - оптимизация, добавлены фичи, исправлены уязвимости - v1.2 - добавлены хэш-функции - v1.2.x - мелкие исправления и улучшения - v1.3 - оптимизация const, добавлены фичи, сравнения - v1.3.1 - добавлен substring - v1.3.2 - поддержка ESP8266 версий 2.x - v1.3.5 - uintToStr: HEX теперь в нижнем регистре как у си-функций - v1.3.6 - в Text добавлены toInt32HEX(), count(), split() и getSub(). Добавлен парсер TextList - v1.3.7 - исправлены варнинги на AVR - v1.4.0 - AnyText переименован в Text, пространство имён sutil - в su, добавлены функции в Text, результат конвертации и substring приведены к Си и JS функциям ## Установка - Библиотеку можно найти по названию **StringUtils** и установить через менеджер библиотек в: - Arduino IDE - Arduino IDE v2 - PlatformIO - [Скачать библиотеку](https://github.com/GyverLibs/StringUtils/archive/refs/heads/main.zip) .zip архивом для ручной установки: - Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64) - Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32) - Распаковать и положить в *Документы/Arduino/libraries/* - (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив - Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA) ### Обновление - Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи - Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить" - Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам! ## Баги и обратная связь При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru) Библиотека открыта для доработки и ваших **Pull Request**'ов! При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать: - Версия библиотеки - Какой используется МК - Версия SDK (для ESP) - Версия Arduino IDE - Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде - Какой код загружался, какая работа от него ожидалась и как он работает в реальности - В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код