Brainfuck Hello World thực sự hoạt động như thế nào?


118

Ai đó đã gửi cái này cho tôi và tuyên bố rằng đó là một thế giới xin chào ở Brainfuck (và tôi hy vọng như vậy ...)

++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.

Tôi biết những điều cơ bản rằng nó hoạt động bằng cách di chuyển một con trỏ và những thứ tăng và giảm ...

Tuy nhiên, tôi vẫn muốn biết, nó thực sự hoạt động như thế nào? Làm thế nào để nó in bất cứ thứ gì trên màn hình ngay từ đầu? Nó mã hóa văn bản như thế nào? Tôi không hiểu gì cả...


31
Phải khá khó khăn để duy trì một ứng dụng được viết bằng ngôn ngữ này ..
e2-e4

17
@ ring0: nah, đó là ngôn ngữ chỉ viết.
LetMeSOThat4U

nó là gì sử dụng thực tế?
Yash Kumar Verma

10
@YashVerma nó không cần một ..
Insane

49
@YashVerma Nó được chỉ định rõ ràng trong tên của ngôn ngữ.
Mateen Ulhaq,

Câu trả lời:


255

1. Khái niệm cơ bản

Để hiểu Brainfuck, bạn phải tưởng tượng một mảng vô hạn các ô được khởi tạo bởi 0mỗi ô.

...[0][0][0][0][0]...

Khi chương trình khốn khổ bắt đầu, nó sẽ trỏ đến bất kỳ ô nào.

...[0][0][*0*][0][0]...

Nếu bạn di chuyển con trỏ sang phải, >bạn đang di chuyển con trỏ từ ô X sang ô X + 1

...[0][0][0][*0*][0]...

Nếu bạn tăng giá trị ô, +bạn nhận được:

...[0][0][0][*1*][0]...

Nếu bạn tăng giá trị ô một lần nữa, +bạn nhận được:

...[0][0][0][*2*][0]...

Nếu bạn giảm giá trị ô, -bạn nhận được:

...[0][0][0][*1*][0]...

Nếu bạn di chuyển con trỏ sang trái, <bạn đang di chuyển con trỏ từ ô X sang ô X-1

...[0][0][*0*][1][0]...

2. Đầu vào

Để đọc ký tự, bạn sử dụng dấu phẩy ,. Những gì nó làm là: Đọc ký tự từ đầu vào chuẩn và ghi mã ASCII thập phân của nó vào ô thực tế.

Hãy xem bảng ASCII . Ví dụ, mã thập phân của !33, trong khi a97.

Vâng, hãy tưởng tượng bộ nhớ chương trình BF của bạn trông như thế nào:

...[0][0][*0*][0][0]...

Giả sử đầu vào tiêu chuẩn là viết tắt của a, nếu bạn sử dụng ,toán tử dấu phẩy , những gì BF làm là đọc amã ASCII thập phân 97vào bộ nhớ:

...[0][0][*97*][0][0]...

Bạn thường muốn nghĩ theo cách đó, tuy nhiên sự thật thì phức tạp hơn một chút. Sự thật là BF không đọc một ký tự mà là một byte (bất kể byte đó là gì). Để tôi cho bạn xem ví dụ:

Trong linux

$ printf ł

bản in:

ł

đó là nhân vật đánh bóng cụ thể. Ký tự này không được mã hóa bằng bảng mã ASCII. Trong trường hợp này, đó là mã hóa UTF-8, vì vậy nó thường chiếm nhiều hơn một byte trong bộ nhớ máy tính. Chúng tôi có thể chứng minh điều đó bằng cách tạo một kết xuất thập lục phân:

$ printf ł | hd

cho thấy:

00000000  c5 82                                             |..|

Các số 0 được bù đắp. 82c5byte đầu tiên và là byte thứ hai đại diện ł(theo thứ tự chúng ta sẽ đọc chúng). |..|là biểu diễn đồ họa không thể thực hiện được trong trường hợp này.

Chà, nếu bạn chuyển łlàm đầu vào cho chương trình BF của mình đọc byte đơn, bộ nhớ chương trình sẽ giống như sau:

...[0][0][*197*][0][0]...

Tại sao 197? Số 197thập phân tốt là c5hệ thập lục phân. Có vẻ quen thuộc ? Tất nhiên. Đó là byte đầu tiên của ł!

3. Đầu ra

Để in ký tự, bạn sử dụng dấu chấm .Điều đó có nghĩa là: Giả sử chúng ta xử lý giá trị ô thực tế như mã ASCII thập phân, hãy in ký tự tương ứng với đầu ra chuẩn.

