เขียนคอมไพเลอร์ brainfuck


13

เขียนโปรแกรมที่ใช้โปรแกรม brainfuck และคอมไพล์มันเป็นรหัสเครื่องที่ปฏิบัติการได้ คุณสามารถกำหนดเป้าหมาย x86, x86_64, jvm (java bytecode) หรือ armv6 และใช้หนึ่งในรูปแบบที่ปฏิบัติการได้ต่อไปนี้: ELF, a.out, ไฟล์คลาส, exe, com ไฟล์สั่งการควรทำงานใน Linux หรือ Windows (หรือ Java อย่างใดอย่างหนึ่ง)

ไม่ว่าโปรแกรมของคุณหรือไฟล์ที่เรียกทำงานที่สร้างขึ้นอาจรันโปรแกรมภายนอกใด ๆ (เช่นคอมไพเลอร์, แอสเซมเบลอร์, แอสเซมเบลอร์หรือล่ามอื่น)

รหัสที่สั้นที่สุดชนะ


2
มีเหตุผลใดบ้างสำหรับ downvoting?
aditsu ออกจากเพราะ SE นั้นชั่วร้าย

โอกาสใด ๆ ที่คุณมีแหล่งที่มาของรหัสเครื่อง? นี่จะเป็นแบบฝึกหัดการเล่นกอล์ฟรหัสเครื่องครั้งแรกของฉันถ้าคุณมีทรัพยากรที่ฉันสามารถใช้เป็นตัวอย่างได้หรือไม่?
WallyWest

@ Eliseod'Annunzio ฉันไม่มีทรัพยากรเฉพาะ แต่โดยทั่วไปคุณสามารถเริ่มต้นด้วยการมองหาภาษาแอสเซมบลีสำหรับแพลตฟอร์มที่คุณเลือกและประกอบ / แยกตัวอย่างบางส่วน Google คือเพื่อนของคุณ :) เวลาที่ผ่านมาฉันเข้าร่วมในการแข่งขันกอล์ฟรหัสสองสามครั้งฉันทำได้ไม่ดีนัก แต่ฉันจำได้ว่าเรากำลังใช้รูปแบบ com สำหรับ DOS เนื่องจากไม่มีส่วนหัวและเนื้อหาเพิ่มเติมเพียงแค่ รหัส. บางทีคนอื่นอาจให้ลิงก์และคำแนะนำเพิ่มเติม
aditsu ออกเพราะ SE เป็นความชั่วร้าย

คำตอบ:


5

C, 866 783 ไบต์

เนื่องจากรหัสของฉันแสดงผลให้เอลฟ์ทำงานได้ 32 บิตฉันจึงไม่สามารถรับประกันได้ว่ามันจะทำงานได้ในทุก ๆ การตั้งค่า ใช้เวลานานพอสมควรในการทำให้โปรแกรมเรียกใช้งานหยุดการ segfaulting บนคอมพิวเตอร์ของฉัน

สำหรับทุกคนที่พยายามเรียกใช้สิ่งนี้:

$ 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

โปรแกรม Brainfuck นั้นอ่านจาก stdin และ ELF ที่คอมไพล์แล้วจะถูกเขียนไปยัง stdout

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

Ungolfed

ในโค้ดเวอร์ชันที่ไม่ดีคุณจะได้รับแนวคิดที่ดีขึ้นว่าเกิดอะไรขึ้น อาร์เรย์อักขระที่ส่วนท้ายของรหัส golfed คือการเข้ารหัสของ ELF และส่วนหัวของโปรแกรมในรหัส ungolfed รหัสนี้ยังแสดงให้เห็นว่าการแปลแต่ละคำสั่ง Brainfuck เป็น bytecode อย่างไร

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

BrainFuck การปรับเปลี่ยนด้วยตนเอง

เพื่อประหยัดเป็นไบต์เทปสำหรับคอมไพเลอร์ของฉันไม่ได้รับการจัดสรรใน.bssส่วนหรืออะไรที่เป็นแฟนซี แต่เทปก็คือ 30,000 null null ที่เขียนโดยตรงหลังจากโค้ดไบต์ที่คอมไพล์ของโปรแกรม Brainfuck การรู้สิ่งนี้และการรับรู้ว่าโค้ดไบต์ใดที่ถูกสร้างขึ้นโดยคอมไพเลอร์ของฉันหมายความว่าคุณสามารถสร้างหรือแก้ไขโค้ดไบต์ที่รันไทม์ ภาพประกอบง่าย ๆ ของ 'ฟีเจอร์' นี้เป็นโปรแกรม Brainfuck ที่กำหนดค่าการออกของตนเอง

 <<<<<<+ 

