Регулярное выражение для анализа функций с произвольной глубиной

Я разбираю простой язык (формулы Excel) для функций, содержащихся внутри. Имя функции должно начинаться с любой буквы, за которой следует любое количество букв / цифр, и заканчиваться открытым паренем (без пробелов между ними). Например MyFunc(, Функция может содержать любые аргументы, включая другие функции, и должна заканчиваться парой ), Конечно, математика в паренсе разрешена =MyFunc((1+1)) а также (1+1) не должен быть обнаружен как функция, потому что он не соответствует правилу функции, которое я только что описал. Моя цель - распознавать вызовы функций самого высокого уровня в формуле, определять имя функции, извлекать аргументы. С помощью аргументов я могу рекурсивно искать другие вызовы функций.

Используя этот урок, я взломал следующие регулярные выражения. Никто, кажется, не делает трюк. Они оба терпят неудачу на тестовом примере, вставленном ниже.

Это должно работать, но полностью терпит неудачу:

(?<name>[a-z][a-z0-9]*\()(?<body>(?>[a-z][a-z0-9]*\((?<DEPTH>)|\)(?<-DEPTH>)|.?)*(?(DEPTH)(?!)))\)

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

(?<name>[a-z][a-z0-9]*\()(?<body>(?>\((?<DEPTH>)|\)(?<-DEPTH>)|.?)*(?(DEPTH)(?!)))\)

Вот тест, который ломает их всех:

=Date(Year(A$5),Month(A$5),1)-(Weekday(Date(Year(A$5),Month(A$5),1))-1)+{0;1;2;3;4;5}*7+{1,2,3,4,5,6,7}-1

Это должно соответствовать:

Date(ARGUMENTS1)
Weekday(ARGUMENTS2)
Where ARGUMENTS2 = Date(Year(A$5),Month(A$5),1)

Вместо этого это соответствует:

ARGUMENTS2 = Date(Year(A$5),Month(A$5),1)-1)

Я использую.net RegEx, который обеспечивает внешнюю память.

2 ответа

Решение

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

using System;
using System.Text.RegularExpressions;

namespace Test
{
  class Test
  {
    public static void Main()
    {
      Regex r = new Regex(@"
        (?<name>[a-z][a-z0-9]*\()
          (?<body>
            (?>
               \((?<DEPTH>)
             |
               \)(?<-DEPTH>)
             |
               [^()]+
            )*
            (?(DEPTH)(?!))
          )
        \)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

      string formula = @"=Date(Year(A$5),Month(A$5),1)-(Weekday(Date(Year((A$5+1)),Month(A$5),1))-1)+{0;1;2;3;4;5}*7+{1,2,3,4,5,6,7}-1";

      foreach (Match m in r.Matches(formula))
      {
        Console.WriteLine("{0}\n", m.Value);
      }
    }
  }
}

выход:

 Дата (Год (A$5), Месяц (A$5),1)

Будни (Дата (Год ((A$5+1)),Month(A$5),1)) 

Основная проблема с вашим регулярным выражением заключалась в том, что вы включали имя функции как часть рекурсивного соответствия - например:

Name1(...Name2(...)...)

Любое открытое имя, которому не предшествовало имя, не учитывалось, потому что оно соответствовало последней альтернативе, |.?), и это сбило с баланса близких паренов. Это также означало, что вы не можете подобрать формулы =MyFunc((1+1)), который вы упомянули в тексте, но не включили в пример. (Я бросил дополнительный набор паренов, чтобы продемонстрировать.)

РЕДАКТИРОВАТЬ: Вот версия с поддержкой незначительных, указанных в кавычках:

  Regex r = new Regex(@"
    (?<name>[a-z][a-z0-9]*\()
      (?<body>
        (?>
           \((?<DEPTH>)
         |
           \)(?<-DEPTH>)
         |
           ""[^""]+""
         |
           [^()""]+
        )*
        (?(DEPTH)(?!))
      )
    \)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

Возможно, вы захотите посмотреть по этой ссылке: http://www.xtremevbtalk.com/archive/index.php/t-177848.html

Другие вопросы по тегам