Depois do hiato da semana passada segue continuação da série sobre a construção de um visualizador de telas do ZX Spectrum para MSX-DOS. Só para recordar a primeira parte tratou da organização da memória de vídeo do speccy, das diferenças dela com a do MSX e de como compensar as tonalidade das cores. Na segunda parte foi sobre como reorganizar os dados do arquivo SCR para apresentá-los na VRAM do MSX e, claro, como fazer a carga dele usando diretamente as rotinas da BDOS.
Há também uma parte complementar com um pequeno programa em Python que converte telas do ZX para dumps da VRAM em arquivos binários do MSX.
Nesta parte, conforme foi prometido, é a recuperação e tratamento dos argumentos passados pela linha de comando do MSX-DOS e como o assunto é meio chato tentarei ser breve — eu juro 😀
A linha de comando
O MSX-DOS armazena diversas informações de ambiente nos primeiros 256 bytes da memória, a página zero¹, lá estão algumas variáveis do sistema operacional, os pontos de chamadas para diversas funções e, claro, o o resto da linha de comando (tudo aquilo depois do nome do programa) inserida pelo usuário a partir do prompt no COMMAND.COM.
O número de caracteres que foram digitados é armazenado no endereço 0x0080 enquanto que o texto em si está entre 0x0081 e 0x00FF (só lembrando que os programas começam em 0x0100). Tecnicamente isto significa que a linha de comando tem no máximo 127 caracteres mas na prática ela é composta de “somente” 126 caracteres já que um caractere de espaço é automaticamente inserido entre o nome do programa e o resto da linha.
(¹) Que é o mesmo comportamento do CP/M no qual ele foi baseado porém é importante ressaltar que o MSX-DOS não é um clone CP/M! Algumas funcionalidades, mas não todas, estão implementadas da mesma forma e no mesmo lugar — hoje seria como dizer que “usa a mesma API” — e isto serve para torná-lo compatível mas não necessariamente igual.
Recuperando a linha de comando
Infelizmente não há uma função dentro do MSX-DOS que interprete e retorne de modo automático os argumentos passados na linha de comando, como acontece como acontece com argc e **argv em C.
Assim a solução é trabalhar com as informações que o sistema operacional disponibiliza para fazer algumas experiências.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; | |
; Visualiza a 'tail' dos comandos do MSX-DOS (e CP/M) | |
; | |
ASCII_LF: equ 10 ; avanço de linha ("line feed") | |
ASCII_CR: equ 13 ; retorno de carro ("carriage return") | |
BOOT: equ $00 ; rotina Boot() | |
PUTCHAR: equ $02 ; rotina PutChar() | |
PUTSTR: equ $09 ; rotina PutString() | |
zpArgvLen: equ $0080 ; tamanho do buffer de argumentos | |
zpArgvStr: equ $0081 ; início da string com os argumentos | |
macro ____bdos,BDOS | |
ld c,BDOS | |
call $0005 ; chamada à BDOS em MSX-DOS | |
endm | |
macro ____print,str ; macro que simula PRINT "str" | |
proc | |
local _jump | |
local _string | |
ld de,_string | |
____bdos PUTSTR | |
jp _jump | |
_string: | |
db str,ASCII_CR,ASCII_LF,"$" | |
_jump: | |
endp | |
endm | |
org 0x0100 | |
____print "View the command line tail" | |
ld a,(zpArgvLen) ; lê o tamanho da string de argumentos | |
cp 0 ; verifica se é igual a zero | |
jr nz,hasTail ; se >0 vai para 'hasTail' | |
____print "*** No arguments!" ; avisa que não há argumentos | |
jp programEnd ; e sai do programa | |
hasTail: | |
____print "Command tail:" ; mensagem de que há argumentos | |
ld hl,zpArgvLen ; 'HL' com a posição do tamanho | |
ld b,(hl) ; lê novamente o tamanho da string | |
inc hl ; 'HL' para o começo da string | |
printLoop: | |
ld e,(hl) ; lê um caracter da string | |
push hl ; salva HL na pilha | |
push bc ; salva BC na pilha | |
____bdos PUTCHAR | |
pop bc ; restaura BC da pilha | |
pop hl ; restaura HL da pilha | |
inc hl ; incrementa 'HL' | |
djnz printLoop ; se 'B' < 0 faz o laço | |
____print "<End>" ; assinala o final da string | |
programEnd: | |
____bdos BOOT ; retorna ao MSX-DOS (ou CP/M) |
Monte com:
$ pasmo -d -v viewArgs.asm viewargs.com
O que faz? Simples, verifica se foi inserido algo após o nome do programa lendo o valor do endereço 0x0080 e sendo ele maior que zero imprime o conteúdo.
Apesar de feito para rodar no MSX-DOS este programa foi intencionalmente escrito para ser compatível com implementações de CP/M em computadores Z80² por um simples motivo:
No CP/M — via emulador rodando dentro no DOSEMU, ufa! — há uma diferença interessante em que a linha é totalmente convertida para caixa alta. Aliás, eu não dissera que MSX-DOS ≠ CP/M? 🙂
(²) Só lembrando que Z80 e 8080 compartilham um conjunto comum de instruções mas não de mnemônicos.
Interpretando argumentos
Então, a partir da estrutura do primeiro programa, é possível criar um interpretador de argumentos passados pela linha de comando mas antes de fazê-lo é necessário considerar algumas premissas importantes:
- Os argumentos são tudo aquilo que segue o nome do comando até o limite dos
127126 caracteres; - O primeiro argumento será sempre um nome/máscara de arquivo;
- Um nome/máscara de arquivo terá no máximo 12 caracteres³ — até 8 caracteres para o nome, até 3 para extensão e um “.” a separá-los;
- Os demais argumentos serão parâmetros indicados pelo caractere de barra (“/”);
- Os parâmetros usarão letras — /a, /b, …, /z — ou números — /0, /1, …, /9 — e
- Terão o tamanho máximo de um caractere.
- Os parâmetros serão sensíveis à caixa das letras, ou seja, serão case sensitive — desculpe CP/M.
Assim:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; | |
; Interpreta a 'tail' dos comandos do MSX-DOS (e CP/M) | |
; | |
ASCII_LF: equ 10 ; avanço de linha ("line feed") | |
ASCII_CR: equ 13 ; retorno de carro ("carriage return") | |
BOOT: equ $00 ; rotina Boot() | |
PUTCHAR: equ $02 ; rotina PutChar() | |
PUTSTR: equ $09 ; rotina PutString() | |
zpArgvLen: equ $0080 ; tamanho do buffer de argumentos | |
zpArgvStr: equ $0081+1 ; início da string com os argumentos (corrigido) | |
macro ____bdos,BDOS | |
ld c,BDOS | |
call $0005 ; faz a chamada à BDOS | |
endm | |
macro ____print,str ; macro que simula PRINT "str" | |
proc | |
local _jump | |
local _string | |
ld de,_string | |
____bdos PUTSTR | |
jp _jump | |
_string: | |
db str,ASCII_CR,ASCII_LF,"$" | |
_jump: | |
endp | |
endm | |
org 0x0100 | |
____print "Test command line tail" | |
ld a,(zpArgvLen) ; Lê o conteúdo do endereço $80 da Zero page | |
cp 0 ; verifica se é zero | |
jr nz,hasTail ; se >0 vai para 'hasTail' | |
____print "*** No arguments!" ; avisa que não há argumentos | |
jp endProgram ; e sai do programa | |
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
macro ____test,param,option ; chama a rotina de lookup | |
ld c,param | |
call lookup | |
jp z,option | |
endm | |
hasTail: | |
ld hl,zpArgvLen ; inicio da string | |
call getFilename ; recupera o nome do arquivo (até 8 caracteres) | |
ld (paramIdx),hl ; guarda o início dos parâmetros | |
ld a,(zpArgvLen) ; tamanho da string | |
sub c ; retira o que foi utilizado | |
ld (paramLen),a ; guarda este novo tamanho | |
____test "H", printHelp1 ; Busca por "/H" | |
____test "h", printHelp2 ; Busca por "/h" | |
ld a,(filename) ; foi recuperado um nome de arquivo? | |
cp "$" | |
jr z,endProgram ; sai se não for inserido nome de arquivo | |
ld de,filename | |
____bdos PUTSTR ; imprime o nome de arquivo | |
jr endProgram ; sai do programa | |
printError: | |
____print "Ilegal parameter" ; caso os parâmetros não sejam entendidos | |
endProgram: | |
____bdos BOOT ; retorna ao MSX-DOS (ou CP/M) | |
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
printHelp1: | |
ld de,mensa1 ; mensagem 1 | |
jr printHelp | |
printHelp2: | |
ld de,mensa2 ; mensagem 2 | |
printHelp: | |
____bdos PUTSTR ; imprime a mensagem na tela | |
jr endProgram ; e sai do programa | |
mensa1: | |
db "Use:",ASCII_CR,ASCII_LF | |
db "testargs <filename> [/H] [/h]",ASCII_CR,ASCII_LF | |
db "Parameters:",ASCII_CR,ASCII_LF | |
db " /H : This help",ASCII_CR,ASCII_LF | |
db " /h : Another help",ASCII_CR,ASCII_LF,"$" | |
mensa2: | |
db "Use:",ASCII_CR,ASCII_LF | |
db "testargs <filename> [/H] [/h]",ASCII_CR,ASCII_LF | |
db "Parameters:",ASCII_CR,ASCII_LF | |
db " /H : Another help",ASCII_CR,ASCII_LF | |
db " /h : This help",ASCII_CR,ASCII_LF,"$" | |
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
macro ____abort,char | |
cp char | |
jr z,endLoop | |
endm | |
getFilename: | |
proc | |
local loop | |
local endLoop | |
ld b,12 ; tamanho máximo da string | |
ld c,1 ; começa com 1, não 0 | |
ld de,filename ; variável com o nome do arquivo | |
ld hl,zpArgvStr ; 'HL' no começo da string | |
loop: | |
ld a,(hl) ; lê um caracter | |
____abort "/" ; sai da rotina se for "/" | |
____abort " " ; sai da rotina se for " " | |
ld (de),a ; copia o caracter para a variável | |
inc c ; contador de caracteres | |
inc de ; incrementa o ponteiro de destino | |
inc hl ; incrementa o ponteiro de origem | |
djnz loop ; enquanto 'B'>0 faz o laço | |
endLoop: | |
ret ; sai da rotina | |
endp | |
paramIdx: dw 0 | |
paramLen: db 0 | |
filename: ds 13,"$" ; só servirá para imprimir, pode ser "$" | |
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | |
lookup: | |
proc | |
local command | |
local endLoop | |
local switch | |
ld a,(paramLen) ; 'A' com o novo tamanho da string | |
ld b,a ; 'B' com o valor em 'A' | |
ld ix,(paramIdx) ; 'IX' começa depois do nome do arquivo | |
switch: | |
ld a,(ix) ; lê um caracter da string | |
cp "/" ; procura pelo início de um parâmetro | |
jr z,command ; é "/" verifica se há um comando | |
inc ix ; incrementa 'IX' | |
djnz switch ; enquanto B>0 faz o laço | |
jr endLoop ; vai para o final da rotina | |
command: | |
ld a,(ix+1) ; lê o caracter seguinte | |
cp c ; compara com 'C' | |
ret z ; sai se for zero | |
inc ix ; incrementa 'IX' | |
djnz switch ; enquanto B>0 faz o laço | |
endLoop: | |
neg ; nega 'A' (força "NÃO ZERO") | |
ret ; sai da rotina | |
endp |
Para montar use:
$ pasmo -d -v testArgs.asm testargs.com
Este programa é só um exemplo, ele identifica um nome de arquivo e a presença dos parâmetros “/H” ou “/h” para exibir a mensagem de ajuda. Estes parâmetros são excludentes (é um ou o outro — no caso é o primeiro a ser utilizado) mas esta é uma característica do programa e não desta rotina.
(³) Neste momento estou desconsiderando a compatibilidade com a versão 2.x do MSX-DOS que tem suporte a subdiretórios.
Finalizando esta parte
Deixei disponível uma imagem de disco com estes dois programas já montados para quem quiser se divertir um pouco. E agora com todas as rotinas prontas é possível juntar tudo para, finalmente, criar a ferramenta. Aliás eu acho que deveria ter escrito o visualizador em SDCC e não diretamente em Assembly, mas agora é tarde… 🙂
Até!
Pingback: Argumentos da linha de comando com docopt | giovannireisnunes