Formatieren einer Lisp-ähnlichen Syntax

23

Hintergrund

(Basierend auf einer wahren, herzzerreißenden Geschichte)

In meiner Zeit habe ich oft mit Lisp und ähnlichen Sprachen gespielt. Ich habe mit ihnen geschrieben, sie ausgeführt, sie interpretiert, sie entworfen und Maschinen dazu gebracht, für mich mit ihnen zu schreiben.

Leider neigen einige Texteditoren ( Husten XCode Husten ) dazu, meine schönen Tabulatoren und Leerzeichen zu entfernen, wenn Code kopiert und eingefügt wird.

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

(Wo ABCDEsind beliebige Funktionen)

EINIGE Texteditoren metzeln diesen schönen Code zu folgendem Zweck:

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

Was für ein Chaos! Das ist nicht lesbar!

Hilf mir hier raus?

Die Herausforderung

Ihr Ziel bei dieser Herausforderung ist es, eine Reihe von Funktionen, die durch Zeilenumbrüche voneinander getrennt sind, in einem unten beschriebenen Format zu verwenden und eine schönere Anordnung zu erhalten, die Lesbarkeit und Eleganz hervorhebt.

Die Eingabe

Wir definieren eine Funktion Fvon Arity- NArgumenten als ein Konstrukt ähnlich dem Folgenden:

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

Wo G1, G2, ..., GNsind alle Funktionen an und für sich? Eine arity 0Funktion Aist einfach (A), während eine arity 2Funktion Bvon der Form(B (...) (...))

Ihr Code sollte als eine Reihe von Funktionen mit einer einzelnen Zeile vor der führenden Klammer jeder Funktion eingegeben werden (mit Ausnahme der ersten Funktion). Das obige Beispiel ist eine gültige Eingabe.

Sie können annehmen:

  • Die Klammern sind ausgeglichen.
  • Eine Funktion muss niemals mehr als 250 Mal eingerückt werden.
  • JEDE Funktion ist in Klammern gesetzt: ()
  • Der Name einer Funktion enthält nur druckbare ASCII-Zeichen.
  • Der Name einer Funktion enthält niemals Klammern oder Leerzeichen.
  • Bei der Eingabe gibt es optional einen nachgestellten Zeilenumbruch.

Die Ausgabe

Ihr Code sollte den gleichen Satz von Funktionen ausgeben , wobei die einzigen vorgenommenen Änderungen das Hinzufügen von Leerzeichen oder Tabulatoren vor den führenden Klammern von Funktionen sind. Die Ausgabe sollte den folgenden Regeln entsprechen:

  • Die erste angegebene Funktion (und spätere Funktionen der obersten Ebene) sollten keine vorangestellten Leerzeichen enthalten
  • Ein Argument für die horizontale Position einer Funktion ist genau eine Registerkarte rechts von der horizontalen Position dieser Funktion.
  • Ein Tab ist implementierungsdefiniert, muss jedoch mindestens 3 Leerzeichen enthalten.
  • Optional können Sie maximal zwei Leerzeichen nach jeder Zeile drucken.

Regeln

Beispiele

Eingang:

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

Ausgabe:

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

Eingang:

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

Ausgabe:

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

Eingang:

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

Ausgabe:

(-: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
quelle
Herzlichen Glückwunsch zur Erstellung der Hot Network Questions-Liste! : D
Alex A.
@AlexA. Hurra! Meine Träume wurden verwirklicht. : D
BrainSteel
Was ist, wenn es keinen Funktionsnamen gibt ()?
Coredump
Muss der Einzug> = 3 Leerzeichen sein oder ist ein Tabulator akzeptabel?
isaacg
@isaacg In diesem Fall können Sie davon ausgehen, dass alle Funktionen benannt sind. Und was auch immer Ihr Betriebssystem / Ihre Sprache als horizontale Registerkarte definiert, ist in Ordnung. Wenn Sie Leerzeichen verwenden, müssen es mindestens 3 sein. Ich werde das klären, wenn ich an einen Computer gelangen kann. Vielen Dank!
BrainSteel

Antworten:

9

Pyth, 24 20 19 18 Bytes

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

Inkrementiert einen Zähler für jede Zeile, zählt die Gesamtzahl der bisherigen schließenden Klammern und subtrahiert ihn vom Zähler. Dann rücken wir durch counterTabulatoren ein.

orlp
quelle
@ Downvoter Care zu erklären?
Orlp
Ich habe nicht abgelehnt, aber das *4ist eine hartkodierte und überflüssige Präferenz. FN.z+*ZC9N~Z-1/N\)Mit dieser Option können Sie die Einzugsbreite Ihres Editors verwenden und ein Byte speichern.
Cees Timmerman
Ich stimme zu, ein Tabulator wäre ein Zeichen kürzer. \<tab>oder C9.
isaacg
9

