¿Existe una expresión regular para detectar una expresión regular válida?

¿Es posible detectar una expresión regular válida con otra expresión regular? Si es así, proporcione el código de ejemplo a continuación.

preguntado Oct 05 '08, 15:10

Entonces, su problema es validar una expresión regular, eligió una expresión regular para resolverlo. Me pregunto si la propiedad de aumento del número de problemas de las expresiones regulares es aditiva o multiplicativa. Se siente como 4 problemas en lugar de 2 :) -

Hay muchas notaciones para las expresiones regulares: algunas características y su ortografía son comunes a la mayoría, algunas se escriben de manera diferente o solo están disponibles en una notación en particular. La mayoría de esas notaciones no son "regulares" en el sentido gramatical regular; necesitaría un analizador sin contexto para manejar el anidamiento ilimitado de subexpresiones, aunque muchas notaciones modernas de "expresiones regulares" tienen extensiones que van más allá de la definición formal original y podría permitir que se reconozcan sus propias notaciones. En cualquier caso, ¿por qué no simplemente preguntarle a su biblioteca de expresiones regulares si cada expresión regular es válida? -

@bevacqua necesito validar expresiones regulares en el esquema XML. ¿Cómo puedo hacerlo sin otra expresión regular? -

Realmente compile / ejecute la expresión regular (patrón) que se va a verificar, bajo un mecanismo de manejo de excepciones que tiene su lenguaje. Entonces, el motor / compilador de expresiones regulares del lenguaje lo verificará. (Esto asume la sintaxis básica correcta para que el programa se ejecute, pero eso se puede incluir en la verificación utilizando las funciones de sus idiomas para evaluar la cadena para la expresión regular como código (posiblemente sintácticamente incorrecto), o algo así).

Esta es la respuesta perfecta para los usuarios de Python: stackoverflow.com/questions/19630994/… -

8 Respuestas

/
^                                             # start of string
(                                             # first group start
  (?:
    (?:[^?+*{}()[\]\\|]+                      # literals and ^, $
     | \\.                                    # escaped characters
     | \[ (?: \^?\\. | \^[^\\] | [^\\^] )     # character classes
          (?: [^\]\\]+ | \\. )* \]
     | \( (?:\?[:=!]|\?<[=!]|\?>)? (?1)?? \)  # parenthesis, with recursive content
     | \(\? (?:R|[+-]?\d+) \)                 # recursive matching
     )
    (?: (?:[?+*]|\{\d+(?:,\d*)?\}) [?+]? )?   # quantifiers
  | \|                                        # alternative
  )*                                          # repeat content
)                                             # end first group
$                                             # end of string
/

Esta es una expresión regular recursiva y no es compatible con muchos motores de expresiones regulares. Los basados ​​en PCRE deberían admitirlo.

Sin espacios en blanco ni comentarios:

/^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*)$/

.NET no admite la recursividad directamente. (La (?1) y (?R) construcciones.) La recursividad tendría que convertirse en contar grupos balanceados:

^                                         # start of string
(?:
  (?: [^?+*{}()[\]\\|]+                   # literals and ^, $
   | \\.                                  # escaped characters
   | \[ (?: \^?\\. | \^[^\\] | [^\\^] )   # character classes
        (?: [^\]\\]+ | \\. )* \]
   | \( (?:\?[:=!]
         | \?<[=!]
         | \?>
         | \?<[^\W\d]\w*>
         | \?'[^\W\d]\w*'
         )?                               # opening of group
     (?<N>)                               #   increment counter
   | \)                                   # closing of group
     (?<-N>)                              #   decrement counter
   )
  (?: (?:[?+*]|\{\d+(?:,\d*)?\}) [?+]? )? # quantifiers
| \|                                      # alternative
)*                                        # repeat content
$                                         # end of string
(?(N)(?!))                                # fail if counter is non-zero.

Comprimido:

^(?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>|\?<[^\W\d]\w*>|\?'[^\W\d]\w*')?(?<N>)|\)(?<-N>))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*$(?(N)(?!))

De los comentarios:

¿Validará esto sustituciones y traducciones?

Validará solo la parte de expresiones regulares de sustituciones y traducciones. s/<this part>/.../

En teoría, no es posible hacer coincidir todas las gramáticas de expresiones regulares válidas con una expresión regular.

Es posible si el motor de expresiones regulares admite la recursividad, como PCRE, pero eso ya no se puede llamar expresiones regulares.

