Trabalho Final

Sumário

Objectivos

Pretende-se neste trabalho desenvolver uma folha de cálculo simples.  O trabalho divide-se em duas fases, correspondendo a duas entregas de resoluções por parte dos alunos.  A primeira fase é, naturalmente, menos ambiciosa nos seus objectivos que a segunda fase, servindo sobretudo como incentivo à dedicação dos alunos ao trabalho o mais cedo possível.  Os objectivos de cada uma das fases serão descritos mais abaixo.  Mas, globalmente, pretende-se que a folha de cálculo desenvolvida tenha as seguintes possibilidades:

As diferenças principais entre as duas fases do trabalho prendem-se com a interface com o utilizador (ver Secções 2.1 e 2.2), que é simplificada na primeira fase, e com a utilização de hierarquias de classes como forma de evitar a análise sintáctica repetida das fórmulas presentes em cada célula da folha.  Além disso, existem outros requisitos do trabalho na globalidade que não são exigidos na primeira fase.

1.1  Folha de cálculo

Uma folha de cálculo consiste num conjunto de células dispostas conceptualmente em linhas e colunas, sendo as linhas e colunas identificadas por números inteiros arbitrários.  A um par (l, c), em que l é o índice de uma linha e c é o índice de uma coluna, chama-se posição da célula na folha.  As células existem sempre, independentemente de terem uma representação física no programa.  As células têm uma fórmula que permite calcular o seu valor.  Inicialmente todas as células estão vazias, i.e., não têm fórmula.  As coordenadas têm índices arbitrários, inclusive negativos.  Assim, a coordenada (-10, 30) é válida e corresponde à coluna 30 da linha -10.

Apenas as posições correspondentes a células não vazias são mostradas visualmente.  As restantes são deixadas em branco.  

1.2  Células

Cada célula não-vazia da folha contém uma dada fórmula, que pode inclusive consistir apenas num valor numérico literal.  As fórmulas são expressas usando uma sintaxe semelhante à do C++, embora muito mais simples, e com algumas diferenças no nome dos operadores.

Se uma célula contiver uma fórmula com um erro de sintaxe (lexical ou gramatical), tal deverá ser assinalado graficamente através da palavra

«sintaxe»

Para efeitos de cálculos de outras células, uma célula nessas circunstâncias terá o valor nan (not a number), que é o valor que obtém com a seguinte definição C++:

double const nan = 0.0 / 0.0;

Se o erro na fórmula for não de sintaxe mas de cálculo, e.g., invocação de uma função inexistente ou com um número errado de argumentos ou utilização de uma constante indefinida, tal deverá ser assinalado graficamente através da palavra

«cálculo»

Para efeitos de cálculos de outras células, uma célula nessas circunstâncias terá o valor nan.

1.2.1  Referências

A fórmula de uma célula pode conter referências a valores de outras células.  Estas referências consistem sempre num par de coordenadas linha/coluna em que cada um dos valores é precedido do caractere '$'.  Por exemplo,

$1$2

é uma referência ao valor da célula na posição (1,2) da folha de cálculo.  As coordenadas numa referência são absolutas e fixas, i.e., referem-se à mesma célula independentemente da célula de cuja fórmula fazem parte, e não sofrem qualquer ajustamento quando as células são copiadas.

Quando a célula à qual uma fórmula se refere não existir ou tiver um erro, deve-se assumir que o seu valor é nan.

1.2.2  Funções

As fórmulas podem conter invocações de funções.  Deverá ser suportado o seguinte conjunto de funções pré-definidas:

Estas funções não podem ser alteradas e estão já previstas no pacote fornecido (ver Secção 1.2.5).

