Formattazione di una sintassi simile a Lisp


23

sfondo

(Basato su una storia vera e straziante)

Ai miei tempi, ho giocato spesso con Lisp e lingue simili. Ho scritto con loro, li ho eseguiti, li ho interpretati, li ho progettati e ho fatto scrivere macchine con loro per me ... E se c'è una cosa che mi disturba, è vedere Lisp che non è conforme al mio stile di formattazione specifico.

Sfortunatamente, alcuni editor di testo ( tosse XCode tosse ) tendono a rimuovere le mie belle schede e spazi ogni volta che il codice viene copiato e incollato ... Prendi questa sintassi splendidamente spaziata simile a Lisp:

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

(Dove ABCDEsono le funzioni arbitrarie)

ALCUNI editor di testo macellano questo adorabile codice fino alla fine seguente:

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

Che casino! Non è leggibile!

Aiutami, qui?

La sfida

Il tuo obiettivo in questa sfida è quello di prendere una serie di funzioni separate da nuove righe in un formato descritto di seguito e restituire una disposizione più bella che evidenzi la leggibilità e l'eleganza.

L'ingresso

Definiamo una funzione Fdi Nargomenti arity come un costrutto simile al seguente:

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

dove G1, G2, ..., GNsono tutte le funzioni in sé e per sé. Una 0funzione arity Aè semplicemente (A), mentre una 2funzione arity Bè della forma(B (...) (...))

Il codice deve essere inserito come una serie di funzioni con una nuova riga prima della parentesi iniziale di ogni funzione (ad eccezione della prima funzione). L'esempio sopra è un input valido.

Puoi presumere:

  • Le parentesi sono bilanciate.
  • Una funzione non dovrà mai essere rientrata più di 250 volte.
  • OGNI funzione è racchiusa tra parentesi: ()
  • Il nome di una funzione conterrà solo caratteri ASCII stampabili.
  • Il nome di una funzione non conterrà mai parentesi o spazi.
  • C'è una nuova riga finale opzionale sull'input.

L'output

Il codice dovrebbe generare lo stesso insieme di funzioni, in cui le uniche modifiche apportate sono le aggiunte di spazi o schede prima delle parentesi iniziali delle funzioni. L'output deve essere conforme alle seguenti regole:

  • La prima funzione (e successivamente le funzioni di livello superiore) fornite non dovrebbero avere spazi precedenti
  • Un argomento per la posizione orizzontale di una funzione è esattamente una scheda a destra della posizione orizzontale di quella funzione.
  • Una scheda è definita dall'implementazione, ma deve essere di almeno 3 spazi.
  • È possibile stampare facoltativamente un massimo di due spazi dopo ogni riga.

Regole

Esempi

Ingresso:

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

Produzione:

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

Ingresso:

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

Produzione:

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

Ingresso:

(-: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)))))

Produzione:

(-: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)))))

Congratulazioni per aver creato la lista delle domande sulla rete calda! : D
Alex A.

@AlexA. Evviva! I miei sogni sono stati realizzati. : D
BrainSteel,

Cosa succede se non esiste un nome di funzione, come ()?
coredump,

Il rientro deve essere> = 3 spazi oppure è accettabile una scheda?
isaacg,

@isaacg Puoi supporre che tutte le funzioni siano nominate in questo caso. E qualunque cosa il tuo sistema operativo / lingua definisca una scheda orizzontale va bene. Se usi gli spazi, devono essercene almeno 3. Chiarirò quando potrò accedere a un computer. Grazie!
BrainSteel,

Risposte:


9

Pyth, 24 20 19 18 byte

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

Incrementa un contatore per ogni riga, conta il numero totale di parentesi chiuse incontrate finora e lo sottrae dal contatore. Quindi indentiamo con le counterschede.


@Downvoter Care per spiegare?
orlp

Non ho votato in negativo, ma *4è una preferenza hardcoded e ridondante. FN.z+*ZC9N~Z-1/N\)ti consente di utilizzare la larghezza del rientro dell'editor e di salvare un byte.
Cees Timmerman,

Concordo, una scheda sarebbe più corta di un personaggio. \<tab>o C9.
isaacg,

9

Common Lisp - 486 414 byte (versione 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)))))

Approccio

Invece di fare come tutti gli altri e contare le parentesi a mano, invociamo il lettore Lisp e facciamolo nel modo giusto :-)

  • Leggere dal flusso di input e scrivere su un flusso di output temporaneo .
  • Mentre lo fai, aggrega caratteri diversi da (, )o spazi bianchi come stringhe.
  • L'output intermedio viene utilizzato per creare una stringa, che contiene forme Common-Lisp sintatticamente ben formate: elenchi nidificati di stringhe.
  • Usando quella stringa come flusso di input, chiama la readfunzione standard per creare elenchi effettivi.
  • Chiama psu ciascuno di quegli elenchi, che li scrivono in modo ricorsivo sullo standard output con il formato richiesto. In particolare, le stringhe vengono stampate senza virgolette.

Come conseguenza di questo approccio:

  1. Ci sono meno restrizioni sul formato di input: puoi leggere input formattati in modo arbitrario, non solo "una funzione per riga" (ugh).
  2. Inoltre, se l'ingresso non è ben formato, verrà segnalato un errore.
  3. Infine, la funzione di stampa carina è ben disaccoppiata dall'analisi: puoi facilmente passare a un altro modo di stampare S-espressioni (e dovresti, se apprezzi il tuo spazio verticale).