De hecho, una "expresión regular recursiva" no es una expresión regular. Pero esta es una extensión a menudo aceptada para los motores de expresiones regulares ... Irónicamente, esta expresión regular extendida no coincide con las expresiones regulares extendidas.

"En teoría, la teoría y la práctica son lo mismo. En la práctica, no lo son". Casi todos los que conocen las expresiones regulares saben que las expresiones regulares no admiten la recursividad. Pero PCRE y la mayoría de las otras implementaciones admiten mucho más que expresiones regulares básicas.

usando esto con el script de shell en el comando grep, me muestra algún error .. grep: contenido no válido de {}. Estoy creando un script que podría grep de una base de código para encontrar todos los archivos que contienen expresiones regulares

Este patrón explota una extensión llamada expresiones regulares recursivas. Esto no es compatible con el tipo POSIX de regex. Puede probar con el interruptor -P, para habilitar el sabor de expresión regular de PCRE.

La expresión regular en sí "no es un lenguaje regular y, por lo tanto, no se puede analizar mediante una expresión regular ..."

Esto es cierto para las expresiones regulares clásicas. Algunas implementaciones modernas permiten la recursividad, lo que lo convierte en un lenguaje libre de contexto, aunque es algo detallado para esta tarea.

Veo donde estas haciendo juego []()/\. y otros caracteres especiales de expresiones regulares. ¿Dónde está permitiendo caracteres no especiales? Parece que esto coincidirá ^(?:[\.]+)$, Pero no ^abcdefg$. Esa es una expresión regular válida.

[^?+*{}()[\]\\|] coincidirá con cualquier carácter, no formará parte de ninguna de las otras construcciones. Esto incluye tanto literal (a - z) y ciertos caracteres especiales (^, $, .).

Respondido el 22 de enero de 20 a las 23:01

Esta respuesta envía a la gente en la dirección completamente equivocada. Nunca deberían usar regEx para localizar expresiones regulares, porque no puede funcionar correctamente en todos los casos. Vea mi respuesta agregada. - vitaly-t

.{,1} es incomparable. Cambiar a ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$ partidos. Cambio \d+ a \d* - Yunzen

regex por def no debería tener recursividad, al menos di algo así en tu respuesta, tu motor de expresiones regulares es probablemente "demasiado poderoso" y no es realmente un motor de expresiones regulares. - Charlie Parker

Solo una nota: olvidó la bandera x - user5147563

Este validador parece estar hecho para expresiones PCRE, pero pasará muchos POSIX ERE no válidos. En particular, son un poco más exigentes en los rangos de clases de caracteres, por ejemplo, esto es válido en PCRE pero no en ERE: [a-b-c]. - Pedro Gimeno

Improbable.

Evalúelo en un try..catch o lo que su idioma proporcione.

Respondido 05 Oct 08, 18:10

No, si habla estrictamente de expresiones regulares y no incluye algunas implementaciones de expresiones regulares que en realidad son gramáticas libres de contexto.

Existe una limitación de las expresiones regulares que hace que sea imposible escribir una expresión regular que coincida con todas y solo con las expresiones regulares. No puede hacer coincidir implementaciones como llaves que están emparejadas. Las expresiones regulares usan muchas de estas construcciones, tomemos [] como ejemplo. Siempre que haya un [ debe haber una coincidencia ], que es lo suficientemente simple para una expresión regular "\[.*\]".

Lo que hace que sea imposible para las expresiones regulares es que se pueden anidar. ¿Cómo se puede escribir una expresión regular que coincida con los corchetes anidados? La respuesta es que no se puede sin una expresión regular infinitamente larga. Puede hacer coincidir cualquier número de paréntesis anidados mediante la fuerza bruta, pero nunca puede hacer coincidir un conjunto arbitrariamente largo de corchetes anidados.

Esta capacidad a menudo se conoce como contar, porque está contando la profundidad del anidamiento. Una expresión regular, por definición, no tiene la capacidad de contar.


Terminé escribiendo "Limitaciones de expresiones regulares" sobre esto.

Respondido 09 Abr '20, 18:04

¿Alguna vez escribiste la pieza en expresiones regulares recursivas a las que te refieres en el artículo mencionado anteriormente (In a future (hopefully soon) post I will explore the recursive extensions to the .Net regular expression language.)? - Verace

Use una expresión regular recursiva como @Markus Jarderot señaló en su respuesta. Alternativamente, use una expresión regular para anotar los corchetes / paréntesis con el nivel de anidación, seguido de llamadas a funciones recursivas con una expresión regular para hacer coincidir los pares, y resolver / validar la expresión regular en cuestión; consulte stackoverflow.com/a/66130030/7475450 - Peter Thoeny