Todas estas funções têm equivalente em C++ (usar #include <cmath>).

1.2.3  Constantes

As fórmulas podem conter constantes.  Estas referem-se a valores numéricos que não são alteráveis por via das fórmulas da folha.  Numa fórmula as constantes são sempre introduzidas através da construção %nome, onde nome é o nome da constante.

Deverão existir apenas duas constantes pré-definidas:

Estas constantes não podem ser alteradas e estão já previstas no pacote fornecido (ver Secção 1.2.5).

1.2.4  Sintaxe das fórmulas

A gramática usada nas fórmulas é a que se define em seguida.  Nesta gramática surgem primeiro as definições com menor precedência.  A gramática é apresentada na forma de uma lista de regras de produção.  As regras de produção consistem no formato:

categoria_sintáctica := sequência_de_símbolos

A sequência de símbolos define possíveis formas para sequências de símbolos de acordo com a sintaxe da categoria sintáctica em definição.  Alguns símbolos pertencem à definição da gramática em si.  É o caso de |, que significa "ou", separando sequências de símbolos possíveis alternativas.  Os restantes símbolos ou são terminais (i.e., correspondem directamente a símbolos que se podem encontrar numa frase escrita na gramática em definição), ou são não-terminais, caso em que corresponderão a uma categoria sintáctica definida na gramática (nesse caso surgem em itálico).  Os símbolos terminais são colocados em tipo proporcional (assim) e assume-se que ocorrem tal e qual nas frase escritas usando a gramática.  Em alguns casos foge-se a uma definição formal, colocando uma definição informal entre {}.

Gramática das fórmulas:

expressão :=
expressão_condicional
expressão_condicional :=
expressão_ou |
expressão_ou ? expressão_condicional : expressão_condicional
expressão_ou :=
expressão_e |
expressão_e | expressão_ou
expressão_e :=
expressão_igualdade |
expressão_igualdade & expressão_e
expressão_igualdade :=
expressão_relacional |
expressão_relacional == expressão_igualdade |
expressão_relacional != expressão_igualdade
expressão_relacional :=
expressão_aditiva |
expressão_aditiva < expressão_relacional |
expressão_aditiva <= expressão_relacional |
expressão_aditiva > expressão_relacional |
expressão_aditiva >= expressão_relacional
expressão_aditiva :=
expressão_multiplicativa |
expressão_multiplicativa + expressão_aditiva |
expressão_multiplicativa - expressão_aditiva
expressão_multiplicativa :=
expressão_potencial |
expressão_potencial * expressão_multiplicativa |
expressão_potencial / expressão_multiplicativa
expressão_potencial :=
expressão_unaria |
expressão_unaria ^ expressão_potencial
expressão_unaria :=
expressão_funcional |
- expressão_unaria |
+ expressão_unaria |
! expressão_unaria
expressão_funcional :=
expressão_primaria |
identificador ( ) |
identificador ( lista_de_expressoes )
lista_de_expressoes :=
expressão |
expressão ',' lista_de_expressoes
expressão_primaria :=
número |
constante |
referência |
( expressão )
número :=
{como os valores literais double do C++}
número_inteiro :=
-{sequência de dígitos} |
+{sequência de dígitos} |
{sequência de dígitos}
constante :=
%identificador
identificador :=
{sequência de letras, dígitos e _(sublinhado), começando por uma letra}
referência :=
$ inteiro $ inteiro

1.2.5  Análise sintáctica das fórmulas

A análise sintáctica das fórmulas ocorre em duas fases: análise lexical e análise gramatical.  Na primeira fase as fórmulas são lidas caractere a caractere e divididas em símbolos (usa-se um símbolo especial para marcar o final da sequência).  Na segunda fase a sequência de símbolos é analisada verificando-se se corresponde à gramática acima (excepto as últimas regras produtivas, que dizem respeito à constituição de alguns tipos especiais de símbolos (número, constante, identificador e referência), e que são tratadas durante a análise lexical.

Por exemplo, a fórmula

5 * (1 + -3) ^ 2

pode ser gerada pelas regras de produção apresentadas na secção anterior.  A divisão em símbolos, que ocorre durante a análise lexical, resulta em:

A análise sintáctica desta fórmula resulta em

Esta parte do trabalho não precisa de ser desenvolvida de raiz.  Está disponível um pequeno pacote de análise e cálculo de fórmulas (calculo sem contexto.tar.gz) que contém as classes necessárias e um pequeno programa demonstrativo do funcionamento do código (teste.C).  O código fornecido deverá ser lido e compreendido, pois será certamente necessário alterá-lo, pois não suporta referências, por exemplo.

Encontra também uma versão um pouco diferente do código (calculo com contexto.tar.gz) que faz uso de um contexto de cálculo das fórmulas.  A obtenção do valor das células referenciadas numa fórmula obriga a que o cálculo seja realizado no contexto da respectiva folha de cálculo.  Pode-se inspirar neste pacote para perceber como isso pode ser conseguido.

Interface com o utilizador

A interface do programa com o utilizador será muito simples.  Na primeira fase do trabalho recorrerá simplesmente a linhas de comandos, usando-se para isso os canais cin e cout.  Na segunda fase será necessário equipar o programa com uma interface mais fácil de usar, recorrendo à biblioteca Slang++.

Atempadamente serão disponibilizados ficheiros executáveis oficiais para cada uma das fases de entrega do trabalho final, cuja interface com o utilizador deverá ser emulada pelos programas desenvolvidos pelos alunos.

2.1  Exemplo de interacção com o utilizador (1ª fase)

Lista-se abaixo um exemplo simples de interacção do programa com o utilizador.  A negrito apresentam-se as entradas do utilizador.  Note-se que o programa mostra apenas as células das linhas 0 a 22 e colunas 0 a 7, e em baixo apenas se mostram as três primeiras colunas:

mms@mercurio ~> editor_de_folha_oficial /usr/local/bin/teste.flh

                 0              1              2              3...
  0             31              0              0                                                                            
  1      «sintaxe»       0.314159       0.309017                                                                            
  2      «calculo»       0.628319       0.587785                                                                            
  3             31       0.942478       0.809017                                                                            
  4            nan        1.25664       0.951057                                                                            
  5            nan         1.5708              1                                                                            
  6         1.0472        1.88496       0.951057                                                                            
  7       0.866025        2.19911       0.809017                                                                            
  8       0.866025        2.51327       0.587785                                                                            
  9        3.14159        2.82743       0.309017                                                                            
 10      «calculo»        3.14159    1.22461e-16                                                                            
 11              1        3.45575      -0.309017                                                                            
 12                       3.76991      -0.587785                                                                            
 13                       4.08407      -0.809017                                                                            
 14                       4.39823      -0.951057                                                                            
 15                       4.71239             -1                                                                            
 16            nan        5.02655      -0.951057                                                                            
 17            nan        5.34071      -0.809017                                                                            
 18      «calculo»        5.65487      -0.587785                                                                            
 19      «calculo»        5.96903      -0.309017                                                                            
 20      «calculo»                                                                                                          
 21                                                                                                                         
 22                                                                                                                         
<enter> para continuar.
Menu:
        edita - ver/definir/apagar célula.
        copia - copia célula.

        guarda - guarda folha em disco.
        carrega - carrega folha do disco.

        sai - sai do editor.

Opção: edita
Qual a posição? [linha coluna] 5 2
Célula (5,2):
        Fórmula: sen($5$1)
        Valor: 1
        Diagnóstico: Ok.
Menu:
        nova - nova fórmula.
        apaga - apaga fórmula/célula.

        sai - volta ao menu anterior.

Opção: nova
Introduza a nova fórmula: sen($5$1) * 2
Célula (5,2):
        Fórmula: sen($5$1) * 2
        Valor: 2
        Diagnóstico: Ok.
Menu:
        nova - nova fórmula.
        apaga - apaga fórmula/célula.

        sai - volta ao menu anterior.

Opção: sai
                 0              1              2              3... 
  0             31              0              0                                                                            
  1      «sintaxe»       0.314159       0.309017                                                                            
  2      «calculo»       0.628319       0.587785                                                                            
  3             31       0.942478       0.809017                                                                            
  4            nan        1.25664       0.951057                                                                            
  5            nan         1.5708              2                                                                            
  6         1.0472        1.88496       0.951057                                                                            
  7       0.866025        2.19911       0.809017                                                                            
  8       0.866025        2.51327       0.587785                                                                            
  9        3.14159        2.82743       0.309017                                                                            
 10      «calculo»        3.14159    1.22461e-16                                                                            
 11              1        3.45575      -0.309017                                                                            
 12                       3.76991      -0.587785                                                                            
 13                       4.08407      -0.809017                                                                            
 14                       4.39823      -0.951057                                                                            
 15                       4.71239             -1                                                                            
 16            nan        5.02655      -0.951057                                                                            
 17            nan        5.34071      -0.809017                                                                            
 18      «calculo»        5.65487      -0.587785                                                                            
 19      «calculo»        5.96903      -0.309017                                                                            
 20      «calculo»                                                                                                          
 21                                                                                                                         
 22                                                                                                                         
<enter> para continuar.

2.2  Exemplo de interacção com o utilizador (2ª fase)

A interface é muito simples.  Em vez de se descrever aqui, optou-se por disponibilizar uma versão oficial do ficheiro executável.  Os alunos devem testá-lo e emular a sua interface.  

Abaixo apresentam-se três exemplos de interacção do programa com o utilizador.

Edição de folha com cursor na célula (8,2) e com a célula (3,1) marcada para cópia.

 

Edição de folha depois de ter premido <enter> sobre a célula (8,2).

 

Edição de folha depois de ter redimensionado a janela da respectiva consola.  Note-se que as duas linhas de informação sobre a célula corrente não são mostradas.

Note-se que:

  1. O comando '?' mostra uma janela de ajuda com todos os comandos disponíveis.
  2. O programa deve estar preparado para lidar com redimensionamentos da janela onde é executado.

Fases do trabalho

O trabalho divide-se em duas fases, correspondendo a final da primeira à entrega intermédia prevista no método de avaliação da disciplina.  A segunda fase corresponde à entrega definitiva.

3.1  Entrega intermédia

Corresponde a um programa que serve de folha de cálculo simplificada, com uma interface trivial.  As únicas operações a suportar pela folha são:

  1. Carregar folha de cálculo de ficheiro (o programa deve estar preparado para receber como argumento a folha a carregar inicialmente).
  2. Editar (ver, definir/alterar fórmula, apagar) célula com coordenadas dadas.
  3. Copiar uma célula para outra.
  4. Assinalar erros de análise das fórmulas (i.e., erros sintácticos), erros de cálculo das fórmulas (e.g., constante inexistente).

Não é obrigatório evitar a análise gramatical antes do cálculo de cada fórmula.  Fazê-lo apropriadamente requer noções de hierarquias de classes, necessárias apenas na segunda fase do trabalho.

Não é necessário garantir que, após uma alteração à folha de cálculo, nenhuma célula seja calculada mais do que uma vez, nem tão pouco verificar se existem dependências circulares entre as células.  No entanto, recomenda-se que tal seja feito, por forma a adiantar trabalho para a entrega definitiva.

É obrigatória a utilização de uma interface com o programa exactamente igual à fornecida pela resolução oficial (a única excepção é o armazenamento da folha em disco, que deverá constar no menu mas não precisará de ser implementada), disponível através do comando editor_de_folha_oficial disponível na máquina mercurio, no directório /usr/local/bin/.  No mesmo directório encontrará o ficheiro teste.flh, que contém os dados de uma folha de cálculo simples (e com erros), que o seu programa deverá ser capaz de ler.  O formato dos ficheiros encontra-se descrito na Secção 4.

3.2  Entrega definitiva

Corresponde a um programa que serve de folha de cálculo simplificada, com uma interface baseada no Slang++.  Os requisitos são:

É obrigatória a utilização de uma interface com o programa exactamente igual à fornecida pela resolução oficial, disponível através do comando editor_de_folha_final_oficial disponível na máquina mercurio, no directório /usr/local/bin/.  No mesmo directório encontrará o ficheiro teste.flh, que contém os dados de uma folha de cálculo simples (e com erros), que o seu programa deverá ser capaz de ler.  O formato dos ficheiros encontra-se descrito na Secção 4.

Formato dos ficheiros

O formato dos ficheiros de folhas de cálculo é simples.  Na descrição abaixo considere todo o texto após e incluindo o símbolo '%' como sendo comentários, que não deverão surgir num ficheiro real. 

3 % número de linhas da folha para as quais se especificam células (células não especificadas assumem-se vazias)
-2 % número da primeira linha para a qual se descrevem células
4 % número de células descritas nesta linha
-4 % número da coluna da primeira célula nesta linha (linha -2)
sin(%pi / 4) % fórmula da célula na linha -2, coluna -4
0 % número da coluna da segunda célula nesta linha (linha -2)
$0$0 > 0 ? $0$1 : $0$2 % fórmula da célula na linha -2, coluna 0
1 % número da coluna da terceira célula nesta linha (linha -2)
1000.4 % fórmula da célula na linha -2, coluna 1
40 % número da coluna da quarta célula nesta linha (linha -2)
% fórmula (vazia!) da célula na linha -2, coluna 40
% Etc.  Aqui surgiriam as descrições das duas linhas seguintes da folha de cálculo.

Ou seja, em primeiro lugar surge o número de linhas descritas.  Depois segue-se informação sobre cada umas dessas linhas.  A informação sobre uma linha consiste no número de células descritas nessa linha.  Depois segue informação acerca das células.  A informação acerca das células consiste na indicação da respectiva coluna e da sua fórmula.  Todas estas informações são dadas em linhas separadas.

Dicas

Esta secção irá crescendo com outras dicas.  Mantenha-se atento.

Requisitos de implementação

  1. A resolução deste problema deverá recorrer a classes C++, a modularização física, e a pacotes (espaços nominativos) sempre que apropriado. (1ª e 2ª fases)
  2. Deverão existir as classes Folha, Posicao e EditorDeFolha, para além das já fornecidas (ver calculo sem contexto.tar.gz). (1ª e 2ª fases)
  3. Deve existir uma hierarquia de classes para representar as fórmulas já analisadas e prontas a calcular.  Essa hierarquia deve recorrer a classes genéricas sempre que possível para evitar repetições de código. (2ª fase)
  4. Deve ser usada uma hierarquia de classes para representar os vários tipos de operações a realizar durante o cálculo de uma fórmula previamente analisada (relevante apenas na entrega definitiva). (2 fase)
  5. A hierarquia de classes representando operações de uma fórmula deve estar construída de tal maneira que seja fácil acrescentar novos tipos concretos de operações. (2ª fase)
  6. A hierarquia deve fazer uso de polimorfismo. (2 fase)
  7. Deve-se evitar ao máximo todas as situações em que seja necessário saber o tipo concreto do objecto apontado por um ponteiro cujo tipo seja de uma classe base.  Ou seja, deve-se evitar ao máximo código que exija conhecimento acerca do tipo dinâmico de objectos polimórficos. (2 fase)
  8. A folha de cálculo deverá ser resistente a todo o tipo de erros (comandos inexistentes, ficheiros de entrada inválidos ou inexistentes, etc.).  Em circunstância alguma deve a folha de cálculo simplesmente abortar! (1ª e 2ª fases)
  9. Todas as rotinas, operações e métodos devem ser claramente documentados, nomeadamente quando a pré-condição e condição objectivo.  As condições invariantes das classes devem ser identificadas. (1ª e 2ª fases)
  10. Devem-se usar asserções para verificar estas condições (PC, CO e CIC) no código. (1ª e 2ª fases)
  11. Erros em recursos externos (e.g., ficheiros) devem ser assinalados através do lançamento de excepções.  Todas as excepções lançadas devem ser capturadas. (1ª e 2ª fases)
  12. Todas as classes que representem conceitos que se pretende preservar entre edições sucessivas devem possuir o trio de métodos constituído por construtor de canal de entrada, carregaDe(), que carrega nova informação a partir de um canal de entrada (1ª fase), e guardaEm(), que guarda num canal de saída toda a informação (2ª fase).  Ver Notas sobre leitura e escrita em ficheiros.
  13. Todas as ferramentas do programa relativas a cálculos devem ser colocadas num pacote à parte, representado por um espaço nominativo próprio. (1ª e 2ª fases)
  14. O formato exacto dos ficheiros de texto onde os documentos são guardados é o indicado na secção apropriada acima. (1ª e 2ª fases)
  15. O código deve fornecer a máxima garantia de segurança face a excepções que for possível.

Condições de entrega e avaliação

7.1  Condições de entrega

O Trabalho Final será resolvido em grupo, devendo manter-se os mesmos grupos constituídos para o Problema.

7.1.1  Entrega intermédia

7.1.2  Entrega definitiva

  1. Este trabalho será resolvido em grupo (ver página da disciplina).  
  2. A resolução deste trabalho deve ser entregue até as 18:30h de segunda-feira, 3 de Junho de 2002 (até as 18:30h de segunda-feira, 10 de Junho de 2002, com penalizações).
  3. A resolução só se considera entregue depois de:
    1. ser entregue pessoalmente a um dos docentes da disciplina uma disquete, devidamente identificada, contendo apenas os ficheiros fonte onde se encontra o código C++ (extensões .C, ._impl.H e .H) e o respectivo ficheiro de construção (Makefile);
    2. ser entregue pessoalmente a um dos docentes da disciplina um relatório em papel, usando o modelo publicado para os problemas (Word, ou RTF), contendo obrigatoriamente a listagem de todos os ficheiros colocados na disquete e contendo adicionalmente um pequeno texto de no máximo uma página A4 explicando as opções tomadas para a resolução; e
    3. serem enviados todos os ficheiros colocados na disquete via correio electrónico para  Ana.Violante@iscte.pt (grupos de IGE) ou para Ricardo.Ribeiro@iscte.pt (grupos de ETI).  Grupos mistos poderão enviar a mensagem para qualquer dos dois endereços (mas apenas para um deles).
  4. Imprima o código em C++ sempre de uma forma bem legível: use sempre um tipo não proporcional (e.g., Courier) e, caso seja possível fazê-lo, imprima em papel reciclado, frente-e-verso.
  5. A entrega do problema fora do prazo implica a penalização de dois (2) valores por cada dia útil de atraso (i.e., 4 de Junho -2 valor, 5 de Junho -4 valores, 6 de Junho -6 valores, 7 de Junho -8 valores e 11 de Junho -10 valores) não se aceitando trabalhos após terça-feira, 11 de Junho de 2002, até às 18:30h.
  6. A não observação dos modos de entrega descritos acima poderá ser penalizada.
  7. Exija ao docente a entrega de um recibo comprovativo da entrega do trabalho.  Este recibo é a única prova que tem da entrega do seu trabalho.

As datas das orais e das inscrições nas orais serão anunciadas logo que possível.

7.2  Avaliação

A avaliação do Trabalho Final terá em conta não só a correcta execução do programa, mas também, e principalmente, a correcta estruturação das classes e métodos  e das rotinas que compõem o programa e ainda a sua legibilidade.  Valorizar-se-á mais a simplicidade que a eficiência.  Valorizar-se-á também código C++ correctamente comentado (mas não excessivamente, não vale a pena comentar o óbvio).

A nota deste trabalho será dada apenas após uma discussão, individual, com cada um dos elementos do grupo. Nesta discussão qualquer elemento do grupo terá de demonstrar um total conhecimento do programa e ser capaz de operar as alterações que forem pedidas.  Nessa oral poderão também ser feitas perguntas sobre a matéria em geral.  A nota final poderá depender não só da qualidade do trabalho, mas também, e principalmente, da avaliação que os docentes façam da participação relativa dos alunos na feitura do trabalho.

Quaisquer funcionalidades extra que não tenham sido pedidas no enunciado, tais como menus mais sofisticados etc., não serão avaliadas.


Depuração com o Slang++

A depuração de programas usando o Slang++ apresenta algumas complicações.  Acontece que estas aplicações não podem ser executadas dentro do depurador, pois fazem uso de capacidades do terminal que este não disponibiliza.  Assim, é necessário executar o programa a depurar numa janela xterm normal, fora do gdb, e depois convencer o gdb a "agarrar-se" ao programa já em execução.  Para isso:
  1. Coloque logo no início do programa a depurar uma instrução de leitura do teclado, de modo a que este não avance antes de o desejar.
  2. Lance o gdb dentro do Emacs, como habitualmente, especificando o ficheiro a depurar.  Não dê o comando r[un]!
  3. Execute o programa dentro de uma janela xterm.
  4. Cada instância de um programa em execução é um processo.  Cada processo, em Linux, tem um número de processo (PID, de Process IDentification).  É necessário identificar o número do processo em execução.  Para isso dê (numa consola) o comando:
    1. ps -auxww | grep nome_do_programa | grep annnnn
    onde nnnnn é o seu número de aluno e nome_do_programa é o nome do programa a depurar.  Surgir-lhe-á uma lista dos processos que são instâncias do seu programa e que lhe pertencem.  É conveniente que tenha apenas um programa em execução de modo a identificar claramente qual o PID que lhe interessa.
  5. No gdb coloque um ponto de paragem onde for conveniente.  Em seguida dê o comando:
    1. attach pid
    onde pid é o PID do processo identificado na alínea anterior.  Este comando faz com que o gdb passe a controlar o processo.  Dê o comando:
      c[ontinue]
  6. A partir deste momento a depuração prossegue como habitualmente.