Viết một trình biên dịch brainfuck


13

Viết chương trình lấy chương trình brainfuck và biên dịch nó thành mã máy thực thi. Bạn có thể nhắm mục tiêu x86, x86_64, jvm (java bytecode) hoặc armv6 và sử dụng một trong các định dạng thực thi sau: ELF, a.out, tệp lớp, exe, com. Tệp thực thi phải hoạt động trong Linux hoặc Windows (hoặc Java trên một trong hai).

Cả chương trình và chương trình thực thi được tạo của bạn đều không thể chạy bất kỳ chương trình bên ngoài nào (chẳng hạn như trình biên dịch, trình biên dịch hoặc trình thông dịch khác).

Mã ngắn nhất sẽ thắng.


2
Bất kỳ lý do cho downvote?
aditsu

Bất kỳ cơ hội bạn có bất kỳ nguồn cho mã máy? Đây sẽ là bài tập chơi gôn mã máy đầu tiên của tôi nếu bạn có bất kỳ tài nguyên nào tôi có thể sử dụng làm ví dụ?
WallyWest

@ Eliseod'Annunzio Tôi không có bất kỳ tài nguyên cụ thể nào, nhưng nhìn chung bạn có thể bắt đầu bằng cách xem xét ngôn ngữ lắp ráp cho nền tảng bạn chọn và lắp ráp / tháo rời một số ví dụ. Google là bạn của bạn :) Cách đây không lâu tôi đã tham gia một vài cuộc thi đánh gôn bằng mã máy , tôi đã không làm tốt lắm nhưng tôi nhớ chúng tôi đang sử dụng định dạng com cho DOS, vì nó không có tiêu đề và công cụ bổ sung, chỉ là mã. Có lẽ những người khác có thể cung cấp thêm liên kết và đề xuất.
aditsu

Câu trả lời:


5

C, 866 783 byte

Vì mã của tôi xuất ra ELF 32 bit có thể thực thi được, tôi không thể hứa rằng nó sẽ hoạt động trên thiết lập của mọi người. Phải mất đủ tinh chỉnh để có thể thực thi để dừng segfaulting trên máy tính của tôi.

Đối với bất cứ ai đang cố gắng để chạy này:

$ uname --all
Linux 4.4.0-24-generic #43-Ubuntu SMP Wed Jun 8 19:27:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Một chương trình Brainfuck được đọc từ stdin và ELF đã biên dịch được viết thành thiết bị xuất chuẩn.

#define P *(t++)
#define C case
#define B break
char a[30000],b[65535],f,*t=b;*c[100];**d=c;main(g){P=188;t+=4;while((f=getchar())!=-1)switch(f){C'>':P=68;B;C'<':P=76;B;C'+':P=254;P=4;P=36;B;C'-':P=254;P=12;P=36;B;C'.':P=187;t+=4;P=137;P=225;P=186;P=1;t+=3;P=184;P=4;t+=3;P=205;P=128;B;C',':P=187;P=1;t+=3;P=137;P=225;P=186;P=1;t+=3;P=184;P=3;t+=3;P=205;P=128;B;C'[':P=138;P=4;P=36;P=133;P=192;P=15;P=132;t+=4;*d=(int*)t-1;d++;B;C']':P=138;P=4;P=36;P=133;P=192;P=15;P=133;t+=4;d--;g=((char*)(*d+1))-t;*((int*)t-1)=g;**d=-g;B;}P=184;P=1;t+=3;P=187;t+=4;P=205;P=128;*(int*)(b+1)=0x8048054+t-b;long long z[]={282579962709375,0,4295163906,223472812116,0,4297064500,4294967296,577727389698621440,36412867248128,30064779550,140720308490240};write(1,&z,84);write(1,b,t-b);write(1,a,30000);}

Ung dung

Trong phiên bản không mã hóa của mã, bạn có thể hiểu rõ hơn về những gì đang diễn ra. Mảng ký tự ở cuối mã golf là mã hóa ELF và tiêu đề chương trình trong mã không mã hóa. Mã này cũng cho thấy mỗi lệnh Brainfuck được dịch sang mã byte.

#include <linux/elf.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>

#define MAX_BIN_LEN 65535
#define MAX_JUMPS 100

unsigned int org = 0x08048000;