Buena pregunta.

Los verdaderos lenguajes regulares no pueden decidir entre paréntesis bien formados y profundamente anidados de forma arbitraria. Si su alfabeto contiene '(' y ')' el objetivo es decidir si una cadena de estos tiene paréntesis coincidentes bien formados. Dado que este es un requisito necesario para las expresiones regulares, la respuesta es no.

Sin embargo, si afloja el requisito y agrega recursividad, probablemente pueda hacerlo. La razón es que la recursividad puede actuar como una pila que le permite "contar" la profundidad de anidación actual presionando sobre esta pila.

Russ Cox escribió "La coincidencia de expresiones regulares puede ser simple y rápida"que es un tratado maravilloso sobre la implementación del motor de expresiones regulares.

Respondido 09 Abr '20, 18:04

Exactamente. Puede usar una expresión regular para anotar los corchetes / paréntesis con el nivel de anidamiento, seguido de llamadas a funciones recursivas con una expresión regular para hacer coincidir pares, y resolver / validar la expresión regular en cuestión - consulte stackoverflow.com/a/66130030/7475450 - Peter Thoeny

No, si usa expresiones regulares estándar.

La razón es que no puede satisfacer la bombeo de lema para idiomas regulares. El lema de bombeo establece que una cadena perteneciente a la lengua "L" es regular si existe un número "N" tal que, después de dividir la cadena en tres subcadenas x, y, ztal que |x|>=1 && |xy|<=N, puedes repetir y tantas veces como quieras y toda la cadena seguirá perteneciendo a L.

Una consecuencia del lema de bombeo es que no puede tener cadenas regulares en la forma a^Nb^Mc^N, es decir, dos subcadenas que tienen la misma longitud separadas por otra cadena. De cualquier forma, dividas esas cadenas en x, y y z, no puedes "bombear" y sin obtener una cadena con un número diferente de "a" y "c", dejando así el idioma original. Ese es el caso, por ejemplo, de los paréntesis en las expresiones regulares.

Respondido 09 Abr '20, 18:04

Esa no es una descripción muy precisa del lema de bombeo. Primero, es todo el lenguaje lo que puede ser regular o no, ni una sola cadena. En segundo lugar, es una condición necesaria, no suficiente, para la regularidad. Finalmente, solo se pueden bombear cuerdas suficientemente largas. - Darij Grinberg

Aunque es perfectamente posible usar una expresión regular recursiva como ha publicado MizardX, para este tipo de cosas es mucho más útil un analizador. Las expresiones regulares fueron originalmente diseñadas para usarse con lenguajes regulares, ser recursivo o tener grupos de equilibrio es solo un parche.

El lenguaje que define expresiones regulares válidas es en realidad una gramática libre de contexto, y debe usar un analizador apropiado para manejarlo. Aquí hay un ejemplo de un proyecto universitario para analizar expresiones regulares simples (sin la mayoría de las construcciones). Utiliza JavaCC. Y sí, los comentarios están en español, aunque los nombres de los métodos se explican por sí mismos.

SKIP :
{
    " "
|   "\r"
|   "\t"
|   "\n"
}
TOKEN : 
{
    < DIGITO: ["0" - "9"] >
|   < MAYUSCULA: ["A" - "Z"] >
|   < MINUSCULA: ["a" - "z"] >
|   < LAMBDA: "LAMBDA" >
|   < VACIO: "VACIO" >
}

IRegularExpression Expression() :
{
    IRegularExpression r; 
}
{
    r=Alternation() { return r; }
}

// Matchea disyunciones: ER | ER
IRegularExpression Alternation() :
{
    IRegularExpression r1 = null, r2 = null; 
}
{
    r1=Concatenation() ( "|" r2=Alternation() )?
    { 
        if (r2 == null) {
            return r1;
        } else {
            return createAlternation(r1,r2);
        } 
    }
}

// Matchea concatenaciones: ER.ER
IRegularExpression Concatenation() :
{
    IRegularExpression r1 = null, r2 = null; 
}
{
    r1=Repetition() ( "." r2=Repetition() { r1 = createConcatenation(r1,r2); } )*
    { return r1; }
}

// Matchea repeticiones: ER*
IRegularExpression Repetition() :
{
    IRegularExpression r; 
}
{
    r=Atom() ( "*" { r = createRepetition(r); } )*
    { return r; }
}

