Тревога Delphi TRegEx нарушена?

У меня проблема с использованием TRegEx.replace:

var
  Value, Pattern, Replace: string;
begin
  Value   := 'my_replace_string(4)=my_replace_string(5)';
  Pattern := 'my_replace_string\((\d+)\)';
  Replace := 'new_value(\1)';
  Value   := TRegEx.Replace(Value, Pattern, Replace);
  ShowMessage(Value);
end;

Ожидаемый результат будет new_value(4)=new_value(5), а мой код (скомпилированный с Delphi XE4) дает new_value(4)=new_value()1)

С Notepad ++ я получаю ожидаемый результат.

Использование именованной группы дает понять, что 1 - это обратная ссылка, обработанная буквально:

Pattern := 'my_replace_string\((?<name>\d+)\)';
Replace := 'new_value(${name})';
// Result: 'new_value(4)=new_value(){name})'

Замена всегда такая простая (может быть ноль или больше раз my_replace_string), поэтому я мог бы легко создать пользовательскую функцию поиска и замены, но я хотел бы знать, что здесь происходит.

Это моя ошибка или это ошибка?

Ответ 1

Я могу воспроизвести ошибку в Delphi XE4. Я получаю правильное поведение в Delphi XE5.

Ошибка была в TPerlRegEx.ComputeReplacement. Код, который я внес в Embarcadero для включения в Delphi XE3, использовал UTF8String. С Delphi XE4 Embarcadero удалил UTF8String из блока RegularExpressionsCore и заменил его на TBytes. Разработчик, который сделал это изменение, похоже, пропустил ключевое различие между строками и динамическими массивами в Delphi. Строки используют механизм копирования на запись, а динамические массивы - нет.

Итак, в моем исходном коде TPerlRegEx.ComputeReplacement может сделать S := FReplacement, а затем изменить временную переменную S, чтобы заменить обратные ссылки, не затрагивая поле FReplacement, потому что оба были строками. В модифицированном коде S := FReplacement делает S указывать на тот же массив, что и FReplacement, и когда обратные ссылки в S заменяются, также изменяется t28. Следовательно, первая замена выполнена правильно, а последующие замены ошибочны, потому что FReplacement был искалечен.

В Delphi XE5 это было исправлено заменой S := FReplacement на это, чтобы создать надлежащую временную копию:

SetLength(S, Length(FReplacement));
Move(FReplacement[0], S[0], Length(FReplacement));

Когда был выпущен Delphi 2009, было много разговоров с Embarcadero о том, что нельзя использовать типы строк для представления последовательностей байтов. Кажется, теперь они делают противоположную ошибку в использовании TBytes для представления строк.

Решение этой проблемы, которую я ранее рекомендовал Embarcadero, заключается в том, чтобы переключиться на новые функции pcre16, которые используют UTF16LE так же, как строки Delphi. Эти функции не существовали, когда Delphi XE был выпущен, но теперь они работают, и они должны использоваться.

Ответ 2

Казалось бы, это ошибка. Здесь моя тестовая программа:

{$APPTYPE CONSOLE}

uses
  RegularExpressions;

var
  Value, Pattern, Replace: string;
begin
  Value   := 'my_replace_string(4)=my_replace_string(5)';
  Pattern := 'my_replace_string\((\d+)\)';
  Replace := 'new_value(\1)';
  Value   := TRegEx.Replace(Value, Pattern, Replace);
  Writeln(Value);
  Readln;
end.

На моем XE3 вывод:

new_value(4)=new_value(5)

Итак, похоже, что ошибка была введена в XE4. Я предлагаю вам представить отчет о контроле качества. Используйте мой SSCCE выше, потому что он является автономным.