Você está na página 1de 43

O COBOL a Pouco e Pouco

87/10/18
(revisto em 91/11/10)

Pedro Guerreiro
Departamento de Informática
Faculdade de Ciências e Tecnologia
Universidade Nova de Lisboa

1. COBOL, Linguagem Maldita

A linguagem de programação COBOL foi definida pela primeira vez em


1960, por uma tal comissão CODASYL, constituída por representantes dos fa-
bricantes e dos utilizadores. A comissão tem-se mantido em funções desde en-
tão, encarregando-se do desenvolvimento da linguagem (ou, como se diz, da
sua “manutenção”). Pontos importantes desse trabalho são as normas publi-
cadas pelo American National Standards Institute (ANSI), em 1968 e 1974.

O campo de aplicação preferencial, para o qual o COBOL foi concebido,


são os problemas administrativos e comerciais, tais como a facturação, a con-
tabilidade, os salários, a gestão de stocks, etc. Isto determinou, aliás, o pró-
prio nome da linguagem, que é uma sigla para a expressão inglesa “Common
Business Oriented Language”.

A propósito do Cobol, como a propósito de muitas outras coisas da vida,


as opiniões encontram-se um bocado extremadas (ou “radicalizadas”, para
usar a terminologia da moda). De um lado estão os que com veemência defen-
dem a sua utilização e o seu ensino, na base de que continua a ser a lingua-
gem mais usada na “vida prática”, e que, portanto, qualquer programador
competente a deve dominar com segurança. Os anúncios constantemente pu-
blicados na imprensa reflectem e sustentam bastante bem este ponto de vista
(ainda que cada vez menos…) De outro, estão aqueles que consideram a lin-
guagem obsoleta, imprópria para consumo e responsável por muitos dos ma-
les que afligem a Programação, e que, por conseguinte, o melhor é eliminá-la
o mais depressa possível. De acordo com esta corrente, e à míngua de medi-
das mais drásticas, deveria ser proibido ensinar COBOL, e assim a lingua-
gem desapareceria quando morresse o último dos programadores COBOL, li-
vrando-se a humanidade de mais uma praga medieval… Existe ainda uma
terceira posição, um pouco mais subtil, opinando que, afinal de contas, a im-
portância do COBOL é mais fictícia que real, pois com o aparecimento dos
“sistemas geradores de aplicações”, também chamados linguagens de quarta
geração, grande parte das tarefas para as quais pretensamente o COBOL es-
tá vocacionado deixarão, pura e simplesmente, de ter razão de existir. Em
1
muitos casos, o utilizador final sentar-se-á ele próprio ao seu terminal, e em
ameno diálogo com o computador definirá as características da sua gestão de
stocks, ou do seu mapa de vendas. Depois, o sistema gerador de aplicações en-
carregar-se-á de gerar automaticamente os programas convenientes, numa
linguagem que até poderá ser o COBOL… Finalmente, para aumentar a con-
fusão, não faltará quem pense que hoje em dia já quase nem é preciso progra-
mar: para resolver o tipo de problemas para os quais o COBOL foi imaginado,
os programas até já estão feitos. Basta ir à loja e comprá-los, o que manifesta-
mente dá muito menos trabalho e chatices.

O compromisso entre estas quatro correntes parece um tanto difícil.


Provavelmente, todas terão um bocado de razão. Por isso, talvez seja pruden-
te explicitarmos um pouco qual a atitude veiculada pelo presente texto.

Num primeiro momento, aceitamos que o COBOL é uma linguagem


que tem o mérito de existir. Enquanto linguagem, é um conjunto de regras de
descrição, e a tarefa de programar em COBOL (como em qualquer outra lin-
guagem) consiste em usar essas regras da melhor maneira para produzir pro-
gramas que sejam soluções eficientes para os problemas que temos entre
mãos. Como tal, não interessa especular sobre se a linguagem é boa ou má, e
carpir indolentemente a falta dos requisitos que nos habituámos a prezar. O
que importa é que o compilador funcione de acordo com o previsto, que a do-
cumentação seja clara e acessível, e que o ambiente de programação não atra-
palhe. Claro que, num segundo momento, teremos que analisar a linguagem
para avaliar os mecanismos de abstracção que ela fornece, tanto ao nível dos
dados, como dos procedimentos e estruturas de controlo, e, então, não pode-
mos deixar de concluir da sua pobreza a esse respeito. Não é de admirar, para
uma linguagem cuja concepção data de 1960. No entanto, como os mecanis-
mos de abstracção constituem a maneira mais eficaz (senão a única maneira)
de dominar a complexidade das situações que os programas têm que descre-
ver, os programadores acostumados a conceitos mais evoluídos não deixam de
sentir-se um bocado espartilhados quando têm que exprimir-se em COBOL.
Mas, mesmo assim, há que ser imparcial, e reconhecer que nem sempre a cul-
pa será de assacar inteiramente à linguagem. Frequentemente, muitas das
dificuldades resultam de se tentar impor um estilo de programação, adequado
noutras circunstâncias, mas ao qual o COBOL reage mal, porque lhe faltam
os conceitos, e isto em vez de se tentar tirar o melhor partido das possibilida-
des que a linguagem realmente oferece.

Programar em COBOL, nos tempos que correm, pode parecer uma per-
da de tempo. Isso é verdade, até certo ponto. Mas, encarando as coisas de ou-
tro modo, podemos retirar bastante gozo desse exercício. Por um lado, há o
gosto um tanto perverso de fazer precisamente o contrário daquilo que as ca-
beças bem-pensantes sabiamente recomendam. Depois, há no COBOL algo de
kitsch, de irremediavelmente antiquado, de confrangedoramente fora de mo-
da, que lhe dá um encanto inigualável. Os programas COBOL bem feitos (ou
seja, os que nós fazemos) nunca deixam de nos surpreender: “como é que se
consegue fazer um programa destes com uma linguagem destas?”. Finalmen-

2
te, resolver um problema em COBOL é como fazer uma viagem num Rolls-
-Royce dos anos vinte: grande, pesado, difícil de manobrar, sedento de gasoli-
na, mas com incomparavelmente mais classe que os pequenos carritos de hoje
em dia, por muito rápidos, confortáveis e económicos que sejam…

A linguagem de programação COBOL vai aparecer nestas notas atra-


vés de exemplos. Não se pretende cobri-la toda (salvo seja…), mas apenas os
conceitos fundamentais, que a diferenciam das restantes. Também não se ga-
rante que todos estes sejam tocados. Para uma descrição completa, dever-se-á
consultar o manual de referência de cada instalação.

2. Características gerais

Sem excessivo rigor, pode considerar-se que as principais característi-


cas distintivas da linguagem COBOL são as seguintes:

• Todos os nomes são globais.


• Os procedimentos não têm parâmetros.
• Não se pode iterar sobre instruções arbitrárias, mas apenas sobre
chamadas de procedimento.
• Os procedimentos podem ser usados antes da declaração.
• Existe uma instrução SEARCH para buscas em tabelas, e uma
instrução SORT para ordenar ficheiros.
• Dispõe de uma variedade de mecanismos para programar a
impressão de mapas.

O facto de todos os nomes serem globais e acessíveis de todo o progra-


ma, aliado à circunstância de haver frequentemente uma grande quantidade
de entidades nomeáveis, obriga a usar uma grande disciplina na escolha dos
nomes, sob pena de a confusão se instalar bem cedo…

Os procedimentos, na nomenclatura COBOL chamados parágrafos, são


a principal ferramenta de estruturação algorítmica. O facto de não terem pa-
râmetros e manipularem apenas variáveis globais causa a princípio uma cer-
ta apreensão. Por outro lado, o só ser possível programar iterações sobre pa-
rágrafos, dá lugar a que por vezes se criem parágrafos “artificiais”, que não
correspondem a nenhuma operação abstracta significativa.

O facto de se poder chamar um procedimento antes de o ter declarado


dá bastante jeito, no quadro de uma programação descendente: a decomposi-
ção escolhida pode ficar registada no programa, pela ordem por que (teorica-
mente) foi alcançada, e não às avessas, como em Pascal, por exemplo.

As instruções SEARCH e SORT, vêm mesmo a calhar, pois são bastante


flexíveis e resolvem problemas que ocorrem frequentemente, e que, de outra
maneira requereriam uma programação explícita, envolvendo algoritmos deli-
cados, para os quais o COBOL não é de utilização cómoda.

3
Finalmente, a abundância de possibilidades para definir os “outputs”
(uma concessãozita ao mau-gosto…) é muito apreciada na produção de todo o
tipo de documentos comerciais e administrativos: recibos, facturas, mapas,
etc. (Só não se percebe bem por que razão aparece sempre tudo em maiúscu-
las, e sem acentos, e numas folhas monstruosas, difíceis de consultar…)

3. Primeiro exemplo: entrada de dados para um ficheiro de


actualização de um ficheiro de pessoal.

3.1. Enunciado do problema

Considere-se o seguinte problema: “Uma certa empresa dispõe de uma


“base de dados” de pessoal, implementada na forma de um ficheiro sequen-
cial, cujos registos contêm a seguinte informação relativa a cada funcionário:
número de funcionário (4 algarismos), apelido (16 caracteres), resto do nome
(32 caracteres), número do bilhete de identidade (8 algarismos), número de
contribuinte (9 algarismos), data de nascimento (6 algarismos, na forma
AAMMDD) e morada (80 caracteres agrupados em duas linhas de 30, e uma de
20). Este ficheiro é periodicamente actualizado, para inserção de novos fun-
cionários, remoção dos que abandonaram a empresa (despedimento, reforma,
etc.), actualização ou correcção de informações obsoletas ou erróneas. A ac-
tualização é efectuada com base num ficheiro de actualizações, que conterá as
informações necessárias. Pretende-se um programa para promover a entrada
de dados interactiva para este ficheiro.”

Existem, pois, três tipos de operações: criação, remoção e modificação.


Quando se cria um funcionário, todos os campos terão que ser preenchidos (no
caso da morada, pelo menos uma das linhas não deverá ficar em branco); pa-
ra remover, basta indicar o número; para modificar, será preciso apenas
preencher os campos objecto de modificação, além do número, para identificar
o funcionário “modificado” (neste caso, a morada é considerada globalmente
como um campo).

3.2. Definição do ficheiro das actualizações

A definição de um ficheiro sequencial em COBOL envolve três opera-


ções: declaração da variável ficheiro, associação desta variável ao ficheiro ex-
terno, e descrição do conteúdo dos registos. As três são especificadas em pon-
tos diferentes do programa.

3.2.1. Identificação do ficheiro

A identificação do ficheiro envolve a declaração da variável e o “come-


ço” da associação ao ficheiro externo. Em COBOL ela é realizada pela cláusu-
la SELECT, que aparece no parágrafo FILE-CONTROL, na secção INPUT–
OUTPUT, na divisão ENVIRONMENT. Em esquema:

4
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT F-ACT ASSIGN TO ID-F-ACT.

F–ACT é a variável ficheiro e ID–F–ACT é a variável que conterá o no-


me do ficheiro externo, uma cadeia de caracteres, portanto. Esta terá que ser
declarada explicitamente mais tarde, na secção WORKING–STORAGE, que faz
parte da divisão DATA. Se estabelecermos que o nome do ficheiro externo tem
no máximo trinta e dois caracteres, as coisas virão mais ou menos assim:

DATA DIVISION.
WORKING-STORAGE SECTION.
77 ID-F-ACT PIC X(32).

Estes dois pequenos troços de programa ilustram muitos pontos impor-


tantes. Primeiro, a organização do programa COBOL em divisões, as quais se
subdividem em secções, que por sua vez são formadas por parágrafos. Depois,
a escrita dos nomes em COBOL: as regras são semelhantes às habituais, com
a diferença de poder usar–se hífenes, excepto na primeira posição. (Isto impli-
ca que o sinal “-” não poderá ser usado para denotar a subtracção, sem pre-
cauções suplementares.). Finalmente, a expressão:

77 ID-F-ACT PIC X(32).

significa que ID–F–ACT é uma variável “indecomponível” (isto é especificado


pelo número de nível 77) de um tipo packed array [1..32] of char (e
isto pelo PIC X(32)). No entanto, ao contrário do Pascal, este vector vem
sem funções de acesso, pelo que não é possível seleccionar directamente o
i-ésimo elemento.

Note-se que, se os identificadores e as palavras reservadas têm apare-


cido em maiúsculas, isso não é por acaso. Com efeito, os programas COBOL
são todos escritos só com maiúsculas, exceptuando eventualmente comentá-
rios e cadeias de caracteres constantes. Um outro ponto que tem a ver com a
escrita em COBOL, e que os exemplos não ilustram, consiste no facto de, tipi-
camente, o texto do programa se encontrar inteiramente entre as posições 8 e
72 de cada linha. Isto tudo pode parecer estranhíssimo, hoje em dia, mas é
preciso lembrar que o COBOL já cá andava na época em que os programas se
escreviam sobre cartões perfurados, cujo modelo mais popular tinha oitenta
colunas. Reservavam-se as seis da esquerda mais as oito da direita para nu-
merar os cartões, não fossem eles cair no meio de chão e lá ficava o programa
todo baralhado. A coluna 7 fica reservada para identificar as linhas de comen-
tário, quando contém um asterisco, e também as de continuação, quando con-
tém um hífen. Mas não é tudo: as colunas da 8ª à 11ª (inclusive) constituem a
chamada área A e só podem conter os nomes (ou o início dos nomes) de divi-
são, secção ou parágrafo; os textos propriamente ditos devem ficar inteira-
mente na área B, entre a 12ª e a 72ª colunas.

5
Quanto às linhas de continuação, convém referir que as instruções
COBOL podem passar de umas linhas para as outras, sem nenhuma indica-
ção especial. O hífen na coluna 7 usa-se, por exemplo, quando se quer “partir”
um identificador ou uma cadeia de caracteres. No entanto, o melhor é organi-
zar as coisas de modo a evitar essas situações, porque as regras de utilização
das linhas de continuação não são assim lá muito “ortogonais”…

3.2.2. Descrição do conteúdo dos registos

Uma das coisas mais engraçadas do COBOL é a maneira como se defi-


nem os registos. A operação ocorre numa “espécie” de parágrafo, dentro da
secção FILE, na divisão DATA. No nosso caso, as coisas apresentar-se-iam as-
sim:

DATA DIVISION.
FILE SECTION.
FD F-ACT.
01 ACT-REG.
02 ACT-TIPO-OP PIC A.
02 ACT-INFO.
03 ACT-NUM-FUNC PIC 9(4).
03 ACT-NOME.
04 ACT-APELIDO PIC X(16).
04 ACT-RESTO-NOME PIC X(32).
03 ACT-BI PIC 9(8).
03 ACT-NO-CONTRIB PIC 9(9).
03 ACT-NASC.
04 ACT-NASC-ANO PIC 99.
04 ACT-NASC-MES PIC 99.
04 ACT-NASC-DIA PIC 99.
03 ACT-MORADA.
04 ACT-MORADA-1 PIC X(30).
04 ACT-MORADA-2 PIC X(30).
04 ACT-MORADA-3 PIC X(20).

O título FD F-ACT indica que o que se segue é a descrição dos registos


do ficheiro F–ACT. Os 01, 02, 03 e 04 são chamados números de nível, e pode-
mos imaginá-los representando os níveis de profundidade de uma árvore.
Com o número 01, encabeçando a descrição, aparece o “nome” do registo. Fa-
zendo mais uma analogia com o Pascal, podemos considerar que o nome do
registo corresponde à variável–janela do ficheiro (f^, para um ficheiro f). A
cada mudança de nível corresponde uma decomposição do campo inicial. Por
exemplo: o campo ACT-NOME, de nível 03 é formado por dois campos ACT–
APELIDO e ACT-RESTO-NOME, ambos de nível 04. Os campos elementares,
que não são mais subdivididos, vêm acompanhados por uma cláusula PIC,
que, tal como na WORKING–STORAGE, especifica a “natureza” ou “tipo” do cam-
po. Nesta descrição encontramos PIC X(16), PIC X(32), etc., representan-
do cadeias com 16, 32, etc., caracteres; PIC 9(4), PIC 9(8) e PIC 9(9) pa-
ra cadeias de 4, 8 e 9 algarismos; PIC 99, que, como se pode adivinhar, é

6
uma abreviatura de PIC 9(2); e PIC A, denotando um carácter que só pode
ser uma letra ou um espaço. Em geral, portanto, poderão especificar-se ca-
deias de caracteres arbitrários, de letras e espaços, ou de algarismos apenas,
com comprimento à escolha, na forma PIC X(...), PIC A(...) e PIC
9(...), respectivamente.

Como o exemplo sugere, apenas os campos elementares têm PIC explí-


cito. O PIC dos restantes fica definido implicitamente, com tipo X e compri-
mento correspondente à soma dos comprimentos de todas as componentes.
Assim, por exemplo, o PIC de ACT-NOME é X(48), o de ACT–NASC é X(6) e o
de ACT–REG é X(156).

Verifica-se que o registo é formado por duas partes principais: a pri-


meira, com um único carácter, indica, como o nome sugere, qual a operação
em causa; a segunda contém os campos necessários para guardar todos os va-
lores que constituem os “argumentos” da operação, por assim dizer.

Da circunstância de no exemplo todos os campos começarem pelas le-


tras A C T, não se deve concluir que existe qualquer espécie de obrigação de
baptizar uniformemente as diversas componentes de um registo. Em COBOL,
a escolha dos nomes é completamente livre, dentro do respeito pelas conven-
ções habituais. No entanto, como fica tudo global, e também porque é comum
haver muitas entidades nomeáveis, por vezes semelhantes, é boa prática im-
por desde logo uma certa disciplina.

3.3. Estratégia de resolução: estrutura global

Trata-se nitidamente de um programa iterativo. Em primeira aproxi-


mação, cada passagem corresponderá à entrada dos dados referentes a um
registo do ficheiro de actualizações, e estes dirão respeito à criação, à remo-
ção, ou então à modificação dos dados dos funcionários. Ter-se-á, claro, que
providenciar um mecanismo para determinar o fim do programa, quando não
houver mais registos a introduzir.

Podemos decidir que o trabalho de entrada de dados fica mais organi-


zado se as operações se fizerem por “lotes”: agora uma série de criações, de-
pois algumas remoções, a seguir talvez um grupo de modificações, ou então
mais criações, etc. Se quisermos consagrar este método, então precisamos de
um programa com dois níveis de iteração: um nível superior, sobre as sequên-
cias de operações da mesma natureza; e um inferior, correspondendo a cada
um dos registos a criar. Naturalmente, vai ser preciso também uma indicação
para o fim de cada “lote”.

Os valores digitados não devem ser aceites sem uma validação prelimi-
nar. Não se pretende um sistema muito sofisticado (que, para ser feito como
deve ser, exigiria outras técnicas de programação) pelo que apenas se verifi-
cará se os campos ficam com valores de tipos apropriados, se a data é plausí-

7
vel, e se a letra que vai para o campo ACT–TIPO–OP identifica bem uma das
três operações possíveis.

Isto lembra-nos, aliás, que é preciso estabelecer uma codificação para


essas operações: a mais normal é a das iniciais: 'C' para criação, 'R' para re-
moção e 'M' para modificação.

Adoptemos, neste problema, uma metodologia descendente “pura”, por


refinamentos sucessivos. Não custa muito aceitar que, em primeira aproxima-
ção, o programa seja composto por uma inicialização, termine por uma finali-
zação, e seja, no essencial, constituído por uma iteração sobre um lote de ope-
rações do mesmo tipo. Consagremos esta primeira decomposição, bem trivial,
afinal de contas, por meio de um boneco arborescente, por exemplo assim:

Programa

Inicialização Finalização
não-há-mais-ops

lote-ops

Os rectângulos etiquetados “inicialização” e “finalização” representam


isso mesmo. O círculo com o diâmetro horizontal simboliza a iteração da ope-
ração que está no rectângulo nele pendurado. A condição de terminação do ci-
clo é a indicada: “não–há-mais-ops”. O ponto esclarece que a iteração se reali-
zará sempre pelo menos uma vez. O rectângulo “lote-ops” corresponde a uma
sequência de entradas de dados referentes a operações do mesmo tipo, ainda
por determinar. A sua decomposição é a etapa seguinte da nossa análise des-
cendente (mesmo se se podia “traduzir” o desenho para COBOL, desde já).

O processamento de um lote de operações deve começar pela determi-


nação do tipo de operação. A seguir, consoante o tipo seleccionado, despacha-
rá para procedimentos especializados. Além disso, pode-se aproveitar a oca-
sião para inquirir o utilizador sobre se está tudo metido e são horas de termi-
nar. Graficamente:

8
lote-ops

qual-op "C" "R" "M" "F"

criações remoções modificações FIM

O procedimento “qual-op” serve para pedir ao utilizador para indicar o


tipo de operação pretendido, ou o fim dos trabalhos, e terá que validar as res-
postas. O rectângulo arredondado da direita representa apenas a operação
que há–de tornar verdadeira a condição de terminação “não-há-mais-ops”. Os
outros três rectângulos terão, em princípio, “estruturas” internas semelhan-
tes: todos são procedimentos iterativos, começam por uma fase de “aquisição”
dos dados e terminam perguntando ao utilizador se tem mais operações do
mesmo tipo a realizar. Antes disto, pode ser bom introduzir um pedido de con-
firmação, para evitar que, em virtude de lapsos de digitação ainda não detec-
tados, se criem registos com informação errónea. Para “criações”, a árvore
vem assim:

criações

não-há-mais-criações

obter-dados confirmação tudo-ok mais-criações

escrever
registo mensagem

O círculo com o diâmetro vertical representa uma escolha binária, cla-


ro: a metade em branco corresponde à negação da outra.

Os procedimentos “remoções” e “modificações” são semelhantes.

Se juntarmos os desenhos todos, pendurando as árvores umas nas ou-


tras, obtemos um boneco muito giro, que evidencia a “estrutura” do progra-

9
ma. É costume chamar-se-lhe a “árvore programática” do programa. Vamos
usá-la como guia para a redacção do programa COBOL:

Programa

Inicialização Finalização
não-há-mais-ops

lote-ops

qual-op "C" "R" "M" "F"

criações remoções modificações FIM


não-há-mais-criações

obter-dados confirmação tudo-ok mais-criações

escrever
registo mensagem

3.4. Escrita do programa

Retomemos a análise descendente, considerando primeiro o nível supe-


rior:

10
3.4.1. Nível superior

PROCEDURE DIVISION.
ESTRUTURA SECTION.
PROGRAMA.
PERFORM INICIALIZACAO.
MOVE 0 TO B-NAO-HA-MAIS-OPS.
PERFORM LOTE-OPS UNTIL NAO-HA-MAIS-OPS.
PERFORM FINALIZACAO.
STOP RUN.

Este pequeno troço inclui muitos conceitos novos. Estudemo-los um por


um:

A parte algorítmica de um programa COBOL aparece na divisão


PROCEDURE. Esta divisão também pode ter secções, cujos nomes são livres
(excepto no caso de algumas secções especiais). Escolhemos começar com uma
secção que baptizamos ESTRUTURA por ser nela que vamos “reproduzir” a es-
trutura principal do programa, aquela que a árvore programática patenteia.
A secção ESTRUTURA, por sua vez, começa por um parágrafo PROGRAMA (nome
inventado, note-se) que corresponde à raiz da árvore. Este parágrafo repre-
senta, afinal, o “programa principal”. Assim, não é difícil adivinhar que a exe-
cução começa pela primeira instrução da divisão PROCEDURE e termina quan-
do for encontrada a instrução STOP RUN. Ao nível mais alto, portanto, o pro-
grama tem cinco instruções: PERFORM…, MOVE…, PERFORM…, PERFORM… e
STOP RUN.

Cada uma das instruções PERFORM corresponde à chamada de um pro-


cedimento. A definição dos procedimentos é feita usando parágrafos. Haverá
então, mais tarde ou mais cedo, parágrafos com os nomes INICIALIZACAO,
LOTE-OPS e FINALIZACAO. Note-se que, em rigor, os parágrafos não corres-
pondem exactamente aos procedimentos de outras linguagens, como o Pascal,
embora quando são chamados com PERFORM se comportem da maneira espe-
rada. Como nos nossos programas isso acontece com quase todos os parágra-
fos, a assimilação é legítima. No entanto, há sempre um parágrafo que não
pode chamado por PERFORM: o primeiro de cada programa. No caso presente,
se não estivesse lá a instrução STOP RUN, para parar depois de retorno de
FINALIZACAO, o controlo passaria alegremente para a instrução executável
seguinte, a primeira do parágrafo imediato, o que não é o que se pretende,
com certeza…

No parágrafo PROGRAMA, surgem dois PERFORMs simples e um


PERFORM UNTIL. Esta última variante corresponde à chamada repetida do
parágrafo indicado enquanto a condição que vem a seguir à palavra UNTIL
não for verdadeira. Precisemos um pouco melhor, fazendo uma analogia com
o Pascal:

PERFORM S UNTIL B

corresponde a
11
while not B do S

Quer dizer, a condição é avaliada antes de cada chamada; quando for


verdadeira, passa-se à instrução seguinte.

Analisemos agora a condição do PERFORM UNTIL. Como ela é avaliada


antes da chamada de LOTE-OPS, é indispensável que esteja convenientemen-
te inicializada. É para isso que ali está a instrução MOVE, a qual desempenha
em COBOL o papel da afectação. Recorrendo novamente à analogia com o
Pascal, pode dizer-se que:

MOVE A TO B

equivale a

B := A

O “destino” de um MOVE deve ser uma variável, portanto. Neste caso


chama-se B-NAO-HA-MAIS-OPS, e é de natureza booleana (daí, o B inicial,
não obrigatório, evidentemente). Como em COBOL não existe um tipo boolea-
no (apesar de haver expressões lógicas), temos que optar por uma codificação
qualquer. Uma que parece simples é usar o PIC 9, associando 0 (zero) ao va-
lor falso e 1 (um) ao valor verdadeiro. Deste modo, a entrada na secção
WORKING-STORAGE referente a B-NAO-HA-MAIS-OPS virá com o seguinte as-
pecto:

77 B-NAO-HA-MAIS-OPS PIC 9.

No entanto, a condição que controla o ciclo é NAO-HA-MAIS-OPS e não


B–NAO-HA-MAIS-OPS = 1, ou qualquer coisa do género, como seríamos leva-
dos a pensar. Ora, o que se pretende é precisamente que NAO–HA–MAIS–OPS
seja o “nome da condição” B-NAO-HA-MAIS-OPS = 1. Esta subtil relação en-
tre a variável e o nome da condição é estabelecida acrescentando uma linha, a
seguir à declaração da primeira:

77 B-NAO-HA-MAIS-OPS PIC 9.
88 NAO-HA-MAIS-OPS VALUE 1.

O nível 88 introduz um nome de condição associado à variável declara-


da imediatamente antes. Note-se que um nome de condição não é uma variá-
vel, e, portanto, o seu valor não pode ser modificado explicitamente por uma
instrução MOVE, mas apenas indirectamente através de afectações à variável
associada.

O conceito de nome de condição compreende-se melhor se o considerar-


mos como uma função booleana sem argumentos que dá verdade se e só se o
valor da variável for um dos que aparece (em geral pode haver vários) na
cláusula VALUE respectiva. Em esquema:

12
77 B-X PIC...
88 X VALUES v1, v2, ..., vn.

corresponderia em Pascal a:

var b_x: ...;


...

function x: boolean;
begin x := b_x in [v1, v2, ..., vn] end;

Claro está que a utilização dos nomes de condição nunca é indispensá-


vel. No entanto, o seu uso esclarecido pode contribuir bastante para a “legibi-
lidade” dos programas. Eis o exemplo clássico: suponhamos que num certo
programa que processa dados pessoais, é preciso considerar o sexo. Então, um
dos campos da variável estruturada que congrega esses dados, há–de servir
para conter essa informação. Por exemplo:

05 P-SEXO PIC A.

A informação sexual estará codificada, de alguma maneira previamen-


te convencionada. Pode ser 'H' para homem e 'M' para mulher. Algures no
programa será necessário realizar operações distintas conforme o indivíduo
seja macho ou fêmea, o que se consegue com uma instrução IF, do estilo:

IF P-SEXO = "M" PERFORM NUMERO-DE-PARTOS


ELSE PERFORM FOI-AH-TROPA.

Se se tivesse definido:

05 P-SEXO PIC A.
88 EH-MULHER VALUE "M".
88 EH-HOMEM VALUE "H".

a instrução IF poderia aparecer assim:

IF EH-MULHER PERFORM NUMERO-DE-PARTOS


ELSE PERFORM FOI-AH-TROPA.

escondendo deste modo a convenção e proporcionando um programa mais


limpo. (Note-se até que, noutras situações, a letra 'M' pode ser usada para
codificar o sexo masculino…)

Para concluir a discussão acerca deste primeiro parágrafo, refiramos


que quase todas as instruções COBOL começam por um verbo, tal como
PERFORM e MOVE. Adiante surgirão outros casos. A única excepção é a instru-
ção IF, usada ainda há pouco. Isto diz algo acerca da natureza fortemente im-
perativa do COBOL, e do estilo de programas que se pode esperar. Há até
quem sustente, e aparentemente acredite nisso, que, devido à abundância de
verbos e de outras construções que dão um ar de linguagem natural, os pro-

13
gramas COBOL são legíveis, mesmo para quem perceba pouco de programa-
ção…

3.4.2. Segundo Nível

No segundo nível da árvore programática aparecem as operações “ini-


cialização”, “lote-ops” e “finalização”. A primeira e a última encarregar–se–ão
essencialmente de tarefas administrativas, estilo abertura e fecho de fichei-
ros, inicialização de algumas variáveis, etc. Não vamos preocupar-nos com
elas agora. Resta então o procedimento “lote-ops”. Eis o parágrafo respectivo:

LOTE-OPS.
PERFORM QUAL-OP.
IF OP-CRIACOES PERFORM CRIACOES
ELSE IF OP-REMOCOES PERFORM REMOCOES
ELSE IF OP-MODIFICACOES PERFORM MODIFICACOES
ELSE MOVE 1 TO B-NAO-HA-MAIS-OPS.

Este parágrafo ilustra a utilização da instrução IF, já mencionada,


aliás. Note-se que não se usa a palavra THEN para introduzir o ramo “positi-
vo”. Também não há os parêntesis begin end, e a convenção é que o ramo
afirmativo vai até ao ELSE seguinte (este caso não é assim lá muito exemplar,
porque só tem uma instrução de cada vez), ou até ao primeiro ponto que apa-
reça, se não houver ELSE. De maneira semelhante, o alcance de ELSE esten-
de-se até ao ponto seguinte. Por sinal, é no contexto de instruções condicio-
nais, como o IF, que o minúsculo ponto revela toda a sua subtil importância
em COBOL. Na divisão PROCEDURE, cada ponto final termina uma instrução
(se não estiver num comentário ou numa cadeia de caracteres, bem entendi-
do); melhor dizendo, no caso de instruções imbricadas, como o IF acima, o
ponto final termina todas as instruções em curso. Assim, não seria legítimo
colocar um ponto a seguir a PERFORM CRIACOES para significar que esta ins-
trução acaba ali, pois com isso estaríamos a terminar prematuramente o IF
(e o compilador daria um erro de sintaxe pois a palavra ELSE não serve para
começar instruções).

Repare-se na afectação MOVE 1 TO B-NAO-HA-MAIS-OPS. Como o


PIC desta variável é 9, os valores aceitáveis são apenas os algarismos. Por is-
so, em rigor, dever-se-ia ter escrito MOVE "1" TO .... Mas o COBOL é uma
linguagem compreensiva, e percebe o que queremos dizer. Aliás, a mesma
simplificação já tinha sido utilizada na própria definição do nome de condição.

Os três identificadores OP-CRIACOES, OP-REMOCOES e OP-MODIFI-


CACOES são nomes de condição. Hão-de aparecer associados a uma variável
cujo valor, aceite do terminal no parágrafo QUAL-OP, especificará o tipo de
operação a realizar. Podemos escolher uma variável de “tipo” carácter, com a
mesma codificação usada para os registos, mais um valor para indicar o fim,
'F', por exemplo.

14
77 TIPO-OP PIC X.
88 OP-OK VALUES "C", "R", "M", "F".
88 OP-CRIACOES VALUE "C".
88 OP-REMOCOES VALUE "R".
88 OP-MODIFICACOES VALUE "M".

Como já foi observado, o parágrafo QUAL-OP não pode limitar-se a acei-


tar um carácter do terminal: tem que validá-lo, isto é, assegurar que esse ca-
rácter é um dos que torna verdadeiro o nome de condição OP-OK. Portanto,
tem que ser de natureza iterativa. Como em COBOL só se pode iterar sobre
parágrafos, temos que inventar aqui um pequeno parágrafo subsidiário, para
suportar essa iteração:

QUAL-OP.
MOVE SPACE TO TIPO-OP.
PERFORM QUAL-OP-BIS UNTIL OP-OK.
QUAL-OP-BIS.
DISPLAY "C-Criações, R-Remoções, M-Modificações".
DISPLAY "(F-Fim, para acabar)".
ACCEPT TIPO-OP.
IF NOT OP-OK DISPLAY "Operação mal especificada".

Repare-se na instrução MOVE, inicializando a variável TIPO–OP, para


que o PERFORM UNTIL arranque como deve (não esquecer que a condição é
avaliada antes de cada passagem do ciclo). A palavra reservada SPACE repre-
senta uma constante, dita constante figurativa, cujo valor é um espaço. O pa-
rágrafo QUAL–OP–BIS aparece ligeiramente para dentro, apenas por uma
questão de estilo, para denunciar a sua pouca importância estrutural. (Note–
se que se bem que seja costume os nomes dos parágrafos começarem algures
na margem A, isso não constitui uma obrigação. O compilador “depreenderá”
que um identificador é o título de um parágrafo, quando, sintacticamente, não
puder deixar de o ser, porque já não pertence à instrução anterior, nem pode
servir para começar uma instrução nova). As operações DISPLAY e ACCEPT
servem para escrever e para ler do terminal, e não apresentam dificuldades.

E, como os parágrafos de inicialização e finalização ficam para mais


tarde, termina aqui a programação do segundo nível da árvore programática.

3.4.3. Terceiro nível

No terceiro nível da árvore programática encontram-se os procedimen-


tos que se encarregam de cada uma das classes de operações. Consideremos
primeiro “criações”, para a recolha de dados referentes a novos funcionários.

3.4.3.1. Criação de novos funcionários

Tal como vimos, trata-se de um processo iterativo. Na árvore deste pro-


cedimento não aparece explicitamente o rectângulo correspondente a cada ite-
ração, pelo que escreveremos directamente o código respectivo:
15
CRIACOES.
MOVE 0 TO B-NAO-HA-MAIS-REGS.
PERFORM CRIACOES-BIS UNTIL NAO-HA-MAIS-CRIACOES.
CRIACOES-BIS.
DISPLAY "***Criação de funcionários***".
PERFORM OBTER-DADOS-CRIACOES.
PERFORM CONFIRMACAO-CRIACOES.
IF TUDO-OK PERFORM ESCR-ACT
ELSE DISPLAY "Não confirmado".
PERFORM MAIS-CRIACOES.