// Matchea regex atomicas: (ER), Terminal, Vacio, Lambda
IRegularExpression Atom() :
{
    String t;
    IRegularExpression r;
}
{
    ( "(" r=Expression() ")" {return r;}) 
    | t=Terminal() { return createTerminal(t); }
    | <LAMBDA> { return createLambda(); }
    | <VACIO> { return createEmpty(); }
}

// Matchea un terminal (digito o minuscula) y devuelve su valor
String Terminal() :
{
    Token t;
}
{
    ( t=<DIGITO> | t=<MINUSCULA> ) { return t.image; }
}

Respondido 06 Oct 08, 15:10

Puede enviar la expresión regular a preg_match que devolverá falso si la expresión regular no es válida. No olvide usar el @ para suprimir los mensajes de error:

@preg_match($regexToTest, '');
  • Devolverá 1 si la expresión regular es //.
  • Devolverá 0 si la expresión regular está bien.
  • Devolverá falso de lo contrario.

Respondido 09 Abr '20, 18:04

El siguiente ejemplo de Paul McGuire, originalmente de la wiki pyparsing, pero ahora disponible solo a través de Wayback Machine, proporciona una gramática para analizar algo expresiones regulares, con el fin de devolver el conjunto de cadenas coincidentes. Como tal, rechaza aquellas re que incluyen términos de repetición ilimitados, como '+' y '*'. Pero debería darte una idea sobre cómo estructurar un analizador que procesaría re.

# 
# invRegex.py
#
# Copyright 2008, Paul McGuire
#
# pyparsing script to expand a regular expression into all possible matching strings
# Supports:
# - {n} and {m,n} repetition, but not unbounded + or * repetition
# - ? optional elements
# - [] character ranges
# - () grouping
# - | alternation
#
__all__ = ["count","invert"]

from pyparsing import (Literal, oneOf, printables, ParserElement, Combine, 
    SkipTo, operatorPrecedence, ParseFatalException, Word, nums, opAssoc,
    Suppress, ParseResults, srange)

class CharacterRangeEmitter(object):
    def __init__(self,chars):
        # remove duplicate chars in character range, but preserve original order
        seen = set()
        self.charset = "".join( seen.add(c) or c for c in chars if c not in seen )
    def __str__(self):
        return '['+self.charset+']'
    def __repr__(self):
        return '['+self.charset+']'
    def makeGenerator(self):
        def genChars():
            for s in self.charset:
                yield s
        return genChars

class OptionalEmitter(object):
    def __init__(self,expr):
        self.expr = expr
    def makeGenerator(self):
        def optionalGen():
            yield ""
            for s in self.expr.makeGenerator()():
                yield s
        return optionalGen

class DotEmitter(object):
    def makeGenerator(self):
        def dotGen():
            for c in printables:
                yield c
        return dotGen

class GroupEmitter(object):
    def __init__(self,exprs):
        self.exprs = ParseResults(exprs)
    def makeGenerator(self):
        def groupGen():
            def recurseList(elist):
                if len(elist)==1:
                    for s in elist[0].makeGenerator()():
                        yield s
                else:
                    for s in elist[0].makeGenerator()():
                        for s2 in recurseList(elist[1:]):
                            yield s + s2
            if self.exprs:
                for s in recurseList(self.exprs):
                    yield s
        return groupGen

class AlternativeEmitter(object):
    def __init__(self,exprs):
        self.exprs = exprs
    def makeGenerator(self):
        def altGen():
            for e in self.exprs:
                for s in e.makeGenerator()():
                    yield s
        return altGen

class LiteralEmitter(object):
    def __init__(self,lit):
        self.lit = lit
    def __str__(self):
        return "Lit:"+self.lit
    def __repr__(self):
        return "Lit:"+self.lit
    def makeGenerator(self):
        def litGen():
            yield self.lit
        return litGen

def handleRange(toks):
    return CharacterRangeEmitter(srange(toks[0]))

def handleRepetition(toks):
    toks=toks[0]
    if toks[1] in "*+":
        raise ParseFatalException("",0,"unbounded repetition operators not supported")
    if toks[1] == "?":
        return OptionalEmitter(toks[0])
    if "count" in toks:
        return GroupEmitter([toks[0]] * int(toks.count))
    if "minCount" in toks:
        mincount = int(toks.minCount)
        maxcount = int(toks.maxCount)
        optcount = maxcount - mincount
        if optcount:
            opt = OptionalEmitter(toks[0])
            for i in range(1,optcount):
                opt = OptionalEmitter(GroupEmitter([toks[0],opt]))
            return GroupEmitter([toks[0]] * mincount + [opt])
        else:
            return [toks[0]] * mincount

