171 байт 1
Wooohoooo! Прошло полдня, но было весело ...
Итак, вот оно. Я думаю, что это соответствует спецификации (обтекание указателя на ячейку, эхо символов на входе, чтение символа на символ, эхо ввода символов, ...), и, похоже, на самом деле работает (ну, я не пробовал много программ , но, учитывая простоту языка, освещение, я думаю, не так уж и плохо).
Ограничения
Одна важная вещь: если ваша программа содержит все символы, отличные от 8 инструкций, или если []
они не сбалансированы, то это может обрушиться на вас, ха-ха-ха!
Кроме того, программа brainfuck не может превышать 512 байт (сектор). Но это кажется соответствующим, поскольку вы говорите, что «исполняемый файл Brainfuck находится во втором секторе диска» .
Последняя деталь: я явно не инициализировал ячейки в ноль. Qemu, кажется, делает это для меня, и я полагаюсь на это, но я не знаю, будет ли это делать настоящий BIOS на реальном компьютере (инициализация в любом случае займет всего несколько байтов).
Код
(основываясь на вашем шаблоне, и, кстати, спасибо за это, я бы никогда не попробовал без него):
[BITS 16]
[ORG 0x7C00]
%define cellcount 30000 ; you can't actually increase this value much beyond this point...
; first sector:
boot:
; initialize segment registers
xor ax, ax
mov ss, ax
mov ds, ax
mov es, ax
jmp 0x0000:$+5
; initialize stack
mov sp, 0x7bfe
; load brainfuck code into 0x8000
; no error checking is used
mov ah, 2 ; read
mov al, 1 ; one sector
mov ch, 0 ; cylinder & 0xff
mov cl, 2 ; sector | ((cylinder >> 2) & 0xc0)
mov dh, 0 ; head
; dl is already the drive number
mov bx, 0x8000 ; read buffer (es:bx)
int 0x13 ; read sectors
; initialize SI (instruction pointer)
mov si, bx ; 0x8000
; initialize DI (data pointer)
mov bh, 0x82
mov di, bx ; 0x8200
decode:
lodsb ; fetch brainfuck instruction character
.theend:
test al, al ; endless loop on 0x00
jz .theend
and ax, 0x0013 ; otherwise, bit shuffling to get opcode id
shl ax, 4
shl al, 2
shr ax, 1
add ax, getchar ; and compute instruction implementation address
jmp ax
align 32, db 0
getchar:
xor ah, ah
int 0x16
cmp al, 13
jne .normal
mov al, 10 ; "enter" key translated to newline
.normal:
mov byte [di], al
push di
jmp echochar
align 32, db 0
decrementdata:
dec byte [di]
jmp decode
align 32, db 0
putchar:
push di
mov al, byte [di]
echochar:
mov ah, 0x0E
xor bx, bx
cmp al, 10 ; newline needs additional carriage return
jne .normal
mov al, 13
int 0x10
mov al, 10
.normal:
int 0x10
pop di
jmp decode
align 32, db 0
incrementdata:
inc byte [di]
jmp decode
align 32, db 0
decrementptr:
dec di
cmp di, 0x8200 ; pointer wraparound check (really, was that necessary?)
jge decode
add di, cellcount
jmp decode
align 32, db 0
jumpback:
pop si
jmp jumpforward
align 32, db 0
incrementptr:
inc di
cmp di, 0x8200+cellcount ; pointer wraparound check
jl decode
sub di, cellcount
jmp decode
align 32, db 0
jumpforward:
cmp byte [di], 0
jz .skip
push si
jmp decode
.skip:
xor bx, bx ; bx contains the count of [ ] imbrication
.loop:
lodsb
cmp al, '['
je .inc
cmp al, ']'
jne .loop
test bx, bx
jz decode
dec bx
jmp .loop
.inc:
inc bx
jmp .loop
; fill sector
times (0x1FE)-($-$$) db 0
; boot signature
db 0x55, 0xAA
; second sector contains the actual brainfuck program
; currently: "Hello world" followed by a stdin->stdout cat loop
db '++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.,[.,]'
times 0x400-($-$$) db 0
Трюки, используемые
Хорошо, я немного обманул. Поскольку вы сказали «будучи загрузчиком, размер программы в скомпилированном коде считается ненулевыми байтами» , я уменьшил код, допустив «дыры» между реализацией восьми кодов операции brainfuck. Таким образом, мне не нужна большая последовательность тестов, таблица переходов или что-то еще: я просто прыгаю к «идентификатору кода операции» (от 0 до 8) умножения, умноженному на 32, чтобы выполнить инструкцию «возрождение мозгов» (стоит отметить, что это означает, что выполнение инструкций не может занимать более 32 байт).
Более того, чтобы получить этот «идентификатор кода операции» от выбранного персонажа программы «brainfuck», я заметил, что нужно немного потасовать. Действительно, если мы просто рассмотрим биты 0, 1 и 4 символа кода операции, мы получим 8 уникальных комбинаций:
X XX
00101100 0x2C , Accept one byte of input, storing its value in the byte at the pointer.
00101101 0x2D - Decrement (decrease by one) the byte at the pointer.
00101110 0x2E . Output the value of the byte at the pointer.
00101011 0x2B + Increment (increase by one) the byte at the pointer.
00111100 0x3C < Decrement the pointer (to point to the next cell to the left).
01011101 0x5D ] Jump back after the corresp [ if data at pointer is nonzero.
00111110 0x3E > Increment the pointer (to point to the next cell to the right).
01011011 0x5B [ Jump forward after the corresp ] if data at pointer is zero.
И, к счастью для меня, на самом деле есть один код операции, для реализации которого требуется более 32 байтов, но это последний (переход вперед [
). После того, как есть больше места, все в порядке.
Другой трюк: я не знаю, как работает типичный интерпретатор brainfuck, но, чтобы сделать вещи намного меньше, я фактически не реализовал ]
как «Переход назад после соответствующего, [
если данные в указателе отличны от нуля» . Вместо этого я всегда возвращаюсь к соответствующей [
и, отсюда, повторно применяю типичную [
реализацию (которая затем, в конце концов, продвигается вперед после ]
повторения, если это необходимо). Для этого каждый раз, когда я прошу a [
, я помещаю текущий «указатель инструкции brainfuck» в стек перед выполнением внутренних инструкций и когда я сталкиваюсь с]
Я выскакиваю обратно указатель инструкции. Как будто это был вызов функции. Таким образом, теоретически вы можете переполнить стек, создав много многозначных циклов, но, в любом случае, не с нынешним ограничением в коде brainfuck, равным 512 байтам.
1. Включая нулевые байты, которые были частью самого кода, но не те, которые были частью некоторого заполнения
Input must be red
Я почти уверен, что большинство загрузчиков изначально не поддерживают цвет.