Formatowanie składni podobnej do Lisp

23

tło

(Na podstawie prawdziwej, budzącej grozę historii)

W swoim czasie często bawiłem się Lispem i podobnymi językami. Pisałem z nimi, prowadziłem je, interpretowałem, projektowałem i zmuszałem maszyny, aby pisały z nimi dla mnie ... A jeśli coś mnie niepokoi, to widzi Lisp, który nie jest zgodny z moim specyficznym stylem formatowania.

Niestety, niektóre edytory tekstowe ( kaszel XCode kaszel ) wydają się rozebrać moje piękne karty i spacji, gdy kod jest skopiowany i wklejony ... Weź to pięknie rozmieszczone Lisp podobną składnię:

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

(Gdzie ABCDEsą arbitralne funkcje)

NIEKTÓRE edytory tekstu rzeźą ten piękny kod w następujący sposób:

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

Co za bałagan! To nieczytelne!

Pomóż mi tutaj?

Wyzwanie

Twoim celem w tym wyzwaniu jest przejęcie szeregu funkcji oddzielonych znakami nowej linii w formacie opisanym poniżej i zwrócenie piękniejszej aranżacji podkreślającej czytelność i elegancję.

Wejście

Definiujemy funkcję argumentów Farity Njako konstrukcję podobną do następującej:

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

gdzie G1, G2, ..., GNwszystkie funkcje są same w sobie. Arity 0funkcja Ajest po prostu (A), natomiast arity 2funkcja Bjest postaci(B (...) (...))

Twój kod powinien przyjmować dane wejściowe jako serię funkcji z pojedynczym znakiem nowej linii przed nawiasiem wiodącym każdej funkcji (z wyjątkiem pierwszej funkcji). Powyższy przykład jest prawidłowym wejściem.

Możesz założyć:

  • Nawiasy są zrównoważone.
  • Funkcja nigdy nie będzie musiała być wcięta więcej niż 250 razy.
  • KAŻDA funkcja jest otoczona nawiasami: ()
  • Nazwa funkcji będzie zawierać tylko drukowalne znaki ASCII.
  • Nazwa funkcji nigdy nie będzie zawierać nawiasów ani spacji.
  • Istnieje opcjonalny końcowy znak nowej linii na wejściu.

Wyjście

Twój kod powinien wypisywać ten sam zestaw funkcji, a jedynymi wprowadzonymi zmianami są dodawanie spacji lub tabulatorów przed nawiasami wiodącymi funkcji. Dane wyjściowe powinny być zgodne z następującymi zasadami:

  • Pierwsza podana funkcja (a później funkcje najwyższego poziomu) nie powinna zawierać poprzedzających spacji
  • Argumentem do poziomego położenia funkcji jest dokładnie jedna zakładka po prawej stronie poziomego położenia tej funkcji.
  • Zakładka jest zdefiniowana w implementacji, ale musi mieć co najmniej 3 spacje.
  • Opcjonalnie możesz wydrukować maksymalnie dwie spacje po każdej linii.

Zasady

Przykłady

Wkład:

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

Wydajność:

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

Wkład:

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

Wydajność:

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

Wkład:

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

Wydajność:

(-: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
źródło
Gratulujemy utworzenia listy gorących pytań w sieci! : D
Alex A.
@AlexA. Brawo! Moje marzenia się spełniły. : D
BrainSteel
Co jeśli nie ma nazwy funkcji, jak ()?
Coredump
Czy wcięcie musi wynosić> = 3 spacje, czy akceptowalna jest tabulacja?
isaacg
@isaacg Możesz założyć, że wszystkie funkcje są nazwane w tym przypadku. I cokolwiek twój system operacyjny / język definiuje jako kartę poziomą, jest w porządku. Jeśli używasz spacji, muszą być co najmniej 3. Wyjaśnię to, kiedy mogę dostać się do komputera. Dzięki!
BrainSteel

Odpowiedzi:

9

Pyth, 24 20 19 18 bajtów

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

Zwiększa licznik dla każdej linii, zlicza całkowitą liczbę napotkanych do tej pory nawiasów zamykających i odejmuje ją od licznika. Następnie wciskamy countertabulatory.

orlp
źródło
@Downvoter Chcesz wyjaśnić?
lub
Nie głosowałem negatywnie, ale *4jest to sztywno określona i zbędna preferencja. FN.z+*ZC9N~Z-1/N\)pozwala użyć szerokości wcięcia edytora i oszczędza jeden bajt.
Cees Timmerman
Zgadzam się, że zakładka byłaby o jedną postać krótsza. \<tab>lub C9.
isaacg
9