Common Lisp - 486 414 Bytes (Rube Goldberg-Version)

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

Ansatz

Anstatt wie alle anderen zu tun und Klammern von Hand zu zählen, rufen wir den Lisp-Leser auf und tun es die richtige Weise :-)

  • Aus dem Eingabestream lesen und in a schreiben temporären Ausgabestream .
  • Aggregieren Sie dabei andere Zeichen als ( , die sich von) oder Leerzeichen als Strings.
  • Die Zwischenausgabe wird verwendet, um eine Zeichenfolge zu erstellen, die syntaktisch wohlgeformte Common-Lisp-Formen enthält: verschachtelte Listen von Zeichenfolgen.
  • Rufen Sie mit dieser Zeichenfolge als Eingabestream den Standard auf read auf, um aktuelle Listen zu erstellen.
  • Rufen Sie pjede dieser Listen auf, die sie mit dem gewünschten Format rekursiv in die Standardausgabe schreiben. Insbesondere werden Zeichenfolgen ohne Anführungszeichen gedruckt.

Infolge dieses Ansatzes:

  1. Das Eingabeformat unterliegt weniger Einschränkungen: Sie können beliebig formatierte Eingaben lesen, nicht nur "eine Funktion pro Zeile" (ugh).
  2. Auch wenn der Eingang nicht richtig geformt ist, wird ein Fehler gemeldet.
  3. Schließlich ist die Funktion für hübsches Drucken von der Syntaxanalyse gut entkoppelt: Sie können problemlos zu einer anderen Methode für hübsches Drucken von S-Ausdrücken wechseln (und Sie sollten dies tun, wenn Sie Wert auf Ihren vertikalen Raum legen).

Beispiel

Lesen aus einer Datei mit diesem Wrapper:

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

Hier ist das Ergebnis:

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

(Tabs werden hier anscheinend in Leerzeichen umgewandelt)

Hübsch gedruckt (Golfversion)

Im Gegensatz zur sichereren Originalversion erwarten wir, dass Eingaben gültig sind.

(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)))))
Core-Dump
quelle
7

Retina , 89 83 Bytes

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

Wobei <tab>für ein tatsächliches Tabulatorzeichen (0x09) und <empty>für eine leere Zeile steht. Nachdem Sie diese Ersetzungen vorgenommen haben, können Sie den obigen Code mit dem Befehl ausführen-s Flag . Ich zähle dieses Flag jedoch nicht, da Sie auch jede Zeile in eine eigene Quelldatei einfügen können. In diesem Fall werden die 7 Zeilen für die zusätzlichen Quelldateien durch 7 Strafbytes ersetzt.

Dies ist ein vollständiges Programm, das Eingaben in STDIN vornimmt und das Ergebnis in STDOUT ausgibt.

Erläuterung

