Memformat Sintaks mirip Lisp

23

Latar Belakang

(Berdasarkan kisah nyata yang mengharukan)

Di waktu saya, saya sudah sering bermain-main dengan Lisp dan bahasa serupa. Saya sudah menulis dengan mereka, menjalankannya, menafsirkannya, mendesainnya, dan membuat mesin menulis untuk saya ... Dan jika ada satu hal yang menggangguku, ia melihat Lisp yang tidak mematuhi gaya pemformatan spesifik saya.

Sayangnya, beberapa editor teks ( batuk XCode batuk ) cenderung melucuti tab dan spasi indah saya setiap kali kode disalin dan ditempelkan ... Ambil sintaks mirip-Lisp seperti ini:

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

(Di mana ABCDEfungsi arbitrer)

BEBERAPA editor teks memotong kode indah ini sampai akhir sebagai berikut:

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

Berantakan sekali! Itu tidak bisa dibaca!

Bantu aku, di sini?

Tantangan

Tujuan Anda dalam tantangan ini adalah untuk mengambil serangkaian fungsi yang dipisahkan oleh baris baru dalam format yang dijelaskan di bawah ini dan mengembalikan pengaturan yang lebih indah yang menyoroti keterbacaan dan keanggunan.

Input

Kita mendefinisikan fungsi Fdari arity Nargumen sebagai konstruk mirip dengan berikut ini:

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

di mana G1, G2, ..., GNsemua fungsi dalam dan dari diri mereka sendiri. Sebuah arity 0fungsi Ahanyalah (A), sedangkan arity 2fungsi Badalah dalam bentuk(B (...) (...))

Kode Anda harus mengambil input sebagai serangkaian fungsi dengan satu baris baru sebelum tanda kurung utama setiap fungsi (kecuali untuk fungsi pertama). Contoh di atas adalah input yang valid.

Anda mungkin berasumsi:

  • Tanda kurung seimbang.
  • Sebuah fungsi tidak akan pernah harus menjorok lebih dari 250 kali.
  • SETIAP fungsi dikelilingi oleh kurung: ()
  • Nama fungsi hanya akan berisi karakter ASCII yang dapat dicetak.
  • Nama fungsi tidak akan pernah mengandung tanda kurung atau spasi.
  • Ada baris tambahan trailing opsional pada input.

Hasil

Kode Anda harus output yang sama set fungsi, di mana satu-satunya perubahan yang dibuat adalah penambahan spasi atau tab sebelum kurung terkemuka fungsi. Keluaran harus mematuhi aturan berikut:

  • Fungsi pertama (dan fungsi tingkat atas kemudian) yang diberikan tidak boleh memiliki ruang sebelumnya
  • Argumen ke lokasi horizontal fungsi adalah tepat satu tab di sebelah kanan lokasi horizontal fungsi itu.
  • Tab adalah implementasi yang ditentukan, tetapi harus minimal 3 spasi.
  • Anda dapat mencetak maksimal dua spasi setelah setiap baris.

Aturan

  • Ini adalah kode-golf: kode terpendek menang!
  • Celah Standar tidak diizinkan.

Contohnya

Memasukkan:

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

Keluaran:

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

Memasukkan:

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

Keluaran:

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

Memasukkan:

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

Keluaran:

(-: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
sumber
Selamat membuat daftar Pertanyaan Jaringan Panas! : D
Alex A.
@AlexA. Hore! Mimpi saya telah terwujud. : D
BrainSteel
Bagaimana jika tidak ada nama fungsi, seperti ()?
coredump
Apakah lekukan harus> = 3 spasi, atau apakah tab dapat diterima?
isaacg
@isaacg Anda dapat menganggap semua fungsi diberi nama dalam kasus ini. Dan apa pun OS / bahasa Anda tentukan sebagai tab horizontal baik-baik saja. Jika Anda menggunakan spasi, setidaknya harus ada 3. Saya akan mengklarifikasi ketika saya bisa sampai ke komputer. Terima kasih!
BrainSteel

Jawaban:

9

Pyth, 24 20 19 18 byte

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

Tambahkan penghitung untuk setiap baris, hitung jumlah total tanda kurung yang ditemui sejauh ini, dan kurangi dari penghitung. Lalu kami indentasi oleh countertab.

orlp
sumber
@Downvoter Care menjelaskan?
orlp
Saya tidak downvote, tetapi *4preferensi hardcoded dan berlebihan. FN.z+*ZC9N~Z-1/N\)memungkinkan Anda menggunakan lebar indentasi editor Anda dan menyimpan satu byte.
Cees Timmerman
Saya setuju, tab akan menjadi satu karakter lebih pendek. \<tab>atau C9.
isaacg
9