Common Lisp - 486 414 bajtów (wersja 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)))))

Podejście

Zamiast robić jak wszyscy inni i liczyć nawiasy ręcznie, przywołajmy czytnik Lisp i zróbmy to we właściwy sposób :-)

  • Odczyt ze strumienia wejściowego i zapis do tymczasowego strumienia wyjściowego.
  • Czyniąc tak, kruszywa znaki różne od (, )lub odstępu jako ciągi.
  • Wyjście pośrednie służy do budowania łańcucha, który zawiera dobrze sformatowane formy Common-Lisp: zagnieżdżone listy ciągów.
  • Używając tego ciągu jako strumienia wejściowego, wywołaj standard read aby zbudować rzeczywiste listy.
  • Wywołaj pkażdą z tych list, które rekurencyjnie zapisują je na standardowe wyjście w żądanym formacie. W szczególności ciągi są drukowane bez cudzysłowu.

W wyniku tego podejścia:

  1. Istnieje mniej ograniczeń dotyczących formatu wejściowego: można odczytać dowolnie sformatowane dane wejściowe, a nie tylko „jedną funkcję na linię” (ugh).
  2. Ponadto, jeśli wejście nie jest dobrze uformowane, zostanie zasygnalizowany błąd.
  3. Wreszcie, funkcja ładnego drukowania jest dobrze oddzielona od parsowania: możesz łatwo przełączyć się na inny sposób ładnego drukowania wyrażeń S (i powinieneś, jeśli cenisz swoją przestrzeń pionową).

Przykład

Odczytywanie z pliku przy użyciu tego opakowania:

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

Oto wynik:

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

(wygląda na to, że tabulatory są tutaj zamieniane na spacje)

Całkiem drukowane (wersja golfowa)

W przeciwieństwie do bezpieczniejszej wersji oryginalnej oczekujemy, że dane wejściowe będą prawidłowe.

(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)))))
rdzeń rdzeniowy
źródło
7

Retina , 89 83 bajtów

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

Gdzie <tab>oznacza rzeczywisty znak tabulacji (0x09) i <empty>oznacza pusty wiersz. Po dokonaniu tych zamian możesz uruchomić powyższy kod za pomocą-s flagą. Jednak nie liczę tej flagi, ponieważ można również po prostu umieścić każdą linię we własnym pliku źródłowym, w którym to przypadku 7 nowych linii zostanie zastąpionych 7 bajtami kary za dodatkowe pliki źródłowe.

Jest to pełny program, który pobiera dane wejściowe do STDIN i wypisuje wynik do STDOUT.

Wyjaśnienie

