Так вот: тема сопрограмм мной уже неявно затрагивалась в статье про фиберы.
Для тех, кому любопытно, как сопрограммы реализуются на чистом и переносимом Си, -- есть статья Саймона Татама. (Авторский комментарий: As far as I know, this is the worst piece of C hackery ever seen in serious production code.)
Я решил перевести пример кода, приведённый в той статье, на ассемблер, чтобы показать, насколько на нём всё выходит просто и естественно. Кроме того, мой код обильно прокомментирован.
Мораль здесь такая: есть некоторые товарищи (кивок в сторону vbnet.ru), которые полагают, что раз в ассемблер x86 встроена поддержка подпрограмм, значит подпрограммы рулят. А между тем, оказываются ситуации, когда от подпрограмм один вред, и самый прозрачный код получается при отказе от них; и на ассемблере писать код, незамутнённый подпрограммами, гораздо легче, чем на ЯВУ.
- Код: Выделить всё
.186
model tiny
switch macro ; требует установленный кадр стека
local resume ; на стеке: ... (старый bp) (адрес возврата) (адрес перехода) ...
push [bp+4]
mov [bp+4], offset resume
ret ; на стеке: (адрес перехода) ... (старый bp) (адрес возврата) (адрес обратного перехода) ...
resume: endm
.code
org 100h
start: ; читаем с клавиатуры строку, кладём её в буфер
mov ah, 10
lea dx, buffer
int 21h
mov si, dx
lodsw ; читаем длину строки
mov cl, ah
xor ch, ch
jcxz quit
; si указывает на строку, cx хранит её длину
lea di, token ; di указывет на накапливаемую лексему
push offset decompressor ; начальный адрес перехода в сопрограмму
call parser ; основной цикл: лексический анализ и вывод на экран лексем
mov ah, 9
lea dx, crlf
int 21h ; перевод строки в конце вывода
quit: int 20h
got_token proc near ; вход: bx -- тип лексемы (_WORD, PUNCT)
; вывод на экран; сохраняет ax
shl bx, 1
mov dx, m_table[bx]
xchg ax, bx
mov ah, 9
int 21h ; вывод типа лексемы
mov al, '$'
stosb
lea dx, token
int 21h ; вывод лексемы
mov di, dx
xchg ax, bx
ret
got_token endp
parser proc near ; параметр на стеке -- адрес перехода в сопрограмму decompressor
push bp
mov bp, sp
next_char:
switch ;nextchar
call isalpha
jc punct
alpha: stosb ;add_to_token
switch ;nextchar
call isalpha
jnc alpha
mov bx, _WORD
call got_token
punct: or ah, ah
jnz break
stosb ;add_to_token
mov bx, PUNCT
call got_token
jmp next_char
break: leave
ret 2
parser endp
decompressor proc near ; выход: ax (-1=EOF)
; вызывается только в качестве сопрограммы; собственного кадра стека не имеет
inc cx
n3xt_char:
xor ax, ax
loop proceed
dec ax ; вернуть EOF
switch
int 3 ; переключаться на нас после EOF уже нельзя
proceed:
lodsb
cmp al, 0FFh
jnz emit
push cx
lodsb
xchg ax, cx
lodsb
emit_loop:
switch
loop emit_loop
pop cx
sub cx, 2
jmp n3xt_char
emit: switch
jmp n3xt_char
decompressor endp
isalpha proc near ; вход: al; вывод: nc=TRUE, cy=FALSE
cmp al, 'A'
jb not_alpha
cmp Z, al
jnb yes_alpha
cmp al, 'a'
jb not_alpha
cmp z, al
; jb not_alpha
yes_alpha:
; clc
not_alpha:
ret
isalpha endp
crlf db 13, 10, '$'
m_word db 13, 10, "WORD: $"
m_punct db 13, 10, "PUNCT: $"
m_table dw m_word, m_punct
_WORD equ 0
PUNCT equ 1
Z db 'Z'
z db 'z'
bufsize equ 79
buffer db bufsize, (bufsize+2)dup(?)
token db (bufsize+1)dup(?)
end start