Common Lisp - 486 414 bytes (versi 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)))))

Pendekatan

Daripada melakukan seperti orang lain dan menghitung tanda kurung dengan tangan, mari kita memanggil pembaca Lisp dan melakukannya dengan Cara yang Benar :-)

  • Baca dari aliran input dan tulis ke aliran output sementara .
  • Saat melakukannya, gabungkan karakter yang berbeda dari (, )atau spasi putih sebagai string.
  • Output antara digunakan untuk membangun string, yang berisi form Common-Lisp yang terbentuk secara sintaksis: daftar string yang bersarang.
  • Menggunakan string itu sebagai aliran input, panggil readfungsi standar untuk membuat daftar aktual.
  • Panggil pmasing-masing daftar itu, yang secara rekursif menuliskannya ke output standar dengan format yang diminta. Secara khusus, string dicetak tanpa tanda kutip.

Sebagai konsekuensi dari pendekatan ini:

  1. Ada sedikit batasan pada format input: Anda dapat membaca input yang diformat secara sewenang-wenang, bukan hanya "satu fungsi per baris" (ugh).
  2. Juga, jika input tidak terbentuk dengan baik, kesalahan akan ditandai.
  3. Akhirnya, fungsi pencetakan yang cantik dipisahkan dengan baik dari penguraian: Anda dapat dengan mudah beralih ke cara lain untuk mencetak ekspresi S yang cantik (dan Anda harus, jika Anda menghargai ruang vertikal Anda).

Contoh

Membaca dari file, menggunakan pembungkus ini:

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

Inilah hasilnya:

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

(tampaknya tab dikonversi menjadi spasi di sini)

Cukup dicetak (versi golf)

Bertentangan dengan versi asli yang lebih aman, kami berharap input valid.

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

Retina , 89 83 byte

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

Di mana <tab>singkatan dari karakter tab aktual (0x09) dan <empty>singkatan dari baris kosong. Setelah melakukan penggantian itu, Anda dapat menjalankan kode di atas dengan -sbendera. Namun, saya tidak menghitung bendera itu, karena Anda juga bisa meletakkan setiap baris di file sumbernya sendiri, dalam hal ini 7 baris baru akan diganti dengan 7 byte penalti untuk file sumber tambahan.

Ini adalah program lengkap, mengambil input pada STDIN dan mencetak hasilnya ke STDOUT.

Penjelasan

