Định dạng một cú pháp giống Lisp


23

Lý lịch

(Dựa trên một câu chuyện đau lòng có thật)

Vào thời của tôi, tôi thường chơi với Lisp và các ngôn ngữ tương tự thường xuyên. Tôi đã viết thư cho họ, điều hành họ, giải thích họ, thiết kế chúng và chế tạo máy viết cho họ ... Và nếu có một điều khiến tôi phiền lòng, thì đó là Lisp không tuân thủ phong cách định dạng cụ thể của tôi.

Thật không may, một số trình soạn thảo văn bản ( ho XCode ho ) có xu hướng loại bỏ các tab và khoảng trắng đẹp của tôi bất cứ khi nào mã được sao chép và dán ... Hãy sử dụng cú pháp giống Lisp đẹp mắt này:

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

(Trường hợp ABCDEcác hàm tùy ý)

MỘT SỐ trình soạn thảo văn bản bán mã đáng yêu này đến cuối:

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

Thật là một mớ hỗn độn! Điều đó không thể đọc được!

Giúp tôi ra, đây?

Các thách thức

Mục tiêu của bạn trong thử thách này là lấy một loạt các chức năng được phân tách bằng các dòng mới trong một định dạng được mô tả dưới đây và trả về một sự sắp xếp đẹp hơn làm nổi bật tính dễ đọc và sang trọng.

Đầu vào

Chúng tôi định nghĩa một hàm Fcủa các Nđối số arity là một cấu trúc tương tự như sau:

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

nơi G1, G2, ..., GNđều là chức năng trong và của chính họ. 0Hàm arity Achỉ đơn giản (A), trong khi 2hàm arity Bcó dạng(B (...) (...))

Mã của bạn nên lấy đầu vào là một chuỗi các hàm với một dòng mới trước mỗi dấu ngoặc đơn hàng đầu của hàm (ngoại trừ hàm đầu tiên). Ví dụ trên là đầu vào hợp lệ.

Bạn có thể giả sử:

  • Các dấu ngoặc đơn được cân bằng.
  • Một chức năng sẽ không bao giờ phải được thụt lề quá 250 lần.
  • MỌI chức năng được bao quanh bởi dấu ngoặc đơn: ()
  • Tên của hàm sẽ chỉ chứa các ký tự ASCII có thể in được.
  • Tên của hàm sẽ không bao giờ chứa dấu ngoặc đơn hoặc dấu cách.
  • Có một dòng mới tùy chọn trên đầu vào.

Đầu ra

Mã của bạn sẽ xuất ra cùng một bộ hàm, trong đó những thay đổi duy nhất được thực hiện là bổ sung khoảng trắng hoặc tab trước dấu ngoặc đơn hàng đầu của hàm. Đầu ra phải tuân thủ các quy tắc sau:

  • Hàm đầu tiên (và các hàm cấp cao nhất sau này) được cung cấp không có khoảng trắng trước
  • Đối số cho vị trí nằm ngang của hàm là chính xác một tab ở bên phải vị trí ngang của hàm đó.
  • Một tab được xác định theo thực hiện, nhưng phải có ít nhất 3 khoảng trắng.
  • Bạn có thể tùy ý in tối đa hai khoảng trắng sau mỗi dòng.

Quy tắc

Ví dụ

Đầu vào:

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

Đầu ra:

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

Đầu vào:

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

Đầu ra:

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

Đầu vào:

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

Đầu ra:

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

Chúc mừng bạn đã tạo được danh sách Câu hỏi về Mạng nóng! : D
Alex A.

@AlexA. Hoan hô! Ước mơ của tôi đã được thực hiện. : D
BrainSteel

Nếu không có tên hàm thì ()sao?
coredump

Có phải thụt lề phải> = 3 dấu cách, hoặc một tab có thể chấp nhận được không?
isaacg

@isaacg Bạn có thể giả sử tất cả các chức năng được đặt tên trong trường hợp này. Và bất cứ điều gì hệ điều hành / ngôn ngữ của bạn xác định là một tab ngang là tốt. Nếu bạn sử dụng khoảng trắng, phải có ít nhất 3. Tôi sẽ làm rõ rằng khi nào tôi có thể đến máy tính. Cảm ơn!
BrainSteel

Câu trả lời:


9

Pyth, 24 20 19 18 byte

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