โปรแกรมจะตัดขอบด้านซ้ายของเทปลงในรหัสไบต์ไปยังจุดที่รหัสการออกปกติตั้งค่า 0 การเพิ่มไบต์นี้ทำให้รหัสออกถูกตั้งค่าเป็น 1 แทน 0 เมื่อโปรแกรมออกในที่สุด ด้วยความคงเส้นคงวานี้สามารถใช้ในการเขียนโปรแกรมระดับระบบใน Brainfuck


นีซ; รหัสของคุณมีขนาด 865 ไบต์ (คุณไม่จำเป็นต้องขึ้นบรรทัดใหม่ที่ท้ายไฟล์) นอกจากนี้คุณสามารถรวมการประกาศตัวแปรถ่าน ฉันสงสัยว่าอาร์เรย์แบบยาวนั้นสามารถบีบอัดได้เช่นกันเพราะมีศูนย์จำนวนมาก
aditsu เลิกเพราะ SE นั้นชั่วร้าย

@aditsu ฉันพยายามหาการเข้ารหัสที่ดีขึ้นสำหรับอาเรย์ส่วนหัว C ไม่ได้มาพร้อมกับไลบรารีการบีบอัดที่มีอยู่แล้วดังนั้นการซิปมันดูเหมือนจะไม่เป็นไปตามที่ต้องการ ดีที่สุดที่ฉันได้มาด้วยการเข้ารหัสเป็นมันเป็นอาร์เรย์ของแทนlong long int charมีที่ว่างสำหรับให้ฉันตีกอล์ฟออกประกาศตัวแปรของฉัน ฉันจะดูว่าฉันสามารถไปถึงที่นั่นและอัปเดตคำตอบของฉัน
ankh-morpork

ฉันคิดว่ารูปแบบของ RLE บางอย่าง แต่ .. สิ่งที่ใช้งานได้ :)
aditsu ออกเพราะ SE เป็นความชั่วร้าย

คุณอาจแทนที่ '577727389698621440' โดย '4105 * pow (2,47)' โดยใช้การประกาศโดยนัยของ pow สำหรับ 4 ไบต์ (ใช้งานได้กับ gcc อย่างน้อย) ~
Maliafo

13

Python 1974 ตัวอักษร

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')

ด้านล่างนี้เป็นคำแปลของ java bytecode ท้องถิ่น 0 เป็นอาร์เรย์ไบต์แทนเทปท้องถิ่น 1 เป็นตัวชี้ข้อมูล

>  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

xx xxมีการชดเชยไปถึงวงเล็บที่ตรงกัน # 2 System.in, # 3 read(), # 4 System.out, # 5 write()และ # flush()6

คำนำจะจัดสรรอาร์เรย์ 30000 ไบต์และเริ่มต้นตำแหน่งเทปเป็น 0

เสื้อคลุมยักษ์ในตอนท้ายนั้นถูกสร้างขึ้นโดยการรวบรวมB.javaไฟล์จำลองที่มีรหัสสำหรับหนึ่งในแต่ละ opcode (เพื่อชักนำให้เกิดการสร้างตารางคงที่ที่ถูกต้องและขยะอื่น ๆ ) จากนั้นทำการผ่าตัดที่ละเอียดอ่อน

เรียกใช้เหมือนกัน

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

ถอดออกด้วย

javap -c B

ฉันแน่ใจว่ามันสามารถตีกอล์ฟได้มากกว่านี้ ฉันมีความสุขที่ได้ผล ...


1
ใช้จาก sys import * จากนั้นโกน 2 chars โดยลบทั้งสอง sys
Timtech

2
คุณสามารถใช้ base64 เพื่อเข้ารหัสข้อมูลไบนารี่นั้นและ
ลบล้าง

3

รหัสแอสเซมบลี x86 แบบ 16 บิต 104 ไบต์

รหัสนี้มาจาก 2014 แต่ฉันเพิ่งพบงาน

;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 

คุณแน่ใจว่ามันไม่ได้เป็นล่าม ?
aditsu ออกเพราะ SE ไม่ทำงาน

1
ไม่มันเป็นคอมไพเลอร์อย่างแน่นอน "bf.com <hello.bf> out.com" จากนั้น out.com จะสามารถใช้งานได้
เตอร์ปีเตอร์

1
ตกลงคุณช่วยอธิบายวิธีการคอมไพล์มันและระบบปฏิบัติการอะไรได้บ้าง ฉันยังไม่สามารถเรียกใช้งานได้
aditsu ออกจากเพราะ SE นั้นชั่วร้าย

ประกอบกับ YASM ทำงานใน MS-DOS (ผ่าน DOSBox ก็โอเค)
เตอร์ปีเตอร์

1
จะต้องหลีกเลี่ยงการ '<' และ '>' อย่างไรก็ตาม Windows 32 บิตมีคอนโซล DOS ที่จะทำงานและ "com" เป็นหนึ่งในรูปแบบที่อนุญาต
เตอร์เฟอร์รี
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.