Desenho de uma Linguagem de Programação

Como já está esboçado um esquema geral para representar programas, agora pode-se tratar da linguagem propriamente dita.

O que se pretende e/ou necessita numa linguagem de programação?

Em geral, numa linguagem de programação encontra-se:

  • Expressões e Variáveis.
  • Instruções e Sequências.
  • Condicionais.
  • Ciclos.
  • Funções.
  • Entrada/Saída.

Mais especificamente,

  • Expressões e Variáveis. As expressões definem os valores que os programas processam e as variáveis "mantêm" esses valores para uso posterior.

Por exemplo, a instrução a = 2 + (3 * 4) em Python define um certo valor que fica guardado na variável a e que pode ser usado posteriormente, como em a % 2 == 0.

  • Instruções e Sequências. Um programa é uma sequência (ou bloco) de instruções. "Correr" um programa significar avançar uma instrução de cada vez ao longo dessa sequência.

São comuns certas convenções sobre as sequências que "efetivamente" definem um programa. Por exemplo, em C o programa é o bloco de instruções que está na função main e tudo o resto é "auxiliar".

  • Condicionais. Um condicional permite certas condições ativarem e desativarem blocos de instruções.

A sintaxe mais comum dos condicionais é if (C) A else B com pequenas variantes conforme a linguagem.

  • Ciclos. Um ciclo repete um certo bloco de instruções em função de uma condição.

Quando o número de repetições é conhecido de antemão é comum usar-se a sintaxe for(i = 0; i < n; i++) A para repetir n vezes as instruções de A.

Quando as repetições estão dependentes de uma condição, a forma while (C) A, que testa C e repete A enquanto a condição C for verdadeira, é comum.

Também se usa a forma do A while (C), que corre A pelo menos uma vez, antes de testar C.

  • Funções. As funções são uma forma simples de evitar repetição de código e de acrescentar instruções "novas" a uma linguagem de programação.

Se for necessário calcular 2 + 2, 2 + 3, 2 + 4, ..., 2 + 42 não se escrevem 40 instruções quase iguais. Define-se uma função que mantém a parte comum do código repetido e usa variáveis locais para os valores que variam. Sendo possível, a função pode ser evocada num ciclo.


Funções Anónimas. Formalmente, uma função não precisa de ter um nome. Nalgumas linguagens mais arcaicas, como o C, as funções anónimas (ou lambdas) não são suportadas diretamente. Com a maior disponibilidade do processamento de coleções têm ganho popularidade. Por exemplo, em Java foi introduzido o operador ->, o Javascript e o C# têm o =>, no Python usam-se expressões lambda e no Rust a sintaxe é |var| instr, etc. TL/DR: As funções anónimas são fixes.

  • Entrada/Saída. Um programa, enquanto processador de informação precisa de canais de entrada e de saída de dados.

As formas mais simples de entrada e saída de dados pressupõem uma consola onde o utilizador digita e lê os dados que, respetivamente, dão entrada no, ou são escritos pelo, programa.

Lisp

O Lisp é uma das mais antigas, influentes e avançadas linguagens de programação.

Tanto que há, não um mas, dois xkcd obrigatórios!!
Dois xkcd sobre o mesmo assunto é... incomum.

Além disso, all the programming languages converge to Lisp e any sufficiently complicated program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.

Aspeto de um programa Lisp (especificamente, Common Lisp):

(defun fib (x)
    (if (< x 2)
        1
        (+ (fib (- x 1)) (fib (- x 2)))
    )
)
(fib 6) ; resultado: 13

A "excecionalidade" do Lisp está nas possibilidades que resultam da sua simplicidade. "Elegant weapons" significa que certas ideias muito simples são extremamente efetivas. Por exemplo, (+ 41 1) é uma lista com três elementos, uma expressão que vale 42 e uma (instrução de) evocação da função +.

  • Lisp significa LISt Processor. Além dos tipos básicos (inteiros, doubles, strings) tem listas. Ao contrário dos array em muitas linguagens, uma lista do Lisp pode ter elementos de tipos diferentes. Por exemplo, (42 3.14 "fourty-two" ("sub" lista)).

  • No Lisp as instruções também são expressões. Isto é, (+ 2 (* 3 4)) é simultaneamente uma expressão (com valor 12) e uma instrução (neste caso, evocar a função "+").

    • Por exemplo, um condicional é (if C A B) e, como C, A e B são expressões, o valor do condicional é o valor do ramo que foi seguido.
  • A sintaxe segue fielmente a representação das árvores feita acima (as vírgulas são descartadas). Isto é: Um programa Lisp é a sua própria representação! É possível um programa Lisp ler, analisar e modificar o seu próprio código enquanto corre. Em particular, o Lisp é homoicónico.

No resto deste capítulo define-se uma linguagem de programação, ALisP, inspirada no Lisp. Além das propriedades acima, acresce ainda que a gramática para a sintaxe é extremamente simples e LL(1), o que permite implementar (quase) diretamente um analisador sintático.

Semântica Informal da ALisP

Uma antevisão daALisP.

  • Variáveis e Expressões: (set a (+ 30 12)) define a variável a e dá-lhe o valor 42. A expressão completa também fica com esse valor.

  • Instruções e Sequências: (seq (set a (+ 40 10)) (set a (- a 8))) executa as duas instruções. Fica com o mesmo valor que a última sub-expressão.

  • Condicionais: (if (== a 42) CERTO (> 0 1)) calcula o valor de (== a 42); Se for True calcula e fica com o mesmo valor que CERTO; Caso contrário calcula (> 0 1) e fica com esse valor.

  • Ciclos: (while (< a 42) (set a (+ a 1))) repete o "passo" (set a (+ a 1)) enquanto a condição (< a 42) for verdadeira. Quando (se) termina fica com o valor do "passo".

  • Funções (anónimas): (fn (x) (== x 42)) define uma função anónima com argumentos x e "corpo" (== x 42). A variável x está limitada ao "corpo" da função. Devolve o valor do "corpo" quando x tem o valor com que a função é evocada. Por exemplo, em

    (seq
        (set dobro (fn (x)
            (* 2 x)
        ))
        (dobro 21)
    )
    

    a evocação (dobro 21) define o valor de x como 21 e, portanto, a função devolve (surpresa!) 42.

  • Entrada/Saída: (read) é uma forma especial. Tem o valor que o utilizador escrever na consola; (write 42) esteve 42 na consola. Tem o valor da expressão que é escrita.

Um programa ALisP simples, que pergunta o nome do utilizador e o cumprimenta:

(seq
    (write (What is your name?))
    (set name (read))
    (Hello name .)
)

Um defeito semântico da atual versão da ALisP é que os símbolos, como em What is your name? são tratados como string, o que simplifica a implementação mas torna o código mais imprevisível. Por exemplo, o valor de (Hello name .) varia conforme name é, ou não, uma variável e esse facto não pode ser deduzido olhando apenas para esta expressão.