Vâng, hãy tưởng tượng bộ nhớ chương trình BF của bạn trông như thế nào:

...[0][0][*97*][0][0]...

Nếu bạn sử dụng toán tử dot (.) Bây giờ, những gì BF làm là in:

a

amã thập phân trong ASCII là 97.

Vì vậy, ví dụ chương trình BF như thế này (97 điểm cộng 2 dấu chấm):

+++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++ ..

Sẽ tăng giá trị của ô mà nó trỏ đến lên đến 97 và in nó ra 2 lần.

aa

4. Vòng lặp

Trong vòng lặp BF bao gồm vòng lặp bắt đầu [và kết thúc vòng lặp ]. Bạn có thể nghĩ nó giống như khi ở trong C / C ++, nơi điều kiện là giá trị ô thực tế.

Hãy xem chương trình BF dưới đây:

++[]

++ tăng giá trị ô thực tế lên hai lần:

...[0][0][*2*][0][0]...

[]giống như while(2) {}, vì vậy nó là vòng lặp vô hạn.

Giả sử chúng ta không muốn vòng lặp này là vô hạn. Chúng tôi có thể làm ví dụ:

++[-]

Vì vậy, mỗi khi vòng lặp lặp lại, nó sẽ giảm giá trị ô thực tế. Khi giá trị ô thực tế là 0vòng lặp kết thúc:

...[0][0][*2*][0][0]...        loop starts
...[0][0][*1*][0][0]...        after first iteration
...[0][0][*0*][0][0]...        after second iteration (loop ends)

Hãy xem xét một ví dụ khác về vòng lặp hữu hạn:

++[>]

Ví dụ này cho thấy, chúng tôi chưa kết thúc vòng lặp tại ô mà vòng lặp đã bắt đầu:

...[0][0][*2*][0][0]...        loop starts
...[0][0][2][*0*][0]...        after first iteration (loop ends)

Tuy nhiên, đó là cách tốt để kết thúc nơi chúng ta bắt đầu. Tại sao ? Bởi vì nếu vòng lặp kết thúc một ô khác thì nó đã bắt đầu, chúng ta không thể giả định con trỏ ô sẽ ở đâu. Thành thật mà nói, cách làm này giúp giảm thiểu tình trạng thiểu não.


4
Tuyệt vời, bây giờ tôi đã hiểu nó :)
speeder

25
Đó là một giải pháp hoàn hảo cho những người mới học đang cố gắng hiểu ý thức hệ ngôn ngữ này. Xin chúc mừng và bài viết tuyệt vời.
Casey

4
Phần giới thiệu Brainfuck hay nhất mà tôi đã xem. Thành thực mà nói bạn lùi lại một chút BF qua đường bưu điện của bạn
Boyang

3
Tôi đoán rằng nếu bạn cần một dự án cho thời gian rảnh, bạn luôn có thể thêm hỗ trợ Unicode cho Brainfuck.
Álvaro González,

3
Sau bài đăng của bạn, BF là! BF nữa!
thanos.a

52

Wikipedia có một phiên bản bình luận của mã.

