Compilar Regexes


17

Nesta tarefa, você deve escrever um programa que leia uma expressão regular e gere outro programa que mostre se uma sequência de entrada é aceita por essa expressão regular. A saída deve ser um programa escrito no mesmo idioma que seu envio.

Entrada

A entrada é uma expressão regular r que corresponde ao seguinte ABNF (a regra de produção inicial é REGEX):

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

Se a entrada não corresponder a esta gramática, o comportamento do seu programa é indefinido.

Interpretação

Interprete a entrada como uma expressão regular, onde *está a estrela Kleene (significando repetir o argumento esquerdo zero ou mais vezes ), |é uma alternativa, (e o )grupo e nenhum operador são concatenados. O agrupamento tem precedência sobre a estrela, a estrela tem precedência sobre a concatenação, a concatenação tem precedência sobre a alternativa.

Diz-se que uma string é aceita se o regex corresponder à string inteira.

Resultado

A saída do programa é outro programa escrito na mesma língua que a sua apresentação que lê uma cadeia s de uma forma implementação definida em tempo de execução, as saídas se r aceita s e depois termina. A saída pode ser feita de maneira definida pelo usuário, embora deva haver apenas duas saídas distintas para programas aceitos e rejeitados.

Você pode supor que a entrada do seu programa de saída nunca seja maior que 2 16 -1 bytes.

Restrições

Nem seu envio nem qualquer programa gerado por ele pode usar a funcionalidade interna ou bibliotecas que

  • regexes de correspondência
  • transformar expressões regulares
  • compilar expressões regulares
  • gerar analisadores a partir de uma gramática
  • simplifique o problema de maneira que sua submissão se torne trivial

Pontuação

A pontuação do seu envio é o número de caracteres. A finalização com a menor pontuação vence.

Casos de teste

Todos os casos de teste contêm uma expressão regular, um conjunto de cadeias aceitas, um conjunto de cadeias rejeitadas e um programa de exemplo no C99, que é uma saída válida de um envio (hipotético) de C99.

(expressão regular vazia)

Strings aceitas

  1. (entrada vazia)

Cadeias rejeitadas

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

Programa de exemplo

#include <stdio.h>

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

    return input[0] != 0;
}

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

cordas aceitas

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

cadeias rejeitadas

  1. afba
  2. foo
  3. babba

programa de exemplo

#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) (números binários de ponto flutuante)

cordas aceitas

  1. 10110100
  2. 0 0
  3. 1A00001

cadeias rejeitadas

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

11
Presumo que ter um programa como esse return (regex.match(stdin) is not null)não seja permitido.
— beary605

11
Você diz que "A saída deve ser um programa escrito no mesmo idioma da entrada", mas a entrada é um regex. E a gramática que você fornece não inclui a regra GROUP, que presumivelmente define colchetes literais.
— Peter Taylor

@ Peter Desculpe, eu pretendia escrever o mesmo idioma da submissão.
— FUZxxl 19/08/2012

@ beary605 Sim, você está certo. Consulte a seção Restrições : nem o seu envio nem qualquer programa gerado pelo seu envio pode usar a funcionalidade incorporada ou as bibliotecas que correspondem às regexes (...).
— FUZxxl 19/08/2012

Eu acho que seu segundo programa de exemplo é incorreto, que está faltando um laço em torno do interruptor externo
— Hasturkun

Respostas:


8

Ruby, 641 651 543 caracteres

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}]!=[]"

Essa versão do ruby ​​se tornou bastante longa devido a vários casos de canto no analisador de expressões regulares (talvez eu devesse tentar uma abordagem diferente). Ele espera a expressão regular em STDIN e gera o código ruby ​​correspondente para o correspondente em STDOUT.

O programa gera código diretamente para um NFA-ε que é então executado no matcher.

Caso de teste 1: (a saída inclui novas linhas e recuo adicionais)

>>>

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

Caso de teste 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]!=[]

Outro exemplo:

>>> 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]!=[]

Edit: Adicionada uma transição para corrigir o bug PleaseStand anotado nos comentários. Também mudou a inicialização do estado.


A entrada 011para (0|1(0|1)*)(|A(0|1)*1)resultados de regex true- deve ser false.
— precisa saber é o seguinte

@PleaseStand Fixed. Por favor, veja minha edição.
— 259 Howard

12

C, 627 caracteres

Este programa trata seu primeiro argumento de linha de comando como entrada e gera código C como saída.

#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]));}");}

Aqui está sua saída para (0|1(0|1)*)(|A(0|1)*1)(com novas linhas adicionadas):

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 você fornecer entrada válida como seu primeiro argumento de linha de comando, ele retornará o status de saída 1. Caso contrário, ele retornará o status de saída 0.

$ ./regexcompiler '(0 | 1 (0 | 1) *) (| A (0 | 1) * 1)'> floatprog.c
$ gcc -o floatprog floatprog.c
floatprog.c: Na função 'main':
floatprog.c: 1: 519: aviso: declaração implícita incompatível da função
interna ' exit' [ativada por padrão] $ ./floatprog '1A00001' && echo inválido || eco válido
válido
$ ./floatprog '100A010' && eco inválido || eco válido
inválido

Qualquer um dos programas, se você não fornecer o argumento da linha de comandos, desreferencia um ponteiro nulo, causando uma falha de segmentação. Uma regex suficientemente longa excederá os buffers desse envio e o tamanho da entrada para um programa gerado é limitado pelo tamanho da pilha. No entanto, todos os casos de teste funcionam.

Observe que e(g=h++,C=h++,0,0);apresenta um comportamento indefinido. Se, por exemplo, os programas gerados não forem compilados, você poderá tentar substituir a instrução por h+=2;e(g=h-1,C=h-2,0,0);cinco caracteres a mais.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.