Każda para linii definiuje podstawienie wyrażenia regularnego. Podstawową ideą jest wykorzystanie grup równoważących .NET do policzenia bieżącej głębokości do określonej (, a następnie wstawienie tylu zakładek (.

s`.+
$0<tab>$0

Najpierw przygotowujemy dane wejściowe. Naprawdę nie możemy zapisać warunkowej liczby zakładek, jeśli nie możemy ich znaleźć gdzieś w ciągu wejściowym, aby je przechwycić. Zaczniemy więc od skopiowania całego wejścia, oddzielonego tabulatorem. Zauważ, że s`just aktywuje modyfikator jednowierszowy (lub „kropka-wszystko”), który zapewnia, że .również pasuje do nowych linii.

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

Teraz zamieniamy każdą postać po tej zakładce w zakładkę. To daje nam wystarczającą liczbę zakładek na końcu łańcucha, bez modyfikacji dotychczasowego łańcucha.

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

To jest mięso rozwiązania. mI swłączyć tryb multi-wiersza (tak, że ^pasuje początki wierszy) i tryb pojedynczej linii. The+Mówi Retina aby powtarzać to podstawienie aż wyjście przestaje zmianę (w tym przypadku, to znaczy dopóki wzorzec nie pasuje ciąg).

Sam wzorzec dopasowuje prefiks wejścia do nieprzetworzonego ((to znaczy, (że nie ma przed nim żadnych kart, ale powinien). Jednocześnie określa głębokość prefiksu z grupami równoważącymi, tak że wysokość stosu 2będzie odpowiadać bieżącej głębokości, a zatem liczbie zakładek, które musimy dołączyć. To jest ta część:

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

Albo dopasowuje a (, pchając go na 2stos, albo dopasowuje a ), wyskakując z ostatniego przechwytywania z2 stosu, lub dopasowuje coś innego i pozostawia stos nietknięty. Ponieważ nawiasy mają gwarancję zrównoważenia, nie musimy się martwić próbą wyskoczenia z pustego stosu.

Po przejściu przez taki ciąg znaków i znalezieniu nieprzetworzonego (do zatrzymania się, lookahead przeskakuje do przodu do końca ciągu i przechwytuje tabulatory w grupie 3, wyskakując ze 2stosu, aż będzie pusty:

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

Używając +tam, zapewniamy, że wzorzec pasuje tylko do czegokolwiek, jeśli do dopasowania należy wstawić co najmniej jedną zakładkę - pozwala to uniknąć nieskończonej pętli, gdy istnieje wiele funkcji na poziomie administratora.

<tab>+$
<empty>

Wreszcie, po prostu pozbywamy się tych zakładek pomocniczych na końcu łańcucha, aby oczyścić wynik.

Martin Ender
źródło
To jest bardzo fajne. Dobra robota! Retina zawsze cieszy się przyjemnością.
BrainSteel
6

C: 95 94 znaków

Nie jest jeszcze bardzo golfowy, a z pytania nie jestem pewien, czy zakładki są dopuszczalne, i to tutaj używam.

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

Nie golfowany:

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

Edycja: Sprawiło, że wychodzi z EOF.

Fors
źródło
Zakładki są całkowicie akceptowalne.
BrainSteel
2
Czy możesz użyć if(c<11)zamiast if(c==10)?
Cyfrowa trauma
5

Julia, 103 99 97 94 88 bajtów

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

Definiuje nienazwaną funkcję, która akceptuje ciąg i drukuje wciętą wersję. Aby to nazwać, nadaj mu nazwę, np f=p->.... Zauważ, że wejście musi być poprawnym ciągiem Julii, więc znaki dolara ( $) muszą być poprzedzone znakiem ucieczki.

Niegolfowane + wyjaśnienie:

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

Przykładem udawania każdego zestawu czterech spacji jest tabulator:

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

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

Wszelkie sugestie są mile widziane!

Alex A.
źródło
4

Haskell, 83 81

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

bardzo darmowe rozwiązanie.

dumny haskeller
źródło
możesz po prostu upuścić h=.
Czy Ness
3

Perl, 41

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

40znaki +1dla -p.

Biegnij z:

cat input.txt | perl -pe'$_="\t"x($i-$j).$_;$i+=y/(/(/;$j+=y/)/)/'
hmatt1
źródło
3

Python 2 - 88 78 bajtów

Dość proste (i niezbyt krótkie) rozwiązanie:

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d
Kade
źródło
Kilka wskazówek: 1) Możesz użyć '\t'zamiast ' 'i zapisać jeden bajt; 2) nie trzeba przypisywać input.split()zmiennej, ponieważ jest ona używana tylko raz (to samo dla c, jak i - dwystarczy przenieść printinstrukcję); 3) pierwszeństwo operatora oznacza, że ​​nawiasy wokół l*cnie są potrzebne. Wygląda też na to, że fdo niczego nie służy - czy to relikt poprzedniej wersji?
DLosc
Ponadto, jeśli jest to Python 2, musisz użyć raw_inputzamiast niego input(i nie zapomnij po nim nawiasów!).
DLosc
2

CJam, 20 bajtów

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

Wypróbuj online w interpretatorze CJam .

Jak to działa

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
źródło