Esempio

Leggendo da un file, usando questo wrapper:

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

Ecco il risultato:

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

(sembra che le schede vengano convertite in spazi qui)

Abbastanza stampato (versione golfizzata)

Contrariamente alla versione originale più sicura, prevediamo che l'input sia valido.

(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)))))

7

Retina , 89 83 byte

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

Dove <tab>sta per un carattere di tabulazione effettivo (0x09) e <empty>indica una riga vuota. Dopo aver effettuato tali sostituzioni, è possibile eseguire il codice sopra con il -sflag. Tuttavia, non sto contando quel flag, perché potresti anche solo mettere ogni riga nel suo file di origine, nel qual caso le 7 nuove righe verrebbero sostituite da 7 byte di penalità per i file di origine aggiuntivi.

Questo è un programma completo, prendendo input su STDIN e stampando il risultato su STDOUT.

Spiegazione

Ogni coppia di righe definisce una sostituzione regex. L'idea di base è quella di utilizzare i gruppi di bilanciamento di .NET per contare la profondità corrente fino a un dato dato (, quindi inserire prima tante schede (.

s`.+
$0<tab>$0

Innanzitutto, prepariamo l'input. Non possiamo davvero riscrivere un numero condizionale di schede, se non riusciamo a trovarle da qualche parte nella stringa di input per catturarle. Quindi iniziamo duplicando l'intero input, separato da una scheda. Nota che il s`giusto attiva il modificatore a linea singola (o "punto-tutto"), che assicura che .corrisponda anche alle nuove righe.

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

Ora trasformiamo anche tutti i personaggi dopo quella scheda in una scheda. Questo ci dà una quantità sufficiente di schede alla fine della stringa, senza modificare finora la stringa originale.

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

Questa è la carne della soluzione. La me sattivare la modalità multi-linea (in modo che ^corrisponda l'inizio di righe) e la modalità a linea singola. Questo +dice a Retina di continuare a ripetere questa sostituzione finché l'output non smette di cambiare (in questo caso, ciò significa che il pattern non corrisponde più alla stringa).

Il modello stesso corrisponde a un prefisso dell'input fino a un non elaborato ((ovvero a (che non ha alcuna scheda prima di esso, ma dovrebbe). Allo stesso tempo determina la profondità del prefisso con gruppi di bilanciamento, in modo tale che l'altezza dello stack 2corrisponda alla profondità corrente e quindi al numero di schede che è necessario aggiungere. Questa è questa parte:

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

O abbina a (, spingendolo sulla 2pila, oppure abbina a ), facendo scattare l'ultima acquisizione dalla 2pila, oppure abbina qualcos'altro e lascia la pila intatta. Dal momento che le parentesi sono garantite per essere bilanciate, non dobbiamo preoccuparci di provare a saltar fuori da uno stack vuoto.

Dopo aver attraversato la stringa in questo modo e trovato un non elaborato (su cui fermarsi, il lookahead salta avanti fino alla fine della stringa e cattura le schede in gruppo 3mentre saltano dallo 2stack fino a quando non è vuoto:

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

Usando a +in, ci assicuriamo che il modello corrisponda a qualsiasi cosa se almeno una scheda deve essere inserita nella corrispondenza - questo evita un ciclo infinito quando ci sono più funzioni a livello di root.

<tab>+$
<empty>

Infine, ci sbarazziamo di quelle schede di supporto alla fine della stringa per ripulire il risultato.


Questo è molto bello. Molto bene! È sempre un piacere vedere Retina.
BrainSteel,

6

C: 95 94 caratteri

Non è ancora molto golfato, e dalla domanda non sono sicuro che le schede siano accettabili, che è quello che uso qui.

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

Modifica: creato in modo che si chiuda su EOF.


Le schede sono perfettamente accettabili.
BrainSteel,

2
Potresti usare al if(c<11)posto di if(c==10)?
Digital Trauma,

5

Julia, 103 99 97 94 88 byte

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

Ciò definisce una funzione senza nome che accetta una stringa e stampa la versione rientrata. Per chiamarlo, dagli un nome, ad es f=p->.... Si noti che l'input deve essere una stringa Julia valida, quindi i simboli di dollaro ( $) devono essere esclusi.

Ungolfed + spiegazione:

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

Esempio, fingendo che ogni set di quattro spazi sia una scheda:

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

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

Tutti i suggerimenti sono più che benvenuti!


4

Haskell, 83 81

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

una soluzione senza punti.


puoi semplicemente lasciar perdere h=.
Will Ness,

3

Perl, 41

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

40personaggi +1per -p.

Corri con:

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

3

Python 2 - 88 78 byte

Soluzione abbastanza semplice (e non molto breve):

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d

Un paio di suggerimenti: 1) È possibile utilizzare '\t'anziché ' 'e salvare un byte; 2) non è necessario assegnare input.split()a una variabile, poiché viene usata una sola volta (lo stesso per c, oltre a - dbasta spostare l' printistruzione); 3) la precedenza dell'operatore significa che le parentesi l*cnon sono necessarie. Inoltre, sembra che fnon sia usato per niente - è una reliquia di una versione precedente?
DLosc,

Inoltre, se questo è Python 2, dovrai usare raw_inputinvece di input(e non dimenticare le parentesi dopo!).
DLosc,

2

CJam, 20 byte

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

Provalo online nell'interprete CJam .

Come funziona

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.
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.