Formatear una sintaxis similar a Lisp

23

Fondo

(Basado en una historia verdadera y desgarradora)

En mi tiempo, he jugado con Lisp y lenguajes similares a menudo. He escrito con ellos, los ejecuté, los interpreté, los diseñé e hice que las máquinas escribieran con ellos por mí ... Y si hay algo que me molesta, es ver a Lisp que no cumple con mi estilo de formato específico.

Desafortunadamente, algunos editores de texto ( tos XCode tos ) tienden a eliminar mis hermosas pestañas y espacios cada vez que se copia y pega el código ... Tome esta sintaxis tipo Lisp bellamente espaciada:

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

(¿Dónde ABCDEestán las funciones arbitrarias)

ALGUNOS editores de texto descifran este hermoso código con el siguiente fin:

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

¡Que desastre! ¡Eso no es legible!

¿Ayudame aqui?

El reto

Su objetivo en este desafío es tomar una serie de funciones separadas por líneas nuevas en un formato que se describe a continuación y devolver un arreglo más hermoso que resalte la legibilidad y la elegancia.

La entrada

Definimos una función Fde Nargumentos de aridad como una construcción similar a la siguiente:

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

donde G1, G2, ..., GNestán todas las funciones en sí mismas. Una 0función arity Aes simplemente (A), mientras que una 2función arity Bes de la forma(B (...) (...))

Su código debe tomar la entrada como una serie de funciones con una sola línea nueva antes del paréntesis principal de cada función (excepto la primera función). El ejemplo anterior es una entrada válida.

Puedes asumir:

  • Los paréntesis son equilibrados.
  • Una función nunca tendrá que sangrarse más de 250 veces.
  • CADA función está rodeada de paréntesis: ()
  • El nombre de una función solo contendrá caracteres ASCII imprimibles.
  • El nombre de una función nunca contendrá paréntesis o espacios.
  • Hay una nueva línea final opcional en la entrada.

La salida

Su código debe generar el mismo conjunto de funciones, donde los únicos cambios realizados son las adiciones de espacios o pestañas antes de los paréntesis principales de las funciones. La salida debe cumplir con las siguientes reglas:

  • La primera función (y las funciones de nivel superior posteriores) proporcionadas no deben tener espacios anteriores
  • Un argumento sobre la ubicación horizontal de una función es exactamente una pestaña a la derecha de la ubicación horizontal de esa función.
  • Una pestaña está definida por la implementación, pero debe tener al menos 3 espacios.
  • Opcionalmente, puede imprimir un máximo de dos espacios después de cada línea.

Reglas

Ejemplos

Entrada:

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

Salida:

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

Entrada:

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

Salida:

(!@#$%^&*
    (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)))))

Salida:

(-: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
fuente
¡Felicidades por hacer la lista de preguntas de Hot Network! : D
Alex A.
@AlexA. ¡Hurra! Mis sueños se han hecho realidad. : D
BrainSteel
¿Qué pasa si no hay un nombre de función, como ()?
coredump
¿La sangría tiene que ser> = 3 espacios, o es aceptable una pestaña?
isaacg
@isaacg Puede asumir que todas las funciones se nombran en este caso. Y lo que sea que su sistema operativo / idioma defina como una pestaña horizontal está bien. Si usa espacios, debe haber al menos 3. Lo aclararé cuando pueda acceder a una computadora. ¡Gracias!
BrainSteel

Respuestas:

9

Pyth, 24 20 19 18 bytes

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

Incrementa un contador para cada línea, cuenta el número total de paréntesis de cierre encontrados hasta el momento y lo resta del contador. Luego sangramos por counterpestañas.

orlp
fuente
@Downvoter Care para explicar?
orlp
No voté en contra, pero *4es una preferencia codificada y redundante. FN.z+*ZC9N~Z-1/N\)le permite usar el ancho de sangría de su editor y guarda un byte.
Cees Timmerman
Estoy de acuerdo, una pestaña sería un carácter más corto. \<tab>o C9.
isaacg
9

Common Lisp - 486414 bytes (versión 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)))))

Enfoque

En lugar de hacer como todos los demás y contar los paréntesis a mano, invoquemos al lector Lisp y hagámoslo de la manera correcta :-)

  • Lea desde la secuencia de entrada y escriba en una secuencia de salida temporal .
  • Al hacerlo, los caracteres diferentes de agregados (, )o espacios en blanco como cadenas.
  • La salida intermedia se usa para construir una cadena, que contiene formas Common-Lisp sintácticamente bien formadas: listas anidadas de cadenas.
  • Usando esa cadena como una secuencia de entrada, llame a la readfunción estándar para construir listas reales.
  • Llama pa cada una de esas listas, que las escriben recursivamente en la salida estándar con el formato solicitado. En particular, las cadenas se imprimen sin comillas.

Como consecuencia de este enfoque:

  1. Hay menos restricciones en el formato de entrada: puede leer entradas con formato arbitrario, no solo "una función por línea" (ugh).
  2. Además, si la entrada no está bien formada, se señalará un error.
  3. Finalmente, la función de impresión bonita está bien desconectada del análisis: puede cambiar fácilmente a otra forma de imprimir expresiones S bonitas (y debería, si valora su espacio vertical).

Ejemplo

Lectura de un archivo, usando este contenedor:

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