Tăng một bộ đếm cho mỗi dòng, tính tổng số dấu ngoặc đơn đóng cho đến nay và trừ nó khỏi bộ đếm. Sau đó, chúng tôi thụt lề bằng countercác tab.


@Downvoter Muốn giải thích?
orlp

Tôi đã không downvote, nhưng đó *4là một ưu tiên mã hóa cứng và dư thừa. FN.z+*ZC9N~Z-1/N\)cho phép bạn sử dụng chiều rộng thụt lề của trình soạn thảo và lưu một byte.
Cees Timmerman

Tôi đồng tình, một tab sẽ ngắn hơn một ký tự. \<tab>hoặc C9.
isaacg

9

Lisp thông thường - 486 414 byte (phiên bả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)))))

Tiếp cận

Thay vì làm như mọi người khác và đếm dấu ngoặc đơn bằng tay, hãy gọi trình đọc Lisp và thực hiện đúng cách :-)

  • Đọc từ luồng đầu vào và ghi vào luồng đầu ra tạm thời .
  • Trong khi làm như vậy, tổng hợp các ký tự khác nhau từ (,) hoặc khoảng trắng như dây đàn.
  • Đầu ra trung gian được sử dụng để xây dựng một chuỗi, chứa các dạng Common-Lisp được hình thành tốt về mặt cú pháp: danh sách các chuỗi được lồng vào nhau.
  • Sử dụng chuỗi đó làm luồng đầu vào, gọi readhàm chuẩn để xây dựng danh sách thực tế.
  • Gọi pcho mỗi danh sách đó, viết đệ quy chúng vào đầu ra tiêu chuẩn với định dạng được yêu cầu. Đặc biệt, chuỗi được in không trích dẫn.

Như một hệ quả của phương pháp này:

  1. Có ít hạn chế hơn về định dạng đầu vào: bạn có thể đọc các đầu vào được định dạng tùy ý, không chỉ là "một chức năng trên mỗi dòng" (ugh).
  2. Ngoài ra, nếu đầu vào không được định dạng tốt, một lỗi sẽ được báo hiệu.
  3. Cuối cùng, chức năng in đẹp được tách rời khỏi phân tích cú pháp: bạn có thể dễ dàng chuyển sang một cách khác của biểu thức S in ấn đẹp (và bạn nên, nếu bạn coi trọng không gian dọc của mình).

Thí dụ

Đọc từ một tập tin, sử dụng trình bao bọc này:

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

Đây là kết quả:

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

(có vẻ như các tab được chuyển đổi thành không gian ở đây)

Khá in (phiên bản golf)

Trái với phiên bản gốc an toàn hơn, chúng tôi hy vọng đầu vào là hợp lệ.

(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

Võng mạc , 89 83 byte

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

Trong đó <tab>viết tắt của một ký tự tab thực tế (0x09) và <empty>là viết tắt của một dòng trống. Sau khi thực hiện các thay thế đó, bạn có thể chạy đoạn mã trên với-s cờ. Tuy nhiên, tôi không tính cờ đó, vì bạn cũng có thể chỉ đặt mỗi dòng vào tệp nguồn riêng của mình, trong trường hợp đó, 7 dòng mới sẽ được thay thế bằng 7 byte hình phạt cho các tệp nguồn bổ sung.

Đây là một chương trình đầy đủ, lấy đầu vào trên STDIN và in kết quả sang STDOUT.

Giải trình

Mỗi cặp dòng xác định một sự thay thế regex. Ý tưởng cơ bản là sử dụng các nhóm cân bằng của .NET để đếm độ sâu hiện tại lên đến một mức nhất định (, sau đó chèn nhiều tab đó trước đó (.

s`.+
$0<tab>$0

Đầu tiên, chúng tôi chuẩn bị đầu vào. Chúng tôi thực sự không thể viết lại một số tab có điều kiện, nếu chúng tôi không thể tìm thấy chúng ở đâu đó trong chuỗi đầu vào để nắm bắt chúng. Vì vậy, chúng tôi bắt đầu bằng cách sao chép toàn bộ đầu vào, được phân tách bằng một tab. Lưu ý rằng s`chỉ cần kích hoạt công cụ sửa đổi một dòng (hoặc "dot-all"), đảm bảo rằng .cũng phù hợp với dòng mới.

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

Bây giờ chúng tôi cũng biến mọi ký tự sau tab đó thành một tab. Điều này cung cấp cho chúng tôi đủ số lượng tab ở cuối chuỗi, mà không sửa đổi chuỗi gốc cho đến nay.

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

Đây là thịt của giải pháp. Các mskích hoạt chế độ đa dòng (để ^phù hợp với sự khởi đầu của dòng) và chế độ single-line. Lệnh +Retina tiếp tục lặp lại sự thay thế này cho đến khi đầu ra dừng thay đổi (trong trường hợp này, điều đó có nghĩa là cho đến khi mẫu không còn khớp với chuỗi).

Bản thân mẫu phù hợp với tiền tố của đầu vào cho đến khi chưa xử lý ((nghĩa là mẫu (không có bất kỳ tab nào trước nó, nhưng nên). Đồng thời, nó xác định độ sâu của tiền tố với các nhóm cân bằng, sao cho chiều cao của ngăn xếp 2sẽ tương ứng với độ sâu hiện tại và do đó với số lượng tab chúng ta cần nối thêm. Đó là phần này:

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

Nó khớp với a (, đẩy nó lên 2ngăn xếp hoặc khớp với a ), bật ra lần chụp cuối cùng từ 2ngăn xếp, hoặc nó khớp với thứ khác và khiến cho ngăn xếp không bị ảnh hưởng. Vì các dấu ngoặc đơn được đảm bảo cân bằng, chúng ta không cần phải lo lắng về việc cố gắng bật từ một ngăn xếp trống.

Sau khi chúng tôi đi qua chuỗi như thế này và thấy một điểm chưa được xử lý (dừng lại, giao diện sau đó bỏ qua phía trước đến cuối chuỗi và bắt các tab thành nhóm 3trong khi bật từ 2ngăn xếp cho đến khi trống:

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

Bằng cách sử dụng một +trong đó, chúng tôi đảm bảo rằng mẫu chỉ khớp với bất cứ thứ gì nếu ít nhất một tab phải được chèn vào khớp - điều này tránh được một vòng lặp vô hạn khi có nhiều hàm cấp gốc.

<tab>+$
<empty>

Cuối cùng, chúng ta chỉ cần loại bỏ các tab trợ giúp ở cuối chuỗi để dọn sạch kết quả.


Điều này là rất mát mẻ. Làm tốt! Thật vui khi thấy Retina.
BrainSteel

6

C: 95 94 ký tự

Nó chưa được đánh gôn lắm và từ câu hỏi tôi không chắc liệu các tab có được chấp nhận hay không, đó là những gì tôi sử dụng ở đây.

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

Ung dung:

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

Chỉnh sửa: Làm cho nó thoát khỏi EOF.


Tab là hoàn toàn chấp nhận được.
BrainSteel

2
Bạn có thể sử dụng if(c<11)thay vì if(c==10)?
Chấn thương kỹ thuật số

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)

Điều này xác định một hàm không tên chấp nhận một chuỗi và in phiên bản thụt lề. Để gọi nó, đặt tên cho nó, vdf=p->... . Lưu ý rằng đầu vào phải là một chuỗi Julia hợp lệ, do đó, ký hiệu đô la ( $) phải được thoát.

Ungolfed + giải thích:

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

Ví dụ, giả vờ mỗi bộ bốn khoảng trắng là một tab:

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

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

Bất kỳ đề xuất đều được chào đón nhiều hơn!



3

Perl, 41

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

40nhân vật +1cho -p.

Chạy với:

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

3

Python 2 - 88 78 byte

Giải pháp khá đơn giản (và không quá ngắn):

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

Một vài mẹo nhỏ: 1) Bạn có thể sử dụng '\t'thay vì ' 'và lưu một byte; 2) không cần gán input.split()cho một biến, vì nó chỉ được sử dụng một lần (tương tự c, cũng như d- điều chỉnh di chuyển printcâu lệnh); 3) ưu tiên toán tử có nghĩa là dấu ngoặc đơn xung quanh l*ckhông cần thiết. Ngoài ra, có vẻ như fkhông được sử dụng cho bất cứ điều gì - đó có phải là di tích của phiên bản trước không?
DLosc

Ngoài ra, nếu đây là Python 2, bạn sẽ cần sử dụng raw_inputthay vì input(và đừng quên dấu ngoặc đơn sau nó!).
DLosc

2

CJam, 20 byte

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

Hãy thử trực tuyến trong trình thông dịch CJam .

Làm thế nào nó hoạt động

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.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.