Olá, estudante! Seja bem-vindo ao segundo ano da disciplina de Ciência da Computação! Vou iniciar esta lição te fazendo alguns questionamentos: você já parou para pensar como uma linguagem de programação funciona? Já pensou em construir a sua própria linguagem de programação?
Em uma das lições anteriores, vimos que Bill Gates fez isso lá no final dos anos 70, quando criou o Basic, você se lembra disso, não é mesmo?
Com certeza, propor novas formas de criar soluções é algo que pode dar a oportunidade de fazer uma coisa inédita. É o caso quando estudamos e aprendemos sobre compiladores. Assim, esteja preparado(a) para conhecer como acontece a especificação e a implementação das linguagens.
A função principal dos computadores é solucionar problemas! Podemos dizer isso porque um algoritmo é definido como sendo algo elaborado para resolver um problema. O computador é composto por uma sequência lógica de passos, que são capazes de demonstrar os processos necessários para se conseguir um resultado.
Um programa deve especificar as transformações (cálculos, passos lógicos, decisões) a serem aplicadas nos dados de entrada para produzir a saída esperada. Assim, podemos definir alguns passos:
Definir o problema a ser solucionado.
Formular o modelo lógico ou matemático do problema.
Expressar o modelo em termos de um programa de computador.
Executar o programa no computador.
Analisar os resultados.
Conforme pode perceber, os 5 passos são bastante interessantes quando pensamos no que já aprendemos, como no caso das codificações e como os computadores funcionam. A cada avanço da tecnologia, fica mais claro que as linguagens de programação precisam tornar-se ainda mais poderosas para conseguirem ser o meio entre o problema e a solução construída.
O mais interessante é que há linguagens de programação criadas nos anos 70 que ainda são amplamente utilizadas no meio científico. Outras amplamente usadas são evoluções de linguagens que nasceram há muito tempo.
Conhecer como os computadores funcionam é extremamente importante, pois o que programamos vai ajudar as próximas gerações de linguagens de programação, que, por sua vez, auxiliarão na construção das próximas tecnologias integradas ou embarcadas que veremos cada dia mais próximas de nós.
Durante a evolução da ciência da computação, uma das principais preocupações era como estabelecer uma forma de construir uma aplicação mais fácil do ponto de vista do ser humano ou do usuário.
Uma vez que os computadores trabalhavam com zero (0) ou um (1), ficava claro que, para ter uma evolução mais acelerada das soluções, era fundamental facilitar a construção das aplicações por meio de um modelo mais próximo da linguagem humana. No entanto, fazer com uma tecnologia pronta para o computador diretamente não era possível. Foi assim que nasceu a ideia de criar linguagens, assim como a que usamos para escrever e falar, mas neste caso, uma linguagem que o computador podia entender.
Toda língua tem regras, caracteres para representar e um alfabeto, e com a linguagem de programação não seria diferente! A linguagem quando criada precisa de regras, palavras exclusivas, símbolos, ou seja, uma forma de comunicação. Assim, da mesma maneira que português, inglês, italiano, espanhol são linguagens, Pascal, JAVA, PHP, Python também são linguagens.
Tudo isso aconteceu devido a estudos de Turing, Chomsky, Markov, Post e Kleene. Todos eles trabalharam nestes estudos entre 1936 e final dos anos 1950, e tudo que conhecemos hoje sobre como conseguimos repassar instruções e passos sequenciais para os computadores devemos a estes pesquisadores que estudaram maneiras de resolver esses problemas.
Que tal pesquisar um pouco sobre cada um deles? Você vai aprender muita coisa e verificar que ainda há muitos problemas que precisam ser resolvidos!
De acordo com Guimarães e Lages (2005), a linguagem de máquina (série de zeros e uns) é utilizada a fim de se passar uma instrução aos computadores. Como exemplo, podemos ter uma instrução como a que temos abaixo:
01011010011000000100000000000000
Imagine se você tivesse que sempre converter os seus comandos ou cliques para uma sequência em binário. Seria bastante complicado, não é verdade? Mas e se fosse convertido para a notação hexadecimal? Bom, neste caso, a instrução citada ficaria assim:
5 A 6 0 4 0 0 0
Não ficaria tão mais fácil, não é verdade?
Para superar essa dificuldade — de converter comandos —, a linguagem Assembly foi desenvolvida. Essa linguagem possui códigos e símbolos mnemônicos (fáceis de recordar) que representam operações e localizações de armazenamento. Ela é utilizada para a escrita de softwares de sistemas operacionais. Contudo, ainda assim, ela não resolve o problema que estamos tratando, pois apresenta muitas dificuldades no uso, como: programas mais complexos exigem um número muito grande de instruções, fazendo com que se perca um grande tempo em codificação e testes, dificuldades na manutenção, dificuldade para entender a lógica. Veja a seguir um exemplo de programa em Assembly para contar de 1 a 10:
mov ecx, 0
loop:
add ecx, 1
cmp ecx, 0x9
jle loop
Percebeu que, ainda assim, não fica tão próximo ou tão fácil considerando a linguagem humana? Para atender as especificidades de cada área e visando facilitar o cotidiano, foram criadas linguagens de alto nível (mais próximas da linguagem humana), estas que facilitam a programação, porém são mais complicadas para o computador.
Para fazer com que o computador entenda, são necessários os compiladores — que são programas que traduzem a linguagem de alto nível para código executável. Ou seja, podemos explicar nessa sequência:
Linguagem fonte - Compilador – Linguagem Objeto
As linguagens de alto nível possuem um vocabulário similar à nossa linguagem! A seguir, temos as etapas pelas quais um programa passa, ao ser compilado e depois executado. Embora somente o caso do compilador propriamente dito seja esquematizado, pela própria descrição dos dois outros métodos, pode-se comparativamente desenhar seus esquemas de funcionamento.
Programa Fonte → Computador → Programa Objeto → Computador → Resultados
Qual o papel do compilador nessa sequência? O Compilador tem o papel de ser um tradutor que faz com que o programa Fonte (linguagem próxima ou parecida com a nossa linguagem humana) seja entendido ou traduzido para a linguagem objeto (de baixo nível para a máquina, ou seja, assembly).
Para realizar essa tradução, o compilador trabalha em algumas etapas ou fases. A primeira fase que ele realiza é a análise léxica. Neste caso, ele realiza uma interface entre o programa fonte e o compilador. Para realizar esse tipo de análise, ele faz a leitura do programa fonte e depois agrupa os caracteres em itens léxicos (tokens).
Nessa verificação, em que ele agrupa os caracteres, tem-se por finalidade encontrar os identificadores, as palavras reservadas, constantes e símbolos especiais. Dessa maneira, é verificado se o conteúdo realmente pertence à linguagem. Depois disso, será realizada uma verificação dos espaços em branco, comentários e caracteres de controle. Após essa fase, é hora de verificar se há presença de símbolos não-válidos ou elementos sem formação.
Veja a seguir, no Quadro 1, como poderia ser esse trabalho do compilador:
A seguir, ele faz a análise sintática, essa que tem por finalidade agrupar os tokens em estruturas sintáticas que formam comandos, expressões ou declarações. A ideia aqui é verificar se a sintaxe (regras) foi respeitada. Por exemplo, veja a análise do trecho B:=A*1.5 no quadro 2:
Outra análise que o compilador faz é a semântica. Diferentemente das anteriores, o papel dela é verificar se as construções do programa fonte são válidas. Por exemplo, a linguagem de programação possui regras de como declarar as variáveis, sendo assim, ela vai verificar se as declarações estão de acordo com o esperado. Desta forma, nesta análise o objetivo é responder à pergunta: Os tipos declarados estão de acordo com o que realmente a linguagem permite?
Esses três tipos de análise permitem uma validação do código fonte e, se não houver nenhum erro em relação aos aspectos léxicos, sintáticos e semânticos, o compilador, então, entende o código e converte para uma linguagem que o computador consegue executar.
Existem dois aspectos muito importantes quando se quer construir ou entender sobre compiladores. Um deles é que o compilador precisa ou deve conservar o que o programa original contém. Outro aspecto é que ele deve conseguir prover melhorias ao programa de entrada.
Conforme você pôde perceber, a compilação não é uma atividade simples. Elas possuem conceitos importantes e missões diferentes. Isso exige várias ferramentas para facilitar a criação e manutenção de compiladores.
Hoje em dia, muitas das ferramentas são escritas em Java, C e C++ e, dessa maneira, grande parte das atividades são automatizadas. Elas geram códigos que, com base em expressões regulares, geram um algoritmo capaz de auxiliar na análise léxica de uma linguagem de programação.
A otimização é um conceito que vem crescendo muito na questão dos compiladores, isso porque tenta produzir a geração de um código mais eficiente. Isso não é algo simples, porque entra em jogo a questão da arquitetura dos processadores e, consequentemente, o tempo que se leva para realizar toda a compilação. Há muitos projetos neste sentido. Separei alguns nos links a seguir. Visite alguns deles:
TIO: https://tio.run/#
ONLINE GDB: https://www.onlinegdb.com/
REPLIT: https://replit.com/
SQL FIDDLE: http://sqlfiddle.com/
Perceba, portanto, como cada vez será mais fundamental aprendermos não apenas a programas, mas entender como otimizar ainda mais os recursos de hardware para que, dessa forma, a computação tenha avanços ainda mais significativos.
Guimarães, M. A.; Lages, N. A. C. Introdução à Ciência da Computação. Rio de Janeiro: LTC, 2005.