quarta-feira, 1 de fevereiro de 2023

Curiosidade: Testei o ChatGPT! E ele NÃO sabe programação!!

Pessoal,


Aproveitando toda a hype do ChatGPT, resolvi fazer um teste também.

Eu já tinha testado algumas vezes e achei bem interessante. Mas tinha sido mais para observar a tão citada naturalidade de linguagem e ver como estava a conversação em inglês e português. Em inglês a coisa flui melhor, confesso.

O ChatGPT, caso você tenha passado os últimos meses em Plutão, é um chatbot com inteligência artificial desenvolvido pela OpenAI, com técnicas de aprendizado supervisionado e por reforço. Apesar de toda a empolgação da imprensa em geral, quando lemos a percepção da imprensa especializada, a coisa muda um pouco de figura, como vocês verão abaixo.

Para esse post, resolvi trazer uma conversa que tive com o ChatGPT sobre programação em MSX, coisa básica mesmo. Percebam que a resposta dele é bonita, mas não tão precisa ou correta.

Iniciei perguntando se ele sabe criar códigos para MSX, especificamente BASIC.


Legal! O código é tipo um "Hello, World!". Ele explica ainda como funciona, fala do comando 'END' para encerrar o programa (o que na prática é desnecessário, porque o MSX-BASIC termina o programa automaticamente quando chega no final) e ainda cita que deve ser apertado o botão 'RUN' ou digitar esse comando para iniciar o programa. Sim, o MSX tinha um botão para iniciar (na verdade era só um atalho de teclado, mas vá lá...).

Depois eu pedi a mesma função em MSX-Assembly, vulga "Linguagem de Máquina".  Aqui as coisas começam a apertar para a Inteligência Artifical. Vejam só:


Ele começa colocando o programa em $0100, que é realmente uma posição possível, pois não é acessível pelo BASIC (os programas em BASIC começam em $8000). Entretanto, geralmente começávamos em $C000, o ínicio da terceira página do bloco de memórias do MSX.

Ele cria uma alocação para colocar uma string ("Olá, eu sou o Jayme") e termina essa string com um caracter nulo (zero). Lá no começo e usa o registrador HL (um registrador de 16 bits muito utilizado para guardar posição de memória) para anotar onde está a string (algo como um ponteiro do C). Cria uma subrotina "print" e manda o processamento para lá. No final, acabando a função da subrotina "print", o processamento volta para a função principal e pula (veja o comando "jp", de jump) para a subrotina "end" que por sua vez está... vazia! O programa provavelmente ira travar ou apresentar um erro...

