Skip to content

fix(utils): ExtractFirstArgument -- разделители и остаток без пробелов - #3812

Closed
bylins wants to merge 1 commit into
masterfrom
fix/extract-first-argument
Closed

fix(utils): ExtractFirstArgument -- разделители и остаток без пробелов#3812
bylins wants to merge 1 commit into
masterfrom
fix/extract-first-argument

Conversation

@bylins

@bylins bylins commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Доделка моей же правки c0675a229 (март 2026): тогда я чинил алиасинг ExtractFirstArgument(str, str), закрыл симптом и не посмотрел вокруг. Заодно комментарий «аналог one_argument для string» — тоже мой, и он неточен.

1. Разделителем был только пробел

one_argument режет по a_isspace — это ещё \t \n \v \f \r. Здесь стояло s.find(' '):

вход: 'frozen\t30 спам'
   one_argument         -> слово='frozen'     остаток='30 спам'
   ExtractFirstArgument -> слово='frozen\t30'  остаток='спам'

Достижимо не в теории: функция разбирает строку dgaffect <цель> <свойство> <значение> <длительность> прямо из текста триггера (dg_misc.cpp:289), а там табуляцию поставить несложно — два аргумента склеятся в один.

2. Остаток начинался с пробелов

remains = s.substr(space_pos + 1) пропускал ровно один пробел. Каждый вызывающий дочищал сам: в dg_misc через Trim, в setall я дописал TrimLeft. Теперь остаток отдаётся уже подчищенным — как и делает skip_spaces внутри half_chop.

3. Комментарий врал

«если нет пробелов то строки пустые» — неправда: строка из одного слова возвращает это слово и пустой остаток. Переписан; отдельно сказано, чем функция не является:

  • one_argument понижает регистр слова, эта — нет;
  • one_argument пропускает служебные слова (in from with the on at to, см. fill_word), эта — нет:
вход: 'to кто'
   one_argument         -> слово='кто'
   ExtractFirstArgument -> слово='to'

Это не баг, а разные задачи: пропуск служебных слов нужен разбору команд игрока и вреден при разборе триггера. Но при переносе кода с half_chop на эту функцию про регистр надо помнить — иначе, например, почта в setall перестанет находиться (она сверяется с player_table подстрокой).

Что проверял

Алиасинг (ExtractFirstArgument(str, str) — основной способ применения, ради него и был фикс #3037). Во всех трёх ветках слово копируется до записи в remains, а последнее чтение s идёт раньше присваивания; в тернарнике обе половины создают временную строку. Есть отдельный тест на разбор в цикле через одну переменную.

Многобайтные буквы. Набор разделителей чисто ASCII, а продолжающие байты UTF-8 всегда ≥ 0x80 — внутри русской буквы «разделитель» не найдётся. Тест есть.

Изменение поведения у существующих вызывающих. Оно одно: значение с ведущими пробелами. dg_scripts.cpp:3517/3557 хранят переменные как имя + " " + значение, и раньше остаток отдавался со всеми пробелами кроме одного:

сохранено: 'имя   текст с пробелами впереди'
   было : значение='  текст с пробелами впереди'
   стало: значение='текст с пробелами впереди'

Считаю это правильным: строка перед разбором уже прошла Split, который делает TrimLeft, то есть ведущие пробелы там и так не сохраняются — а после имени переменной они были артефактом формата, не данными. Но если для DG-переменных важно хранить значение байт в байт, лучше менять формат хранения, а не разбор — скажи, вынесу отдельно.

Тесты

Восемь новых в tests/utils.string.cpp: разбиение, табуляция как разделитель, чистый остаток, одно слово, пустой ввод, сохранение регистра и служебных слов, разбор в цикле через одну переменную, многобайтные буквы. Всего 681 тест, сборка чистая.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XUwDWDnYdXrJdvjDVd36QH

Три правки в разборе аргументов на std::string.

Разделителем считался только пробел, а one_argument режет по a_isspace.
Табуляция между аргументами склеивала два поля в одно: "frozen\t30 спам" давал
слово "frozen\t30". Достижимо не в теории -- функция разбирает строку dgaffect
прямо из текста триггера, где табуляцию поставить несложно.

Остаток начинался с пробелов: substr(pos + 1) пропускал ровно один. Каждый
вызывающий подчищал сам -- в dg_misc через Trim, в setall через TrimLeft. Теперь
остаток отдаётся уже без ведущих пробелов, лишние Trim у вызывающих просто
становятся холостыми.

Комментарий утверждал, что "если нет пробелов то строки пустые". На деле строка
из одного слова возвращает это слово и пустой остаток. Заодно описано, чем
функция НЕ является: one_argument вдобавок понижает регистр и пропускает
служебные слова (in, from, with, the, on, at, to), здесь ни того, ни другого нет.
Это и есть причина, по которой при переносе кода с half_chop приходится звать
to_lower руками.

Восемь тестов: разбиение, табуляция, чистый остаток, одно слово, пустой ввод,
сохранение регистра и служебных слов, разбор в цикле через одну переменную,
многобайтные буквы.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUwDWDnYdXrJdvjDVd36QH
@bylins

bylins commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

Собрано вместе с остальными в #3813 — там все три темы одним PR, потому что ExtractFirstArgument из третьего нужен первым двум. Этот закрываю.

@bylins bylins closed this Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant