Orientação a “objetos” em assembly Z80

oop_z80-abertura

Esta ideia surgiu enquanto pensava em formas de implementar o “herói” e as demais “caixas” (ou inimigos) no Survive sem transformar o programa em uma “maçaroca” de código. Mas seria possível utilizar o tal paradigma de orientação a objetos diretamente em assembly E, caso possível, seria prático, funcional ou uma mera curiosidade?

A primeira pergunta foi fácil de responder, me lembrava vagamente de um livro¹ sobre o assunto e numa busca rápida encontrei um artigo da Dr. Dobbs de março de 1990 que me ajudou a visualizar como poderia ser feito e uma (re)leitura da documentação do Pasmo indicou como fazê-lo.

Quanto a funcionalidade, vejamos se consigo ser convincente… 🙂

(¹) Em tempo, o Object-Oriented Assembly Language escrito por Len Dorfman e publicado pela Windcrest em 1990. Foi publicado no Brasil pela Makron Books sob o título de Linguagem Assembler Orientada para Objetos no ano de 1992.

Definindo uma “classe”

Após algumas experiências montei um modelo genérico, ou seja, uma estrutura básica para a simulação de classes de objetos em assembly de Z80 utilizando apenas os recursos disponíveis no Pasmo:

macro ____«Nome da Classe», object, pointer
          proc
          ; scope definition
          local «atributo/método local»_of_ ## object
          public «atributo/método público»_of_ ## object
          ; definições de atributos
          «atributo»_of_ ## object : equ pointer
              ...
          ; construtor
          initialize_ ## object :
                  ...
                  jp «nome da classe»_initialize
          ; chamadas dos métodos
          «método»_of_ ## object :
                  ...
              ...
          endp
      endm

; rotinas usadas pelos métodos desta classe
...
«nome da classe»_initialize
    ...

Na definição da macro o parâmetro ‘object‘ indica o nome da instância, ‘pointer‘ é o endereço de memória onde os dados estarão armazenados — sim, não deixa de ser assembly, logo onde os dados ficarão continua “problema” do programador — e «Nome da Classe» é justamente o que diz, o nome “classe”.

A utilização do operador ##, informa ao Pasmo para utilizar o nome do parâmetro e não o valor que referencia fazendo com que o assembler crie novos rótulos durante o processo de expansão da macro.

E com as diretivas proc e endp dentro da macro é possível controlar o escopo dos rótulos criados.

Uma leve subversão

A sintaxe tradicional de «nome do objeto»{separador}«nome do atributo/método» precisou ser invertida para que o Pasmo não acusasse erros na tradução dos rótulos referenciados com o operador ##. Daí a inversão na posição dos nomes, primeiro o atributo/método e depois o nome do objeto e com a string “_of_” como separador.

Os “métodos”

Para economizar memória é útil deixar a parte funcional dos “métodos” do lado de fora da macro, assim o código não será replicados sem necessidade quando ela for expandida. Na definição do “método” fica apenas o código necessário para a passagem de parâmetros.

A estrutura do programa

Para se instanciar corretamente uma “classe” a estrutura do código precisa ser assim:

            ...
            org «endereço inicial»
            ...
            call initialize_«nome da instância»
            ...
            ; arquivos da classe (macro e rotinas)
            ...
        ____«Nome da Classe» «nome da instância», «endereço»
            ...

Esta ordem é importante já que o Pasmo precisa da definição da macro definida antes de poder utilizá-la, diferentemente dos rótulos que (ainda) serão criados na expansão.

A “classe” Person

Para colocar em prática o conceito escolhi implementar uma “classe” Person (original, não?) para ver se isto realmente funcionaria.

Definindo os “atributos”

A partir da estrutura básica comecei com a definição dos “atributos” da classe.

macro   ____Person, object, pointer
            proc 
            local name_of_ ## object
            public age_of_ ## object
            name_of_ ## object : equ pointer
            age_of_ ## object : equ pointer+49
            endp
        endm

Nesta “classe” estão definidos os seguintes rótulos que funcionarão como “atributos”:

  • name_of_ ## object” — como seu escopo é local dentro da macro é, portanto, de acesso privado e visível apenas dentro da “classe” e
  • age_of_ ## object” — com escopo público e funcionando como “atributo” de acesso público.

Claro, aí não está tão explícito, o primeiro rótulo equivale a uma string de 49 posições (até 48 caracteres e terminado em NULL) e o segundo um valor numérico de 1 byte.

Criando o “método” construtor

O método construtor de Person é bem simples e tudo o que faz é zerar o valor de age_of_ ## object e armazenar um “$”+NULL² a partir do endereço de memória name_of_ ## object (o que basicamente “zera” a string):

            ...
            initialize_ ## object
                ld de,age_of_ ## object
                ld hl,name_of_ ## object
                jp person_initialize
            ...

Dentro da rotina coloco os ponteiros dos”atributos”, em DE o de age e em  HL o de name, algo equivalente a fazer ld de,age_of_this e ld  hl, name_of_this, e então chamo o código responsável pela inicialização da “classe”.

(²) Para simplificar o exemplo, escolhi usar a rotina de escrita de strings do MSX-DOS e que imprime na tela uma sequência de caracteres terminada em “$”.