É preciso declarar algumas variáveis:

77 B-NAO-HA-MAIS-CRIACOES PIC 9.
88 NAO-HA-MAIS-CRIACOES VALUE 1.
77 B-TUDO-OK PIC 9.
88 TUDO-OK VALUE 1.

A parte essencial do trabalho é feita pelo procedimento “obter–da-


dos…”. Já vamos a ele. Vejamos antes os aspectos mais “administrativos”. A
primeira mensagem é para confortar o utilizador. O procedimento “mais-cria-
ções” servirá para saber se há mais registos de criação nesta série. Arrume-
mos já com ele:

MAIS-CRIACOES.
DISPLAY "Mais criações? (S/N) ".
ACCEPT RESP.
IF RESP = "S" MOVE 0 TO B-NAO-HA-MAIS-CRIACOES
ELSE MOVE 1 TO B-NAO-HA-MAIS-CRIACOES.

Mas calma! Se vai ser preciso mais uma variável RESP para aceitar a
resposta e depois testá-la para definir o valor “booleano” de B–NAO–HA–MAIS–
CRIACOES, o melhor é modificar a definição desta e evitar os intermediários:

77 B-NAO-HA-MAIS-CRIACOES PIC X.
88 NAO-HA-MAIS-CRIACOES VALUE "N".

MAIS-CRIACOES.
DISPLAY "Mais criações? (S/N) ".
ACCEPT B-NAO-HA-MAIS-CRIACOES.

Podemos antecipar que, por este caminho, vão ser precisas mais duas
variáveis do género de B–NAO–HA-MAIS-CRIACOES, uma para as remoções,
outra para as modificações. Mas, em vez de atravancar o programa com va-
riáveis booleanas, não será má ideia partilhar uma entre todas essas opera-
ções, visto que a sua utilização não se sobrepões no tempo. Temos que lhe dar
um nome neutro:

77 B-NAO-HA-MAIS-REGS PIC X.
88 NAO-HA-MAIS-REGS VALUE "N".

16
Haverá sim três parágrafos diferentes, para a continuação da introdu-
ção de registos da mesma classe, porque as mensagens afixadas no ecrã não
são as mesmas:

MAIS-CRIACOES.
DISPLAY "Mais criações? (S/N) ".
ACCEPT B-NAO-HA-MAIS-REGS.

...

MAIS-REMOCOES.
DISPLAY "Mais remoções? (S/N) ".
ACCEPT B-NAO-HA-MAIS-REGS.

...

MAIS-MODIFICACOES.
DISPLAY "Mais modificações? (S/N) ".
ACCEPT B-NAO-HA-MAIS-REGS.

O PERFORM do parágrafo CRIACOES passará a ser:

PERFORM CRIACOES-BIS UNTIL NAO-HA-MAIS-REGS.

A confirmação poderia limitar-se a inquirir se todos os valores introdu-


zidos estão correctos. No entanto, é possível que tenha havido alguns lapsos
de digitação logo detectados e corrigidos, e que por isso o ecrã esteja um boca-
do confuso. Por isso, é aconselhável reafixar os valores aceites, porventura de
uma maneira mais arrumada. Quanto à variável B–TUDO–OK, tudo indica que
deva ser tratada como B-NAO-HA-MAIS-REGS:

77 B-TUDO-OK PIC X.
88 TUDO-OK VALUE "S".

CONFIRMACAO-CRIACOES.
DISPLAY "---Dados referentes a um novo funcionário---".
PERFORM AFIXAR-DADOS.
DISPLAY "Tudo bem? (S/N) ".
ACCEPT B-TUDO-OK.

Com um bocado de jeito, se calhar consegue-se reutilizar o procedimen-


to “afixar-dados” também quando se programar as modificações.

É a altura de abordar o procedimento “obter-dados-criações”. Natural-


mente, será formado por uma sequência de operações (que não aparecem de-
talhadas na árvore programática), cada uma preocupando-se com um dos ele-
mentos de informação: número do funcionário, apelido, resto do nome, etc. Se
mais não fosse porque estas operações voltarão a ser necessárias no progra-
ma, o melhor é encará-las como procedimentos também. Por outro lado, não
basta aceitar o que quer que seja. É indispensável verificar se a informação é
plausível. Se não for, é preciso alertar o utilizador, e pedir-lhe que reintrodu-

17
za. Donde, um esquema iterativo ao nível da aceitação de cada informação
elementar:

OBTER-DADOS-CRIACOES.
MOVE 0 TO B-OK. PERFORM ACEITAR-NUM-FUNC UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-APELIDO UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-RESTO-NOME UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-BI UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-NO-CONTRIB UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-NASC UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-MORADA UNTIL OK.

Surge uma nova variável booleana:

77 B-OK PIC 9.
88 OK VALUE 1.

Os sucessivos procedimentos “aceitar-num-func”, “aceitar-apelido”, etc.,


não aparecem na árvore programática que estabelece a estrutura do progra-
ma. Por isso, convencionamos que serão arrumados numa nova secção, a
TRATAMENTO SECTION. Todos têm uma forma semelhante: começam por uma
fase de pergunta-resposta, a que se segue a validação da resposta. Podemos
prever que as perguntas-respostas serão análogas às que se farão mais tarde
na parte das modificações. Por isso, vamos construir pequenos procedimentos
“display-accept”, que, devido ao seu baixo nível e pobreza de conteúdo funcio-
nal, remeteremos para uma terceira secção: a SERVISSO SECTION.

Temos agora que decidir quanto às variáveis que vão receber os valores
introduzidos. Em primeira aproximação poderíamos usar directamente os
campos do registo ACT-REG. Mas isso não pode ser, pois correríamos o risco
de o programa funcionar mal se, por exemplo, o utilizador se enganasse e in-
cluísse uma letra no número de funcionário. Uma alternativa seria dispor de
uma série de variáveis avulso, uma para cada coisa. Mas então porque não
agrupá-las numa variável estruturada cuja forma, afinal, até poderá ser com-
patível com a de ACT-REG? Chamemos-lhe ED-REG (uma sigla para “registo
de entrada de dados”). A sua declaração virá na secção WORKING-STORAGE:

18
WORKING-STORAGE SECTION.
77 ...
01 ED-REG.
02 ED-TIPO-OP PIC A.
02 ED-INFO.
03 ED-NUM-FUNC PIC X(4).
03 ED-NOME.
04 ED-APELIDO PIC X(16).
04 ED-RESTO-NOME PIC X(32).
03 ED-BI PIC X(8).
03 ED-NO-CONTRIB PIC X(9).
03 ED-NASC.
04 ED-NASC-ANO PIC XX.
04 ED-NASC-MES PIC XX.
04 ED-NASC-DIA PIC XX.
03 ED-MORADA.
04 ED-MORADA-1 PIC X(30).
04 ED-MORADA-2 PIC X(30).
04 ED-MORADA-3 PIC X(20).

Podemos observar que este registo é “isomorfo” ao registo ACT-REG,


apesar de os PICs no campo ED-INFO serem sempre X, para poderem ser
aceites quaisquer caracteres. Por outro lado, mais cedo ou mais tarde será
preciso preencher também o campo ED-TIPO-OP, naturalmente com o valor
da variável TIPO-OP. Isto deve ser feito logo à entrada do procedimento “ob-
ter-dados-criações”:

OBTER-DADOS-CRIACOES.
MOVE TIPO-OP TO ED-TIPO-OP.
MOVE 0 TO B-OK. PERFORM ACEITAR-NUM-FUNC UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-APELIDO UNTIL OK.
...

Vejamos então os procedimentos “aceitar-…”, na secção TRATAMENTO:

TRATAMENTO SECTION.
ACEITAR-NUM-FUNC.
PERFORM D-A-NUM-FUNC.
IF ED-NUM-FUNC IS NUMERIC MOVE 1 TO B-OK
ELSE DISPLAY "??? valor não numérico".

ACEITAR-APELIDO.
...

O procedimento “d-a-num-func” é um dos tais que vai para a secção


SERVISSO:

SERVISSO SECTION.
D-A-NUM-FUNC.
DISPLAY "Número de funcionário: " NO ADVANCING.
ACCEPT ED-NUM-FUNC.

19
Repare-se em como é feita a validação do valor introduzido: a expressão
IS NUMERIC (o IS é facultativo) é um exemplo de uma condição de classe. A
regra é: para uma cadeia de caracteres S, a condição S IS NUMERIC dá ver-
dade se e só se S for composta exclusivamente por algarismos.

...
ACEITAR-APELIDO.
PERFORM D-A-APELIDO.
IF ED-APELIDO IS ALPHABETIC AND
ED-APELIDO NOT = SPACES MOVE 1 TO B-OK
ELSE DISPLAY "??? Apelido inválido".

...
D-A-APELIDO.
DISPLAY "Apelido: " NO ADVANCING.
ACCEPT ED-APELIDO.

Agora, o controlo que se faz é para que o apelido contenha apenas le-
tras e espaços, e pelo menos uma letra. O IS ALPHABETIC é outra condição
de classe, a qual dá verdade apenas quando todos os caracteres são letras
maiúsculas ou espaços. (Se houver algum funcionário com um nome esquisito,
com apóstrofo, por exemplo, o nome terá que ser reortografado). O NOT = é o
operador de desigualdade em COBOL.

As operações de aceitação do resto do nome e dos números de bilhete de


identidade e de contribuinte não trazem novidades. Vejamos então a data de
nascimento. Para não sobrecarregar demasiado a operação com três pergun-
tas sucessivas, todas de resposta curta, o melhor é pedir tudo de uma vez. Há
que ter o cuidado, mesmo assim, de contar com uma resposta na forma mais
habitual, que é na ordem dia, mês, ano, ainda que isto seja ao contrário do
que está organizado na variável ED-REG. Também, podemos admitir que a
aceitação e validação de uma data feita nestes moldes é uma operação sufi-
cientemente abstracta para merecer um tratamento o mais genérico possível.
Portanto, vamos baseá-la integralmente numa nova variável:

01 DDATA.
02 DIA PIC XX.
02 MES PIC XX.
02 ANO PIC XX.

(A variável chama-se DDATA porque DATA é uma palavra reservada do


COBOL…)

Como também se quer validar a data o melhor é prever já uma variável


booleana:

77 B-OK-DATA PIC 9.
88 OK-DATA VALUE 1.

Eis uma primeira versão para a leitura e validação de datas:

20
LER-DATA.
ACCEPT DDATA.
IF DDATA IS NUMERIC AND
DIA NOT < "01" AND NOT > "31" AND
MES NOT < "01" AND NOT > "12" MOVE 1 TO B-OK-DATA
ELSE MOVE 0 TO B-OK-DATA.

Em vez do teste triplo DIA IS NUMERIC AND MES IS NUMERIC AND


ANO IS NUMERIC, optou-se pelo equivalente, mais compacto DDATA IS
NUMERIC. Repare-se nos operadores relacionais NOT < e NOT >. Eles repre-
sentam de facto as relações de ordem maior ou igual e menor ou igual, respec-
tivamente, neste caso aplicadas a cadeias de caracteres. Recordando que o
operador de desigualdade é NOT =, temos a seguinte correspondência de ope-
radores relacionais entre o Pascal e o COBOL:

Pascal = <> > >= < <=


COBOL = NOT = > NOT < < NOT >

Vejamos uma variante, mais elegante, tirando partido dos nomes de


condição:

01 DDATA.
02 ANO PIC XX.
02 MES PIC XX.
88 MES-OK VALUES "01" THRU "12".
02 DIA PIC XX.
88 DIA-OK VALUES "01" THRU "31".
...

LER-DATA.
ACCEPT DDATA.
IF DDATA IS NUMERIC AND DIA-OK AND MES-OK
MOVE 1 TO B-OK-DATA
ELSE MOVE 0 TO B-OK-DATA.

É altura de integrarmos isto na leitura da data de nascimento do fun-


cionário:

ACEITAR-NASC.
DISPLAY "Data de nascimento (DDMMAA): " NO ADVANCING.
PERFORM LER-DATA.
IF OK-DATA THEN MOVE ANO TO ED-NASC-ANO
MOVE MES TO ED-NASC-MES
MOVE DIA TO ED-NASC-DIA
MOVE 1 TO B-OK
ELSE DISPLAY "??? Data inválida".

Repare-se que é preciso afectar os três campos separadamente, porque


a ordem por que aparecem nos registos é diferente.

Depois deste esforço, a leitura da morada é uma pêra-doce:

21
OBTER-MORADA.
PERFORM D-A-MORADA.
IF ED-MORADA NOT = SPACES MOVE 1 TO B-OK
ELSE DISPLAY "??? a morada não pode ficar em branco"

...

D-A-MORADA.
DISPLAY "Morada -- Primeira linha: ". ACCEPT ED-MORADA-1.
DISPLAY "Morada -- Segunda linha: ". ACCEPT ED-MORADA-2.
DISPLAY "Morada -- Terceira linha: ". ACCEPT ED-MORADA-3.

Regressemos por um momento ao parágrafo “confirmação-criações”. O


procedimento “afixar-dados” aí invocado realiza uma afixação formatada dos
campos de ED-REG. Por isso vamos mudar-lhe o nome para “d–ed-reg” e colo-
cá-lo com os outros procedimentos de “display-accept” na secção SERVISSO:

SERVISSO SECTION.
...
D-ED-REG.
DISPLAY "Func. Num. " ED-NUM-FUNC.
DISPLAY "Apelido: ", ED-APELIDO, " ",
"Outros nomes: ", ED-RESTO-NOME.
DISPLAY "BI: ", ED-BI,
" No.Contrib: ", ED-NO-CONTRIB, " ",
"Data Nasc: ",
ED-NASC-DIA, "/", ED-NASC-MES, "/", ED-NASC-ANO.
DISPLAY "Morada: ", ED-MORADA-1.
DISPLAY " ", ED-MORADA-2.
DISPLAY " ", ED-MORADA-3.

As vírgulas são facultativas, e servem só para melhorar a legibilidade.


No entanto, a seguir a uma vírgula, como a seguir a um ponto, aliás, deve ha-
ver sempre um espaço (ou o fim da linha, claro).

Para terminar a análise e programação do procedimento “criações” só


falta ver “escr-act”, o procedimento que se encarrega da escrita de cada regis-
to no ficheiro de saída. Como sempre, antes de escrever o registo há que com-
pô-lo. Isso não é muito difícil. Em primeira aproximação basta uma série de
afectações, dos campos de ED-REG aos campos correspondentes de ACT–REG:

MOVE ED-TIPO-OP TO ACT-TIPO-OP.


MOVE ED-NUM-FUNC TO ACT-NUM-FUNC.
MOVE ED-APELIDO TO ACT-APELIDO.
...

Mas logo notamos que, bem vistas as coisas uma afectação global deve
bastar:

MOVE ED-REG TO ACT-REG.

22
Deste modo, o procedimento “escr-act” fica, simplesmente:

ESCR-ACT.
MOVE ED-REG TO ACT-REG.
WRITE ACT-REG.

Fazendo mais uma analogia com o Pascal, a sequência de instruções


deste parágrafo corresponde a:

f_act^ := ed_reg; put(f_act)

Tal como em Pascal, existe em Cobol uma maneira para fazer as duas
coisas de uma vez só. Em Pascal seria:

write(f_act, ed_reg)

Em COBOL é:

WRITE ACT-REG FROM ED-REG.

Com esta técnica, o procedimento “escr-reg” fica reduzido a uma instru-


ção.

Os dois procedimentos “remoções” e “modificações” são semelhantes,


pelo que não vale a pena elaborar muito sobre eles. Limitamos a apresentá-
-los, sem grandes comentários.

REMOCOES.
MOVE 0 TO B-NAO-HA-MAIS-REGS.
PERFORM REMOCOES-BIS UNTIL NAO-HA-MAIS-REGS.
REMOCOES-BIS.
DISPLAY "***Remoção de funcionários***".
PERFORM OBTER-DADOS-REMOCOES.
PERFORM CONFIRMACAO-REMOCOES.
IF TUDO-OK PERFORM ESCR-ACT
ELSE DISPLAY "Não confirmado".
PERFORM MAIS-REGS.

OBTER-DADOS-REMOCOES.
MOVE SPACES TO ED-REG.
MOVE TIPO-OP TO ED-TIPO-OP.
MOVE 0 TO B-OK. PERFORM ACEITAR-NUM-FUNC UNTIL OK.

CONFIRMACAO-REMOCOES.
DISPLAY "---Remoção de um funcionário---"
PERFORM D-ED-NUM-FUNC.
DISPLAY "Tudo bem? (S/N) ".
ACCEPT B-TUDO-OK.

D-ED-NUM-FUNC.
DISPLAY "Func. Num. ", ED-NUM-FUNC.

23
Os parágrafos referentes às modificações são quase iguais aos das cria-
ções, apenas com a diferença de se permitir que alguns campos fiquem em
branco.

MODIFICACOES.
MOVE 0 TO B-NAO-HA-MAIS-REGS.
PERFORM MODIFICACOES-BIS UNTIL NAO-HA-MAIS-REGS.
MODIFICACOES-BIS.
DISPLAY "***Modificação de dados de funcionários***".
PERFORM OBTER-DADOS-MODIFICACOES.
PERFORM CONFIRMACAO-MODIFICACOES.
IF TUDO-OK PERFORM ESCR-ACT
ELSE DISPLAY "Não confirmado".
PERFORM MAIS-REGS.

OBTER-DADOS-MODIFICACOES.
MOVE 0 TO B-OK. PERFORM ACEITAR-NUM-FUNC UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-APELIDO-MOD UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-RESTO-NOME-MOD UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-BI-MOD UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-NO-CONTRIB-MOD UNTIL OK.
MOVE 0 TO B-OK. PERFORM ACEITAR-NASC-MOD UNTIL OK.
PERFORM ACEITAR-MORADA.

Repare-se que a morada não é sujeita a qualquer controlo: a única res-


trição, que era não ficar em branco, desaparece agora. Os parágrafos cujo no-
me acaba em MOD têm que ser reescritos, para aliviar a validação: Eis os refe-
rentes ao apelido e à data de nascimento:

ACEITAR-APELIDO-MOD.
PERFORM D-A-APELIDO.
IF ED-APELIDO IS ALPHABETIC MOVE 1 TO B-OK
ELSE DISPLAY "??? apelido não alfabético".

ACEITAR-NASC-MOD.
DISPLAY "Data de nascimento (DDMMAA): " NO ADVANCING.
PERFORM LER-DATA.
IF DDATA = SPACES MOVE SPACES TO ED-NASC
MOVE 1 TO B-OK
ELSE IF OK-DATA MOVE DIA TO ED-NASC-DIA
MOVE MES TO ED-NASC-MES
MOVE ANO TO ED-NASC-ANO
MOVE 1 TO B-OK
ELSE DISPLAY "??? Data inválida".

Nesta altura, em que o grosso do programa está sob controlo, podemos


reparar que os campos do registo ACT–REG, tão laboriosamente definidos logo
no início, nunca são referenciados. De facto, as operações fazem-se sobre a va-
riável ED-REG, declarada na secção WORKING-STORAGE, e há apenas uma
afectação global, no âmbito da operação de escrita. Isto quer dizer que real-
mente podíamos ter poupado esse esforço, pondo simplesmente:

24
...
FD F-ACT.
01 ACT-REG PIC X(156).

De qualquer modo, como já tivemos o trabalho, o melhor é conservar a


definição original, para efeitos de documentação.

Para terminar a escrita do programa, resta considerar os procedimen-


tos de inicialização e finalização. Como os nomes indicam, o primeiro encarre-
ga-se de preparar as coisas para o ciclo sobre “lote-ops”, e o segundo, de arru-
mar a casa, findo o processamento.

Entre as operações típicas de um procedimento de inicialização, con-


tam-se as “aberturas” dos ficheiros que vão ser usados. No caso de um progra-
ma interactivo, como este, também não fica mal o programa começar por se
identificar, assim, por exemplo:

DISPLAY "Gestão de Pessoal".


DISPLAY "Criação do Ficheiro de Actualizações".

Antes de “abrir” cada ficheiro é preciso escolher o ficheiro externo, o


que será feito através da variável de identificação que aparece na cláusula
SELECT:

DISPLAY "Nome do Ficheiro de Actualizações: "


NO ADVANCING.
ACCEPT ID-F-ACT.

Ao abrir o ficheiro é preciso indicar se ele vai ser lido ou escrito, e no


caso de ser escrito se se pretende criá-lo de raiz, ou não necessariamente. O
verbo é sempre o mesmo — OPEN — e vem qualificado, consoante o tipo de
abertura: INPUT (leitura), OUTPUT (escrita com criação, no caso de existir um
ficheiro com o nome indicado, deveria ocorrer um erro), e EXTEND (se não
existir ficheiro com esse nome então é criado; se existir, o seu conteúdo inicial
não se perde). No nosso caso, o mais razoável é escolher OPEN EXTEND:

OPEN EXTEND F-ACT.

Não fica mal a um programa interactivo ser simpático: Como indicação


de que a fase de inicialização terminou e que vai começar o trabalho propria-
mente dito, pode incluir-se uma mensagem de saudação:

DISPLAY "Bom trabalho!".

Tudo junto:

25
INICIALIZACAO.
DISPLAY "Gestão do Pessoal".
DISPLAY "Criação do ficheiro de actalizações".
DISPLAY "Nome do ficheiro de actualizacoes: "
NO ADVANCING.
ACCEPT ID-F-ACT.
OPEN EXTEND F-ACT.
DISPLAY "Bom trabalho!".

A finalização é ainda mais simples. Em COBOL é preciso fechar expli-


citamente os ficheiros, e neste programa é a isso que praticamente se reduz o
procedimento. Mas também se pode aproveitar para deixar uma mensagem
de despedida:

FINALIZACAO.
CLOSE F-ACT.
DISPLAY "Fim da sessão".

Para ter um programa completo só falta incluir a primeira das divi-


sões, a divisão IDENTIFICATION. Para este programa ela poderia ser assim:

IDENTIFICATION DIVISION.
PROGRAM-ID. CRIACT.
AUTHOR. GUERREIRO.

O primeiro parágrafo, obrigatório, “baptiza” o programa. Como norma,


deve escolher-se um nome relacionado com o do ficheiro que contém o progra-
ma, para evitar as confusões. O segundo é facultativo, e serve apenas para
alimentar a vaidade dos programadores que gostam de deixar a assinatura
nas suas obras.

3.5. Recapitulação final

Com este primeiro exemplo, cuja listagem completa se encontra em


anexo, foram introduzidos muitos dos conceitos importantes da linguagem
COBOL. Recapitulemo-los:

• Divisões: IDENTIFICATION, ENVIRONMENT, DATA, PROCEDURE.


• Secções: INPUT-OUTPUT (na divisão ENVIRONMENT), FILE e
WORKING-STORAGE (na divisão DATA).
• Parágrafos: PROGRAM-ID, AUTHOR (na divisão IDENTIFICATION)
FILE-CONTROL (na secção INPUT-OUTPUT).
• Cláusula SELECT ... ASSIGN TO ....
• Descrição de ficheiros: FD ....
• PICs X(...), 9(...) e A(...).
• Variáveis simples: nível 77.
• Nomes de condição: nível 88.
• Instruções: PERFORM simples, PERFORM UNTIL, MOVE, DISPLAY,
ACCEPT, OPEN, CLOSE, WRITE, IF.

26
• Condição de classe: NUMERIC, ALPHABETIC.
• Constantes figurativas: SPACE, SPACES.

Além disso, ilustrou-se a aplicação de uma notação gráfica de apoio à


