Compila regexes


17

In questa attività devi scrivere un programma che legge un'espressione regolare e genera un altro programma che genera se una stringa di input è accettata da quell'espressione regolare. L'output deve essere un programma scritto nella stessa lingua della richiesta.

Ingresso

L'input è un'espressione regolare r corrispondente al seguente ABNF (la regola di produzione iniziale è REGEX):

REGEX       = *( STAR / GROUP / LITERAL / ALTERNATIVE )
STAR        = REGEX '*'
GROUP       = '(' REGEX ')'
LITERAL     = ALPHA / DIGIT
ALTERNATIVE = REGEX '|' REGEX

Se l'input non corrisponde a questa grammatica, il comportamento del programma non è definito.

Interpretazione

Interpreta l'input come espressione regolare, dove *è la stella di Kleene (che significa ripetere l'argomento sinistro zero o più volte ), |è un'alternativa, (e il )gruppo e nessun operatore sono affatto concatenati. Il raggruppamento ha la precedenza sulla stella, la stella ha la precedenza sulla concatenazione, la concatenazione ha la precedenza sull'alternativa.

Si dice che una stringa sia accettata se il regex corrisponde all'intera stringa.

Produzione

L'output del programma è un altro programma scritto nella stessa lingua come la vostra presentazione che legge una stringa s in modo implementazione definita in fase di esecuzione, le uscite se r accetta s e poi termina. L'output può essere eseguito in modo definito dall'utente, sebbene per i programmi accettati e rifiutati debbano esserci solo due output distinti.

Si può presumere che l'input del programma di output non sia mai più lungo di 2 16 -1 byte.

restrizioni

Né l'invio né alcun programma generato dall'invio possono utilizzare funzionalità integrate o librerie

  • abbina regex
  • trasforma le espressioni regolari
  • compilare espressioni regolari
  • generare parser da una grammatica
  • semplifica il problema in modo che la tua presentazione diventi banale

punteggio

Il punteggio della tua presentazione è il numero di caratteri. Vince l'invio con il punteggio più basso.

Casi test

Tutte le prove contengono un'espressione regolare, una serie di stringhe accettate, una serie di stringhe rifiutate e un programma di esempio in C99 che è un output valido di un invio (ipotetico) C99.

(espressione regolare vuota)

Stringhe accettate

  1. (input vuoto)

Stringhe rifiutate

  1. foo
  2. bar
  3. baz
  4. quux

Programma di esempio

#include <stdio.h>

int main() {
    char input[65536];
    gets(input);

    return input[0] != 0;
}

(b|)(ab)*(a|)( ae balternando)

stringhe accettate

  1. a
  2. ba
  3. abababababa
  4. abab

stringhe rifiutate

  1. afba
  2. foo
  3. babba

programma di esempio

#include <stdio.h>

