web-dev-qa-db-fra.com

Les expressions régulières peuvent-elles être utilisées pour faire correspondre les modèles imbriqués?

Est-il possible d'écrire une expression régulière qui correspond à un modèle imbriqué qui se produit un nombre inconnu de fois? Par exemple, une expression régulière peut-elle correspondre à une accolade ouvrante et fermante lorsqu'il existe un nombre inconnu d'accolades ouvrantes/fermées imbriquées dans les accolades extérieures?

Par exemple:

public MyMethod()
{
  if (test)
  {
    // More { }
  }

  // More { }
} // End

Devrait correspondre:

{
  if (test)
  {
    // More { }
  }

  // More { }
}
222
Richard Dorman

C'est aussi simple que ça. Un automate fini (qui est la structure de données sous-jacente à une expression régulière) n'a pas de mémoire en dehors de l'état dans lequel il se trouve, et si vous avez une imbrication arbitrairement profonde, vous avez besoin d'un automate arbitrairement volumineux, qui se heurte à la notion de automate fini .

Vous pouvez faire correspondre des éléments imbriqués/appariés jusqu'à une profondeur fixe, où la profondeur n'est limitée que par votre mémoire, car l'automate devient très volumineux. En pratique, cependant, vous devriez utiliser un automate Push-down, c'est-à-dire un analyseur syntaxique pour une grammaire sans contexte, par exemple LL (de haut en bas) ou LR (de bas en haut). Vous devez prendre en compte le comportement d’exécution le plus défavorable: O (n ^ 3) vs O (n), avec n = longueur (entrée).

Il existe de nombreux générateurs d'analyseurs disponibles, par exemple ANTLR pour Java. Trouver une grammaire existante pour Java (ou C) n'est également pas difficile.
Pour plus d’arrière-plan: Théorie des automates sur Wikipedia

256
Torsten Marek

L'utilisation d'expressions régulières pour rechercher des modèles imbriqués est très simple.

'/(\((?>[^()]+|(?1))*\))/'
36
MichaelRushton

Solution Perl fonctionnant probablement, si la chaîne est sur une ligne:

my $NesteD ;
$NesteD = qr/ \{( [^{}] | (??{ $NesteD }) )* \} /x ;

if ( $Stringy =~ m/\b( \w+$NesteD )/x ) {
    print "Found: $1\n" ;
  }

HTH

EDIT: vérifier:

Et encore une chose de Torsten Marek (qui a bien fait remarquer que ce n’est plus une regex):

33
Zsolt Botykai

Oui, s'il s'agit d'un moteur .NET RegEx. Le moteur .Net prend en charge la machine à états finis fournie avec une pile externe. voir détails

19
Pavlush

Le lemme de pompage pour les langues ordinaires est la raison pour laquelle vous ne pouvez pas faire cela.

L'automate généré aura un nombre fini d'états, disons k, donc une chaîne de k + 1 accolades ouvrantes est obligée d'avoir un état répété quelque part (lorsque l'automate traite les caractères). La partie de la chaîne entre le même état peut être dupliquée à l'infini et l'automate ne saura pas la différence.

En particulier, si elle accepte k + 1 accolades suivies de k + 1 accolades fermantes (ce qui devrait être le cas), elle acceptera également le nombre pompé d’accolades ouvrantes suivies de k + 1 accolades fermantes (ce qui ne devrait pas être le cas).

15
Rafał Dowgird

Les expressions régulières appropriées ne pourraient pas le faire car vous quitteriez le domaine des langages normaux pour atterrir dans les territoires sans contexte du langage.

Néanmoins, les packages "expressions régulières" proposés par de nombreuses langues sont strictement plus puissants.

Par exemple, les expressions rationnelles Lua ont la reconnaissance "%b()" qui correspondra à la parenthèse équilibrée. Dans votre cas, vous utiliseriez "%b{} "

