Formatando uma sintaxe do tipo Lisp

23

fundo

(Baseado em uma história verdadeira e comovente)

No meu tempo, eu brinquei com o Lisp e idiomas semelhantes com frequência. Eu escrevi com eles, os executei, os interpretei, os projetei e fiz máquinas escreverem com eles para mim ... E se há uma coisa que me incomoda, é ver o Lisp que não está de acordo com o meu estilo de formatação específico.

Infelizmente, alguns editores de texto ( tosse XCode tosse ) tendem a tirar minhas lindas abas e espaços sempre que o código é copiado e colado ... Pegue esta sintaxe do Lisp, muito bem espaçada:

(A
    (B
        (C)
        (D))
    (E))

(Onde ABCDEestão as funções arbitrárias)

ALGUNS editores de texto restringem esse código adorável para o seguinte fim:

(A
(B
(C)
(D))
(E))

Que bagunça! Isso não é legível!

Me ajuda aqui?

O desafio

Seu objetivo neste desafio é pegar uma série de funções separadas por novas linhas em um formato descrito abaixo e retornar um arranjo mais bonito que destaque legibilidade e elegância.

A entrada

Definimos uma função Fdos Nargumentos arity como uma construção semelhante à seguinte:

(F (G1 ...) (G2 ...) (G3 ...) ... (GN ...))

onde G1, G2, ..., GNestão todas as funções em si mesmas. Uma 0função de aridade Aé simplesmente (A), enquanto uma 2função de aridade Bé da forma(B (...) (...))

Seu código deve receber entrada como uma série de funções com uma única nova linha antes dos parênteses principais de cada função (exceto a primeira função). O exemplo acima é uma entrada válida.

Você pode assumir:

  • Os parênteses estão equilibrados.
  • Uma função nunca precisará ser recuada mais de 250 vezes.
  • TODAS as funções estão entre parênteses: ()
  • O nome de uma função conterá apenas caracteres ASCII imprimíveis.
  • O nome de uma função nunca conterá parênteses ou espaços.
  • Há uma nova linha à direita opcional na entrada.

A saída

Seu código deve produzir o mesmo conjunto de funções, onde as únicas alterações feitas são as adições de espaços ou tabulações antes dos parênteses principais de funções. A saída deve obedecer às seguintes regras:

  • A primeira função (e as funções posteriores de nível superior) fornecidas não deve ter espaços anteriores
  • Um argumento para a localização horizontal de uma função é exatamente uma guia à direita da localização horizontal dessa função.
  • Uma guia é definida pela implementação, mas deve ter pelo menos três espaços.
  • Opcionalmente, você pode imprimir no máximo dois espaços após cada linha.

Regras

  • Este é o código-golfe: o código mais curto vence!
  • As brechas padrão não são permitidas.

Exemplos

Entrada:

(A
(B
(C)
(D))
(E))

Saída:

(A
    (B
        (C)
        (D))
    (E))

Entrada:

(!@#$%^&*
(asdfghjklm
(this_string_is_particularly_long
(...))
(123456789)))
(THIS_IS_TOP_LEVEL_AGAIN
(HERE'S_AN_ARGUMENT))

Saída:

(!@#$%^&*
    (asdfghjklm
        (this_string_is_particularly_long
            (...))
        (123456789)))
(THIS_IS_TOP_LEVEL_AGAIN
    (HERE'S_AN_ARGUMENT))

Entrada:

(-:0
(*:0
(%:0
(Arg:6)
(Write:0
(Read:0
(Arg:30))
(Write:0
(Const:-6)
(Arg:10))))
(%:0
(Const:9)
(/:0
(Const:-13)
(%:0
(Arg:14)
(Arg:0)))))
(WriteArg:22
(-:0
(Const:45)
(?:0
(Arg:3)
(Arg:22)
(Arg:0)))))

Saída:

(-:0
    (*:0
        (%:0
            (Arg:6)
            (Write:0
                (Read:0
                    (Arg:30))
                (Write:0
                    (Const:-6)
                    (Arg:10))))
        (%:0
            (Const:9)
            (/:0
                (Const:-13)
                (%:0
                    (Arg:14)
                    (Arg:0)))))
    (WriteArg:22
        (-:0
            (Const:45)
            (?:0
                (Arg:3)
                (Arg:22)
                (Arg:0)))))
BrainSteel
fonte
Parabéns por fazer a lista de perguntas da Hot Network! : D
Alex A.
@AlexA. Viva! Meus sonhos foram realizados. : D
BrainSteel
E se não houver um nome de função, como ()?
Coredump 22/05
O recuo deve ter> = 3 espaços ou uma tabulação é aceitável?
Isaacg #
@isaacg Você pode assumir que todas as funções são nomeadas neste caso. E qualquer que seja o seu sistema operacional / idioma definido como uma guia horizontal, tudo bem. Se você usa espaços, deve haver pelo menos 3. Vou esclarecer isso quando puder acessar um computador. Obrigado!
BrainSteel #

Respostas:

9

Pitão, 24 20 19 18 bytes

FN.z+*ZC9N~Z-1/N\)

Incrementa um contador para cada linha, conta o número total de parênteses de fechamento encontrados até o momento e o subtrai do contador. Em seguida, recuamos por countertabulações.

orlp
fonte
@Downvoter Cuidado para explicar?
orlp
Não diminuí o voto, mas a *4preferência é codificada e redundante. FN.z+*ZC9N~Z-1/N\)permite usar a largura do recuo do editor e salvar um byte.
Cees Timmerman
Concordo, uma guia seria um caractere menor. \<tab>ou C9.
Isaacg
9

Lisp comum - 486 414 bytes (versão Rube Goldberg)

(labels((p(x d)(or(when(listp x)(#2=princ #\()(p(car x)d)(incf d)(dolist(a(cdr x))(format t"~%~v{   ~}"d'(t))(p a d))(#2# #\)))(#2# x))))(let((i(make-string-input-stream(with-output-to-string(o)(#1=ignore-errors(do(b c)(())(if(member(setq c(read-char))'(#\( #\) #\  #\tab #\newline):test'char=)(progn(when b(prin1(coerce(reverse b)'string)o))(#2# c o)(setq b()))(push c b))))))))(#1#(do()(())(p(read i)0)(terpri)))))

Aproximação

Em vez de fazer como todos os outros e contar parênteses à mão, vamos chamar o leitor Lisp e fazê-lo da maneira certa :-)

  • Leia do fluxo de entrada e grave em um fluxo de saída temporário .
  • Enquanto isso, personagens agregados diferentes (, )ou espaços em branco como strings.
  • A saída intermediária é usada para criar uma sequência que contém formulários Common-Lisp sintaticamente bem formados: listas de sequências aninhadas.
  • Usando essa sequência como um fluxo de entrada, chame a readfunção padrão para criar listas reais.
  • Chame pcada uma dessas listas, que as grava recursivamente na saída padrão com o formato solicitado. Em particular, as strings são impressas sem aspas.

Como conseqüência dessa abordagem:

  1. Existem menos restrições no formato de entrada: você pode ler entradas formatadas arbitrariamente, não apenas "uma função por linha" (ugh).
  2. Além disso, se a entrada não estiver bem formada, um erro será sinalizado.
  3. Por fim, a função de impressão bonita é bem dissociada da análise: você pode facilmente mudar para outra maneira de imprimir expressões S bonitas (e você deve, se valorizar seu espaço vertical).

Exemplo

Lendo de um arquivo, usando este wrapper:

(with-open-file (*standard-input* #P"path/to/example/file")
    ...)

Aqui está o resultado:

(!@#$%^&*
    (asdfghjklm
        (this_string_is_particularly_long
            (...))
        (123456789)))
(THIS_IS_TOP_LEVEL_AGAIN
    (HERE'S_AN_ARGUMENT))
(-:0
    (*:0
        (%:0
            (Arg:6)
            (Write:0
                (Read:0
                    (Arg:30))
                (Write:0
                    (Const:-6)
                    (Arg:10))))
        (%:0
            (Const:9)
            (/:0
                (Const:-13)
                (%:0
                    (Arg:14)
                    (Arg:0)))))
    (WriteArg:22
        (-:0
            (Const:45)
            (?:0
                (Arg:3)
                (Arg:22)
                (Arg:0)))))

(parece que as guias são convertidas em espaços aqui)

Pretty-impresso (versão golfed)

Ao contrário da versão original mais segura, esperamos que a entrada seja válida.

(labels ((p (x d)
           (or
            (when (listp x)
              (princ #\()
              (p (car x) d)
              (incf d)
              (dolist (a (cdr x)) (format t "~%~v{  ~}" d '(t)) (p a d))
              (princ #\)))
            (princ x))))
  (let ((i
         (make-string-input-stream
          (with-output-to-string (o)
            (ignore-errors
             (do (b
                  c)
                 (nil)
               (if (member (setq c (read-char)) '(#\( #\) #\  #\tab #\newline)
                           :test 'char=)
                   (progn
                    (when b (prin1 (coerce (reverse b) 'string) o))
                    (princ c o)
                    (setq b nil))
                   (push c b))))))))
    (ignore-errors (do () (nil) (p (read i) 0) (terpri)))))
coredump
fonte
7

Retina , 89 83 bytes

s`.+
$0<tab>$0
s`(?<=<tab>.*).
<tab>
+ms`^((\()|(?<-2>\))|[^)])+^(?=\(.*^((?<-2><tab>)+))
$0$3
<tab>+$
<empty>

Onde <tab>representa um caractere de tabulação real (0x09) e <empty>representa uma linha vazia. Depois de fazer essas substituições, você pode executar o código acima com o -ssinalizador No entanto, não estou contando esse sinalizador, porque você também pode colocar cada linha em seu próprio arquivo de origem. Nesse caso, as 7 novas linhas serão substituídas por 7 bytes de penalidade para os arquivos de origem adicionais.

Este é um programa completo, inserindo STDIN e imprimindo o resultado em STDOUT.

Explicação

Cada par de linhas define uma substituição de regex. A idéia básica é usar os grupos de balanceamento do .NET para contar a profundidade atual até um determinado (e, em seguida, inserir várias guias antes disso (.

s`.+
$0<tab>$0

Primeiro, preparamos a entrada. Não podemos realmente escrever de volta um número condicional de guias, se não pudermos encontrá-las em algum lugar da string de entrada para capturá-las. Então começamos duplicando toda a entrada, separada por uma guia. Observe que s`apenas ativa o modificador de linha única (ou "ponto tudo"), o que garante que o mesmo .também corresponda a novas linhas.

s`(?<=<tab>.*).
<tab>

Agora, transformamos todos os personagens depois dessa guia em uma guia também. Isso nos fornece uma quantidade suficiente de guias no final da sequência, sem modificar a sequência original até o momento.

+ms`^((\()|(?<-2>\))|[^)])+^(?=\(.*^((?<-2><tab>)+))
$0$3

Esta é a carne da solução. O me sativa o modo de várias linhas (para ^coincidir com o início das linhas) e o modo de linha única. O +instrui o Retina a continuar repetindo essa substituição até que a saída pare de mudar (nesse caso, isso significa até que o padrão não corresponda mais à sequência).

O padrão em si corresponde a um prefixo da entrada até um não processado ((ou seja, um (que não possui nenhuma guia antes, mas deve). Ao mesmo tempo, determina a profundidade do prefixo com grupos de balanceamento, de modo que a altura da pilha 2corresponda à profundidade atual e, portanto, ao número de guias que precisamos anexar. Essa é esta parte:

((\()|(?<-2>\))|[^)])+

Ele corresponde a (, empurrando-o para a 2pilha ou a ), capturando a última captura da 2pilha ou corresponde a outra coisa e deixa a pilha intocada. Como os parênteses garantem um equilíbrio, não precisamos nos preocupar em tentar sair de uma pilha vazia.

Depois de analisarmos a sequência dessa maneira e encontrarmos um não processado (para parar, a cabeça de impressão então pula para o final da sequência e captura as guias no grupo 3enquanto aparece da 2pilha até ficar vazia:

(?=\(.*^((?<-2><tab>)+))

Ao usar um +in, garantimos que o padrão corresponda apenas a qualquer coisa se pelo menos uma guia for inserida na correspondência - isso evita um loop infinito quando existem várias funções no nível raiz.

<tab>+$
<empty>

Por fim, apenas nos livramos dessas guias auxiliares no final da string para limpar o resultado.

Martin Ender
fonte
Isso é muito legal. Bem feito! É sempre um prazer ver Retina.
BrainSteel
6

C: 95 94 caracteres

Ainda não é muito disputado e, pela pergunta, não tenho certeza se as abas são aceitáveis, e é isso que uso aqui.

i,j;main(c){for(;putchar(c=getchar()),c+1;i+=c==40,i-=c==41)if(c==10)for(j=i;j--;putchar(9));}

Ungolfed:

i,j;
main(c){
  for(
    ;
    putchar(c=getchar()),
    c+1;
    i+=c==40,
    i-=c==41
  )
    if(c==10)
      for(
        j=i;
        j--;
        putchar(9)
      );
}

Edit: Feito para que saia no EOF.

Para s
fonte
Guias são perfeitamente aceitáveis.
BrainSteel
2
Você poderia usar em if(c<11)vez de if(c==10)?
Digital Trauma
5

Julia, 103 99 97 94 88 bytes

p->(i=j=0;for l=split(p,"\n") i+=1;println("\t"^abs(i-j-1)*l);j+=count(i->i=='\)',l)end)

Isso define uma função sem nome que aceita uma sequência e imprime a versão recuada. Para chamá-lo, dê um nome, por exemplof=p->... . Observe que a entrada deve ser uma sequência Julia válida, portanto, os cifrões ( $) devem ser escapados.

Ungolfed + explicação:

function f(p)
    # Set counters for the line number and the number of close parens
    i = j = 0

    # Loop over each line of the program
    for l in split(p, "\n")
        # Increment the line number
        i += 1

        # Print the program line with |i-j-1| tabs
        println("\t"^abs(i-j-1) * l)

        # Count the number of close parens on this line
        j += count(i -> i == '\)', l)
    end
end

Exemplo, fingir cada conjunto de quatro espaços é uma guia:

julia> f("(A
(B
(C)
(D))
(E))")

(A
    (B
        (C)
        (D))
    (E))

Todas as sugestões são bem-vindas!

Alex A.
fonte
4

Haskell, 83 81

unlines.(scanl(\n s->drop(sum[1|')'<-s])$n++['\t'|'('<-s])"">>=zipWith(++)).lines

uma solução sem muitos pontos.

orgulhoso haskeller
fonte
você pode simplesmente largar h=.
Will Ness
3

Perl, 41

$_="\t"x($i-$j).$_;$i+=y/(/(/;$j+=y/)/)/

40caracteres +1para-p .

Correr com:

cat input.txt | perl -pe'$_="\t"x($i-$j).$_;$i+=y/(/(/;$j+=y/)/)/'
hmatt1
fonte
3

Python 2-88 78 bytes

Solução bastante simples (e não muito curta):

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d
Kade
fonte
Algumas dicas: 1) Você pode usar em '\t'vez de ' 'e salvar um byte; 2) não é necessário atribuir input.split()a uma variável, pois ela é usada apenas uma vez (o mesmo para ce também - dbasta mover a printinstrução); 3) a precedência do operador significa que l*cnão são necessários parênteses . Além disso, parece que fnão é usado para nada - isso é uma relíquia de uma versão anterior?
DLosc
Além disso, se esse for o Python 2, você precisará usá-lo em raw_inputvez de input(e não se esqueça dos parênteses depois dele!).
DLosc 23/05
2

CJam, 20 bytes

r{_')e=NU)@-:U9c*r}h

Experimente online no intérprete CJam .

Como funciona

r                    e# Read a whitespace-separated token R from STDIN.
{                 }h e# Do, while R is truthy: 
  _')e=              e#   Push C, the number of right parentheses in R. 
       NU            e#   Push a linefeed and U (initially 0).
         )@-         e#   Compute U + 1 - C.
            :U       e#   Save in U.
              9c*    e#   Push a string of U tabulators.
                 r   e#   Read a whitespace-separated token R from STDIN.
Dennis
fonte