int main() {
  char input[65536];
  int state = 0;

  for (;;) switch (state) {
    case 0: switch (getchar()) {
      case 'a': state = 1; break;
      case 'b': state = 2; break;
      case EOF: return 0;
      default:  return 1;
    } break;
    case 1: switch (getchar()) {
      case 'b': state = 2; break;
      case EOF: return 0;
      default:  return 1;
    } break;
    case 2: switch (getchar()) {
      case 'a': state = 1; break;
      case EOF: return 0;
      default:  return 1;
    } break;
}

(0|1(0|1)*)(|A(0|1)*1) (numeri binari in virgola mobile)

stringhe accettate

  1. 10110100
  2. 0
  3. 1A00001

stringhe rifiutate

  1. 011
  2. 10A
  3. 1A00
  4. 100A010

1
Presumo che return (regex.match(stdin) is not null)non sia permesso avere un programma simile .
beary605,

1
Dici che "L'output deve essere un programma scritto nella stessa lingua dell'input", ma l'input è una regex. E la grammatica che fornisci non include la regola GROUP, che presumibilmente definisce parentesi letterali.
Peter Taylor,

@Peter Spiacente, intendevo scrivere nella stessa lingua della presentazione.
FUZxxl,

@ beary605 Sì, hai ragione. Vedere la sezione Restrizioni : né l'invio né alcun programma generato dall'invio possono utilizzare funzionalità incorporate o librerie che corrispondono a regex (...).
FUZxxl,

Penso che il tuo secondo programma di esempio sia errato, manca un loop attorno all'interruttore esterno
Hasturkun

Risposte:


8

Rubino, 641 651 543 caratteri

H=Hash.new{|h,k|[k]}
d=[[i=0,0,[]]]
o=[?(]
L="t,u,v=d.pop;q,r,s=d.pop;o.pop<?|&&(H[r]<<=t)||(H[q]<<=t;H[r]<<=u);d<<[q,u,s+v]"
gets.chop.chars.map{|c|c==?*&&(q,r,s=d.pop;H[r]|=[q,i+=1];d<<=[r,i,s];next)
eval(L)while/[|)]/=~c ?o[-1]>?(:o[-1]==?.
/[|(]/=~c&&d<<[i+=1,i,o<<c&&[]]||c!=?)&&d<<[i+=1,i+1,["s==#{o<<?.;i}&&c=='#{c}'&&#{i+=1}"]]||o[-1]=?.}
eval(L)while o.size>1
H.map{H.map{|k,v|v.map{|v|H[k]|=H[v]}}}
t,u,v=d[0]
$><<"s=#{H[t]};gets.chop.chars.map{|c|s=s.map{|s|#{v*'||'}}-[!0];#{H.map{|k,v|"s&[#{k}]!=[]&&s|=#{v}"}*?;}};p s&[#{u}]!=[]"

Questa versione ruby ​​è diventata piuttosto lunga a causa di diversi casi angolari nel parser regex (forse dovrei provare un approccio diverso). Si aspetta l'espressione regolare su STDIN e genera il corrispondente codice rubino per il matcher su STDOUT.

Il programma genera direttamente il codice per un NFA-ε che viene quindi eseguito nel matcher.

Caso di prova 1: (l'output include nuove righe e rientri aggiuntivi)

>>>

s=[0];
gets.chop.chars.map{|c|
  s=s.map{|s|}-[!0];
};
p s&[0]!=[]

Caso di prova 2:

>>> (b|)(ab)*(a|)

s=[0, 1, 2, 4, 9, 5, 10, 6, 11, 12, 14];
gets.chop.chars.map{|c|
  s=s.map{|s|s==2&&c=='b'&&3||s==6&&c=='a'&&7||s==8&&c=='b'&&9||s==12&&c=='a'&&13}-[!0];
  s&[1]!=[]&&s|=[1, 2, 4, 9, 5, 10, 6, 11, 12, 14];
  s&[3]!=[]&&s|=[3, 4, 9, 5, 10, 6, 11, 12, 14];
  s&[0]!=[]&&s|=[0, 1, 2, 4, 9, 5, 10, 6, 11, 12, 14];
  s&[5]!=[]&&s|=[5, 6];
  s&[7]!=[]&&s|=[7, 8];
  s&[9]!=[]&&s|=[9, 5, 10, 6, 11, 12, 14];
  s&[4]!=[]&&s|=[4, 9, 5, 10, 6, 11, 12, 14];
  s&[11]!=[]&&s|=[11, 12, 14];
  s&[13]!=[]&&s|=[13, 14];
  s&[10]!=[]&&s|=[10, 11, 12, 14]
};
p s&[14]!=[]

Un altro esempio:

>>> a|bc

s=[0, 1, 3, 4];
gets.chop.chars.map{|c|
  s=s.map{|s|s==1&&c=='a'&&2||s==4&&c=='b'&&5||s==6&&c=='c'&&7}-[!0];
  s&[0]!=[]&&s|=[0, 1, 3, 4];
  s&[3]!=[]&&s|=[3, 4];
  s&[5]!=[]&&s|=[5, 6];
  s&[2]!=[]&&s|=[2, 7]
};
p s&[7]!=[]

Modifica: aggiunta una transizione per correggere il bug PleaseStand annotato nei commenti. Modificata anche l'inizializzazione dello stato.


Inserimento 011per (0|1(0|1)*)(|A(0|1)*1)risultati regex in true- dovrebbe essere false.
Please Stand

@PleaseStand Fixed. Si prega di vedere la mia modifica.
Howard,

12

C, 627 caratteri

Questo programma considera il suo primo argomento della riga di comando come input e genera il codice C come output.

#define A(v) F[v]+strlen(F[v])
#define S sprintf
char*B="&&f%d(s)||f%d(s)",*J="&&f%d(s+%d)",*r,F[256][65536];h=2;e(f,n,o,R,C,O,t,g){for(C=O=f;*r;++r)switch(*r){case 40:r++;e(g=h++,C=h++,0,0);r[1]^42?t=g:(t=C,S(A(t),B,g,C=h++),r++);o=!S(A(O),J,t,o);O=C;break;case 41:goto L;case'|':S(A(C),J,n,o);o=!S(A(O=f),"||1");break;default:r[1]^42?S(A(C),"&&s[%d]==%d",o++,*r,O^f||R++):(o=!S(A(O),J,t=h++,o),O=C=h++,g=h++,S(A(g),"&&*s==%d&&f%d(s+1)",*r++,t),S(A(t),B,g,C));}L:S(A(C),J,n,o);}main(int c,char**v){r=v[1];for(e(1,!S(*F,"&&!*s"),0,0);h--;)printf("f%d(char*s){return 1%s;}",h,F[h]);puts("main(int c,char**v){exit(f1(v[1]));}");}

Ecco il suo output per (0|1(0|1)*)(|A(0|1)*1)(con le nuove righe aggiunte):

f11(char*s){return 1&&s[0]==49&&f7(s+1);}
f10(char*s){return 1&&s[0]==48&&f9(s+1)||1&&s[0]==49&&f9(s+1);}
f9(char*s){return 1&&f10(s)||f11(s);}
f8(char*s){return 1&&f7(s+0)||1&&s[0]==65&&f9(s+1);}
f7(char*s){return 1&&f0(s+0);}
f6(char*s){return 1&&f2(s+0);}
f5(char*s){return 1&&s[0]==48&&f4(s+1)||1&&s[0]==49&&f4(s+1);}
f4(char*s){return 1&&f5(s)||f6(s);}
f3(char*s){return 1&&s[0]==48&&f2(s+1)||1&&s[0]==49&&f4(s+1);}
f2(char*s){return 1&&f8(s+0);}
f1(char*s){return 1&&f3(s+0);}
f0(char*s){return 1&&!*s;}
main(int c,char**v){exit(f1(v[1]));}

Se si fornisce un input valido come primo argomento della riga di comando, restituisce lo stato di uscita 1. Altrimenti, restituisce lo stato di uscita 0.

$ ./regexcompiler '(0 | 1 (0 | 1) *) (| A (0 | 1) * 1)'> floatprog.c
$ gcc -o floatprog floatprog.c
floatprog.c: nella funzione 'main':
floatprog.c: 1: 519: avviso: dichiarazione implicita incompatibile della funzione integrata 'exit' [abilitata per impostazione predefinita]
$ ./floatprog '1A00001' && echo invalid || echo valido
valido
$ ./floatprog '100A010' && echo non valido || eco valido
non valido

Entrambi i programmi, se non si riesce a fornire l'argomento della riga di comando, dereferenzia un puntatore nullo, causando un errore di segmentazione. Un regex sufficientemente lungo traboccerà dei buffer di questo invio e la dimensione dell'input per un programma generato è limitata dalla dimensione dello stack. Tuttavia, tutti i casi di test funzionano.

Si noti che e(g=h++,C=h++,0,0);introduce un comportamento indefinito. Se, ad esempio, i programmi generati non vengono compilati, puoi provare a sostituire l'istruzione con h+=2;e(g=h-1,C=h-2,0,0);cinque caratteri in più.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.