Os demais “métodos”

Inicialmente um “método” para armazenar um valor no “atributo” nome  (visto que ele é privado dentro da “classe”):

            ...
            store_name_of_ ## object
                ld hl,name_of_ ## object
                jp person_store_value
            ...

O que a rotina em person_store_value faz é copiar o conteúdo de uma string apontada em DE para a string em HL — é quase como a strcpy() do C — e como o rótulo só existe dentro da estrutura da macro a escrita de valores nele só funciona se chamada de dentro da “instância”.

Um outro “método” serve para escrever o conteúdo do “atributo” nome na tela:

            ...
            print_name_of_ ## object
                ld de,name_of_ ## object
                jp person_print_string
            ...

Assim como no primeiro ele posiciona DE para o endereço onde o valor do “atributo” está armazenado e aí chama a rotina de impressão. O correto seria fazer algo que permitisse extrair o conteúdo do “atributo” mas o código ficaria desnecessariamente longo.

Aqui há um padrão, ou seja, as rotinas person_ são as mesmas para qualquer “objeto”, o que muda são os parâmetros que elas recebem. Ao alterá-las você muda o comportamento de quem a utilize. Por exemplo, a person_print_string usa a rotina de impressão de strings do MSX-DOS (que é lenta) e se eu a alterasse para usar diretamente as rotinas da BIOS todos os “objetos” seriam afetados.

Por último um “método” que não faz chamadas para rotinas externas à “classe”:

            ...
            increase_age_of_ ## object
                ld hl,age_of_ ## object
                inc (hl)
                ret
            ...

Ele coloca em HL o a posição onde o “atributo” age está armazenado e ai incrementa o conteúdo daquele endereço da memória. Aliás, como este “atributo” é público isto poderia ser feito normalmente fora da “classe”.

Instanciando a “classe” Person

Seguindo o roteiro mostrado acima o código final seria assim:

            ...
ramArea:    equ 0xc000
            org 0x0100
            ...
            call initialize_giovanni
            call initialize_fulano
            ...
            ; arquivos da classe Person (macro e rotinas)
            ...
        ____Person giovanni, ramArea
        ____Person fulano, ramArea+50
            ...

A execução do construtor da “classe” não acontece de forma automática então é necessário fazê-la manualmente no começo do programa.

O resultado final

Juntando tudo, o programa fica assim:

O resultado da execução (pois as pessoas sempre querem ver screenshots dos programas):

oop_z80-msx-dos

Apenas para comparação, uma versão análoga do programa escrita em C++:

A escolha do C++ foi proposital, repare como os dois programas tem estruturas parecidas.

Considerações finais

Funcionou sem produzir muito malabarismo no código — exceto, claro, a necessidade de execução manual do “método” construtor, mas isto poderia ser resolvido com um pré-processador. Estão simulados diretamente os conceitos de abstração (as instâncias dos “objetos” são únicas e com “atributos” e “métodos” próprios) e de encapsulamento³ (os “atributos” e “métodos” definidos como locais existem somente dentro da “classe”).

E de forma indireta, também existe herança (é possível compor manualmente uma nova “classe” elencando “atributos” e “métodos” de outras) mas, com certeza, não há polimorfismo (confesso que nem quis me aventurar nesta parte).

Voltando ao Survive, até é possível imaginar como seriam as “classes”, “atributos” e até alguns dos “métodos” que definiriam os elementos que se movimentam pela tela. 🙂

(³) Claro, todo o “encapsulamento” possível considerando que seu programa, em tese, tem acesso indiscriminado a todo o conteúdo da RAM.

Anúncios

5 comentários sobre “Orientação a “objetos” em assembly Z80

  1. No Tetrinet eu fiz de um jeito mais natural. Usei as macros do m80 para calcular sozinho os offsets de cada parâmetro (eu só preciso passar o tamanho de cada um), e o ponteiro para o “this”/”self” fica no IX, então eu não preciso gerar um método para cada instância como você fez (isso aumenta o tamanho do executável, no meu caso era importante porque eu queria 4kb). Na minha abordagem o construtor vira um LDIR.

    https://github.com/ricbit/Oldies/blob/master/2000-05-tetrinet/tetrinet.mac

    Curtir

    • Dá para padronizar a passagem de parâmetros com IX e IY (ou fazer com ‘push’ e ‘pop’) mas quis manter tanto a clareza do código (a pessoa vê e, bem ou mal, encontra onde estão as coisas) como garantir ainda um certo grau de encapsulamento para os “objetos”, inclusive com a definição de “atributos” e “métodos” privados — por mais que o Guido van Rossum diga que todos nós somos adultos e nada temos a esconder, hahaha…

      Curtir

  2. Pingback: Orientação a objetos em Python, o básico | giovannireisnunes

  3. Hehehe, quando eu estava na faculdade eu mencionei esse livro “Linguagem Assembler Orientada para Objetos” pro meu professor de Linguagens de Programação, o Roberto Bigonha (que aliás é um cara que eu admiro muito), ele achou a ideia uma perversão! 😀 Ele também dizia que FORTH mal era linguagem de programação, já que na verdade o parser era o próprio desenvolvedor… 😀

    Curtir

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s