Miglioramento / correzione di un Regex per i commenti del blocco di stile C

Sto scrivendo (in C #) un semplice parser per elaborare un linguaggio di scripting che assomigli molto alla classica C.

Su un file di script che ho, l’espressione regolare che sto usando per riconoscere / * blocca commenti * / sta entrando in una sorta di ciclo infinito, prendendo il 100% della CPU per secoli.

Il Regex che sto usando è questo:

/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ 

Qualche suggerimento sul perché questo potrebbe essere bloccato?

In alternativa, qual è invece un altro Regex che potrei usare?

    Maggiori informazioni:

    • Funzionando in C # 3.0 che mira a .NET 3.5;
    • Sto usando il metodo Regex.Match (string, int) per iniziare la corrispondenza su un particolare indice della stringa;
    • Ho lasciato il programma in esecuzione per oltre un’ora, ma la partita non è stata completata;
    • Le opzioni trasmesse al costruttore Regex sono RegexOptions.Multiline e RegexOptions.IgnorePatternWhitespace ;
    • La regex funziona correttamente per 452 dei miei 453 file di test.

    Alcuni problemi che vedo con la tua espressione regolare:

    Non c’è bisogno delle sequenze |[\r\n] nella tua regex; una class di caratteri negata come [^*] corrisponde a tutto tranne * , compresi i separatori di riga. È solo il . (punto) metacarattere che non corrisponde a quelli.

    Una volta che sei all’interno del commento, l’unico personaggio che devi cercare è un asterisco; finché non ne vedi uno, puoi inghiottire tutti i personaggi che desideri. Ciò significa che non ha senso usare [^*] quando invece puoi usare [^*]+ . In effetti, potresti anche metterlo in un gruppo atomico – (?>[^*]+) – perché non avrai mai motivo di rinunciare a nessuno di questi non-asterischi dopo averli abbinati.

    Filtrando la spazzatura estranea, l’alternativa finale all’interno dei tuoi parenti più esterni è \*+[^*/] , che significa “uno o più asterischi, seguiti da un carattere che non è un asterisco o una barra”. Questo corrisponderà sempre all’asterisco alla fine del commento, e dovrà sempre rinunciare perché il prossimo carattere è una barra. In effetti, se ci sono venti asterischi che precedono la barra finale, quella parte della tua espressione regolare sarà uguale a tutti, quindi li darà tutti, uno per uno. Quindi la parte finale – \*+/ – li abbinerà per conservare.

    Per le massime prestazioni, vorrei usare questo regex:

     /\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/ 

    Questo farà corrispondere molto velocemente un commento ben formato, ma soprattutto se inizia a corrispondere a qualcosa che non è un commento valido, fallirà il più rapidamente ansible.


    Per gentile concessione di David , ecco una versione che combina i commenti nidificati con qualsiasi livello di nidificazione:

     (?s)/\*(?>/\*(?)|\*/(?<-LEVEL>)|(?!/\*|\*/).)+(?(LEVEL)(?!))\*/ 

    Usa i gruppi di bilanciamento di .NET, quindi non funzionerà in nessun altro modo. Per completezza, ecco un’altra versione (dalla Libreria di RegexBuddy) che utilizza la syntax dei Gruppi Ricorsivi supportata da Perl, PCRE e Oniguruma / Onigmo:

     /\*(?>[^*/]+|\*[^/]|/[^*])*(?>(?R)(?>[^*/]+|\*[^/]|/[^*])*)*\*/ 

    No no no! Qualcun altro ha letto Mastering Regular Expressions (3rd Edition) !? In questo, Jeffrey Friedl esamina questo problema esatto e lo usa come esempio (pagine 272-276) per illustrare la sua tecnica del “srotolare-il-ciclo”. La sua soluzione per la maggior parte dei motori regex è così:

    /\*[^*]*\*+(?:[^*/][^*]*\*+)*/

    Tuttavia, se il motore regex è ottimizzato per gestire quantificatori pigri (come Perl), l’espressione più efficiente è molto più semplice (come suggerito sopra):

    /\*.*?\*/

    (Con l’equivalente ‘s’ “punto corrisponde a tutti” il modificatore applicato ovviamente.) Nota che non uso .NET quindi non posso dire quale versione è più veloce per quel motore.

    Potresti provare l’opzione Singleline piuttosto che Multiline, quindi non devi preoccuparti di \ r \ n. Con quello abilitato il seguente ha funzionato per me con un semplice test che includeva commenti che si estendevano su più di una riga:

     /\*.*?\*/ 

    Penso che la tua espressione sia troppo complicata. Applicato a una stringa di grandi dimensioni, le molte alternative implicano un sacco di backtracking. Immagino che questa sia la fonte del successo in termini di prestazioni che vedi.

    Se l’assunto di base è quello di far corrispondere ogni cosa da "/*" fino a quando non viene incontrato il primo "*/" , un modo per farlo sarebbe (come al solito, l’espressione regolare non è adatta per le strutture nidificate, quindi i commenti del blocco nesting non lo fanno non funziona):

     /\*(.(?!\*/))*.?\*/ // run this in single line (dotall) mode 

    In sostanza questo dice: "/*" , seguito da tutto ciò che non è seguito da "*/" , seguito da "*/" .

    In alternativa, puoi usare il più semplice:

     /\*.*?\*/ // run this in single line (dotall) mode 

    Un abbinamento non avido come questo ha il potenziale di sbagliare in un caso limite – al momento non riesco a pensare a uno in cui questa espressione potrebbe fallire, ma non ne sono del tutto sicuro.

    Sto usando questo al momento

     \/\*[\s\S]*?\*\/