Compiladores/Projecto de Compiladores/Projecto 2020-2021/Manual de Referência da Linguagem FIR

From Wiki**3

< Compiladores‎ | Projecto de Compiladores
AVISOS - Avaliação em Época Normal

Esclarecimento de dúvidas:

  • Consultar sempre o corpo docente atempadamente: presencialmente ou através do endereço oficial da disciplina [1].
  • Não utilizar fontes de informação não oficialmente associadas ao corpo docente (podem colocar em causa a aprovação à disciplina).
  • Não são aceites justificações para violações destes conselhos: quaisquer consequências nefastas são da responsabilidade do aluno.

Requisitos para desenvolvimento, material de apoio e actualizações do enunciado (ver informação completa em Projecto de Compiladores):

  • O material de apoio é de uso obrigatório e não pode ser alterado.
  • Verificar atempadamente (mínimo de 48 horas antes do final de cada prazo) os requisitos exigidos pelo processo de desenvolvimento.

Processo de avaliação (ver informação completa em Avaliação do Projecto):

  • Datas: 2021/03/22 17:00 (inicial); 2021/04/30 17:00 (intercalar); 2021/05/28 17:00 (final); 2021/06/01-04 (teste prático).
  • Todas as entregas são cruciais para o bom desenvolvimento do projecto, sendo obrigatórias: a não realização de uma entrega implica a exclusão da avaliação do projecto e, por consequência, da avaliação da disciplina.
  • Verificar atempadamente (até 48 horas antes do final de cada prazo) os requisitos exigidos pelo processo de avaliação, incluindo a capacidade de acesso ao repositório.
  • Apenas se consideram para avaliação os projectos existentes no repositório oficial. Apenas se considera para avaliação o ramo 'main'.
  • 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.
  • A avaliação do projecto pressupõe o compromisso de honra de que o trabalho correspondente foi realizado pelos alunos correspondentes ao grupo de avaliação.
  • Fraudes na execução do projecto terão como resultado a exclusão dos alunos implicados do processo de avaliação em curso.
Material de Uso Obrigatório
As bibliotecas CDK e RTS de apoio ao desenvolvimento do projecto 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 GIT
Apenas se consideram para avaliação os projectos existentes no repositório GIT oficial. Apenas se considera para avaliação o ramo main.

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.

FIR é uma linguagem imperativa. Este manual apresenta de forma intuitiva as características da linguagem: tipos de dados; manipulação de nomes; convenções lexicais; estrutura/sintaxe; especificação das funções; semântica das instruções; semântica das expressões; e, finalmente, alguns exemplos.

Tipos de Dados

A linguagem é fracamente tipificada (são efectuadas algumas conversões implícitas). Existem 4 tipos de dados, todos compatíveis com a linguagem C, e com alinhamento em memória sempre a 32 bits:

  • Tipos numéricos: os inteiros, em complemento para dois, ocupam 4 bytes; os reais, em vírgula flutuante, ocupam 8 bytes (IEEE 754).
  • As cadeias de caracteres são vectores de caracteres terminados por ASCII NULL (0x00, '\0' em C). Variáveis e literais deste tipo só podem ser utilizados em atribuições, impressões, ou como argumentos/retornos de funções.
  • Os ponteiros representam endereços de objectos e ocupam 4 bytes. Podem ser objecto de operações aritméticas (deslocamentos) e permitem aceder ao valor apontado.

Os tipos suportados por cada operador e a operação a realizar são indicados na definição das expressões.

Manipulação de Nomes

Os nomes (identificadores) correspondem a variáveis e funções. Nos pontos que se seguem, usa-se o termo entidade para as designar indiscriminadamente, explicitando-se quando a descrição for válida apenas para um subconjunto.

Espaço de nomes e visibilidade dos identificadores

O espaço de nomes global é único, pelo que um nome utilizado para designar uma entidade num dado contexto não pode ser utilizado para designar outras (ainda que de natureza diferente).

Os identificadores são visíveis desde a declaração até ao fim do alcance: ficheiro (globais) ou função (locais). A reutilização de identificadores em contextos inferiores encobre declarações em contextos superiores: redeclarações locais podem encobrir as globais até ao fim de uma função. Não é possível importar ou definir símbolos globais nos contextos das funções (ver símbolos globais).

Não é possível definir funções dentro de blocos.

Validade das variáveis

As entidades globais (declaradas fora de qualquer função), existem durante toda a execução do programa. As variáveis locais a uma função existem apenas durante a sua execução. Os argumentos formais são válidos enquanto a função está activa.

Convenções Lexicais

Para cada grupo de elementos lexicais (tokens), considera-se a maior sequência de caracteres constituindo um elemento válido.

Caracteres brancos

São considerados separadores e não representam nenhum elemento lexical: mudança de linha ASCII LF (0x0A, '\n' em C), recuo do carreto ASCII CR (0x0D, '\r' em C), espaço ASCII SP (0x20) e tabulação horizontal ASCII HT (0x09, '\t' em C).

Comentários

Existem dois tipos de comentários, que também funcionam como elementos separadores:

  • explicativos -- começam com !! e acabam no fim da linha; e
  • operacionais -- começam com (* e terminam com *), não podendo estar aninhados.

Se as sequências de início fizerem parte de uma cadeia de caracteres, não iniciam um comentário (ver definição das cadeias de caracteres).

Palavras chave

As palavras indicadas de seguida estão reservadas (palavras chave), não podendo ser utilizadas como identificadores. Estas palavras têm de ser escritas exactamente como indicado:

  • int float string void sizeof null
  • while do finally leave restart return if then else write writeln

O identificador fir, embora não reservado, quando refere uma função, corresponde à função principal, devendo ser declarado público.

Tipos

Os seguintes elementos lexicais designam tipos em declarações (ver gramática): int (inteiro), float (real), string (cadeia de caracteres), < e > (ponteiros). Ver gramática.

Operadores de expressões

São considerados operadores os elementos lexicais apresentados na definição das expressões.

Delimitadores e terminadores

Os seguintes elementos lexicais são delimitadores/terminadores: , (vírgula), ; (ponto e vírgula), e ( e ) (delimitadores de expressões).

Identificadores (nomes)

São iniciados por uma letra, seguindo-se 0 (zero) ou mais letras, dígitos ou _ (sublinhado). 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.

Literais

São notações para valores constantes de alguns tipos da linguagem (não confundir com constantes, i.e., identificadores que designam elementos cujo valor não pode ser alterado durante a execução do programa).

Inteiros

Um literal inteiro é um número não negativo. Uma constante inteira pode, contudo, ser negativa: números negativos são construídos pela aplicação do operador de negação aritmética unária (-) a um literal positivo.

Literais inteiros decimais são constituídos por sequências de 1 (um) ou mais dígitos de 0 a 9, em que o primeiro dígito não é 0 (zero), excepto no caso do número 0 (zero). Neste caso, é composto apenas pelo dígito 0 (zero) (em qualquer base).

Literais inteiros em octal começam sempre pelo dígito 0 (zero), sendo seguido de um ou mais dígitos de 0 a 7 (note-se que 09 é um literal octal inválido). Exemplo: 007.

Se não for possível representar o literal inteiro na máquina, devido a um overflow, deverá ser gerado um erro lexical.

Reais em vírgula flutuante

Os literais reais positivos são expressos tal como em C (apenas é suportada a base 10).

Não existem literais negativos (números negativos resultam da aplicação da operação de negação unária).

Um literal sem . (ponto decimal) nem parte exponencial é do tipo inteiro.

Se não for possível representar o literal real na máquina, devido a um overflow, deverá ser gerado um erro lexical.

Exemplos: 3.14, 1E3 = 1000 (número inteiro representado em virgula flutuante). 12.34e-24 = 12.34 x 10-24 (notação cientifica).

Cadeias de caracteres

