Сложный поиск и замена с использованием регулярных выражений

#regex

Вопрос:

Я пытаюсь преобразовать кучу пользовательских «рецептов» из старого проприетарного формата во что-то, что в конечном итоге совместимо с C#. И я думаю, что самый простой способ сделать это-использовать регулярные выражения. Но мне трудно понять это выражение. Часть, которую мне нужно преобразовать с помощью этого регулярного выражения, — это операторы IF. Вот несколько примеров оригинальных рецептов…

  • ЕСЛИ(A = B,C,D)
  • ЕСЛИ(AA = BB,ЕСЛИ(E=F,G,H),DD)
  • ЕСЛИ(S1<>R1,РАУНД(РАУНД(S2/S1,R2)*S3,R3),R4)

Первый из них прост… Если A = B, то C еще D.
Второй похож, за исключением того, что операторы IF вложены.
И третий включает в себя дополнительные вызовы функций ROIND в результатах.

Я наткнулся на regex101.com и сумели собрать воедино следующую закономерность, которая становится все ближе. Это работает для первого примера, но не для двух других: (.*?)IF[^Srn]*((.*?),(.*?),(.*?))

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

  • если (A == B) { C } еще { D }
  • если (AA == BB) { если (E == F) { G } еще { H } } еще { DD }
  • если (S1 <> R1) { РАУНД(РАУНД(S2/S1,R2)*S3,R3) } еще { R4 }

Обратите внимание, что пробелы в результатах не особенно важны. Я просто отформатировал его для удобства чтения. Кроме того, «КРУГЛЫЕ» функции будут заменены отдельно Math.Round() вызовами C#. Здесь не нужно беспокоиться об этом. (Все, что мне нужно сделать для них, это добавить «Математика» и исправить заглавную букву.)

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

РЕДАКТИРОВАТЬ: Приложив некоторые дополнительные усилия, я изложил свое первое выражение и привел его к следующему… (.*?)IF[^Srn]*((.*?),(([^(]*)|(.*?(.*?))),(([^(]*)|(.*?(.*?)))) И со следующим выражением замены… $1if($2) {$3} else {$6} Я почти на месте. Это просто вложенные операторы IF, которые остались. (И хотя я бы предпочел сделать это за один проход, если рекурсивное выражение не сработает, я мог бы что-нибудь придумать, чтобы запустить результаты выражения во второй раз, чтобы справиться с вложенными операторами IF. Это не идеально, но если это лучшее, что у меня есть, я мог бы с этим смириться.

Комментарии:

1. Если вы специально не хотите решить эту проблему с помощью регулярных выражений, я предлагаю вам взглянуть на библиотеки синтаксического анализатора/комбинатора AST (они доступны для большинства языков). Было бы проще и надежнее применить ваше преобразование к проанализированному AST.

2. @Aivean — я разберусь с этим, спасибо. (Никогда не слышал об этом раньше.) Но на данный момент регулярное выражение-это молоток, который мне доступен. Поэтому я пытаюсь заставить это работать.

3. Проблема с использованием регулярных выражений для вашей задачи заключается в том, что они не особенно подходят для рекурсивных выражений. Существуют расширения , которые могут помочь в этом, но, боюсь, попытаться заставить регулярное выражение работать для этой задачи будет сложнее, чем использовать более подходящий инструмент. В качестве альтернативы, поскольку у вас довольно простая грамматика, вы можете написать свой собственный простой синтаксический анализатор на любом языке по вашему выбору, что может быть самым простым решением.

4. Ладно, может быть, я все усложняю. В третьем случае гарантируется ли, что S1, R1 и R4 не содержат скобок?

5. ДА. Гарантируется, что в рецепте не будет никаких скобок, кроме тех, которые жестко закодированы.

Ответ №1:

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

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

В IF(S1<>R1,ROUND(ROUND(S2/S1,R2)*S3,R3),R4)

если гарантируется, что оба S1<>R1 и R4 не содержат символа запятой, то вы можете использовать следующее регулярное выражение:

 IF(([^,]*),(.*),([^,] ))
 

Попробуйте это здесь: https://regexr.com/67r56

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


Однако, как я уже упоминал в комментариях, если S1 R1 или R4 являются самими выражениями , этот трюк с регулярным выражением не сработает, и вам потребуется использовать правильный рекурсивный синтаксический анализатор. К счастью, существует множество библиотек синтаксического анализа/комбинаторов для пользовательских грамматик (или вы можете даже найти одну, которая уже работает для вашей грамматики). Когда ваше выражение анализируется на AST, его довольно легко преобразовать в нужную форму.

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

Комментарии:

1. Спасибо. Это выглядит гораздо элегантнее, чем то, что я придумал до сих пор. Я поиграю с этим и посмотрю, как все пойдет.

2. Так что, хотя это сработало не совсем так, как я надеялся, это было очень полезно для достижения желаемого результата и достаточно близко, чтобы я отметил это как принятый ответ. Еще раз спасибо вам.