Un autre outil sophistiqué similaire à sed est gema , où vous pourrez associer très facilement des accolades équilibrées à {#}.

Ainsi, en fonction des outils à votre disposition, votre "expression régulière" (au sens large) pourra peut-être correspondre aux parenthèses imbriquées.

13
Remo.D

L'utilisation de la correspondance récursive dans le moteur de regex PHP est beaucoup plus rapide que la correspondance procédurale des crochets. Surtout avec des chaînes plus longues.

http://php.net/manual/en/regexp.reference.recursive.php

par exemple.

$patt = '!\( (?: (?: (?>[^()]+) | (?R) )* ) \)!x';

preg_match_all( $patt, $str, $m );

vs.

matchBrackets( $str );

function matchBrackets ( $str, $offset = 0 ) {

    $matches = array();

    list( $opener, $closer ) = array( '(', ')' );

    // Return early if there's no match
    if ( false === ( $first_offset = strpos( $str, $opener, $offset ) ) ) {
        return $matches;
    }

    // Step through the string one character at a time storing offsets
    $paren_score = -1;
    $inside_paren = false;
    $match_start = 0;
    $offsets = array();

    for ( $index = $first_offset; $index < strlen( $str ); $index++ ) {
        $char = $str[ $index ];

        if ( $opener === $char ) {
            if ( ! $inside_paren ) {
                $paren_score = 1;
                $match_start = $index;
            }
            else {
                $paren_score++;
            }
            $inside_paren = true;
        }
        elseif ( $closer === $char ) {
            $paren_score--;
        }

        if ( 0 === $paren_score ) {
            $inside_paren = false;
            $paren_score = -1;
            $offsets[] = array( $match_start, $index + 1 );
        }
    }

    while ( $offset = array_shift( $offsets ) ) {

        list( $start, $finish ) = $offset;

        $match = substr( $str, $start, $finish - $start );
        $matches[] = $match;
    }

    return $matches;
}
5
Pete B

OUI

... en supposant qu'il y ait un nombre maximal de nids où vous seriez heureux de vous arrêter.

Laissez-moi expliquer.


@ torsten-marek a raison de dire qu'une expression régulière ne peut pas rechercher de modèles imbriqués comme celui-ci, [~ # ~] mais [~ # ~] il est possible de define un motif de regex imbriqué qui vous permettra de capturer des structures imbriquées comme ceci jusqu'à une profondeur maximale. J'ai créé un pour capturer style EBNF commentaires ( essayez-le ici ), comme:

(* This is a comment (* this is nested inside (* another level! *) hey *) yo *)

La regex (pour les commentaires mono-profondeur) est la suivante:

m{1} = \(+\*+(?:[^*(]|(?:\*+[^)*])|(?:\(+[^*(]))*\*+\)+

Cela pourrait facilement être adapté à vos besoins en remplaçant le \(+\*+ et \*+\)+ avec { et } et remplacer tout ce qui se trouve entre les deux par un simple [^{}]:

p{1} = \{(?:[^{}])*\}

( Voici le lien pour l'essayer.)

Pour imbriquer, laissez simplement ce motif dans le bloc lui-même:

p{2} = \{(?:(?:p{1})|(?:[^{}]))*\}
  ...or...
p{2} = \{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\}

Pour rechercher des blocs triples imbriqués, utilisez:

p{3} = \{(?:(?:p{2})|(?:[^{}]))*\}
  ...or...
p{3} = \{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}

Un schéma clair est apparu. Pour trouver des commentaires imbriqués à une profondeur de N, utilisez simplement l'expression régulière:

p{N} = \{(?:(?:p{N-1})|(?:[^{}]))*\}

  where N > 1 and
  p{1} = \{(?:[^{}])*\}

Un script pourrait être écrit pour générer récursivement ces regex, mais cela dépasse le cadre de ce pour quoi j'ai besoin. (Ceci est laissé comme un exercice pour le lecteur. ????)

4
awwsmm

comme mentionné par zsolt, certains moteurs de regex prennent en charge la récursivité - bien sûr, ce sont généralement ceux qui utilisent un algorithme de retour en arrière, de sorte qu'il ne sera pas particulièrement efficace. exemple: /(?>[^{}]*){(?>[^{}]*)(?R)*(?>[^{}]*)}/sm

Non, vous entrez dans le domaine de Grammars sans contexte à ce stade.

2
Craig H

Cela semble fonctionner: /(\{(?:\{.*\}|[^\{])*\})/m

0
Sean Huber