fundo
Para meus envios de código-golfe em C, preciso de uma ferramenta de processamento. Como em muitas outras línguas, o espaço em branco é principalmente irrelevante na fonte C (mas nem sempre!) - ainda torna o código muito mais compreensível para os seres humanos. Um programa C totalmente desenvolvido que não contém um único espaço em branco redundante geralmente é pouco legível.
Portanto, eu gosto de escrever meu código em C para um envio de código-golfe, incluindo espaço em branco e, às vezes, comentários, para que o programa mantenha uma estrutura compreensível enquanto escreve. O último passo é remover todos os comentários e espaços em branco redundantes. Esta é uma tarefa tediosa e irracional que realmente deve ser realizada por um estagiário de um programa de computador.
Tarefa
Escreva um programa ou função que elimine comentários e espaços em branco redundantes de alguma fonte C "pré-golfada" de acordo com as seguintes regras:
- A
\
(barra invertida) como o último caractere de uma linha é uma continuação de linha . Se você encontrar isso, deverá tratar a seguinte linha como parte da mesma linha lógica (você pode, por exemplo, remover completamente a (nova linha)\
e a seguinte\n
(nova linha) antes de fazer qualquer outra coisa) - Os comentários usarão apenas o formato de uma linha, começando com
//
. Portanto, para removê-los, você ignora o restante da linha lógica sempre que encontrar//
fora de uma literal de cadeia de caracteres (veja abaixo). - Os caracteres de
espaço em branco são (espaço),
\t
(tabulação) e\n
(nova linha, então aqui o final de uma linha lógica). Quando você encontrar uma sequência de espaço em branco, examine os caracteres que não são de espaço em branco ao seu redor. E se
- ambos são alfanuméricos ou sublinhados (intervalo
[a-zA-Z0-9_]
) ou - ambos são
+
ou - ambos são
-
ou - o anterior é
/
e o seguinte é*
substitua a sequência por um único
caractere espaço ( ).
Caso contrário, elimine a sequência completamente.
Esta regra tem algumas exceções :
- As diretivas de pré-processador devem aparecer em suas próprias linhas na saída. Uma diretiva de pré-processador é uma linha que começa com
#
. - Dentro de uma string literal ou literal de caracteres , você não deve remover nenhum espaço em branco. Qualquer
"
(aspas duplas) /'
(aspas simples) que não seja precedido diretamente por um número ímpar de barras invertidas (\
) inicia ou termina uma string literal / literal de caracteres . Você tem certeza de que os literais de sequência e caractere terminam na mesma linha em que foram iniciados. strings literais e literais de caracteres não podem ser aninhados, por isso, um'
dentro de um literal de cadeia , bem como"
dentro de um caractere literal não tem nenhum significado especial.
- ambos são alfanuméricos ou sublinhados (intervalo
Especificação de E / S
A entrada e a saída devem ser sequências de caracteres (cadeias), incluindo caracteres de nova linha ou matrizes / listas de cadeias que não contêm caracteres de nova linha. Se você optar por usar matrizes / listas, cada elemento representará uma linha; portanto, as novas linhas estarão implícitas após cada elemento.
Você pode assumir que a entrada é um código fonte válido do programa C. Isso também significa que ele contém apenas caracteres ASCII, guias e novas linhas imprimíveis. Comportamento indefinido na entrada malformada é permitido.
Linhas em branco à esquerda e à esquerda / linhas vazias não são permitidas .
Casos de teste
entrada
main() { printf("Hello, World!"); // hi }
resultado
main(){printf("Hello, World!");}
entrada
#define max(x, y) \ x > y ? x : y #define I(x) scanf("%d", &x) a; b; // just a needless comment, \ because we can! main() { I(a); I(b); printf("\" max \": %d\n", max(a, b)); }
resultado
#define max(x,y)x>y?x:y #define I(x)scanf("%d",&x) a;b;main(){I(a);I(b);printf("\" max \": %d\n",max(a,b));}
entrada
x[10];*c;i; main() { int _e; for(; scanf("%d", &x) > 0 && ++_e;); for(c = x + _e; c --> x; i = 100 / *x, printf("%d ", i - --_e)); }
resultado
x[10];*c;i;main(){int _e;for(;scanf("%d",&x)>0&&++_e;);for(c=x+_e;c-->x;i=100/ *x,printf("%d ",i- --_e));}
entrada
x; #include <stdio.h> int main() { puts("hello // there"); }
resultado
x; #include<stdio.h> int main(){puts("hello // there");}
input (um exemplo do mundo real)
// often used functions/keywords: #define P printf( #define A case #define B break // loops for copying rows upwards/downwards are similar -> macro #define L(i, e, t, f, s) \ for (o=i; o e;){ strcpy(l[o t], l[o f]); c[o t]=c[s o]; } // range check for rows/columns is similar -> macro #define R(m,o) { return b<1|b>m ? m o : b; } // checking for numerical input is needed twice (move and print command): #define N(f) sscanf(f, "%d,%d", &i, &j) || sscanf(f, ",%d", &j) // room for 999 rows with each 999 cols (not specified, should be enough) // also declare "current line pointers" (*L for data, *C for line length), // an input buffer (a) and scratch variables r, i, j, o, z, c[999], *C, x=1, y=1; char a[999], l[999][999], (*L)[999]; // move rows down from current cursor position D() { L(r, >y, , -1, --) r++ ? strcpy(l[o], l[o-1]+--x), c[o-1]=x, l[o-1][x]=0 : 0; c[y++] = strlen(l[o]); x=1; } // move rows up, appending uppermost to current line U() { strcat(*L, l[y]); *C = strlen(*L); L(y+1, <r, -1, , ++) --r; *l[r] = c[r] = 0; } // normalize positions, treat 0 as max X(b) R(c[y-1], +1) Y(b) R(r, ) main() { for(;;) // forever { // initialize z as current line index, the current line pointers, // i and j for default values of positioning z = i = y; L = l + --z; C = c + z; j = x; // prompt: !r || y/r && x > *C ? P "end> ") : P "%d,%d> ", y, x); // read a line of input (using scanf so we don't need an include) scanf("%[^\n]%*c", a) // no command arguments -> make check easier: ? a[2] *= !!a[1], // numerical input -> have move command: // calculate new coordinates, checking for "relative" N(a) ? y = Y(i + (i<0 | *a=='+') * y) , x = X(j + (j<0 || strchr(a+1, '+')) * x) :0 // check for empty input, read single newline // and perform <return> command: : ( *a = D(), scanf("%*c") ); switch(*a) { A 'e': y = r; x = c[r-1] + 1; B; A 'b': y = 1; x = 1; B; A 'L': for(o = y-4; ++o < y+2;) o<0 ^ o<r && P "%c%s\n", o^z ? ' ' : '>', l[o]); for(o = x+1; --o;) P " "); P "^\n"); B; A 'l': puts(*L); B; A 'p': i = 1; j = 0; N(a+2); for(o = Y(i)-1; o<Y(j); ++o) puts(l[o]); B; A 'A': y = r++; strcpy(l[y], a+2); x = c[y] = strlen(a+2); ++x; ++y; B; A 'i': D(); --y; x=X(0); // Commands i and r are very similar -> fall through // from i to r after moving rows down and setting // position at end of line: A 'r': strcpy(*L+x-1, a+2); *C = strlen(*L); x = 1; ++y > r && ++r; B; A 'I': o = strlen(a+2); memmove(*L+x+o-1, *L+x-1, *C-x+1); *C += o; memcpy(*L+x-1, a+2, o); x += o; B; A 'd': **L ? **L = *C = 0, x = 1 : U(); y = y>r ? r : y; B; A 'j': y<r && U(); } } }
resultado
#define P printf( #define A case #define B break #define L(i,e,t,f,s)for(o=i;o e;){strcpy(l[o t],l[o f]);c[o t]=c[s o];} #define R(m,o){return b<1|b>m?m o:b;} #define N(f)sscanf(f,"%d,%d",&i,&j)||sscanf(f,",%d",&j) r,i,j,o,z,c[999],*C,x=1,y=1;char a[999],l[999][999],(*L)[999];D(){L(r,>y,,-1,--)r++?strcpy(l[o],l[o-1]+--x),c[o-1]=x,l[o-1][x]=0:0;c[y++]=strlen(l[o]);x=1;}U(){strcat(*L,l[y]);*C=strlen(*L);L(y+1,<r,-1,,++)--r;*l[r]=c[r]=0;}X(b)R(c[y-1],+1)Y(b)R(r,)main(){for(;;){z=i=y;L=l+--z;C=c+z;j=x;!r||y/r&&x>*C?P"end> "):P"%d,%d> ",y,x);scanf("%[^\n]%*c",a)?a[2]*=!!a[1],N(a)?y=Y(i+(i<0|*a=='+')*y),x=X(j+(j<0||strchr(a+1,'+'))*x):0:(*a=D(),scanf("%*c"));switch(*a){A'e':y=r;x=c[r-1]+1;B;A'b':y=1;x=1;B;A'L':for(o=y-4;++o<y+2;)o<0^o<r&&P"%c%s\n",o^z?' ':'>',l[o]);for(o=x+1;--o;)P" ");P"^\n");B;A'l':puts(*L);B;A'p':i=1;j=0;N(a+2);for(o=Y(i)-1;o<Y(j);++o)puts(l[o]);B;A'A':y=r++;strcpy(l[y],a+2);x=c[y]=strlen(a+2);++x;++y;B;A'i':D();--y;x=X(0);A'r':strcpy(*L+x-1,a+2);*C=strlen(*L);x=1;++y>r&&++r;B;A'I':o=strlen(a+2);memmove(*L+x+o-1,*L+x-1,*C-x+1);*C+=o;memcpy(*L+x-1,a+2,o);x+=o;B;A'd':**L?**L=*C=0,x=1:U();y=y>r?r:y;B;A'j':y<r&&U();}}}
Isso é código-golfe , então a resposta mais curta (em bytes) válida vence.
Respostas:
Pip ,
148135133138 bytesOs bytes são contados no CP-1252 , portanto,
¶
e·
são um byte cada. Observe que isso espera o código C como um único argumento de linha de comando, que (em uma linha de comando real) exigiria o uso de sequências de escape abundantes. É muito mais fácil em Experimente online!Explicação da versão ligeiramente não destruída
O código realiza várias operações de substituição, com alguns truques.
Continuações de barra invertida
Nós
RM
todas as ocorrências da string literalisto é, barra invertida seguida por nova linha.
Literais de cadeia e caracteres
Usamos uma substituição de regex com uma função de retorno de chamada:
A regex corresponde a uma citação simples ou dupla, seguida por uma não gananciosa
.*?
que corresponde a 0 ou mais caracteres, o mínimo possível. Temos uma visão negativa para garantir que o personagem anterior não seja uma barra invertida; em seguida, correspondemos a um número par de barras invertidas seguidas pelo delimitador de abertura novamente.A função de retorno de chamada pega o literal de string / caractere e o envia para o final da lista
l
. Em seguida, ele retorna um caractere começando com o código de caractere 192 (À
) e aumentando com cada literal substituído. Assim, o código é transformado da seguinte maneira:É garantido que esses caracteres de substituição não ocorram no código fonte, o que significa que podemos substituí-los sem ambiguidades posteriormente.
Comentários
O regex corresponde
//
mais tudo até a nova linha e substitui porx
(predefinido para a sequência vazia).Diretivas de pré-processador
Agrupa séries de caracteres que não são de nova linha, começando com um sinal de libra
¶
.Espaços que não devem ser eliminados
Há muita coisa acontecendo aqui. A primeira parte gera esta lista de regexes para substituir:
Observe o uso de lookaheads para corresponder, por exemplo, apenas ao
e
indefine P printf
. Dessa forma, essa partida não consome oP
, o que significa que a próxima partida pode usá-lo.Geramos essa lista de expressões regulares mapeando uma função para uma lista, onde a lista contém
e a função faz isso para cada elemento:
Depois que tivermos nossas expressões regulares, substituímos suas ocorrências por esta função de retorno de chamada:
que substitui a execução do espaço em branco em cada correspondência por
·
.Eliminação e limpeza de espaço em branco
Três substituições sucessivas substituem as execuções restantes do espaço em branco (
w
) pela sequência vazia (x
), as execuções¶
pela nova linha e·
pelo espaço.Substituição traseira de literais de cadeia e caracteres
Construímos uma lista de todos os caracteres que usamos como substitutos de literais, obtendo
192 + range(len(l))
e convertendo em caracteres. Em seguida, podemos substituir cada um deles pelo literal associadol
.E é isso! A sequência resultante é impressa automaticamente.
fonte
//
literal interno de uma string é definitivamente uma boa ideia para um caso de teste. Acrescentarei um amanhã.Haskell ,
327360418394 bytesExperimente online!
Foi muito divertido escrever! Primeiro, a
f
função aparece e remove todas as barras invertidas no final das linhas e depois alines
divide em uma lista de strings nas novas linhas. Em seguida, mapeamos várias funções nas linhas e concatenamos todas juntas novamente. Essas funções: tira o espaço em branco da esquerda (t
) e da direita (r.t.r
onder
estáreverse
); remova os espaços em branco do meio, ignorando os literais de string e de caracteres, além de remover comments (w
); e, finalmente, adiciona um caractere de nova linha ao final se a linha começar com um #. Depois que todas as linhas são concatenadas novamente,g
procura por # caracteres e garante que eles sejam precedidos por uma nova linha.w
é um pouco complexo, então vou explicar melhor. Primeiro, verifico "//", poisw
sei que não estou em uma string literal. Sei que isso é um comentário, então deixo o resto da linha. Em seguida, verifico se o cabeçalho é um delimitador para uma string ou caractere literal. Se for, eu o prefixo e passo o bastão para ol
qual os personagens passam, rastreando o estado de "escape" com on
qual será verdadeiro se houver um número par de barras consecutivas. Quandol
detecta um delimitador e não está no estado de escape, ele passa o bastão de volta paraw
, aparando para eliminar o espaço em branco após o literal, porquew
espera que o primeiro caractere não seja em branco. Quandow
não encontra um delimitador, usa span para procurar espaços em branco na cauda. Se houver, verifica se os caracteres ao seu redor não podem ser colocados em contato e, se houver, insere um espaço. Em seguida, ele ocorre novamente após o término do espaço em branco. Se não houvesse espaço em branco, nenhum espaço é inserido e ele continua assim mesmo.EDIT: Muito obrigado ao @DLosc por apontar um erro no meu programa que realmente levou a uma maneira de reduzi-lo também! Viva a correspondência de padrões!
EDIT2: Eu sou um idiota que não terminou de ler as especificações! Mais uma vez obrigado DLosc por apontar isso!
EDIT3: Acabei de notar uma coisa chata de redução de tipo que virou
e=elem
transformouChar->[Char]->Bool
por algum motivo, interrompendo assime[a,q]
. Eu tive que adicionar uma assinatura de tipo para forçá-la a estar correta. Alguém sabe como eu poderia consertar isso? Eu nunca tive esse problema em Haskell antes. TIOEDIT4: correção rápida para bug @FelixPalmen me mostrou. Eu posso tentar jogar no golfe mais tarde, quando tiver algum tempo.
EDIT5: -24 bytes graças a @Lynn! Obrigado! Eu não sabia que você poderia atribuir coisas no escopo global usando a correspondência de padrões, como
n:c:z=...
isso é muito legal! Também é uma boa ideia fazer um operador porelem
desejo, pensei nisso.fonte
e x y=elem x y
(ou atée x=elem x
) resolve seu problema. (I renomeadoe
para um operador,(!)
.)C,
497494490489 bytesComo estamos processando C, vamos fazê-lo usando C! A função
f()
recebe a entrada do ponteiro de charp
e as saídas para o ponteiroq
e assume que a entrada está em ASCII:Assumimos que o arquivo esteja bem formado - os literais de string e de caracteres estão fechados e, se houver um comentário na linha final, deve haver uma nova linha para fechá-lo.
Explicação
A versão pré-golfe é apenas um pouco mais legível, receio:
Ele implementa uma máquina de estado por recursão da cauda. As macros e variáveis auxiliares são
O
para o utputR
a r de entrada em EADr
V
para determinar v caracteres identificador alid (uma vez!isalnum('_')
)p
eq
- ponteiros de E / S, conforme descritor
- último caractere a ser r eads
- s caractere recente não-espaço em branco recentet
- t ag quando se trabalha em um pré-processador diretivaNossos estados são
a()
- código C normalb()
- literal da stringc()
- Comented()
- código C normal, depois de lerr
e()
- sequência de fugaf()
- estado inicial (função principal)g()
- em brancoh()
- em branco - expedição parag()
oui()
i()
- imediatamente após o espaço em branco - precisamos inserir um caractere de espaço?j()
- espaço em branco inicial - nunca insira um caractere de espaçoPrograma de teste
Isso produz
Limitação
Isso quebra definições como
removendo o espaço que separa o nome da expansão, fornecendo
com um significado completamente diferente. Este caso está ausente nos conjuntos de teste, por isso não vou abordá-lo.
Eu suspeito que talvez eu consiga produzir uma versão mais curta com uma conversão no local com várias passagens - talvez eu tente na próxima semana.
fonte
=
no final da definiçãoO
e alterando o espaço que segue cada chamada paraO
para=
.O'\\'
eO' '
ambos adquiriram um espaço.C,
705663640 bytesObrigado a @ Zacharý por jogar 40 bytes e a @Nahuel Fouilleul por jogar 23 bytes!
Experimente online!
fonte
for(;W;C++){}
se tornarfor(;W;C++);
?Perl 5,
250 + 3 (-00n), 167 + 1 (-p) bytesExperimente online
fonte
Python 2 ,
479456445434502497 bytesExperimente online!
Edit: Corrigido para incluir
- -
,+ +
e/ *
fonte