(→Interacção com o Utilizador) |
(→Tabela de operadores) |
||
Line 125: | Line 125: | ||
! style="vertical-align: top; font-weight: normal;" | chamada (unário) | ! style="vertical-align: top; font-weight: normal;" | chamada (unário) | ||
! style="vertical-align: top; font-weight: normal;" | '''call''' | ! style="vertical-align: top; font-weight: normal;" | '''call''' | ||
− | ! style="vertical-align: top; font-weight: normal;" | o nome do programa a chamar deve ser uma cadeia de caracteres | + | ! style="vertical-align: top; font-weight: normal;" | o nome do programa a chamar deve ser uma cadeia de caracteres literal |
! style="vertical-align: top; font-weight: normal;" | Executa o programa nomeado pelo argumento, devolvendo o valor da sua última expressão. <br>Exemplo: '''(call "nome-do-programa")''' | ! style="vertical-align: top; font-weight: normal;" | Executa o programa nomeado pelo argumento, devolvendo o valor da sua última expressão. <br>Exemplo: '''(call "nome-do-programa")''' | ||
|} | |} |
AVISOS - Avaliação em Época Normal |
---|
Esclarecimento de dúvidas:
|
Requisitos para desenvolvimento, material de apoio e actualizações do enunciado (ver informação completa em Projecto de Programação com Objectos):
|
Processo de avaliação (ver informação completa em Avaliação do Projecto):
|
Material de Uso Obrigatório |
---|
As bibliotecas po-uuilib e o conteúdo inicial do CVS são de uso obrigatório: |
|
A máquina virtual, fornecida para desenvolvimento do projecto, já contém todo o material de apoio. |
Uso Obrigatório: Repositório CVS |
Apenas se consideram para avaliação os projectos existentes no repositório CVS oficial.
Trabalhos não presentes no repositório no final do prazo têm classificação 0 (zero) (não são aceites outras formas de entrega). Não são admitidas justificações para atrasos em sincronizações do repositório. A indisponibilidade temporária do repositório, desde que inferior a 24 horas, não justifica atrasos na submissão de um trabalho. |
O objectivo do projecto é criar uma aplicação que permite gerir um conjunto de programas e as expressões de que são compostos.
Neste texto, o tipo negrito indica um literal (i.e., é exactamente como apresentado); o símbolo indica um espaço; e o tipo itálico indica uma parte variável (i.e., uma descrição).
Uma expressão é uma representação algébrica de uma quantidade, ou seja, todas as expressões podem ser avaliadas para obteção de um valor.
Uma expressão pode ser descrita por uma linguagem cuja interpretação resulta directamente, ou no valor (tipicamente, aplicável apenas em situações muito simples), ou na representação dessa expressão como uma estrutura de dados (tal como neste projecto), sobre a qual pode ser então executado o processo de avaliação, para determinação do valor correspondente.
Neste documento utiliza-se uma linguagem simples e uniforme para descrever qualquer expressão, que pode ser categorizada como expressão primitiva (literais e identificadores) ou como expressão composta (operadores).
As expressões primitivas, ou representam directamente um valor (expressões literais), ou referem outra expressão (identificadores).
As expressões literais são de dois tipos: inteiros e cadeias de caracteres.
Um identificador permite referenciar um valor. A avaliação do identificador corresponde à obtenção do seu valor.
São iniciados por uma letra, seguindo-se 0 (zero) ou mais letras ou dígitos. O comprimento do nome é ilimitado e dois nomes são distintos se houver alteração de maiúscula para minúscula, ou vice-versa, de pelo menos um carácter.
Se for referenciado um identificador que não tenha sido definido, o valor devolvido é 0 (zero) e o identificador mantém-se indefinido.
Uma expressão composta é constituída por um operador, podendo ter argumentos. Cada argumento é representado por uma expressão.
O valor de uma expressão composta corresponde à avaliação do operador sobre os seus argumentos (no caso dos operadores com argumentos) ou simplesmente do operador (no caso dos operadores sem argumentos). O tipo de argumentos e de resultado depende do operador.Por exemplo, há operadores que só suportam argumentos do tipo inteiro. A passagem de tipos inapropriados para um operador é um erro (mas esta verificação apenas deve ser feita quando se executa o programa).
O formato de representação de uma expressão composta é como se segue:
( nome-do-operador argumento-1 ... argumento-N )
Alguns operadores têm um número fixo de argumentos, enquanto outros têm um número variável de argumentos (operadores variádicos).
Exemplos:
Os vários operadores e a correspondente semântica são apresentados na tabela. A maioria dos operadores segue a semântica da família da linguagem C, excepto onde explicitamente indicado. Tal como em C, os valores lógicos são 0 (zero) (valor falso), e diferente de zero (valor verdadeiro). A ordem de avaliação dos argumentos é sempre da esquerda para a direita, excepto onde indicado.
Designação | Operadores | Operandos | Semântica |
aritméticos (unários) | neg | inteiros | C |
---|---|---|---|
aritméticos (binários) | add sub mul div mod | inteiros | C |
comparação (binários) | lt le ge gt | inteiros | C |
igualdade (binários) | eq ne | inteiros | C |
lógicos (unários) | not | inteiros | C |
lógicos (binários) | and or | inteiros | C -- Para and, o 2º argumento só é avaliado se o 1º não for falso. Para or, o 2º argumento só é avaliado se o 1º não for verdadeiro. |
atribuição (binário) | set | todos os tipos | O valor da expressão (2º argumento) é associado ao identificador (1º argumento). O valor da expressão é o do seu 2º argumento. |
sequência (variádico) | seq | todos os tipos | As sub-expressões são avaliadas em sequência. O valor da expressão é o do seu último argumento. |
impressão (variádico) | todos os tipos | As sub-expressões são avaliadas em sequência e os valores correspondentes apresentados na saída. O valor da expressão é o do seu último argumento. | |
leitura (sem argumentos) | readi reads | - | Pedem a introdução (pelo utilizador) de valores com os tipos correspondentes. |
condicional (trenário) | if | condição (1º argumento) deve ser inteira | C -- Comporta-se de modo semelhante ao do operador trenário ?: (não exige concordância de tipos entre os 2º e 3º argumentos). Apenas é avaliado o argumento correspondente ao valor lógico indicado pela condição. |
ciclo (binário) | while | condição (1º argumento) deve ser inteira | Avalia o 1º argumento: se o valor lógico for falso, retorna-o; se o valor lógico for verdadeiro, avalia o 2º argumento, após o que reinicia o ciclo com nova avaliação do 1º argumento, etc. O valor da expressão é o valor da mais recente avaliação do seu primeiro argumento, ou seja, devolve sempre o valor correspondente à condição falsa (0, zero). |
chamada (unário) | call | o nome do programa a chamar deve ser uma cadeia de caracteres literal | Executa o programa nomeado pelo argumento, devolvendo o valor da sua última expressão. Exemplo: (call "nome-do-programa") |
Os exemplos seguintes apresentam casos de uso simples dos operadores acima.
Exemplo 1: sequência com 3 expressões (valor 9):
Exemplo 2: sequência com 5 expressões (valor "olá"):
Exemplo 3: sequência com 2 expressões (valor final 0) (note-se que esta sequência corresponde a um pequeno "programa" que apresenta o valor do identificador ix na saída):
Um programa é formado por um conjunto de expressões, tem um nome e pode ser executado. A execução de um programa corresponde a avaliar todas as suas expressões sequencialmente, com início na primeira.
O interpretador permite interpretar (representar o texto como objectos) e avaliar expressões (calcular os seus valores). Possui também várias formas de preservar o seu estado (não é possível manter várias versões do estado do interpretador em simultâneo).
As expressões, apresentadas ao interpretador em forma de texto, devem ser interpretadas antes de serem armazenadas. O texto original não é preservado. Note-se que o processo de interpretação (conversão do texto para estruturas/objectos) é diferente do de avaliação (conversão das estruturas/objectos para valores).
O texto a interpretar (expressões ou programas) pode ser providenciado via interface do próprio interpretador ou na forma de um ficheiro que contém um programa.
Uma falha de interpretação causa o lançamento de uma excepção no analisador do interpretador.
O interpretador permite associar nomes a programas, preservando-os. Note-se que isto não corresponde a guardar valores calculados por esses programas, mas a guardar as suas descrições, i.e., as resultantes da interpretação da forma textual correspondente. Se um nome já estiver em uso, a associação ao programa anterior é perdida.
A interpretação de programas e, consequentemente, das suas expressões, deve ser feita de forma flexível. Ou seja, deve ser possível -- sem alterar o código das expressões ou do interpretador -- definir novas formas de avaliação. Por omissão, a interpretação corresponde simplesmente à apresentação de uma forma textual do programa (e das suas expressões).
Uma outra interpretação possível é o cálculo dos valores das expressões do programa, correspondente à execução desse programa.
É possível reiniciar, guardar e recuperar o estado actual do interpretador, preservando todos os programas e correspondentes nomes.
Descreve-se nesta secção a funcionalidade máxima da interface com o utilizador. Em geral, os comandos pedem toda a informação antes de proceder à sua validação (excepto onde indicado). Todos os menus têm automaticamente a opção Sair (fecha o menu).
As operações de leitura e escrita devem realizar-se através dos objectos form e display, respectivamente, presentes em cada comando. As mensagens são produzidas pelos métodos das bibliotecas de suporte (po-uuilib e pex-app). As mensagens não podem ser usadas no núcleo da aplicação (pex-core). Além disso, não podem ser definidas novas. Potenciais omissões devem ser esclarecidas antes de qualquer implementação.
As excepções usadas na interacção, excepto se indicado, são subclasses de pt.tecnico.po.ui.DialogException, são lançadas pelos comandos e tratadas por pt.tecnico.po.ui.Menu. Outras excepções não devem substituir as fornecidas nos casos descritos.
As acções do menu, listadas em pex.app.main.MenuEntry, permitem gerir a salvaguarda do estado da aplicação: Criar, Abrir, Guardar, Criar Programa, Ler Programa, Escrever Programa e Manipulação de Programa. A classe pex.app.main.Message define os métodos para geração das mensagens de diálogo. Inicialmente, a aplicação tem uma memória vazia.
O conteúdo do interpretador (inclui todos os programas actualmente carregados pelo interpretador) pode ser guardado para posterior recuperação (via serialização Java: java.io.Serializable). Na leitura e escrita do estado da aplicação, devem ser tratadas as excepções associadas. A funcionalidade é a seguinte:
A opção Sair nunca guarda o estado da aplicação, mesmo que existam alterações.
É possível criar novos programas (vazios), ler programas a partir de ficheiros textuais e escrever programas sob a forma de ficheiros textuais. As operações são as seguintes:
Abre o menu de edição (alteração) de um programa e do seu conteúdo. O interpretador pede o identificador do programa requestProgramId(). Se o programa não existir, é comunicado o erro através de noSuchProgram().
Este menu permite efectuar operações sobre um programa. A lista completa é a seguinte: Listar programa, Executar, Adicionar expressão, Substituir expressão, Mostrar os identificadores presentes no programa, Mostrar os identificadores não inicializados do programa.
As etiquetas das opções deste menu estão definidas na classe pex.app.evaluator.Label. Todos os métodos correspondentes às mensagens de diálogo para este menu estão definidos na classe pex.app.evaluator.Message.
Este comando apresenta a lista de expressões do programa em formato textual.
Este comando executa o programa.
Este comando permite adicionar uma nova expressão ao programa. Para tal, é pedida a posição de inserção, através de requestPosition(), sendo a nova expressão aí inserida. A nova expressão é lida como resposta a requestExpression().
Se a indicação de posição for inválida, o comando deve lançar a excepção pex.app.BadPositionException e o programa não é alterado.
Note-se que a nova expressão é interpretada: se a excepção pex.ParserException (do "core") for recebida, então a excepção pex.app.BadExpressionException deve ser lançada pelo comando. Neste caso, o programa não deve ser alterado.
Este comando permite substituir uma expressão existente no programa por uma nova expressão. Para tal, é pedida a posição de inserção, através de requestPosition(), sendo a expressão aí existente substituída pela nova expressão. A nova expressão é lida como resposta a requestExpression().
Se a indicação de posição for inválida, o comando deve lançar a excepção pex.app.BadPositionException e o programa não é alterado.
Note-se que a nova expressão é interpretada: se a excepção pex.ParserException (do "core") for recebida, então a excepção pex.app.BadExpressionException deve ser lançada pelo comando. Neste caso, o programa não deve ser alterado.
Este comando permite listar todos os identificadores presentes no programa (apresentado um identificador por linha), tanto nas expressões de definição, como nas que usam identificadores.
Este comando permite listar todos os identificadores presentes no programa que não são objecto de nenhuma incialização explícita (apresentado um identificador por linha), i.e., não terem um valor previamente associado via operador set.
Além das opções de manipulação de ficheiros descritas no menu principal, é possível iniciar a aplicação com um ficheiro de texto especificado pela propriedade Java import. Este ficheiro contém um programa que é avaliado pelo interpretador. O programa fica registado com o nome import.
Este programa é composto por três expressões, a última das quais é um ciclo.
(seq (add 1 2) (sub 1 2) (mul (add 1 2) 3)) (seq (add 1 2) (sub 1 2) (mul (add 1 2) 3) (div 2 5) "olá") (seq (set ix 0) (while (lt ix 30) (seq (print "ix =" ix) (set ix (add ix 1)))))
Este exemplo corresponde a um pequeno "programa" (triangulos.pex) para verificar se três segmentos de recta formam um triângulo.
(print "Introduza as dimensões do 1º lado do triângulo: ") (set a (readi)) (print "Introduza as dimensões do 2º lado do triângulo: ") (set b (readi)) (print "Introduza as dimensões do 3º lado do triângulo: ") (set c (readi)) (if (lt a 1) (print "As dimensões dos lados do triângulo devem ser positivas") (if (lt b 1) (print "As dimensões dos lados do triângulo devem ser positivas") (if (lt c 1) (print "As dimensões dos lados do triângulo devem ser positivas") (if (le (add a b) c) (print "Não é um triângulo") (if (le (add a c) b) (print "Não é um triângulo") (if (le (add c b) a) (print "Não é um triângulo") (if (eq a b) (if (eq b c) (print "Triângulo equilátero") (print "Triângulo isósceles")) (if (eq b c) (print "Triângulo isósceles") (print "Triângulo escaleno")))))))))
Embora o interpretador de expressões tenha de assinalar problemas relativos à interpretação de expressões mal especificadas, assume-se que não existem entradas mal-formadas nestes ficheiros (embora tenham de ser detectadas).
Devem ser possíveis extensões ou alterações de funcionalidade com impacto mínimo no código já produzido para a aplicação. O objectivo é aumentar a flexibilidade da aplicação relativamente ao suporte de novas funções. Em particular, a solução encontrada para salvaguardar textualmente o conteúdo do documento deve ser suficientemente flexível de modo a permitir visualizar o conteúdo de um documento noutro formato (por exemplo, XML) sem que isso implique alterações no código core da aplicação.
Usando os ficheiros test.pex, test.in e test.out, é possível verificar automaticamente o resultado correcto do programa. Note-se que é necessária a definição apropriada da variável CLASSPATH (ou da opção equivalente -cp do comando java), para localizar as classes do programa, incluindo a que contém o método correspondente ao ponto de entrada da aplicação (pex.app.App.main). As propriedades são tratadas automaticamente pelo código de apoio.
java -Dimport=test.pex -Din=test.in -Dout=test.outhyp pex.app.App
Assumindo que aqueles ficheiros estão no directório onde é dado o comando de execução, o programa produz o ficheiro de saída test.outhyp. Em caso de sucesso, os ficheiros das saídas esperada (test.out) e obtida (test.outhyp) devem ser iguais. A comparação pode ser feita com o comando:
diff -b test.out test.outhyp
Este comando não deve produzir qualquer resultado quando os ficheiros são iguais. Note-se, contudo, que este teste não garante o correcto funcionamento do código desenvolvido, apenas verificando alguns aspectos da sua funcionalidade.