Ответ 1
Существует как простое решение, так и более продвинутое решение (добавлено после edit) для обработки более сложных функций.
Чтобы достичь приведенного вами примера, я предлагаю сделать это в два этапа, первым шагом будет извлечение параметров (пояснения в конце объясняются):
\b[^()]+\((.*)\)$
Теперь, чтобы проанализировать параметры.
Простое решение
Извлеките параметры, используя:
([^,]+\(.+?\))|([^,]+)
Вот несколько примеров кода С# (все утверждения проходят):
string extractFuncRegex = @"\b[^()]+\((.*)\)$";
string extractArgsRegex = @"([^,]+\(.+?\))|([^,]+)";
//Your test string
string test = @"func1(2 * 7, func2(3, 5))";
var match = Regex.Match( test, extractFuncRegex );
string innerArgs = match.Groups[1].Value;
Assert.AreEqual( innerArgs, @"2 * 7, func2(3, 5)" );
var matches = Regex.Matches( innerArgs, extractArgsRegex );
Assert.AreEqual( matches[0].Value, "2 * 7" );
Assert.AreEqual( matches[1].Value.Trim(), "func2(3, 5)" );
Объяснение регулярных выражений. Извлечение аргументов как одна строка:
\b[^()]+\((.*)\)$
где:
-
[^()]+
символы, которые не являются открывающей, закрывающей скобкой. -
\((.*)\)
все внутри скобок
Извлечение аргументов:
([^,]+\(.+?\))|([^,]+)
где:
-
([^,]+\(.+?\))
, которые не являются запятыми, за которыми следуют символы в скобках. Это подбирает аргументы func. Обратите внимание на +? так что матч ленив и останавливается при первом), он встречается. -
|([^,]+)
Если предыдущее не соответствует, сопоставьте последовательные символы, которые не являются запятыми. Эти совпадения попадают в группы.
Более продвинутое решение
Теперь есть некоторые очевидные ограничения с этим подходом, например, он соответствует первой закрывающей скобке, поэтому он не очень хорошо обрабатывает вложенные функции. Для более полного решения (если вам это нужно) нам нужно использовать определения балансировочной группы (как я уже упоминал перед этим редактированием). Для наших целей определения балансировочных групп позволяют нам отслеживать экземпляры открытых скобок и вычитать экземпляры закрывающей скобки. По существу, открывающие и закрывающие скобки будут взаимно компенсировать друг друга в балансирующей части поиска до тех пор, пока не будет найден конечный скользящий кронштейн. То есть совпадение будет продолжаться до тех пор, пока не будут найдены скобки и конечная скобка закрытия.
Итак, регулярное выражение для извлечения парм теперь (извлечение func может оставаться неизменным):
(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+
Вот несколько тестовых примеров, чтобы показать это в действии:
string extractFuncRegex = @"\b[^()]+\((.*)\)$";
string extractArgsRegex = @"(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+";
//Your test string
string test = @"func1(2 * 7, func2(3, 5))";
var match = Regex.Match( test, extractFuncRegex );
string innerArgs = match.Groups[1].Value;
Assert.AreEqual( innerArgs, @"2 * 7, func2(3, 5)" );
var matches = Regex.Matches( innerArgs, extractArgsRegex );
Assert.AreEqual( matches[0].Value, "2 * 7" );
Assert.AreEqual( matches[1].Value.Trim(), "func2(3, 5)" );
//A more advanced test string
test = @"someFunc(a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2)";
match = Regex.Match( test, extractFuncRegex );
innerArgs = match.Groups[1].Value;
Assert.AreEqual( innerArgs, @"a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2" );
matches = Regex.Matches( innerArgs, extractArgsRegex );
Assert.AreEqual( matches[0].Value, "a" );
Assert.AreEqual( matches[1].Value.Trim(), "b" );
Assert.AreEqual( matches[2].Value.Trim(), "func1(a,b+c)" );
Assert.AreEqual( matches[3].Value.Trim(), "func2(a*b,func3(a+b,c))" );
Assert.AreEqual( matches[4].Value.Trim(), "func4(e)+func5(f)" );
Assert.AreEqual( matches[5].Value.Trim(), "func6(func7(g,h)+func8(i,(a)=>a+2))" );
Assert.AreEqual( matches[6].Value.Trim(), "g+2" );
Обратите внимание, что этот метод теперь достаточно продвинутый:
someFunc(a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2)
Итак, снова посмотрев на регулярное выражение:
(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+
В итоге, он начинается с символов, которые не являются запятыми или скобками. Затем, если в аргументе есть скобки, он сопоставляет и вычитает скобки, пока они не будут сбалансированы. Затем он пытается повторить это совпадение, если в аргументе есть другие функции. Затем он переходит к следующему аргументу (после запятой). Подробнее:
-
[^,()]+
соответствует любому, что не является,() ' -
?:
означает группу без захвата, т.е. не сохранять совпадения в скобках в группе. -
\(
означает запуск в открытой скобке. -
?>
означает атомная группировка - по существу, это означает, что он не помнит позиции возврата. Это также помогает повысить производительность, потому что есть меньше отступлений, чтобы попробовать разные комбинации. -
[^()]+|
означает что-либо, кроме открывающей или закрывающей скобки. За этим следует | (Или) -
\((?<open>)|
Это хороший материал и говорит совпадение '(' или -
(?<-open>)
Это лучший материал, который соответствует совпадению ')' и уравновешивает '('. Это означает, что эта часть матча (все после первой скобки) будет продолжаться до тех пор, пока все внутренние скобки не совпадут. Без балансировочных выражений совпадение закончилось бы на первой закрывающей скобке. Суть в том, что движок не соответствует этому ")" против финала "), вместо этого он вычитается из соответствия" ( "Когда нет" еще один выдающийся '(', -open не удается, так что окончательный ')' может быть сопоставлен. - Остальная часть регулярного выражения содержит закрывающие круглые скобки для группы и повторений (, и +), которые являются соответственно: повторите совпадение внутренней скобки 0 или более раз, повторите полный поиск в скобках 0 или более раз (0 разрешает аргументы без скобок) и повторите полное совпадение 1 или более раз (позволяет foo (1) + foo (2))
Окончательное украшение:
Если вы добавите (?(open)(?!))
в регулярное выражение:
(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*(?(open)(?!))\)))*)+
(?!) всегда будет терпеть неудачу, если open что-то захватил (который не был вычтен), т.е. он всегда будет терпеть неудачу, если имеется открывающая скобка без закрывающей скобки. Это полезный способ проверить, не сработала ли балансировка.
Некоторые примечания:
- \b не будет совпадать, если последний символ является символом ')', потому что это не символ слова и \b тесты для границ символа слова, поэтому ваше регулярное выражение будет не соответствует.
- В то время как регулярное выражение является мощным, если вы не гуру среди гуру, лучше всего держать выражения просто, потому что в противном случае их трудно поддерживать и трудно понять другим людям. Вот почему иногда лучше разбить проблему на подзадачи и более простые выражения и позволить языку выполнять некоторые операции поиска и поиска, в которых это хорошо. Таким образом, вы можете смешивать простые регулярные выражения с более сложным кодом или наоборот, в зависимости от того, где вам удобно.
- Это будет соответствовать некоторым очень сложным функциям, но это не лексический анализатор для функций.
- Если вы можете иметь строки в аргументах, и сами строки могут содержать скобки, например. "go (...", тогда вам нужно будет изменить регулярное выражение, чтобы вывести строки из сравнения. То же, что и в комментариях.
- Некоторые ссылки для определения балансирующих групп: здесь, здесь, здесь и здесь.
Надеюсь, что это поможет.