unsigned char move_right[] = {0x44};                              /*inc   esp         */

unsigned char move_left[]  = {0x4c};                              /*dec   esp         */

unsigned char inc_cell[]   = {0xfe,0x04,0x24};                    /*inc   [esp]       */

unsigned char dec_cell[]   = {0xfe,0x0c,0x24};                    /*dec   [esp]       */

unsigned char read_char[]  = {0xbb,0x00,0x00,0x00,0x00,           /*mov   ebx,  0     */
                              0x89,0xe1,                          /*mov   ecx,  esp   */
                              0xba,0x01,0x00,0x00,0x00,           /*mov   edx,  1     */
                              0xb8,0x03,0x00,0x00,0x00,           /*mov   eax,  3     */
                              0xcd,0x80};                         /*int   0x80        */

unsigned char print_char[] = {0xbb,0x01,0x00,0x00,0x00,           /*mov   ebx,  1     */
                              0x89,0xe1,                          /*mov   ecx,  esp   */
                              0xba,0x01,0x00,0x00,0x00,           /*mov   edx,  1     */
                              0xb8,0x04,0x00,0x00,0x00,           /*mov   eax,  4     */
                              0xcd,0x80};                         /*int   0x80        */


unsigned char loop_start[] = {0x8a,0x04,0x24,                     /*mov   eax,  [esp] */
                              0x85,0xc0,                          /*test  eax,  eax   */
                              0x0f,0x84,0x00,0x00,0x00,0x00};     /*je    int32_t     */

unsigned char loop_end[]   = {0x8a,0x04,0x24,                     /*mov   eax,  [esp] */
                              0x85,0xc0,                          /*test  eax,  eax   */
                              0x0f,0x85,0x00,0x00,0x00,0x00};     /*jne   int32_t     */

unsigned char call_exit[]  = {0xb8,0x01,0x00,0x00,0x00,           /*mov   eax,  1     */
                              0xbb,0x00,0x00,0x00,0x00,           /*mov   ebx,  0     */
                              0xcd,0x80};                         /*int   0x80        */
unsigned char prelude[]    = {0xbc,0x00,0x00,0x00,0x00};          /*mov   esp, int32_t*/

unsigned char tape[100];

int main(){
    unsigned char text[MAX_BIN_LEN];
    unsigned char *txt_ptr = text;

    int32_t *loop_jmps[MAX_JUMPS];
    int32_t **loop_jmps_ptr = loop_jmps;

    Elf32_Off entry;

    entry = org + sizeof(Elf32_Ehdr) + 1 * sizeof(Elf32_Phdr);

    memcpy(txt_ptr,prelude,sizeof(prelude));
    txt_ptr += sizeof(prelude);
    char input;
    while((input = getchar()) != -1){
        switch(input){
            case '>':
                memcpy(txt_ptr,move_right,sizeof(move_right));
                txt_ptr += sizeof(move_right);
                break;
            case '<':
                memcpy(txt_ptr,move_left,sizeof(move_left));
                txt_ptr += sizeof(move_left);
                break;
            case '+':
                memcpy(txt_ptr,inc_cell,sizeof(inc_cell));
                txt_ptr += sizeof(inc_cell);
                break;
            case '-':
                memcpy(txt_ptr,dec_cell,sizeof(dec_cell));
                txt_ptr += sizeof(dec_cell);
                break;
            case '.':
                memcpy(txt_ptr,print_char,sizeof(print_char));
                txt_ptr += sizeof(print_char);
                break;
            case ',':
                memcpy(txt_ptr,read_char,sizeof(read_char));
                txt_ptr += sizeof(read_char);
                break;
            case '[':
                memcpy(txt_ptr,loop_start,sizeof(loop_start));
                txt_ptr += sizeof(loop_start);
                *loop_jmps_ptr = (int32_t*) txt_ptr - 1;
                loop_jmps_ptr++;
                break;
            case ']':
                memcpy(txt_ptr,loop_end,sizeof(loop_end));
                txt_ptr += sizeof(loop_end);
                loop_jmps_ptr--;
                int32_t offset = ((unsigned char*) (*loop_jmps_ptr + 1)) - txt_ptr;
                *((int32_t*)txt_ptr - 1) = offset;
                **loop_jmps_ptr = -offset;
                break;
        }
    }

    memcpy(txt_ptr,call_exit,sizeof(call_exit));
    txt_ptr += sizeof(call_exit);

    *(int32_t*)(text + 1) = entry + (txt_ptr - text);


    Elf32_Ehdr ehdr = {
        {0x7F,'E','L','F',ELFCLASS32,ELFDATA2LSB,EV_CURRENT,0,0,0,0,0,0,0,0,0},
        ET_EXEC,
        EM_386,
        EV_CURRENT,
        entry,
        sizeof(Elf32_Ehdr),
        0,
        0,
        sizeof(Elf32_Ehdr),
        sizeof(Elf32_Phdr),
        1,
        0,
        0,
        SHN_UNDEF,
    };

    Elf32_Phdr phdr = {
        PT_LOAD,
        0,
        org,
        org,
        sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) + (txt_ptr - text),
        sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) + (txt_ptr - text),
        PF_R | PF_X | PF_W,
        0x1000,
    };

    int out = open("a.out",O_CREAT|O_TRUNC|O_WRONLY,S_IRWXU);
    write(out,&ehdr,sizeof(Elf32_Ehdr));
    write(out,&phdr,sizeof(Elf32_Phdr));

    write(out,text,txt_ptr-text);
    write(out,tape,sizeof(tape));
    close(out);
}

