Programa de exemplo para os programas lex e yacc

Esta seção contém programas de exemplo para os comandos lex e yacc .

Juntos, esses programas de exemplo criam um programa simples, de calculadora de mesa que executa operações de adição, subtração, multiplicação e divisão. Este programa de calculadora também permite atribuir valores às variáveis (cada uma designada por uma letra única, minúscula) e, em seguida, utilizar as variáveis em cálculos. Os arquivos que contêm os programas de exemplo lex e yacc são os seguintes:

Arquivo Conteúdo
calc.lex Especifica o arquivo de especificação de comandos lex que define as regras de análise lexical.
calc.yacc Especifica o arquivo de gramática de comando yacc que define as regras de análise, e chama a subroutine yylex criada pelo comando lex para fornecer entrada.

As descrições a seguir assumem que os programas de exemplo calc.lex e calc.yacc estão localizados em seu diretório atual.

Compilando o programa de exemplo

Para criar o programa de exemplo de calculadora de escrivaninha, faça o seguinte:

  1. Processe o arquivo de gramática yacc usando a sinalização opcional -d (que informa o comando yacc para criar um arquivo que define os tokens usados além do código fonte de linguagem C):
    yacc -d calc.yacc
  2. Use o comando ls para verificar se os seguintes arquivos foram criados:
    y.tab.c
    O arquivo de origem de linguagem C que o comando yacc criou para o analisador
    y.tab.h
    Um arquivo de cabeçalho contendo instruções de definição para os tokens utilizados pelo analisador
  3. Processe o arquivo de especificação lex :
    lex calc.lex
  4. Use o comando ls para verificar se o seguinte arquivo foi criado:
    lex.yy.c
    O arquivo de origem de linguagem C que o comando lex criou para o analisador lexical
  5. Compile e vincule os dois arquivos de origem do idioma C:
    cc y.tab.c lex.yy.c
  6. Use o comando ls para verificar se os seguintes arquivos foram criados:
    y.tab.o
    O arquivo objeto para o arquivo de origem y.tab.c
    lex.yy.o
    O arquivo de objeto para o arquivo de origem lex.yy.c
    a.out
    O arquivo do programa executável
Para executar o programa diretamente a partir do arquivo a.out , digite:
$ a.out

OU

Para mover o programa para um arquivo com um nome mais descritivo, como no exemplo a seguir, e execute-o, digite:
$ mv a.out calculate
$ calculate
Em qualquer um dos casos, após iniciar o programa, o cursor se desloca para a linha abaixo do$(prompt de comando). Em seguida, insira números e operadores como você faria em uma calculadora. Quando você pressiona a tecla Enter, o programa exibe o resultado da operação. Depois de atribuir um valor a uma variável, da seguinte forma, o cursor se move para a próxima linha.
m=4 <enter>
_
Ao utilizar a variável em cálculos subsequentes, ela terá o valor atribuído:
m+5 <enter>
9
_

Código de origem do Parser

O exemplo a seguir mostra o conteúdo do arquivo calc.yacc Este arquivo possui entradas em todas as três seções de um arquivo de gramática yacc : declarações, regras e programas.

%{
#include<stdio.h>

int regs[26];
int base;

%}

%start list

%union { int a; }


%token DIGIT LETTER

%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left UMINUS  /*supplies precedence for unary minus */

%%                   /* beginning of rules section */

list:                       /*empty */
         |
        list stat '\n'
         |
        list error '\n'
         {
           yyerrok;
         }
         ;
stat:    expr
         {
           printf("%d\n",$1);
         }
         |
         LETTER '=' expr
         {
           regs[$1.a] = $3.a;
         }

         ;