Setiap pasangan garis mendefinisikan substitusi regex. Ide dasarnya adalah untuk menggunakan kelompok penyeimbang .NET untuk menghitung kedalaman saat ini hingga yang diberikan (, dan kemudian menyisipkan banyak tab sebelum itu (.

s`.+
$0<tab>$0

Pertama, kami menyiapkan input. Kami tidak dapat benar-benar menulis kembali sejumlah tab bersyarat, jika kami tidak dapat menemukannya di suatu string input untuk menangkapnya. Jadi kita mulai dengan menduplikasi seluruh input, dipisahkan oleh tab. Perhatikan bahwa s`just mengaktifkan pengubah single-line (atau "dot-all"), yang memastikan bahwa .juga cocok dengan baris baru.

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

Sekarang kita mengubah setiap karakter setelah tab menjadi tab juga. Ini memberi kita jumlah tab yang cukup di akhir string, tanpa memodifikasi string asli sejauh ini.

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

Ini adalah daging solusinya. The mdan smengaktifkan mode multi-line (sehingga ^cocok awal dari garis) dan mode single-line. The +memberitahu Retina terus mengulangi substitusi ini sampai output berhenti berubah (dalam hal ini, itu berarti sampai pola tidak lagi cocok string).

Pola itu sendiri cocok dengan awalan input hingga yang tidak diproses ((yaitu, (yang tidak memiliki tab sebelumnya, tetapi harus). Pada saat yang sama menentukan kedalaman awalan dengan kelompok penyeimbang, sehingga ketinggian tumpukan 2akan sesuai dengan kedalaman saat ini, dan oleh karena itu untuk jumlah tab yang perlu kita tambahkan. Itulah bagian ini:

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

Itu cocok dengan (, mendorongnya ke 2tumpukan, atau cocok dengan ), popping menangkap terakhir dari 2tumpukan, atau cocok dengan sesuatu yang lain dan meninggalkan tumpukan tidak tersentuh. Karena tanda kurung dijamin seimbang, kita tidak perlu khawatir untuk mencoba keluar dari tumpukan kosong.

Setelah kami melewati string seperti ini dan menemukan yang belum diproses (untuk dihentikan, lookahead kemudian melompat ke depan hingga akhir string, dan menangkap tab ke dalam grup 3sambil muncul dari 2tumpukan hingga kosong:

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

Dengan menggunakan +di sana, kami memastikan bahwa pola hanya cocok dengan apa pun jika setidaknya satu tab harus dimasukkan ke dalam kecocokan - ini menghindari loop tak terbatas ketika ada beberapa fungsi level root.

<tab>+$
<empty>

Terakhir, kita singkirkan tab pembantu di akhir string untuk membersihkan hasilnya.

Martin Ender
sumber
Ini keren sekali. Sudah selesai dilakukan dengan baik! Selalu menyenangkan melihat Retina.
BrainSteel
6

C: 95 94 karakter

Ini belum bermain golf, dan dari pertanyaan saya tidak yakin apakah tab dapat diterima, yang saya gunakan di sini.

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

Tidak Disatukan:

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

Sunting: Dibuat agar berhenti di EOF.

Untuk S
sumber
Tab bisa diterima.
BrainSteel
2
Bisakah Anda menggunakan if(c<11)bukan 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)

Ini mendefinisikan fungsi tanpa nama yang menerima string dan mencetak versi indentasi. Untuk menyebutnya, berikan nama, mis f=p->.... Perhatikan bahwa input harus berupa string Julia yang valid, sehingga tanda dolar ( $) harus diloloskan.

Penjelasan + tidak dikumpulkan:

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

Contoh, berpura-pura setiap set empat spasi adalah tab:

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

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

Setiap saran lebih dari diterima!

Alex A.
sumber
4

Haskell, 83 81

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

solusi poin yang sangat gratis.

haskeller bangga
sumber
Anda bisa saja drop h=.
Will Ness
3

Perl, 41

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

40karakter +1untuk -p.

Jalankan dengan:

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

Python 2 - 88 78 Bytes

Solusi yang cukup mudah (dan tidak terlalu singkat):

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d
Kade
sumber
Beberapa tips: 1) Anda dapat menggunakan dan '\t'bukannya ' 'menyimpan satu byte; 2) tidak perlu menetapkan input.split()ke variabel, karena hanya digunakan sekali (sama untuk c, dan juga - hanya dmemindahkan printpernyataan); 3) operator diutamakan berarti tanda kurung di sekitar l*ctidak diperlukan. Juga, sepertinya ftidak digunakan untuk apa pun - apakah itu peninggalan versi sebelumnya?
DLosc
Juga, jika ini adalah Python 2, Anda harus menggunakan raw_inputalih-alih input(dan jangan lupa tanda kurung setelah itu!).
DLosc
2

CJam, 20 byte

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

Cobalah online di juru bahasa CJam .

Bagaimana itu bekerja

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
sumber