Tự sửa đổi BrainFuck

Để tiết kiệm byte, băng cho trình biên dịch của tôi không được phân bổ trong một .bssphần hoặc bất cứ thứ gì lạ mắt như thế. Thay vào đó, băng là 30.000 byte null được viết trực tiếp sau mã byte được biên dịch của chương trình Brainfuck. Biết điều này và nhận thức được mã byte nào được tạo bởi trình biên dịch của tôi có nghĩa là bạn có thể tạo hoặc sửa đổi mã byte khi chạy. Một minh họa đơn giản về 'tính năng' này là chương trình Brainfuck đặt giá trị thoát riêng của nó.

 <<<<<<+ 

Chương trình đi ra khỏi cạnh trái của băng vào mã byte đến điểm mà mã thoát thường được đặt 0. Tăng byte này làm cho mã thoát được đặt thành 1 thay vì 0 khi cuối cùng chương trình thoát. Với sự kiên trì, điều này có thể được sử dụng để thực hiện lập trình cấp hệ thống trong Brainfuck.


Đẹp; mã của bạn thực sự có 865 byte (bạn không cần một dòng mới ở cuối tệp). Ngoài ra, bạn có thể hợp nhất khai báo biến char. Tôi tự hỏi nếu mảng dài đó có thể được nén quá, vì nó có nhiều số không.
aditsu

@aditsu Tôi đã nghiên cứu tìm một số mã hóa tốt hơn cho mảng tiêu đề. C không đi kèm với bất kỳ thư viện nén nào được tích hợp sẵn nên việc nén nó dường như không còn vấn đề nữa. Điều tốt nhất tôi nghĩ ra là mã hóa nó như một mảng long long intthay vì char. Chắc chắn có chỗ cho tôi chơi golf một số tuyên bố biến. Tôi sẽ xem tôi có thể đến đó bao nhiêu và cập nhật câu trả lời của mình.
ankh-morpork

Tôi đã suy nghĩ một số hình thức của RLE, nhưng .. bất cứ điều gì hoạt động :)
aditsu

bạn có thể thay thế '577727389698621440' bằng '4105 * pow (2,47)' bằng cách sử dụng khai báo ngầm, cho 4 byte (ít nhất là hoạt động với gcc, ít nhất) ~
Maliafo

13

Python, 1974 ký tự

import sys
s='\x11\x75\x30\xbc\x08\x4b\x03\x3c'
k=[]
for c in sys.stdin.read():
 if'>'==c:s+='\x84\x01\x01'
 if'<'==c:s+='\x84\x01\xff'
 if'+'==c:s+='\x2a\x1b\x5c\x33\x04\x60\x91\x54'
 if'-'==c:s+='\x2a\x1b\x5c\x33\x04\x64\x91\x54'
 if'['==c:k+=[len(s)];s+='\x2a\x1b\x33\x99\x00\x00'
 if']'==c:a=k[-1];k=k[:-1];d=len(s)-a;s=s[:a+4]+'%c%c'%(d>>8,d&255)+s[a+6:]+'\xa7%c%c'%(-d>>8&255,-d&255)
 if','==c:s+='\x2a\x1b\xb2\x00\x02\xb6\x00\x03\x91\x54'
 if'.'==c:s+='\xb2\x00\x04\x59\x2a\x1b\x33\xb6\x00\x05\xb6\x00\x06'