As cadeias de caracteres são delimitadas por plicas (') e podem conter quaisquer caracteres, excepto ASCII NULL (0x00 ou \0 em C). Nas cadeias, os delimitadores de comentários não têm significado especial. Se for escrito um literal que contenha o carácter nulo (~0 em FIR), então a cadeia termina nessa posição. Exemplo: 'ab~0xy' tem o mesmo significado que 'ab'.

É possível designar caracteres por sequências especiais (iniciadas por ~), especialmente úteis quando não existe representação gráfica directa. As sequências especiais correspondem aos caracteres ASCII LF, CR e HT (\n, \r e \t, respectivamente, em C e ~n, ~r, ~t, respectivamente, em FIR), plica (~'), til (~~), ou a quaisquer outros especificados através de 1 ou 2 digitos hexadecimais (e.g. ~0a ou apenas ~a se o carácter seguinte não representar um dígito hexadecimal). Outras sequências ignoram o prefixo ~).

Elementos lexicais distintos que representem duas ou mais cadeias consecutivas são representadas na linguagem como uma única cadeia que resulta da concatenação.

Note-se que, apesar de ser possível ter um carácter de mudança de linha numa cadeia, não são aceites literais multi-linha (excepo como sequências elementos como descrito no parágrafo anterior).

Exemplos:

  • 'ab' 'cd' é o mesmo que 'abcd'.
  • 'ab' (* comentário com 'cadeia de caracteres falsa' *) 'cd' é o mesmo que 'abcd'.

Ponteiros

O único literal admissível para ponteiros é indicado pela palavra reservada null, indicando o ponteiro nulo.

Gramática

A gramática da linguagem está resumida abaixo. Considerou-se que os elementos em tipo fixo são literais (terminais da gramática); que os parênteses curvos agrupam elementos: [math]([/math] e [math])[/math]; que elementos alternativos são separados por uma barra vertical: [math]|[/math]; que elementos opcionais estão entre parênteses rectos: [math][[/math] e [math]][/math]; que os elementos que se repetem zero ou mais vezes estão entre [math]\langle[/math] e [math]\rangle[/math]. Alguns elementos usados na gramática também são elementos da linguagem descrita se representados em tipo fixo (e.g., parênteses).

ficheiro [math]\rightarrow[/math] declaração [math]\langle[/math] declaração [math]\rangle[/math]
declaração [math]\rightarrow[/math] variável ; [math]|[/math] função
variável [math]\rightarrow[/math] tipo [math][[/math] * [math]|[/math] ? [math]][/math] identificador [math][[/math] = expressão [math]][/math]
função [math]\rightarrow[/math] tipo [math][[/math] * [math]|[/math] ? [math]][/math] identificador ( [math][[/math] variáveis [math]][/math] ) [math][[/math] default-return-value [math]][/math] [math][[/math] corpo [math]][/math]
default-return-value [math]\rightarrow[/math] -> literal
corpo [math]\rightarrow[/math] [math][[/math] prólogo [math]][/math] [math][[/math] bloco [math]][/math] [math][[/math] epílogo [math]][/math]
prólogo [math]\rightarrow[/math] @ bloco
epílogo [math]\rightarrow[/math] >> bloco
identificadores [math]\rightarrow[/math] identificador [math]\langle[/math] , identificador [math]\rangle[/math]
expressões [math]\rightarrow[/math] expressão [math]\langle[/math] , expressão [math]\rangle[/math]
variáveis [math]\rightarrow[/math] variável [math]\langle[/math] , variável [math]\rangle[/math]
tipo [math]\rightarrow[/math] void [math]|[/math] int [math]|[/math] float [math]|[/math] string [math]|[/math] < tipo >
bloco [math]\rightarrow[/math] { [math]\langle[/math] declaração [math]\rangle[/math] [math]\langle[/math] instrução [math]\rangle[/math] }
instrução [math]\rightarrow[/math] expressão ; [math]|[/math] write expressões ; [math]|[/math] writeln expressões ;
[math]\rightarrow[/math] leave [math][[/math] integer-literal [math]][/math] ; [math]|[/math] restart [math][[/math] integer-literal [math]][/math] ; [math]|[/math] return
[math]\rightarrow[/math] instrução-condicional [math]|[/math] instrução-de-iteração [math]|[/math] bloco
instrução-condicional [math]\rightarrow[/math] if expressão then instrução [math][[/math] else instrução [math]][/math]
instrução-de-iteração [math]\rightarrow[/math] while expressão do instrução [math][[/math] finally instrução [math]][/math]

Tipos, identificadores, literais e definição de expressões

Algumas definições foram omitidas da gramática: tipos de dados, identificador (ver identificadores), literal (ver literais); expressão (ver expressões).

Left-values

Os left-values são posições de memória que podem ser modificadas (excepto onde proibido pelo tipo de dados). Os elementos de uma expressão que podem ser utilizados como left-values encontram-se individualmente identificados na semântica das expressões.

Ficheiros

Um ficheiro é designado por principal se contiver a função principal (a que inicia o programa).

Declaração de variáveis

Uma declaração de variável indica sempre um tipo de dados e um identificador. O tipo void não pode ser usado para declarar variáveis.

Exemplos:

  • Inteiro: int i
  • Real: float f
  • Cadeia de caracteres: string s
  • Ponteiro para inteiro: <int> p1 (equivalente a int* em C)
  • Ponteiro para real: <float> p2 (equivalente a double* em C)
  • Ponteiro para cadeia de caracteres: <string> p3 (equivalente a char** em C)
  • Ponteiro para ponteiro para inteiro: <<int>> p4 (equivalente a int** em C)

Símbolos globais

Por omissão, os símbolos são privados a um módulo, não podendo ser importados por outros módulos.

O símbolo * permite declarar um identificador como público, tornando-o acessível a partir de outros módulos.

O símbolo ? (opcional para funções) permite declarar num módulo entidades definidas em outros módulos. As entidades não podem ser inicializadas nestas declarações.

Exemplos:

  • Declarar variável privada ao módulo: float pi = 22
  • Declarar variável pública: float *pi = 22
  • Usar definição externa: float ?pi

Inicialização

Quando existe, é uma expressão que segue o sinal = ("igual"): inteira, real, ponteiro. Entidades reais podem ser inicializadas por expressões inteiras (conversão implícita). A expressão de inicialização deve ser um literal se a variável for global.

As cadeias de caracteres são (possivelmente) inicializadas com uma lista não nula de valores sem separadores.

Exemplos:

  • Inteiro (literal): int i = 3
  • Inteiro (expressão): int i = j+1
  • Real (literal): float f = 3.2
  • Real (expressão): float f = i - 2.5 + f(3)
  • Cadeia de caracteres (literal): string s = 'olá'
  • Cadeia de caracteres (literais): string s = 'olá' 'mãe'
  • Ponteiro (literal): <<<float>>> p = null
  • Ponteiro (expressão): <float> p = q + 1

Funções

Uma função permite agrupar um conjunto de instruções num corpo, executado com base num conjunto de parâmetros (os argumentos formais), quando é invocada a partir de uma expressão.

Declaração

As funções são sempre designadas por identificadores constantes precedidos do tipo de dados devolvido pela função. Se a função não devolver um valor, é declarada como tendo tipo void para o indicar.

As funções que recebam argumentos devem indicá-los no cabeçalho. Funções sem argumentos definem um cabeçalho vazio. Não é possível aplicar os qualificadores de exportação/importação * ou ? (ver símbolos globais) às declarações dos argumentos de uma função. Não é possível especificar valores iniciais (valores por omissão) para argumentos de funções.

A declaração de uma função sem corpo é utilizada para tipificar um identificador exterior ou para efectuar declarações antecipadas (utilizadas para pré-declarar funções que sejam usadas antes de ser definidas, por exemplo, entre duas funções mutuamente recursivas). Caso a declaração tenha corpo, define-se uma nova função (neste caso, não pode utilizar-se o símbolo ?).

Invocação

A função só pode ser invocada através de um identificador que refira uma função previamente declarada ou definida.

Se existirem argumentos, na invocação da função, o identificador é seguido de uma lista de expressões delimitadas por parênteses curvos. Esta lista é uma sequência, possivelmente vazia, de expressões separadas por vírgulas. O número e tipo de parâmetros actuais deve ser igual ao número e tipo dos parâmetros formais da função invocada (a menos de conversões implícitas). A ordem dos parâmetros actuais deverá ser a mesma dos argumentos formais da função a ser invocada.

De acordo com a convenção Cdecl, a função chamadora coloca os argumentos na pilha e é responsável pela sua remoção, após o retorno da chamada. Assim, os parâmetros actuais devem ser colocados na pilha pela ordem inversa da sua declaração (i.e., são avaliados da direita para a esquerda antes da invocação da função e o resultado passado por cópia/valor). O endereço de retorno é colocado no topo da pilha pela chamada à função.

Corpo

O corpo de uma função consiste num prólogo (indicado com @), num bloco principal e num epílogo (indicado com >>), cada um dos quais contém um bloco que pode ter declarações (opcionais) seguidas de instruções (opcionais). Todos são opcionais, mas a função tem de ter pelo menos um deles. Uma função sem corpo é uma declaração e é considerada indefinida.

O prólogo (quando existe) é sempre executado antes de qualquer outra parte da função. Tanto o corpo principal como o epílogo têm acesso às variáveis declaradas no nível de topo do prólogo. No entsnto, as variáveis declaradas fora do prólogo são visíveis apenas no bloco em que a declaração ocorre. O epílogo (quando existe) é sempre executado antes do fim da função (mesmo na presença de instrução de retorno).

Não é possível aplicar os qualificadores de exportação (*) ou de importação (?) (ver símbolos globais) em declarações dentro do corpo de uma função. Qualquer sub-bloco (usado, por exemplo, numa instrução condicional ou de iteração) pode definir variáveis.

O valor devolvido por uma função, através de atribuição ao left-value especial com o nome da função, deve ser do tipo declarado. É um erro especificar um valor de retorno para funções declaradas void.

Se existir um valor declarado por omissão para o retorno da função (indicado pela notação -> seguindo a assinatura da função), então deve ser utilizado se não for especificado outro. A especificação do valor de retorno por omissão é obrigatoriamente um literal do tipo indicado. É um erro especificar um valor de retorno se a função for declarada como não retornando um valor (indicada como tendo tipo void). Uma função cujo retorno seja inteiro ou ponteiro retorna 0 (zero) ou null por omissão (i.e., se não for especificado o valor de retorno). Em todos os outros casos, o valor de retorno é indeterminado se não for definido explicitamente.

Uma instrução return, em qualquer parte que não o epílogo, causa a interrupção imediata dessa parte da função e a execução da epílogo (se existir). A instrução return, no epílogo, causa a interrupção da própria função, assim como o retorno do seu valor de retorno actual ao chamador.

Função principal e execução de programas

Um programa inicia-se com a invocação da função fir (sem argumentos). Os argumentos com que o programa foi chamado podem ser obtidos através das seguintes funções:

  • int argc() (devolve o número de argumentos);
  • string argv(int n) (devolve o n-ésimo argumento como uma cadeia de caracteres) (n>0); e
  • string envp(int n) (devolve a n-ésima variável de ambiente como uma cadeia de caracteres) (n>0).

O valor de retorno da função principal é devolvido ao ambiente que invocou o programa. Este valor de retorno segue as seguintes regras (sistema operativo):

  • 0 (zero): execução sem erros;
  • 1 (um): argumentos inválidos (em número ou valor);
  • 2 (dois): erro de execução.

Os valores superiores a 128 indicam que o programa terminou com um sinal. Em geral, para correcto funcionamento, os programas devem retornar 0 (zero) se a execução foi bem sucedida e um valor diferente de 0 (zero) em caso de erro.

A biblioteca de run-time (RTS) contém informação sobre outras funções de suporte disponíveis, incluindo chamadas ao sistema (ver também o manual da RTS).

Instruções

Excepto quando indicado, as instruções são executadas em sequência.

Blocos

Cada bloco tem uma zona de declarações de variáveis locais (facultativa), seguida por uma zona com instruções (possivelmente vazia). Não é possível declarar ou definir funções dentro de blocos.

A visibilidade das variáveis é limitada ao bloco em que foram declaradas (excepto as que são declaradas no bloco princiapal do prólogo: são visíveis em toda a função). As entidades declaradas podem ser directamente utilizadas em sub-blocos ou passadas como argumentos para funções chamadas dentro do bloco. Caso os identificadores usados para definir as variáveis locais já estejam a ser utilizados para definir outras entidades ao alcance do bloco, o novo identificador passa a referir uma nova entidade definida no bloco até que ele termine (a entidade previamente definida continua a existir, mas não pode ser directamente referida pelo seu nome). Esta regra é também válida relativamente a argumentos de funções (ver corpo das funções).

Instrução condicional

Esta instrução tem comportamento idêntico ao da instrução if-else em C.

Instrução de iteração

Esta instrução tem comportamento idêntico ao da instrução while em C. A instrução que segue a palavra finally é executada sempre que a expressão de controlo do ciclo for falsa.

Instrução de terminação

A instrução leave termina o ciclo mais interior em que a instrução se encontrar, tal como a instrução break em C. Esta instrução só pode existir dentro de um ciclo(não pode ser usada na parte que segue a palavra finally), sendo a última instrução do seu bloco. Se for usada com um literal inteiro, termina o ciclo correspondente à ordem indicada pelo inteiro (1 é o mais interno, 2 é o seguinte, etc.). A parte do finally, se existir, é executada depois do leave.

Instrução de reinício

A instrução restart reinicia o ciclo mais interior em que a instrução se encontrar, tal como a instrução continue em C. Esta instrução só pode existir dentro de um ciclo (não pode ser usada na parte que segue a palavra finally), sendo a última instrução do seu bloco. Se for usada com um literal inteiro, reinicia o ciclo correspondente à ordem indicada pelo inteiro (1 é o mais interno, 2 é o seguinte, etc.).

Instrução de retorno

A instrução return, se existir, é a última instrução do seu bloco. Ver comportamento na descrição do corpo de uma função.

Expressões como instruções

As expressões utilizadas como instruções são avaliadas, mesmo que não produzam efeitos secundários. A notação é como indicada na gramática (expressão seguida de ;).

Instruções de impressão

As palavras chave write e writeln podem ser utilizadas para apresentar valores na saída do programa. A primeira apresenta a expressão sem mudar de linha; a segunda apresenta a expressão mudando de linha. Quando existe mais de uma expressão, as várias expressões são apresentadas sem separação. Valores numéricos (inteiros ou reais) são impressos em decimal. As cadeias de caracteres são impressas na codificação nativa. Ponteiros não podem ser impressos.

Expressões

Uma expressão é uma representação algébrica de uma quantidade: todas as expressões têm um tipo e devolvem um valor.

Existem expressões primitivas e expressões que resultam da avaliação de operadores.

A tabela seguinte apresenta as precedências relativas dos operadores: é a mesma para operadores na mesma linha, sendo as linhas seguintes de menor prioridade que as anteriores. A maioria dos operadores segue a semântica 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).

Tipo de Expressão Operadores Associatividade Operandos Semântica
primária ( ) [ ] não associativos - parênteses curvos, indexação, reserva de memória
unária + - ? não associativos - identidade e simétrico, indicação de posição
multiplicativa * / % da esquerda para a direita inteiros, reais C (% é apenas para inteiros)
aditiva + - da esquerda para a direita inteiros, reais, ponteiros C: se envolverem ponteiros, calculam: (i) deslocamentos, i.e., um dos operandos deve ser do tipo ponteiro e o outro do tipo inteiro; (ii) diferenças de ponteiros, i.e., apenas quando se aplica o operador - a dois ponteiros do mesmo tipo (o resultado é o número de objectos do tipo apontado entre eles). Se a memória não for contígua, o resultado é indefinido.
comparativa < > <= >= da esquerda para a direita inteiros, reais C
igualdade == != da esquerda para a direita inteiros, reais, ponteiros C
"não" lógico ~ não associativo inteiros C
"e" lógico && da esquerda para a direita inteiros C: o 2º argumento só é avaliado se o 1º não for falso.
"ou" lógico || da esquerda para a direita inteiros C: o 2º argumento só é avaliado se o 1º não for verdadeiro.
atribuição = da direita para a esquerda todos os tipos O valor da expressão do lado direito do operador é guardado na posição indicada pelo left-value (operando esquerdo do operador). Podem ser atribuídos valores inteiros a left-values reais (conversão automática). Nos outros casos, ambos os tipos têm de concordar.

Expressões primitivas

As expressões literais e a invocação de funções foram definidas acima.

Identificadores

Um identificador é uma expressão se tiver sido declarado. Um identificador pode denotar uma variável.

Um identificador é o caso mais simples de um left-value, ou seja, uma entidade que pode ser utilizada no lado esquerdo (left) de uma atribuição.

Leitura

A operação de leitura de um valor inteiro ou real pode ser efectuado pela expressão indicada pelo símbolo @, que devolve o valor lido, de acordo com o tipo esperado (inteiro ou real). Caso se use como argumento dos operadores de impressão ou noutras situações que permitam vários tipos (write ou writeln), deve ser lido um inteiro.

Exemplos: a = @ (leitura para a), f(@) (leitura para argumento de função), write @ (leitura e impressão).

Parênteses curvos

Uma expressão entre parênteses curvos tem o valor da expressão sem os parênteses e permite alterar a prioridade dos operadores. Uma expressão entre parênteses não pode ser utilizada como left-value (ver também a expressão de indexação).

Expressões resultantes de avaliação de operadores

Indexação de ponteiros

A indexação de ponteiros devolve o valor de uma posição de memória indicada por um ponteiro. Consiste de uma expressão ponteiro seguida do índice entre parênteses rectos. O resultado de uma indexação de ponteiros é um left-value.

Exemplo (acesso à posição 0 da zona de memória indicada por p): p[0]

Identidade e simétrico

Os operadores identidade (+) e simétrico (-) aplicam-se a inteiros e reais. Têm o mesmo significado que em C.

Reserva de memória

A expressão reserva de memória devolve o ponteiro que aponta para a zona de memória, na pilha da função actual, contendo espaço suficiente para o número de objectos indicados pelo seu argumento inteiro.

Exemplo (reserva vector com 5 reais, apontados por p): <float>p = [5]

Expressão de indicação de posição

O operador sufixo ? aplica-se a left-values, retornando o endereço (com o tipo ponteiro) correspondente.

Exemplo (indica o endereço de a): a?

Expressão de dimensão

O operador sizeof tem um único argumento e aplica-se a expressões, retornando a dimensão correspondente em bytes.

Exemplo: sizeof(a) (dimensão de a).

Exemplos

Os exemplos não são exaustivos e não ilustram todos os aspectos da linguagem. Podem obter-se outros na página da disciplina.

Programa com vários módulos

Definição da função factorial num ficheiro (factorial.fir):

int *factorial(int n) {
  if n > 1 then factorial = n * factorial(n-1); else factorial = 1;
}

Exemplo da utilização da função factorial num outro ficheiro (main.fir):

!! external builtin functions
int ?argc()
string ?argv(int n)
int ?atoi(string s)

!! external user functions
int ?factorial(int n)

!! the main function
int *fir() -> 0
@ { int f = 1; }
{
  writeln 'Teste para a função factorial';
  if argc() == 2 then f = atoi(argv(1));
  writeln f, '! = ', factorial(f);
}
>> { (* nothing to do, really *) }

Como compilar:

fir --target asm factorial.fir
fir --target asm main.fir
yasm -felf32 factorial.asm
yasm -felf32 main.asm
ld -melf_i386 -o main factorial.o main.o -lrts

Outros testes

Estão disponíveis outros pacotes de testes.

Omissões e Erros

Casos omissos e erros serão corrigidos em futuras versões do manual de referência.