Jedes Zeilenpaar definiert eine reguläre Ersetzung. Die Grundidee besteht darin, die Bilanzgruppen von .NET zu verwenden, um die aktuelle Tiefe bis zu einer bestimmten Zahl zu zählen (, und dann so viele Registerkarten davor einzufügen (.

s`.+
$0<tab>$0

Zuerst bereiten wir die Eingabe vor. Wir können eine bedingte Anzahl von Tabs nicht wirklich zurückschreiben, wenn wir sie nicht irgendwo in der Eingabezeichenfolge finden, um sie zu erfassen. Also beginnen wir mit dem Duplizieren der gesamten Eingabe, getrennt durch einen Tabulator. Beachten Sie, dass mit " s`Just" der Modifikator "Single Line" (oder "Dot-All") aktiviert wird, wodurch sichergestellt wird, dass .auch Zeilenumbrüche berücksichtigt werden.

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

Jetzt verwandeln wir jedes Zeichen nach diesem Tab auch in einen Tab. Dadurch erhalten wir eine ausreichende Anzahl von Tabulatoren am Ende der Zeichenfolge, ohne die ursprüngliche Zeichenfolge bisher zu ändern.

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

Das ist das Fleisch der Lösung. Die mund saktivieren den Mehrzeilenmodus (damit ^der Zeilenanfang übereinstimmt) und den Einzeilenmodus. Das+ weist Retina an, diese Ersetzung so lange zu wiederholen, bis sich die Ausgabe nicht mehr ändert (in diesem Fall bedeutet dies, dass das Muster nicht mehr mit der Zeichenfolge übereinstimmt).

Das Muster selbst stimmt mit einem Präfix der Eingabe bis zu einem nicht verarbeiteten überein ((d. H. Einem (, das keine Registerkarten vor sich hat, aber sollte). Gleichzeitig wird die Tiefe des Präfixes mit Ausgleichsgruppen so festgelegt, dass die Höhe des Stapels 2der aktuellen Tiefe und damit der Anzahl der anzuhängenden Registerkarten entspricht. Das ist dieser Teil:

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

Es entspricht entweder a (, indem es auf den 2Stapel geschoben wird, oder es entspricht a ), indem das letzte Capturing aus dem entfernt wird2 Stapel, oder es passt zu etwas anderem und lässt den Stapel unberührt. Da die Klammern garantiert ausgeglichen sind, brauchen wir uns keine Gedanken darüber zu machen, ob wir versuchen, von einem leeren Stapel zu springen.

Nachdem wir die Zeichenfolge wie (folgt durchgearbeitet haben und eine unverarbeitete gefunden haben , bei der wir anhalten können, springt der Lookahead zum Ende der Zeichenfolge und fängt die Tabs in Gruppen ein, 3während wir vom 2Stapel springen, bis sie leer sind:

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

Durch die Verwendung von a +in there stellen wir sicher, dass das Muster nur dann mit etwas übereinstimmt, wenn mindestens ein Tabulator in die Übereinstimmung eingefügt werden soll. Dadurch wird eine Endlosschleife vermieden, wenn mehrere Funktionen auf Stammebene vorhanden sind.

<tab>+$
<empty>

Zuletzt werden die Hilfslaschen am Ende der Zeichenfolge entfernt, um das Ergebnis zu bereinigen.

Martin Ender
quelle
Das ist sehr cool. Gut gemacht! Es ist immer eine Freude, Retina zu sehen.
BrainSteel
6

C: 95 bis 94 Zeichen

Es ist noch nicht sehr gut gespielt, und ich bin mir nicht sicher, ob Tabs akzeptabel sind, was ich hier benutze.

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

Bearbeiten: Es wurde so eingestellt, dass es bei EOF beendet wird.

Fors
quelle
Tabs sind vollkommen akzeptabel.
BrainSteel
2
Könnten Sie if(c<11)anstelle von verwenden if(c==10)?
Digitales 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)

Dies definiert eine unbenannte Funktion, die eine Zeichenfolge akzeptiert und die eingerückte Version ausgibt. Um es zu nennen, geben Sie ihm einen Namen, zf=p->... . Beachten Sie, dass die Eingabe eine gültige Julia-Zeichenfolge sein muss, sodass Dollarzeichen ( $) maskiert werden müssen.

Ungolfed + Erklärung:

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

Wenn Sie beispielsweise vorgeben, dass jeder Satz von vier Leerzeichen vorhanden ist, handelt es sich um eine Registerkarte:

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

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

Anregungen sind mehr als willkommen!

Alex A.
quelle
4

Haskell, 83 81

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

Eine sehr punktefreie Lösung.

stolzer haskeller
quelle
Sie können einfach fallen h=.
Will Ness
3

Perl, 41

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

40Zeichen +1für -p.

Laufen mit:

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

Python 2 - 88 78 Bytes

Ziemlich einfache (und nicht sehr kurze) Lösung:

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d
Kade
quelle
Ein paar Tipps: 1) Sie können '\t'anstelle von ' 'ein Byte verwenden und speichern; 2) keine Notwendigkeit zuweisen input.split()zu einer Variablen, da sie nur einmal (gleich für verwendet wird c, sowie d--just bewegen , um die printAussage); 3) Operator-Rangfolge bedeutet, dass l*ckeine runden Klammern erforderlich sind. Es sieht auch so aus, als würde es ffür nichts gebraucht - ist das ein Relikt einer früheren Version?
DLosc
Wenn es sich um Python 2 handelt, müssen Sie raw_inputstattdessen verwenden input(und die Klammern danach nicht vergessen!).
DLosc
2

CJam, 20 Bytes

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

Versuchen Sie es online in dem CJam Dolmetscher .

Wie es funktioniert

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
quelle