expr:    '(' expr ')'
         {
           $$ = $2;
         }
         |
         expr '*' expr
         {

           $$.a = $1.a * $3.a;
         }
         |
         expr '/' expr
         {
           $$.a = $1.a / $3.a;
         }
         |
         expr '%' expr
         {
           $$.a = $1.a % $3.a;
         }
         |
         expr '+' expr
         {
           $$.a = $1.a + $3.a;
         }
         |
         expr '-' expr
         {
           $$.a = $1.a - $3.a;
         }
         |
         expr '&' expr
         {
           $$.a = $1.a & $3.a;
         }
         |
         expr '|' expr
         {
           $$.a = $1.a | $3.a;
         }
         |

        '-' expr %prec UMINUS
         {
           $$.a = -$2.a;
         }
         |
         LETTER
         {
           $$.a = regs[$1.a];
         }

         |
         number
         ;

number:  DIGIT
         {
           $$ = $1;
           base = ($1.a==0) ? 8 : 10;
         }       |
         number DIGIT
         {
           $$.a = base * $1.a + $2.a;
         }
         ;

%%
main()
{
 return(yyparse());
}

yyerror(s)
char *s;
{
  fprintf(stderr, "%s\n",s);
}

yywrap()
{
  return(1);
}
O arquivo contém as seguintes seções:
  • Seção de Declarações. Esta seção contém entradas que:
    • Incluir arquivo de cabeçalho de E/S padrão
    • Definir variáveis globais
    • Defina olistregra como o lugar para iniciar o processamento
    • Definir os tokens utilizados pelo analisador
    • Definir as operadoras e sua precedência
  • Seção de regras. A seção de regras define as regras que partem o fluxo de entrada.
    • %start -Especifica que a entrada inteira deve combinar com stat.
    • %union -Por padrão, os valores retornados por ações e o analisador lexical são inteiros. yacc também pode suportar valores de outros tipos, incluindo estruturas. Além disso, yacc mantém a faixa dos tipos, e insere nomes de membros do sindicato apropriados para que o analisador resultante seja rigorosamente tipo verificado. A pilha de valor yacc é declarada como uma união dos vários tipos de valores desejados. O usuário declara o sindicato, e associa nomes de membros do sindicato a cada token e símbolo não terminal tendo um valor. Quando o valor é referenciado através de uma construção $$ ou $n, yacc irá inserir automaticamente o nome do sindicato apropriado, de modo que não ocorrerão conversões indesejadas.
    • %type -Makes uso dos membros da declaração %union e dá um tipo individual para os valores associados a cada parte da gramática.
    • %toksn -Listas os tokens que vêm da ferramenta lex com seu tipo.
  • Seção de Programas. A seção de programas contém as subroutines a seguir. Como essas subroutines estão incluídas neste arquivo, você não precisa usar a biblioteca yacc ao processar este arquivo.
    Sub-rotina Descrição
    Principa O programa principal necessário que chama a subroutine yyparse para iniciar o programa.
    yyerror (s) Esta subroutinha de manipulação de erros imprime apenas uma mensagem de erro de sintaxe.
    yywrap A subroutine de embrulho que retorna um valor de 1 quando ocorre o fim da entrada.

Código de origem do analisador Lexical

Esse arquivo contém instruções include para entrada e saída padrão, bem como para o arquivo y.tab.h . Se você usar a sinalização -d com o comando yacc , o programa yacc gera esse arquivo a partir das informações do arquivo de gramática yacc . O arquivo y.tab.h contém definições para os tokens usados pelo programa analisador. Além disso, o arquivo calc.lex contém as regras para gerar esses tokens do fluxo de entrada. A seguir estão os conteúdos do arquivo calc.lex ..
%{

#include <stdio.h>
#include "y.tab.h"
int c;
%}
%%
" "       ;
[a-z]     {
            c = yytext[0];
            yylval.a = c - 'a';
            return(LETTER);
          }
[0-9]     {
            c = yytext[0];
            yylval.a = c - '0';
            return(DIGIT);
          }
[^a-z0-9\b]    {
                 c = yytext[0];
                 return(c);
              }
%%