concepção descendente, que se adapta bastante bem ao COBOL — a árvore
programática — e ensaiámos um esquema de arrumação dos parágrafos da
divisão PROCEDURE em três secções: ESTRUTURA, TRATAMENTO e SERVISSO.

4. Segundo exemplo: ordenação de um ficheiro sequencial

Continuemos a desenvolver o sistema de gestão de pessoal, introduzido


com o problema da secção anterior. O ficheiro que então se queria criar desti-
nava-se a ser usado para promover a actualização do ficheiro de identificação
do pessoal. Dado que este é um ficheiro sequencial, para que a actualização
possa ser feita de maneira eficiente é preciso que ele e o ficheiro com os regis-
tos de actualização estejam ordenados pelo mesmo critério. É natural que es-
se critério tenha a ver com o valor do campo número de funcionário, o qual
identifica univocamente os funcionários. Por isso, estabelecemos que ambos
os ficheiros deverão estar ordenados crescentemente por número de funcioná-
rio.

No entanto, é claro que nada garante que os registos do ficheiro de ac-


tualizações sejam produzidos na boa ordem. Logo, é indispensável ordená-lo,
isto é, criar um outro ficheiro, com os mesmos registos, mas dispostos como
convém.

Ordenar ficheiros é uma das operações mais frequentes na vida de um


computador. Por isso, os sistemas de operação dispõem quase sempre de pro-
gramas utilitários especializados nisso, e que são bestialmente flexíveis, per-
mitindo especificar critérios de ordenação complicadíssimos. Sendo assim,
normalmente não haverá mais que procurar esse utilitário, aprender a usá-lo
e resolver com ele o problema.

Mas acontece que também é possível ordenar ficheiros “dentro” de pro-


gramas COBOL, de maneira muito fácil, através da instrução SORT. Na reali-
dade, a instrução SORT limita-se a chamar o tal utilitário, passando-lhe os pa-
râmetros convenientes. Por conseguinte, a menos que se pretenda fazer algo
mais que ordenar o ficheiro, é perfeitamente supérfluo passar pelo COBOL.

Mas, para emprestar alguma emoção a esta história, suponhamos que


alguém roubou o manual do utilitário (ou o levou para casa durante o fim-de-
-semana, e esqueceu-se de devolver…), pelo que, na emergência, é preciso es-
colher entre “fabricar” um novo, do qual se conhecerá o modo de utilização, ou
usá-lo através do COBOL, de cujo manual há sempre muitas fotocópias… É
claro que esta segunda solução dá bastante menos trabalho.

27
4.1. Ordenação do ficheiro F-ACT

Vejamos então um programa COBOL para realizar a ordenação do fi-


cheiro F-ACT.

Estão em jogo dois ficheiros: o de entrada, construído pelo programa da


secção anterior, representado pela variável F-ACT, e o de saída, ordenado,
chamado, por exemplo, F-ACT-ORD. A instrução SORT exige ainda um terceiro
ficheiro, dito de ordenação, que tem uma existência um tanto misteriosa, e
que tem que ser declarado de forma especial. Para começar é preciso pô-lo, co-
mo aos outros, num SELECT:

ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT F-ACT ASSIGN TO ID-F-ACT.
SELECT F-ACT-ORD ASSIGN TO ID-F-ACT-SORT.
SELECT SORT-F ASSIGN TO ID-SORT-F.

DATA DIVISION.
WORKING-STORAGE SECTION.
77 ID-F-ACT PIC X(32).
77 ID-F-ACT-SORT PIC X(32).
77 ID-SORT-F PIC X(32).

Na secção FILE devem aparecer as descrições dos registos respectivos.


As dos dois primeiros são como de costume, com a simplificação resultante de
ser desnecessário pormenorizar os campos: basta indicar o nome do registo, e
o seu comprimento por meio de um PIC:

FILE SECTION.
FD F-ACT.
01 ACT-REG PIC X(156).
FD F-ACT-ORD.
01 ACT-ORD-REG PIC X(156).

A descrição do ficheiro de ordenação vem encabeçada pelas letras SD,


em vez de FD. O registo deve ter o comprimento comum, 156, e pôr em evidên-
cia os campos que vão servir para a ordenação. Neste caso, trata-se do núme-
ro de funcionário, que é formado pelos quatro caracteres (algarismos) desde a
segunda até à quinta posição (inclusive). Para os efeitos da ordenação, todos
os restantes caracteres são irrelevantes. Por isso, podemos atribuir-lhes glo-
balmente nomes quaisquer, ou então deixá-los anónimos, empregando a pala-
vra reservada FILLER (economizando a energia que sempre se gasta à procu-
ra de nomes sugestivos…)

SD SORT-F.
01 SORT-F-REG.
02 FILLER PIC X.
02 S-F-CHAVE PIC 9(4).
02 FILLER PIC X(151).
28
A instrução que desencadeia a ordenação é a seguinte:

SORT SORT-F ON ASCENDING KEY S-F-CHAVE


USING F-ACT
GIVING F-ACT-ORD.

No momento em que a instrução SORT começa a ser executada, ne-


nhum dos ficheiros envolvidos deve estar aberto. No entanto, é indispensável
que os valores das variáveis de identificação ID–F-ACT e ID–F–ACT–ORD es-
tejam definidos. Quanto à variável ID–SORT-F, não há nada a fazer, o pro-
grama tratará dela automaticamente. Portanto, a parte algorítmica do pro-
grama de ordenação resume-se a:

PROCEDURE DIVISION.
DISPLAY "Nome do ficheiro a ordenar: " NO ADVANCING.
ACCEPT ID-F-ACT.
DISPLAY "Nome do ficheiro ordenado: " NO ADVANCING.
ACCEPT ID-F-ACT-ORD.
SORT SORT-F ON ASCENDING KEY S-F-CHAVE
USING F-ACT
GIVING F-ACT-ORD.
DISPLAY "Ordenação concluída".
STOP RUN.

4.2. Recapitulação

Neste pequeno programa COBOL (em COBOL também se fazem pro-


gramas pequenos) aparece um exemplo de utilização da instrução SORT, bem
como a declaração do ficheiro de ordenação. Surgem também, pela primeira
vez, os FILLERs, que constituem uma facilidade muito bem-vinda, porque
poupa o trabalho de inventar nomes para coisas que não interessam, e que,
por isso mesmo, acabam por contribuir para a legibilidade, permitindo fazer
realçar aquelas variáveis que são realmente importantes.

5. Detecção de chaves duplicadas num ficheiro ordenado

Admitamos que, normalmente, o ficheiro de actualizações não deve


conter chaves duplicadas, isto é, que nunca surgem no ficheiro dois registos
com o mesmo valor no campo número de funcionário. Claro que essa possibili-
dade não pode ser excluída à partida, nem seria prático verificá-la no momen-
to da introdução dos dados. Tão pouco seria razoável tentar controlá-la antes
de ter ordenado o ficheiro. Mas, uma vez ordenado, as coisas devem ser mais
simples, pois as chaves repetidas, a existirem, vêm todas de seguida. Nestas
condições, o problema é o seguinte: “Dado o ficheiro F-ACT-ORD, construir a
partir dele dois ficheiros complementares, F–ACT-UNI e F–ACT–DUP, o pri-
meiro com todos os registos cuja chave é única, e o segundo com os restantes.
O programa deverá anunciar o número de chaves diferentes que aparecem re-
petidas.”

29
A parte declarativa do programa é análoga à dos precedentes. Vejamos
então a algoritmia:

Adoptemos a seguinte estratégia: o ficheiro F-ACT-ORD contém uma


sequência de registos. No entanto, esses registos não aparecem um a um, des-
garradamente, mas sim em grupos, a saber: grupos de registos com o mesmo
número de funcionário. Haverá grupos unitários e grupos não-unitários. Os
primeiros devem ser transferidos para F-ACT-UNI e os outros para F–ACT–
DUP. Claro que não faz sentido “ler” todos os registos de um grupo e decidir
depois se se trata de um grupo unitário ou não. Lido o primeiro de cada gru-
po, basta observar o registo seguinte, se houver. Se houver realmente, isto é,
se não se tiver atingido o fim do ficheiro, a sua chave permite descobrir ime-
diatamente qual é o tipo de grupo. Donde, a seguinte árvore programática:

Programa

Inicialização Finalização
eof-act-ord

fim-
Início-grupo Avançar grupo

Funcs-uni Funcs-dup

A condição EOF-ACT-ORD deverá tornar-se verdadeira quando se che-


gar ao fim do ficheiro (como o seu nome sugere, aliás). Como de costume, virá
associada a uma variável “booleana” B–EOF–ACT–ORD:

77 B-EOF-ACT-ORD PIC 9.
88 EOF-ACT-ORD VALUE 1.

O procedimento “início-grupo” memoriza o primeiro registo de cada


grupo, pois no momento em que um tal registo aparece ainda não se sabe
quantos vai ter o grupo. “Avançar” limita-se a passar ao registo seguinte (cor-
responde afinal a uma espécie de get, em Pascal). Nesta altura, olhando para
a chave do registo obtido, fica a saber-se qual é a natureza do grupo corrente:
se se tiver detectado o fim do grupo (chave diferente ou fim do ficheiro) é um
grupo unitário; se não, é um grupo múltiplo. A condição FIM–GRUPO está lá
para controlar isso mesmo. Consoante o caso, assim se despacha para o trata-
mento simples de grupo unitário (tudo o que há a fazer é acrescentar o registo

30
memorizado ao ficheiro F–ACT-UNI), ou um tratamento iterativo de recolha
de todos os registos com a mesma chave para o ficheiro F–ACT–DUP.

O procedimento "avançar" é o único que contém uma novidade: a ins-


trução READ, para "ler" registos de ficheiros. Esta instrução tem inúmeras va-
riantes, mas nós desprezaremos a maior parte delas, usando-a sempre, disci-
plinadamente, em formas bem determinadas.

Como é bem sabido, a execução de uma instrução de leitura pode cau-


sar o esgotamento do ficheiro, fenómeno que de maneira alguma pode passar
desapercebido. Por isso, sempre que houver um ficheiro sequencial para leitu-
ra de nome F–X, (com um registo associado de nome X–REG) nós declararemos
uma variável booleana B–EOF–X, que deverá ser inicializada a falso e coloca-
da a verdade quando se detectar fim do ficheiro (já vamos ver como):

77 B-EOF-X PIC 9.
88 EOF-X VALUE '1'.

Nestas condições, a leitura de um registo será feita usando a instrução


READ na seguinte forma:

READ F-X AT END MOVE 1 TO B-EOF-X.

Na realidade, apesar do seu nome, esta instrução corresponde exacta-


mente a um get(f_x), em Pascal (fazendo a assimilação do registo X-REG à
variável-janela f^), com a diferença de que, por não haver uma função para
determinar o fim do ficheiro esse controlo tem que ser feito “manualmente”,
pela cláusula AT END. Isto é verdade para todos os READs, excepto o primeiro.
De facto, este, juntamente com a inicialização da variável B–EOF–X, desempe-
nha as funções do reset(f_x), do seguinte modo:

MOVE '0' TO B-EOF-X.


READ F-X AT END MOVE 1 TO B-EOF-X.

Em conclusão: o procedimento “cada-grupo” pode ser programado as-


sim:

CADA-GRUPO.
MOVE '0' TO B-FIM-GRUPO.
MOVE ACT-ORD-REG TO TAMPAO.
PERFORM AVANCAR-ACT-ORD.
IF FIM-GRUPO PERFORM GRUPO-UNI
ELSE PERFORM GRUPO-DUP.

Convém notar que se o READ simples do COBOL corresponde ao get do


Pascal, existe uma variante que funciona um pouco como o read: é o chamado
READ INTO. De facto, a instrução:

READ F-X INTO Z

é equivalente a:
31
READ F-X. MOVE X-REG TO Z.

Note-se que as coisas se passam ao contrário do Pascal, onde primeiro