def handleLiteral(toks):
    lit = ""
    for t in toks:
        if t[0] == "\\":
            if t[1] == "t":
                lit += '\t'
            else:
                lit += t[1]
        else:
            lit += t
    return LiteralEmitter(lit)    

def handleMacro(toks):
    macroChar = toks[0][1]
    if macroChar == "d":
        return CharacterRangeEmitter("0123456789")
    elif macroChar == "w":
        return CharacterRangeEmitter(srange("[A-Za-z0-9_]"))
    elif macroChar == "s":
        return LiteralEmitter(" ")
    else:
        raise ParseFatalException("",0,"unsupported macro character (" + macroChar + ")")

def handleSequence(toks):
    return GroupEmitter(toks[0])

def handleDot():
    return CharacterRangeEmitter(printables)

def handleAlternative(toks):
    return AlternativeEmitter(toks[0])


_parser = None
def parser():
    global _parser
    if _parser is None:
        ParserElement.setDefaultWhitespaceChars("")
        lbrack,rbrack,lbrace,rbrace,lparen,rparen = map(Literal,"[]{}()")

        reMacro = Combine("\\" + oneOf(list("dws")))
        escapedChar = ~reMacro + Combine("\\" + oneOf(list(printables)))
        reLiteralChar = "".join(c for c in printables if c not in r"\[]{}().*?+|") + " \t"

        reRange = Combine(lbrack + SkipTo(rbrack,ignore=escapedChar) + rbrack)
        reLiteral = ( escapedChar | oneOf(list(reLiteralChar)) )
        reDot = Literal(".")
        repetition = (
            ( lbrace + Word(nums).setResultsName("count") + rbrace ) |
            ( lbrace + Word(nums).setResultsName("minCount")+","+ Word(nums).setResultsName("maxCount") + rbrace ) |
            oneOf(list("*+?")) 
            )

        reRange.setParseAction(handleRange)
        reLiteral.setParseAction(handleLiteral)
        reMacro.setParseAction(handleMacro)
        reDot.setParseAction(handleDot)

        reTerm = ( reLiteral | reRange | reMacro | reDot )
        reExpr = operatorPrecedence( reTerm,
            [
            (repetition, 1, opAssoc.LEFT, handleRepetition),
            (None, 2, opAssoc.LEFT, handleSequence),
            (Suppress('|'), 2, opAssoc.LEFT, handleAlternative),
            ]
            )
        _parser = reExpr

    return _parser

def count(gen):
    """Simple function to count the number of elements returned by a generator."""
    i = 0
    for s in gen:
        i += 1
    return i

def invert(regex):
    """Call this routine as a generator to return all the strings that
       match the input regular expression.
           for s in invert("[A-Z]{3}\d{3}"):
               print s
    """
    invReGenerator = GroupEmitter(parser().parseString(regex)).makeGenerator()
    return invReGenerator()

def main():
    tests = r"""
    [A-EA]
    [A-D]*
    [A-D]{3}
    X[A-C]{3}Y
    X[A-C]{3}\(
    X\d
    foobar\d\d
    foobar{2}
    foobar{2,9}
    fooba[rz]{2}
    (foobar){2}
    ([01]\d)|(2[0-5])
    ([01]\d\d)|(2[0-4]\d)|(25[0-5])
    [A-C]{1,2}
    [A-C]{0,3}
    [A-C]\s[A-C]\s[A-C]
    [A-C]\s?[A-C][A-C]
    [A-C]\s([A-C][A-C])
    [A-C]\s([A-C][A-C])?
    [A-C]{2}\d{2}
    @|TH[12]
    @(@|TH[12])?
    @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9]))?
    @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9])|OH(1[0-9]?|2[0-9]?|30?|[4-9]))?
    (([ECMP]|HA|AK)[SD]|HS)T
    [A-CV]{2}
    A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|S[bcegimnr]?|T[abcehilm]|Uu[bhopqst]|U|V|W|Xe|Yb?|Z[nr]
    (a|b)|(x|y)
    (a|b) (x|y)
    """.split('\n')

    for t in tests:
        t = t.strip()
        if not t: continue
        print '-'*50
        print t
        try:
            print count(invert(t))
            for s in invert(t):
                print s
        except ParseFatalException,pfe:
            print pfe.msg
            print
            continue
        print

if __name__ == "__main__":
    main()

Respondido 12 Abr '19, 23:04

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.