s+='\xb1'
n=len(s)
sys.stdout.write('\xca\xfe\xba\xbe\x00\x03\x00-\x00+\n\x00\x08\x00\x13\t\x00\x14\x00\x15\n\x00\x16\x00\x17\t\x00\x14\x00\x18\n\x00\x19\x00\x1a\n\x00\x19\x00\x1b\x07\x00\x1c\x07\x00\x1d\x01\x00\x06<init>\x01\x00\x03()V\x01\x00\x04Code\x01\x00\x0fLineNumberTable\x01\x00\x04main\x01\x00\x16([Ljava/lang/String;)V\x01\x00\nExceptions\x07\x00\x1e\x01\x00\nSourceFile\x01\x00\x06B.java\x0c\x00\t\x00\n\x07\x00\x1f\x0c\x00 \x00!\x07\x00"\x0c\x00#\x00$\x0c\x00%\x00&\x07\x00\'\x0c\x00(\x00)\x0c\x00*\x00\n\x01\x00\x01B\x01\x00\x10java/lang/Object\x01\x00\x13java/io/IOException\x01\x00\x10java/lang/System\x01\x00\x02in\x01\x00\x15Ljava/io/InputStream;\x01\x00\x13java/io/InputStream\x01\x00\x04read\x01\x00\x03()I\x01\x00\x03out\x01\x00\x15Ljava/io/PrintStream;\x01\x00\x13java/io/PrintStream\x01\x00\x05write\x01\x00\x04(I)V\x01\x00\x05flush\x00!\x00\x07\x00\x08\x00\x00\x00\x00\x00\x02\x00\x01\x00\t\x00\n\x00\x01\x00\x0b\x00\x00\x00\x1d\x00\x01\x00\x01\x00\x00\x00\x05*\xb7\x00\x01\xb1\x00\x00\x00\x01\x00\x0c\x00\x00\x00\x06\x00\x01\x00\x00\x00\x03\x00\t\x00\r\x00\x0e\x00\x02\x00\x0b\x00\x00'+'%c%c'%((n+60)>>8,(n+60)&255)+'\x00\x04\x00\x03\x00\x00'+'%c%c'%(n>>8,n&255)+s+'\x00\x00\x00\x01\x00\x0c\x00\x00\x00*\x00\n\x00\x00\x00\x05\x00\x06\x00\x06\x00\x08\x00\t\x00\x0b\x00\x0b\x00\x13\x00\r\x00\x1d\x00\x0f\x00&\x00\x11\x00,\x00\x12\x002\x00\x14\x008\x00\x15\x00\x0f\x00\x00\x00\x04\x00\x01\x00\x10\x00\x01\x00\x11\x00\x00\x00\x02\x00\x12')

Dưới đây là các bản dịch sang java bytecode. local 0 là một mảng byte đại diện cho băng, local 1 là con trỏ dữ liệu.

>  iinc 1,+1
<  iinc 1,-1
+  aload_0;iload_1;dup2;baload;iconst_1;iadd;i2b;bastore
-  aload_0;iload_1;dup2;baload;iconst_1;isub;i2b;bastore
[  aload_0;iload_1;baload;ifeq xx xx
]  goto xx xx
,  aload_0;iload_1;getstatic #2;invokevirtual #3;i2b;bastore
.  getstatic #4;dup;aload_0;iload_1;baload;invokevirtual #5;invokevirtual #6

Các xx xxbù đắp để đạt được khung phù hợp. # 2 là System.in, # 3 là read(), # 4 là System.out, # 5 là write()và # 6 là flush().

Lời mở đầu phân bổ một mảng 30000 byte và khởi tạo vị trí băng thành 0.

Trình bao bọc khổng lồ ở cuối được tạo bằng cách biên dịch một B.javatệp giả với mã cho một trong mỗi opcode (để tạo ra các bảng không đổi chính xác và các thứ linh tinh khác), sau đó thực hiện phẫu thuật tinh vi trên nó.