ocorre a afectação e só depois o get. Então e o que é que acontece se com o
READ se atinge o fim do ficheiro? Bom, se houver essa possibilidade deve utili-
zar-se a variante:

READ F-X INTO Z AT END MOVE 1 TO B-EOF-X.

sabendo-se que na condição de fim de ficheiro o MOVE X-REG TO Z não é


executado.

Passemos ao procedimento “avançar-act-ord”:

AVANCAR-ACT-ORD.
READ F-ACT-ORD AT END MOVE '1' TO B-EOF-ACT-ORD.
IF EOF-ACT-ORD MOVE '1' TO B-FIM-GRUPO
ELSE IF ACT-ORD-NUM-FUNC NOT = TAMPAO-NUM-FUNC
MOVE '1' TO B-FIM-GRUPO.

A seguir vem “grupo-uni”, que é muito simples:

GRUPO-UNI.
WRITE ACT-UNI-REG FROM TAMPAO.

Aqui está uma instrução de escrita! Tal como os READs, os WRITEs têm
em COBOL muitas variantes. Para já, chegam-nos duas, uma que correspon-
de ao write do Pascal, e outra ao put. A forma WRITE FROM, agora utilizada,
é a que está no primeiro caso. De facto, WRITE ACT-UNI-REG FROM TAMPAO
escrever-se-ia em Pascal write(f_act_uni, tampao). Em geral, dado um
ficheiro F-Y em escrita, com registo Y-REG, e uma variável (ou constante) Z,
podemos considerar que:

WRITE Y-REG FROM Z.

é equivalente a:

write(f_y, z)

Por sua vez, a forma simples:

WRITE Y-REG.

corresponde a:

put(f_y)

Esta comparação é reforçada pelo facto de ser explícito em COBOL


que:

WRITE Y-REG FROM Z.

32
pode ser visto como uma abreviatura de:

MOVE Z TO Y-REG. WRITE Y-REG.

O procedimento “grupo-dup” é sensivelmente mais complicado, por ser


iterativo:

GRUPO-DUP.
WRITE ACT-DUP-REG FROM TAMPAO.
PERFORM MAIS-UM UNTIL FIM-GRUPO.
MAIS-UM.
WRITE ACT-DUP-REG FROM ACT-ORD-REG.
PERFORM AVANCAR-ACT-ORD.

Já está quase tudo. Mas o enunciado pede também para contar o nú-
mero de chaves que aparecem repetidas. Para contar, é preciso um contador,
isto é, uma variável inteira, inicializada a zero (no procedimento de inicializa-
ção, que trata das inicializações globais), e incrementada de cada vez que se
detecta um grupo múltiplo. O lugar ideal para colocar esta operação é à en-
trada de GRUPO–DUP. Vejamos primeiro a forma da declaração da variável:

WORKING-STORAGE SECTION.
...
77 N-DUPS PIC 9(4) USAGE COMP.

Esta complicação toda é para significar que N-DUPS é uma variável in-
teira (como as do Pascal, por exemplo), e não uma cadeia de algarismos (aten-
ção que o PIC 9(4) esclarece, neste contexto, que a representação binária do
inteiro ocupa quatro bytes, e não que os valores possíveis vão de 0 a 9999).

A inicialização de N–DUPS é trivial:

MOVE ZERO TO N-DUPS.

Note-se o aparecimento de uma nova constante figurativa, esta mais


interessante ainda que SPACE. Consoante o contexto, ZERO representa o intei-
ro 0 ou o algarismo '0'. Aqui vale a primeira interpretação, claro.

O incremento é muito mais espectacular:

ADD 1 TO N-DUPS.

Repare-se como esta formulação é mais concisa que a convencional


n_dups := succ(n_dups).

Aliás, o COBOL é muito poupado neste tipo de operações aritméticas


em que a variável resultado é também um dos operandos. Os casos mais fre-
quentes são os seguintes, de significado evidente para quem saiba inglês (re-
sultado sempre em R):

33
ADD N TO R.
SUBTRACT N FROM R.
MULTIPLY N BY R.
DIVIDE N INTO R.

Note-se que N deve ser uma variável ou uma constante, e não pode ser
uma expressão arbitrária. No caso da divisão de um inteiro por outro, o resul-
tado é o quociente da divisão inteira. Quando se quiser o resto, tem que usar-
-se o seguinte esquema:

DIVIDE N1 BY N2 GIVING R REMAINDER M.

Para variáveis ou constantes inteiras N1 e N2, esta única instrução cor-


responde em Pascal à sequência:

r := n1 div n2; m := n1 mod n2

Aqui está um caso em que o obsoleto COBOL bate muitos dos seus con-
correntes mais recentes!

Em geral, quando houver expressões mais complicadas, o melhor é re-


correr à instrução COMPUTE, cuja forma geral é:

COMPUTE R = exp

onde exp representa uma expressão aritmética convencional, escrita com os


operadores +, -, * e /, com as prioridades habituais, e com ou sem parêntesis.

Para terminar vejamos o aspecto dos procedimentos de inicialização e


de finalização. O primeiro ocupa-se da abertura dos ficheiros, e da inicializa-
ção da variável N-DUPS:

INICIALIZACAO.
DISPLAY "Nome do ficheiro de entrada? " NO ADVANCING.
ACCEPT ID-F-ACT-ORD.
DISPLAY "Nome do ficheiro verificado? " NO ADVANCING.
ACCEPT ID-F-ACT-UNI.
DISPLAY "Nome do ficheiro dos duplicados? " NO ADVANCING.
ACCEPT ID-F-ACT-DUP.
OPEN INPUT F-ACT-ORD
OUTPUT F-ACT-UNI F-ACT-DUP.
MOVE 0 TO B-EOF-ACT-ORD.
READ F-ACT-ORD AT END MOVE 1 TO B-EOF-ACT-ORD.
MOVE O TO N-DUPS.

A finalização é bem mais curta, bastando-lhe fechar os ficheiros e dizer


quantos registos com chave duplicada apareceram:

FINALIZACAO.
CLOSE F-ACT-ORD, F-ACT-UNI, F-ACT-DUP.
DISPLAY "Registos com chaves duplicadas: ", N-DUPS.

34
6. Actualização do ficheiro de identificação de pessoal

Desde o início, a ideia era realizar a actualização do ficheiro de pessoal.


Começou-se por criar interactivamente um ficheiro (secção 3), depois esse fi-
cheiro foi ordenado (secção 4), a seguir verificado para eliminar casos anóma-
los (secção 5), e só agora se está em condições de concretizar a actualização
propriamente dita. Esta operação deverá envolver, por um lado, a versão cor-
rente do ficheiro de pessoal e o ficheiro F-ACT-UNI, estes em leitura, e, por
outro, a nova versão do ficheiro de pessoal e também um ficheiro de erros, es-
tes em escrita. O ficheiro de erros registará aquelas actualizações que não pu-
deram concretizar-se, por não fazerem sentido. Três tipos de situações destas
podem ocorrer: criação de um funcionário com um número já atribuído, remo-
ção ou modificação de funcionários inexistentes.

Para efectuar um acompanhamento mais cuidado do que se passa du-


rante a actualização, pode-se até decidir que em vez de um ficheiro de “erros”,
se pretende um ficheiro de controlo, no qual se deverá assinalar o desfecho de
cada operação: operação realizada normalmente, operação não concretizada,
com indicação do motivo. Este ficheiro de controlo destina-se a ser consultado
por pessoas, e por isso, é bom que esteja numa forma legível. Normalmente, o
que as pessoas querem é um mapa em papel, convenientemente paginado,
com cabeçalhos e rodapés, com um resumo no fim, etc. Podia ser o programa
de actualização a construir esse mapa, mas isso viria sobrecarregá-lo bastan-
te. Por isso, é uma melhor solução, mais modular, mais fácil de compreender
e manter, criar apenas um ficheiro com a informação necessária para a poste-
rior elaboração do mapa. Aliás, nada impede que esse mapa possa ser feito
por um programa escrito noutra linguagem, especializada em tarefas desse
género. Resta saber qual a informação que se pretende. Suponhamos que é a
seguinte: composição de cada registo criado de novo; valores dos campos mo-
dificados, nas operações de modificação, com a ressalva que o apelido deve ser
sempre incluído; número e nome dos funcionários removidos; composição dos
registos de actualização que correspondem a operações impossíveis.

Em resumo: o programa envolve quatro ficheiros: dois de entrada — F–


ID–V (“V” para “velho”) e F–ACT-UNI — e dois de saída — F–ID–N (“N” para
“novo”) e F–CTRL —. Como de costume, há uma variável booleana associada a
cada ficheiro de entrada:

77 B-EOF-ACT PIC 9.
88 EOF-ACT VALUE '1'.
77 B-EOF-ID-V PIC 9.
88 EOF ID-V VALUE '1'.

Trata-se de um clássico problema de actualização de um ficheiro mes-


tre, a partir de um ficheiro de transacções que não tem chaves repetidas. Ti-
picamente, o programa para este tipo de problemas envolve três ciclos: o pri-
meiro “roda” enquanto ambos os ficheiros de entrada não se esgotarem; cada
um dos restantes trata do remanescente de um dos ficheiros de entrada, de-
pois do outro ter acabado (por isso, apenas um desses dois ciclos é executado).

35
Durante o processamento do ciclo inicial — chamemos-lhe caso normal — es-
tão disponíveis em cada momento dois registos, um para cada ficheiro de en-
trada. Se o número de funcionário em F–ACT–UNI for inferior ao de F–ID–V,
só pode tratar-se de uma criação; se os dois números de funcionário são
iguais, tem que ser uma remoção ou uma modificação; senão, não há actuali-
zação, e tudo o que há a fazer é copiar o registo de F–ID–V para F–ID–N. Es-
tas considerações conduzem-nos à seguinte árvore programática:

Programa

Inicialização Finalização
eof-id-v ou eof-act-uni eof-id-v
eof-act-uni

Caso Novos
Cópia
normal funcs

act-num-func < = > id-v-num-func

Criação Modif ou
Cópia
remoção

(Em rigor, esta árvore não é uma árvore: diversos procedimentos apa-
recem em sítios diferentes. Mas transformamo-la de novo numa árvore, de-
cretando que se trata de exemplares diferentes do mesmo procedimento…)

Eis, para começar as secções INPUT–OUTPUT e FILE:

ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT F-ID-V ASSIGN TO ID-F-ID-V.
SELECT F-ACT ASSIGN TO ID-F-ACT.
SELECT F-ID-N ASSIGN TO ID-F-ID-N.
SELECT F-CTRL ASSIGN TO ID-F-CTRL.
...

DATA DIVISION.
FILE SECTION.
FD F-ID-V.
01 ID-V-REG.
02 ID-V-NUM-FUNC PIC 9(4).
02 FILLER PIC X(151).

36
FD F-ACT.
01 ACT-REG.
02 ACT-TIPO-OP PIC A.
88 OP-CRIACAO VALUE "C".
88 OP-REMOCAO VALUE "R".
88 OP-MODIFICACOES VALUE "M".
02 ACT-INFO.
03 ACT-NUM-FUNC PIC 9(4).
03 ACT-NOME.
04 ACT-APELIDO PIC X(16).
04 ACT-RESTO-NOME PIC X(32).
03 ACT-BI PIC 9(8).
03 ACT-NO-CONTRIB PIC 9(9).
03 ACT-NASC.
04 ACT-NASC-ANO PIC 99.
04 ACT-NASC-MES PIC 99.
04 ACT-NASC-DIA PIC 99.
03 ACT-MORADA.
04 ACT-MORADA-1 PIC X(30).
04 ACT-MORADA-2 PIC X(30).
04 ACT-MORADA-3 PIC X(20).