Aquí está el 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 las pestañas se convierten en espacios aquí)

Bonito estampado (versión golfizada)

Contrariamente a la versión original más segura, esperamos que la entrada sea 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)))))
volcado de memoria
fuente
7

Retina , 89 83 bytes

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

Donde <tab>representa un carácter de tabulación real (0x09) y <empty>representa una línea vacía. Después de hacer esos reemplazos, puede ejecutar el código anterior con la -sbandera. Sin embargo, no estoy contando ese indicador, porque también podría poner cada línea en su propio archivo fuente, en cuyo caso las 7 nuevas líneas serían reemplazadas por 7 bytes de penalización para los archivos fuente adicionales.

Este es un programa completo, que toma información sobre STDIN e imprime el resultado en STDOUT.

Explicación

Cada par de líneas define una sustitución de expresiones regulares. La idea básica es hacer uso de los grupos de equilibrio de .NET para contar la profundidad actual hasta un determinado (, y luego insertar tantas pestañas antes de eso (.

s`.+
$0<tab>$0

Primero, preparamos la entrada. Realmente no podemos volver a escribir un número condicional de pestañas, si no podemos encontrarlas en algún lugar de la cadena de entrada para capturarlas. Entonces comenzamos duplicando toda la entrada, separada por una pestaña. Tenga en cuenta que el s`solo activa el modificador de una sola línea (o "punto-todo"), que garantiza que .también coincida con las nuevas líneas.

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

Ahora convertimos cada personaje después de esa pestaña en una pestaña también. Esto nos da una cantidad suficiente de pestañas al final de la cadena, sin modificar la cadena original hasta el momento.

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

Esta es la carne de la solución. El my sactiva el modo multilínea (para que ^coincida con el comienzo de las líneas) y el modo de una sola línea. El +le dice a Retina que siga repitiendo esta sustitución hasta que la salida deje de cambiar (en este caso, eso significa hasta que el patrón ya no coincida con la cadena).

El patrón en sí coincide con un prefijo de la entrada hasta un no procesado ((es decir, un (que no tiene pestañas antes, pero debería). Al mismo tiempo, determina la profundidad del prefijo con grupos de equilibrio, de modo que la altura de la pila 2corresponderá a la profundidad actual y, por lo tanto, al número de pestañas que necesitamos agregar. Esa es esta parte:

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

O coincide con a (, empujándolo hacia la 2pila, o coincide con a ), sacando la última captura de la 2pila, o coincide con otra cosa y deja la pila intacta. Dado que se garantiza que los paréntesis estén equilibrados, no debemos preocuparnos por tratar de saltar de una pila vacía.

Después de haber pasado por la cadena de esta manera y encontrar un no procesado (para detenerse, la búsqueda anticipada salta hacia adelante hasta el final de la cadena y captura pestañas en el grupo 3mientras emerge de la 2pila hasta que está vacía:

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

Al usar un +allí, nos aseguramos de que el patrón solo coincida con cualquier cosa si se inserta al menos una pestaña en la coincidencia; esto evita un bucle infinito cuando hay múltiples funciones de nivel raíz.

<tab>+$
<empty>

Por último, nos deshacemos de esas pestañas de ayuda al final de la cadena para limpiar el resultado.

Martin Ender
fuente
Esto es muy genial. ¡Bien hecho! Siempre es un placer ver Retina.
BrainSteel
6

C: 95 94 caracteres

Todavía no está muy golfizado, y de la pregunta no estoy seguro de si las pestañas son aceptables, que es lo que uso aquí.

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));}

Sin golf:

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)
      );
}

Editar: Hecho para que se cierre en EOF.

Fors
fuente
Las pestañas son perfectamente aceptables.
BrainSteel
2
¿Podrías usar en if(c<11)lugar de if(c==10)?
Trauma digital
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)

Esto define una función sin nombre que acepta una cadena e imprime la versión con sangría. Para llamarlo, dale un nombre, por ejemplo f=p->.... Tenga en cuenta que la entrada debe ser una cadena de Julia válida, por lo $que se deben escapar los signos de dólar ( ).

Ungolfed + explicación:

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

Ejemplo, pretender que cada conjunto de cuatro espacios es una pestaña:

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

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

Cualquier sugerencia es más que bienvenida!

Alex A.
fuente
4

Haskell, 83 81

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

Una solución muy libre de puntos.

orgulloso Haskeller
fuente
puedes simplemente caer h=.
Will Ness
3

Perl, 41

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

40 caracteres +1 para -p.

Corre con:

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

Python 2 - 88 78 Bytes

Solución bastante sencilla (y no muy corta):

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d
Kade
fuente
Un par de consejos: 1) Puede usar en '\t'lugar de ' 'y guardar un byte; 2) no es necesario asignar input.split()a una variable, ya que solo se usa una vez (lo mismo para c, así como - dsolo mueve la printinstrucción); 3) la precedencia del operador significa que l*cno se necesitan paréntesis . Además, parece que fno se usa para nada, ¿es una reliquia de una versión anterior?
DLosc
Además, si se trata de Python 2, deberá usarlo en raw_inputlugar de input(¡y no olvide los paréntesis después!).
DLosc
2

CJam, 20 bytes

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

Pruébelo en línea en el intérprete de CJam .

Cómo 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
fuente