Chạy nó như

python bfc.py < input.b > B.class
java B

Tháo rời với

javap -c B

Tôi chắc chắn rằng nó có thể được đánh gôn thêm. Tôi rất vui vì nó hoạt động ...


1
Sử dụng từ nhập sys * và sau đó cạo 2 ký tự bằng cách xóa cả hai sys.
TimTech

2
Bạn có thể sử dụng base64 để mã hóa dữ liệu nhị phân đó và tắt một số byte
tecywiz121

3

Mã lắp ráp x86 16 bit, 104 byte

Mã này là từ năm 2014, nhưng tôi chỉ tìm thấy nhiệm vụ.

;compliant version, non-commands are ignored, but 104 bytes long

[bits 16]  
[org 0x100]  
; assume bp=091e used  
; assume di=fffe  
; assume si=0100  
; assume dx=cs (see here)  
; assume cx=00ff  
; assume bx=0000  
; assume ax=0000 used (ah)  
; assume sp=fffe  
start:
        mov al, code_nothing - start  
code_start:
        mov ch, 0x7f ; allow bigger programs  
        mov bx, cx  
        mov di, cx  
        rep stosb  
        mov bp, find_right + start - code_start ;cache loop head for smaller compiled programs  
        jmp code_start_end  
find_right:
        pop si  
        dec si  
        dec si ;point to loop head  
        cmp [bx], cl  
        jne loop_right_end  
loop_right:
        lodsb  
        cmp al, 0xD5 ; the "bp" part of "call bp" (because 0xFF is not unique, watch for additional '[')  
        jne loop_left  
        inc cx  
loop_left:
        cmp al, 0xC3 ; ret (watch for ']')  
        jne loop_right  
        loop loop_right ;all brackets matched when cx==0  
        db 0x3c ;cmp al, xx (mask push)  
loop_right_end:
        push si  
        lodsw ; skip "call" or dummy "dec" instruction, depending on context  
        push si  
code_sqright:
        ret  
code_dec:
        dec byte [bx]  
code_start_end:
        db '$' ;end DOS string, also "and al, xx"  
code_inc:
        inc byte [bx]  
        db '$'  
code_right:
        inc bx ;al -> 2  
code_nothing:
        db '$'  
code_left:
        dec bx  
        db '$'  
code_sqleft:
        call bp  
        db '$'  
; create lookup table  
real_start:
        inc byte [bx+'<'] ;point to code_left  
        dec byte [bx+'>'] ;point to code_right  
        mov byte [bx+'['], code_sqleft - start  
        mov byte [bx+']'], code_sqright - start  
        lea sp, [bx+45+2] ;'+' + 4 (2b='+', 2c=',', 2d='-', 2e='.')  
        push (code_dec - start) + (code_dot - start) * 256  
        push (code_inc - start) + (code_comma - start) * 256  
pre_write:
        mov ah, code_start >> 8  
        xchg dx, ax  
; write  
        mov ah, 9  
        int 0x21  
; read  
code_comma:
        mov dl, 0xff  
        db 0x3d ; cmp ax, xxxx (mask mov)  
code_dot:
        mov dl, [bx]  
        mov ah, 6  
        int 0x21  
        mov [bx], al  
        db '$'  
        db 0xff ; parameter for '$', doubles as test for zero  
; switch  
        xlatb  
        jne pre_write  
  ; next two lines can also be removed  
  ; if the program ends with extra ']'  
  ; and then we are at 100 bytes... :-)  
the_end:
        mov dl, 0xC3  
        int 0x21  
        int 0x20 

Bạn có chắc đó không phải là thông dịch viên ?
aditsu

1
Không, nó hoàn toàn là một trình biên dịch. "bf.com <hello.bf> out.com", sau đó out.com sẽ được thực thi.
perr ferrie

1
Ok, bạn có thể giải thích làm thế nào để biên dịch nó và nó hoạt động trong hệ điều hành nào không? Tôi chưa thể chạy nó được.
aditsu

lắp ráp với YASM, chạy trong MS-DOS (thông qua DOSBox là được).
perr ferrie

1
'<' Và '>' cần phải được thoát. Dù sao, Windows 32 bit có bảng điều khiển DOS sẽ chạy và "com" rõ ràng là một trong những định dạng được phép.
perr ferrie
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.