FD F-ID-N.
01 ID-N-REG.
02 ID-N-NUM-FUNC PIC 9(4).
02 ID-N-NOME.
03 ID-N-APELIDO PIC X(16).
03 ID-N-RESTO-NOME PIC X(32).
02 ID-N-BI PIC 9(8).
02 ID-N-NO-CONTRIB PIC 9(9).
02 ID-N-NASC PIC X(6).
02 ID-N-MORADA PIC X(80).

FD F-CTRL.
01 CTRL-REG.
02 CTRL-ACT-REG.
03 CTRL-OP PIC A.
03 FILLER PIC X(4).
03 CTRL-APELIDO PIC X(16).
03 FILLER PIC X(135).
02 CTRL-STATUS PIC X.

Para fazer avançar os ficheiros de entrada, usaremos três procedimen-


tos de serviço:

GET-ID-V.
READ F-ID-V AT END MOVE 1 TO B-EOF-ID-V.

GET-ACT.
READ F-ACT AT END MOVE 1 TO B-EOF-ACT.

37
ESCR-CTRL.
WRITE CTRL-REG.

Eis os parágrafos correspondentes aos procedimentos principais:

CASO-NORMAL.
IF ACT-NUM-FUNC < ID-V-NUM-FUNC PERFORM NOVOS-FUNCS
ELSE IF ACT-NUM-FUNC = ID-V-NUM-FUNC PERFORM MODIF-REM
ELSE PERFORM COPIA.

NOVOS-FUNCS.
IF OP-CRIACAO PERFORM CRIACAO
MOVE ZERO TO CTRL-STATUS
ELSE MOVE '1' TO CTRL-STATUS.
MOVE ACT-REG TO CTRL-ACT-REG.
PERFORM ESCR-CTRL.
PERFORM GET-ACT.

MODIF-REM.
IF OP-REMOCAO PERFORM REMOCAO
MOVE ZERO TO CTRL-STATUS
ELSE IF OP-MODIFICACOES PERFORM MODIFICACOES
MOVE ZERO TO CTRL-STATUS
ELSE MOVE '1' TO CTRL-STATUS.
MOVE ACT-REG TO CTRL-ACT-REG.
IF CTRL-APELIDO = SPACES
MOVE ID-V-APELIDO TO CTRL-APELIDO.
PERFORM ESCR-CTRL.
PERFORM GET-ACT. PERFORM GET-ID-V.

COPIA.
WRITE ID-N-REG FROM ID-V-REG.
PERFORM GET-ID-V.

CRIACAO.
WRITE ID-N-REG FROM ACT-INFO.

REMOCAO.

38
MODIFICACAO.
MOVE ID-V-ACT TO ID-N-ACT.
IF ACT-APELIDO NOT = SPACES
MOVE ACT-APELIDO TO ID-N-APELIDO.
IF ACT-RESTO-NOME NOT = SPACES
MOVE ACT-RESTO-NOME TO ID-N-RESTO-NOME.
IF ACT-BI NOT = SPACES
MOVE ACT-BI TO ID-N-BI.
IF ACT-NO-CONTRIB NOT = SPACES
MOVE ACT-NO-CONTRIB TO ID-N-NO-CONTRIB.
IF ACT-NASC NOT = SPACES
MOVE ACT-NASC TO ID-N-NASC.
IF ACT-MORADA NOT = SPACES
MOVE ACT-MORADA TO ID-N-MORADA.

Apenas duas observações: o parágrafo REMOCAO é mesmo assim, vazio;


ao contrário do que poderia parecer, não existe em COBOL a possibilidade de
escrever uma instrução de estilo:

IF ACT-MORADA NOT = SPACES MOVE IT TO ID-N-MORADA.

e, muito menos:

MOVE ALL FIELDS IN REG-ACT WHICH ARE NOT = SPACES


TO CORRESPONDING FIELDS IN ID-N-REG.

7. Construção de um ficheiro de etiquetas

Suponhamos que se pretende construir, a partir do ficheiro de pessoal,


um ficheiro de texto, contendo o endereço de cada funcionário, tal como deve-
ria aparecer numa carta. A ideia é fazer imprimir esse ficheiro em papel de
etiquetas autocolante. O programa deve funcionar igualmente bem com qual-
quer outro ficheiro de registos do mesmo tipo, obtido, por exemplo, por filtra-
ção do ficheiro de pessoal.

Cada endereço deve ser formado por cinco linhas: a primeira é constan-
te, contendo a cadeia “EXMO(A) SENHOR(A)”; a segunda deve exibir o nome
do funcionário, escrito como habitualmente, isto é com o apelido no fim; as
três restantes limitam-se a reproduzir as três linhas do campo morada nos re-
gistos de identificação. Cada endereço aparece numa página diferente. (A im-
pressora deverá ser programada para compatibilizar o comprimento das pági-
nas com o tamanho das etiquetas.)

Ou seja: a única linha interessante é a segunda. No ficheiro, o apelido e


o resto do nome aparecem em campos separados, e agora quer-se juntá-los,
colocando primeiro o resto do nome (presumivelmente os nomes próprios e os
apelidos menos significativos) e depois o apelido final, devendo existir apenas
um espaço entre este e o nome anterior. Em Pascal, conseguir este efeito é
muito fácil: bastam dois ou três ciclos, que percorreriam as cadeias, primeiro

39
para achar o último carácter diferente de espaço no resto do nome, e depois
para escrever os caracteres significativos.

As cadeias de caracteres em Pascal são vectores, e, como tal, o acesso


directo aos caracteres individuais pode ser feito por indexação. Em COBOL, o
mesmo não acontece, e, para se dispor desse tipo de acesso é preciso explicitar
que a cadeia tem uma estrutura vectorial. Vejamos como isso se faz, usando
uma cadeia genérica com 80 caracteres:

WORKING-STORAGE SECTION.
01 CADEIA.
02 CARS PIC X OCCURS 80.

Para se aceder ao primeiro carácter da cadeia CADEIA escrever-se-á


CARS (1) (os parêntesis são curvos), para aceder ao i-ésimo, CARS (I), etc.

Em COBOL pode declarar-se conjuntamente com cada vector uma va-


riável inteira para servir como índice “preferencial”, nas operações sobre ele,
nomeadamente nos ciclos. Neste caso, fica assim:

01 CADEIA.
02 CARS PIC X OCCURS 80 INDEXED BY I-CARS.

Isto não é obrigatório, mas como quase sempre que se tem um vector se
quer fazer algum tipo de iteração sobre os seus elementos, o esquema até é
bastante simpático. Além disso, o funcionamento da instrução SEARCH, de
busca em vectores, baseia-se num ciclo implícito, controlado pelo índice asso-
ciado ao vector, que nesse caso não pode ser omitido.

Uma operação que vai ser preciso realizar é determinar o comprimento


de uma cadeia. Em COBOL, isso não se consegue fazer com a “limpeza” do
Pascal, por exemplo, porque não há funções, nem parâmetros, nem nada…
Seja como for, vai ser preciso dispor de uma variável inteira para guardar o
comprimento, uma vez calculado:

WORKING-STORAGE SECTION.
...
77 N-CARS PIC 9(4) USAGE COMP.

Vejamos então como determinar o comprimento de uma cadeia, em


COBOL. A ideia é percorrê-la da direita para a esquerda, procurando o pri-
meiro carácter diferente de espaço: o seu índice dará o comprimento; se a ca-
deia for vazia, isto é, se todos os caracteres forem espaços, então o compri-
mento é zero. A instrução SEARCH trata de tudo:

CALC-COMPRIMENTO.
MOVE 0 TO I-CARS.
SEARCH CARS AT END MOVE ZERO TO N-CARS
WHEN CARS (80 - I-CARS) NOT = SPACE
COMPUTE N-CARS = 80 - I-CARS.

40
Palavras para quê?

Falta ver como se consegue a concatenação do resto do nome com o ape-


lido. Intuitivamente, o que há a fazer é colocar o resto do nome na cadeia, de-
terminar a última posição ocupada, isto é, o comprimento, e acrescentar os
caracteres do apelido, um a um, a partir da segunda posição à direita dessa.
Mas, para aceder aos caracteres do apelido é preciso que eles tenham estrutu-
ra de vector. O melhor, para não complicar as definições dos ficheiros, é mes-
mo arranjar outra cadeia na secção WORKING-STORAGE:

01 CADEIA-2.
02 CARS-2 PIC X OCCURS 80 INDEXED BY I-CARS-2.
...
77 N-CARS-2 PIC 9(4) USAGE COMP.

Claro que não vale a pena duplicar também o parágrafo que serve para
calcular o comprimento. Essa operação poderá em regra ser feita colocando a
cadeia na variável CADEIA e indo recolher o resultado a N-CARS. Podemos
ainda decidir que, ao fazer a concatenação, seria má programação perder tem-
po a acrescentar os espaços não significativos do apelido, e que, por isso, é
preciso começar por determinar o seu comprimento:

CONSTRUCAO-NOME.
MOVE ID-APELIDO TO CADEIA, CADEIA-2.
PERFORM CALC-COMPRIMENTO.
MOVE N-CARS TO N-CARS-2.
MOVE ID-RESTO-NOME TO CADEIA.
PERFORM CALC-COMPRIMENTO.
COMPUTE I-CARS = N-CARS + 1.
PERFORM JUNTAR-LETRA VARYING I-CARS-2 FROM 1 BY 1
UNTIL I-CARS-2 > N-CARS-2.
JUNTA-LETRA.
ADD 1 TO I-CARS.
MOVE CARS-2 (I-CARS-2) TO CARS (I-CARS).

Neste parágrafo aparecem duas coisas novas. A primeira é o MOVE com


vários destinos, e a segunda o PERFORM VARYING. Esta última é mais uma
variante da instrução PERFORM, e corresponde ao for do Pascal, com a diferen-
ça que se pode especificar um incremento diferente de 1, e que o critério de
terminação é uma condição geral.

Resta ver o processamento global. Trata-se, de certa maneira de um


problema de “tradução” de ficheiros: parte-se de um ficheiro com um certo
formato e certas informações e constrói-se, registo a registo, um outro fichei-
ro, com um formato diferente, e com um subconjunto daquelas informações. A
árvore programática é tão simples, que até poderia ser dispensada:

41
Programa

Inicialização Finalização
eof-id

uma
etiqueta

Eis a secção INPUT-OUTPUT, que contém uma novidade:

ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
SELECT F-ID ASSIGN TO ID-F-ID.
SELECT F-ETIQ ASSIGN TO PRINTER ID-F-ETIQ.

A novidade é o qualificativo PRINTER, no SELECT do F-ETIQ. Informa


o compilador que o ficheiro se destina a ser impresso, por outras palavras,
que se trata de um ficheiro de texto, e que, portanto, é de esperar que surjam
algumas instruções próprias desta classe de ficheiros.

Cada “registo” deste ficheiro F-ETIQ é uma linha. Não tem qualquer
estrutura, para além de ser formado por uma sequência de caracteres. A li-
nha mais comprida é a do nome, que pode ter até 49 caracteres. Por isso, bas-
ta declarar as coisas assim:

DATA DIVISION.
FILE SECTION.
FD F-ETIQ.
01 ETIQ-REG PIC X(49).

A inicialização e a finalização são como de costume. Não vale a pena


detalhá-las aqui. O processamento de cada etiqueta também não é complica-
do:

UMA-ETIQUETA.
PERFORM CONSTRUCAO-NOME.
PERFORM ESCR-ETIQ.
READ F-ID AT END MOVE 1 TO B-EOF-ID.

O parágrafo ESCR-ETIQ é o que se encarrega da impressão das cinco li-


nhas que formam cada etiqueta. Não se pode esquecer que cada etiqueta apa-
rece numa nova página:

42
ESCR-ETIQ.
WRITE ETIQ-REG FROM "EXMO(A) SENHOR(A)" AFTER PAGE.
WRITE ETIQ-REG FROM CADEIA.
WRITE ETIQ-REG FROM ID-MORADA-1.
WRITE ETIQ-REG FROM ID-MORADA-2.
WRITE ETIQ-REG FROM ID-MORADA-3.

43