+++++ +++++             initialize counter (cell #0) to 10
[                       use loop to set the next four cells to 70/100/30/10
    > +++++ ++              add  7 to cell #1
    > +++++ +++++           add 10 to cell #2 
    > +++                   add  3 to cell #3
    > +                     add  1 to cell #4
    <<<< -                  decrement counter (cell #0)
]                   
> ++ .                  print 'H'
> + .                   print 'e'
+++++ ++ .              print 'l'
.                       print 'l'
+++ .                   print 'o'
> ++ .                  print ' '
<< +++++ +++++ +++++ .  print 'W'
> .                     print 'o'
+++ .                   print 'r'
----- - .               print 'l'
----- --- .             print 'd'
> + .                   print '!'
> .                     print '\n'

Để trả lời câu hỏi của bạn, ký tự ,.được sử dụng cho I / O. Văn bản là ASCII.

Các Wikipedia Điều xảy ra trong một số sâu hơn, là tốt.

Dòng đầu tiên khởi tạo a[0] = 10đơn giản bằng cách tăng mười lần từ 0. Vòng lặp từ dòng 2 thiết lập hiệu quả các giá trị ban đầu cho mảng: a[1] = 70(gần bằng 72, mã ASCII cho ký tự 'H'), a[2] = 100(gần bằng 101 hoặc 'e' ), a[3] = 30(gần 32, mã cho khoảng trắng) và a[4] = 10(dòng mới). Vòng lặp hoạt động bằng cách thêm 7, 10, 3, và 1, các tế bào a[1], a[2], a[3]a[4]tương ứng mỗi lần thông qua các vòng lặp - 10 bổ sung cho mỗi tế bào trong tổng (cho a[1]=70vv). Sau khi vòng lặp kết thúc, a[0]là số không. >++.sau đó di chuyển con trỏ đến a[1], giữ 70, thêm hai vào nó (tạo ra 72, là mã ký tự ASCII của chữ H viết hoa) và xuất ra.

Dòng tiếp theo di chuyển con trỏ mảng đến a[2]và thêm một con trỏ vào nó, tạo ra 101, chữ 'e' viết thường, sau đó được xuất.

Vì 'l' là ký tự thứ bảy sau 'e', ​​để xuất ra 'll', bảy chữ cái khác được thêm vào ( +++++++) a[2]và kết quả là xuất hai lần.

'o' là chữ cái thứ ba sau 'l', vì vậy a[2]được tăng thêm ba lần nữa và xuất ra kết quả.

Phần còn lại của chương trình diễn ra theo cách tương tự. Đối với khoảng trắng và chữ in hoa, các ô mảng khác nhau được chọn và tăng hoặc giảm khi cần.


Nhưng TẠI SAO nó lại in? hoặc thế nào? Các bình luận đang giải thích cho tôi mục đích của dòng, bây giờ nó làm gì.
speeder

8
Nó in vì trình biên dịch biết điều đó ,.được sử dụng cho I / O, giống như in C bằng cách sử dụng putchar. Nó là một chi tiết thực hiện được xử lý bởi trình biên dịch.
ken

1
Và cũng bởi vì nó đang đặt các ô bắt buộc thành giá trị nguyên cho các ký tự ASCII trong "Hello World"
slugonamission

Tôi mong đợi một sâu hơn trong lời giải thích ... nhưng: /
Speeder

1
@speeder - Tôi đã thêm phần giải thích chuyên sâu về mã từ Wikipedia vào câu trả lời. Bạn có thể xem bài viết được liên kết để biết thêm thông tin.
ken

9

Để trả lời câu hỏi làm thế nào nó biết được những gì cần in, tôi đã thêm phép tính giá trị ASCII vào bên phải của mã nơi quá trình in diễn ra:

> just means move to the next cell
< just means move to the previous cell
+ and - are used for increment and decrement respectively. The value of the cell is updated when the increment/decrement happens

+++++ +++++             initialize counter (cell #0) to 10

[                       use loop to set the next four cells to 70/100/30/10

> +++++ ++              add  7 to cell #1

> +++++ +++++           add 10 to cell #2 

> +++                   add  3 to cell #3

> +                     add  1 to cell #4

<<<< -                  decrement counter (cell #0)

]            

> ++ .                  print 'H' (ascii: 70+2 = 72) //70 is value in current cell. The two +s increment the value of the current cell by 2

> + .                   print 'e' (ascii: 100+1 = 101)

+++++ ++ .              print 'l' (ascii: 101+7 = 108)

.                       print 'l' dot prints same thing again

+++ .                   print 'o' (ascii: 108+3 = 111)

> ++ .                  print ' ' (ascii: 30+2 = 32)

<< +++++ +++++ +++++ .  print 'W' (ascii: 72+15 = 87)

> .                     print 'o' (ascii: 111)

+++ .                   print 'r' (ascii: 111+3 = 114)

----- - .               print 'l' (ascii: 114-6 = 108)

----- --- .             print 'd' (ascii: 108-8 = 100)

> + .                   print '!' (ascii: 32+1 = 33)

> .                     print '\n'(ascii: 10)

9

Brainfuck giống như tên của nó. Nó chỉ sử dụng 8 ký tự > [ . ] , - +khiến nó trở thành ngôn ngữ lập trình nhanh nhất để học nhưng khó thực hiện và hiểu nhất. … .Và cuối cùng khiến bạn kết thúc với việc f * cking não của bạn.

Nó lưu trữ các giá trị trong mảng: [72] [101] [108] [111]

hãy để, ban đầu con trỏ trỏ đến ô 1 của mảng:

  1. > di chuyển con trỏ sang phải 1

  2. < di chuyển con trỏ sang trái 1

  3. + tăng giá trị của ô lên 1

  4. - tăng giá trị của phần tử lên 1

  5. . giá trị in của ô hiện tại.

  6. , lấy đầu vào cho ô hiện tại.

  7. [ ] loop, +++ [-] bộ đếm 3 số đếm bcz nó có 3 ′ + 'trước nó, và - biến đếm giảm đi 1 giá trị.

giá trị được lưu trữ trong ô là giá trị ascii:

vì vậy đề cập đến mảng trên: [72] [101] [108] [108] [111] nếu bạn khớp với các giá trị ascii, bạn sẽ thấy rằng đó là Xin chào văn bản

Chúc mừng! bạn đã học cú pháp của BF

——- Còn nữa ———

hãy để chúng tôi thực hiện chương trình đầu tiên, tức là Hello World , sau đó bạn có thể viết tên của mình bằng ngôn ngữ này.

+++++ +++++[> +++++ ++ >+++++ +++++ >+++ >+ <<<-]>++.>+.+++++ ++..+++.++.+++++ +++++ +++++.>.+++.----- -.----- ---.>+.>.

vỡ thành nhiều mảnh:

+++++ +++++[> +++++ ++ 
                  >+++++ +++++ 
                  >+++ 
                  >+ 
                  <<<-]

Tạo một mảng 4 ô (số>) và đặt bộ đếm 10 ô như: —- mãpsuedo—-

array =[7,10,3,1]
i=10
while i>0:
 element +=element
 i-=1

bởi vì giá trị bộ đếm được lưu trữ trong ô 0 và> di chuyển đến ô 1 cập nhật giá trị của nó bằng + 7> di chuyển đến ô 2 tăng 10 lên giá trị trước đó của nó, v.v.

<<< trở về ô 0 và giảm giá trị của nó đi 1

do đó sau khi hoàn thành vòng lặp chúng ta có mảng: [70,100,30,10]

>++. 

di chuyển đến phần tử thứ nhất và tăng giá trị của nó lên 2 (hai '+') rồi in ra ký tự ('.') với giá trị ascii đó. ví dụ như trong python: chr (70 + 2) # in ra 'H'

>+.

chuyển sang ô thứ 2 tăng 1 thành giá trị 100 + 1 và in ('.') giá trị của nó tức là chr (101) chr (101) #prints 'e' bây giờ không có> hoặc <trong phần tiếp theo nên nó có giá trị hiện tại của phần tử mới nhất và chỉ gia tăng cho nó

+++++ ++..

phần tử mới nhất = 101 do đó, 101 + 7 và in nó hai lần (vì có hai '..') chr (108) #prints l hai lần có thể được sử dụng như

for i in array:
    for j in range(i.count(‘.’)):
           print_value

——— Nó được sử dụng ở đâu? ——-

Nó chỉ là một ngôn ngữ đùa được tạo ra để thách thức các lập trình viên và không được sử dụng thực tế ở bất cứ đâu.


4

Tất cả các câu trả lời đều thấu đáo, nhưng chúng thiếu một chi tiết nhỏ: In ấn. Trong quá trình xây dựng bộ phiên dịch ngu ngốc của bạn, bạn cũng phải xem xét nhân vật ., đây thực sự là những gì một tuyên bố in ấn trông giống như trong sự ngu ngốc. Vì vậy, những gì bộ biên dịch khốn khổ của bạn nên làm là, bất cứ khi nào nó gặp một .ký tự, nó sẽ in byte hiện đang được trỏ.

Thí dụ:

giả sử bạn có -> char *ptr = [0] [0] [0] [97] [0]... nếu đây là một câu lệnh ngu ngốc: >>>.con trỏ của bạn phải được di chuyển 3 dấu cách để hạ cánh bên phải tại:, [97]vì vậy bây giờ *ptr = 97, sau khi làm điều đó mà người dịch của bạn gặp phải một ., sau đó nó sẽ gọi

write(1, ptr, 1)

hoặc bất kỳ câu lệnh in tương đương nào để in byte hiện được trỏ, có giá trị 97 và chữ cái asau đó sẽ được in trên std_output.


1

Tôi nghĩ những gì bạn đang hỏi là làm thế nào để Brainfuck biết phải làm gì với tất cả các mã. Có một trình phân tích cú pháp được viết bằng ngôn ngữ cấp cao hơn chẳng hạn như Python để giải thích ý nghĩa của dấu chấm hoặc dấu cộng có nghĩa gì trong mã.

Vì vậy, trình phân tích cú pháp sẽ đọc từng dòng mã của bạn và nói ok, có một ký hiệu> vì vậy tôi phải nâng cấp vị trí bộ nhớ, mã chỉ đơn giản là, nếu (nội dung trong vị trí bộ nhớ đó) ==>, memlocation = + memlocation đó là được viết bằng ngôn ngữ cấp cao hơn, tương tự if (nội dung trong vị trí bộ nhớ) == ".", sau đó in (nội dung của vị trí bộ nhớ).

Hi vọng điều này sẽ làm sáng tỏ nó. tc

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.