A subrotina "print" é bem interessante. Ele carrega ("ld", de load) o conteúdo do endereço registrado em HL para o registrador "a", incrementa em um o endereço de HL ("inc hl), faz um teste lógico com o conteúdo de "a" e toma um decisão: se o conteúdo de "a" é zero/nulo (lembra que ele colocou um zero no final da string?), chegou ao final da string e aí ele desvia para a subrotina "endprint"; se o conteúdo de "a" não é nulo, ele chama um função da BIOS ($bb18) para imprimir um caractere na tela e depois volta para o início da subrotina "print" para imprimir o próximo caractere da string.

Tem outros modos mais rápidos e que gastam menos bytes para fazer isso, como mostrarei abaixo.

De todo modo, algumas cosiderações são pertinentes e devem ser feitas:

* - "or a, a" - Esse é um teste que ele sugere para saber se o caractere de "a" é zero. 

(Linguagem de Máquina, Rossini-Figueredo, 1a edição - 1987 - Ed. Aleph)

Ou seja, ele compara "a" com "a"; se "a" já tiver chegado ao zero e for zero, essa operação da zero e ele salta a subrotina "endprint", caso contrário segue em frente. Só que a sintaxe correta é apenas OR A, uma vez que ele faz a operação de A com o que você pedir (no caso A). Assim, OR A, A escreve-se apenas OR A. O mesmo se fôssemos comparar B, seria OR B (de OR A, B). A lógica está correta, a sintaxe está errada.

* - "$bb18" - Em Assembly, muitas vezes, usamos os números invertidos para representar os endereços. Esse endereço aí $bb18, confesso que nunca vi.

Imaginei então que seria $18bb. Fui procurar nos livros aqui em casa porque realmente não me lembrava qual a rotina da BIOS que imprimia caractere na tela. E... não é essa, até porque essa não existe :(

O correto era usar o ponteiro para indicar a subrotina e não o endereço direto. A função que eu usava era a WRTVRM (Write To VRAM), acessível com um CALL $004D, onde o byte a ser impresso estava no registrador (acumulador) "a" e o endereço da VRAM era o de HL. Então vemos que o programa tem alguns erro... E erros sérios!

Além disso, a subrotina "endprint" contém o comando "ret", de Return. Mas não é retorno para onde estava quando foi acessado, é retorno para o MSX-BASIC. Ou seja, acaba o programa. Esse final deveria ser na subrotina "end", que como disse acima, está vazia.

Confesso que não consultei na hora o valor da função da BIOS mas questionei a subrotina e o comando "ret". Então ele corrigiu:


Depois, tivemos uma longa conversa onde eu expliquei todos esses problema.

A função da BIOS talvez devesse ter sido a WRTVRM ($004D), como eu citei acima, desde o início, mas o ChatGPT ainda tentou usar a função RDSLT que apenas lê o byte do endereço apontado pelo registrador HL.

(Fonte: Livro Vermelho do MSX)

Depois tentou fazer outro código usando a mesma lógica, mas com a função da BIOS correta. Exceto que usou o registrador HL para apontar para o endereço da string enquanto, nessa função, ele aponta para o endereço da VRAM.

Bom, abaixo eu mostro o que eu faria de diferente (e que funciona!).

Esse primeiro caso usa a função WRTVRM, carrega o programa para a posição correta na memória e faz a sequência correta dos passos:
WRTVRM: EQU $4D             ;ponteiro para a função WRTVRM
        ORG $C000           ;inicia o programa
        LD HL,0             ;endereço da VRAM - posição 0x0 da tela
        LD BC, MESSAGE      ;endereço do início da mensagem
INICIO: LD A, (BC)          ;carrega a primeira letra em A
        CALL WRTVRM         ;chama a função
        INC HL              ;próxima posição na tela
        INC BC              ;próximo caracter
        OR A                ;se A for nulo,
        JR Z, END           ;vai para o fim
        JR INICIO           ;se A não for nulo, reinicia
END:    RET
MESSAGE:DEFM "Olá, eu sou o Jayme"
        DEFB 0
Outra opção seria trocar a condicional: ao invés de "OR A", fazer um "CP 0":

WRTVRM: EQU $4D             ;ponteiro para a função WRTVRM
        ORG $C000           ;inicia o programa
        LD HL,0             ;endereço da VRAM - posição 0x0 da tela
        LD BC, MESSAGE      ;endereço do início da mensagem
INICIO: LD A, (BC)          ;carrega a primeira letra em A
        CALL WRTVRM         ;chama a função
        INC HL              ;próxima posição na tela
        INC BC              ;próximo caracter
        CP 0                ;testa para saber se A é nulo
        JR NZ, INICIO       ;se A não for nulo, reinicia
        RET                 ;fim 
MESSAGE:DEFM "Olá, eu sou o Jayme"
        DEFB 0

Essa última opção economiza alguns bytes. Muito importante se a gente usa um computador lá da década de 1980 com escassos 64KB...

Outra opção seria usar a função LDRIKVM, que copia um bloco inteiro da RAM para a VRAM (ou seja, copia a string inteira). Aqui precisamos de alguns outros registradores. 

(Fonte: Livro Vermelho do MSX)

Veja que usaremos outros registradores agora. Como iremos copiar um bloco da RAM, precisamos saber o tamanho da string.

O

l

á

,


e

u


s

o

u


o


J

a

y

m

e

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20


Aí está, nossa string tem 19 caracteres (20 se fossemos contar o caracter nulo).


LDIRVM: EQU $5C             ;ponteiro para a função WRTVRM
        ORG $C000           ;inicia o programa
        LD DE, 0            ;endereço da VRAM - posição 0x0 da tela
        LD HL, MESSAGE      ;endereço do início da mensagem
	LD BC, 19	    ;tamanho da string
        CALL LDIRVM         ;chama a função
RET ;fim MESSAGE:DEFM "Olá, eu sou o Jayme"
Vejam como essa fica bem mais enxuta. Tem um ponto aqui que definimos o tamanho da string no código. Poderíamos ter criado uma subrotina que contaria o tamanho da string até o nulo (daria 20) e retiraríamos o nulo (20 - 1 = 19). Outra opção.

E uma última opção seria a função CHPUT ($00A2). Essa função imprime o byte presente no acumulador em modo texto (seja Screen 0, 40x24 ou Screen 1, 32x24).  Nesse caso, o processo é similar ao que ele estava fazendo.

(Fonte: Livro Vermelho do MSX)

Aqui seria mais ou menos igual as anteriores, imprimindo um caracter por vez, só que obrigatoriamente em modo texto (Screen 0 ou 1).

CHPUT:  EQU $A2             ;ponteiro para a função CHPUT
        ORG $C000           ;inicia o programa
        LD HL, MESSAGE      ;endereço do ínicio da mensagem
INICIO: LD A, (HL)          ;carrega a primeira letra em A
        CALL CHPUT          ;chama a função
        INC HL              ;próximo caracter
        CP 0                ;testa para saber se A é nulo
        JR NZ, INICIO       ;se A não for nulo, reinicia
        RET                 ;fim 
MESSAGE:DEFM "Olá, eu sou o Jayme"
        DEFB 0


Quando joguei este código no ChatGPT, ele escreveu isso:


Ele falou exatamente o que o programa faz e o que eu havia pedido lá no início...

Concluindo: a ferramenta é muito poderosa, inquestionável. Mas ainda está longe de ser autosuficiente.  A ferramenta parece genial, de outro mundo, desde que você não domine o assunto a ser discutido. Neste caso, você consegue encontrar vários erros.

Cabe lembrar que o ChatGPT está funcionando desde novembro de 2022, ou seja, menos de 3 meses do dia em que escrevo esse post. Imagine como estará em um ou em dez anos...

Em tempo: não me lembrava como era gostoso programar em Assembly!

Caso queiram fazer alguma brincadeira, testem os códigos no vários emuladores online:





Por enquanto é isso!