Programa de ejemplo para los programas lex y yacc

Esta sección contiene programas de ejemplo para los mandatos lex y yacc .

Juntos, estos programas de ejemplo crean un programa simple de calculadora de escritorio que realiza operaciones de suma, resta, multiplicación y división. Este programa de calculadora también le permite asignar valores a las variables (cada una designada por una sola letra en minúsculas) y luego utilizar las variables en los cálculos. Los archivos que contienen los programas lex y yacc de ejemplo son los siguientes:

Archivo Conten.
calc.lex Especifica el archivo de especificación de mandatos lex que define las reglas de análisis léxico.
calc.yacc Especifica el archivo de gramática del mandato yacc que define las reglas de análisis y llama a la subrutina yylex creada por el mandato lex para proporcionar entrada.

En las descripciones siguientes se presupone que los programas de ejemplo calc.lex y calc.yacc se encuentran en el directorio actual.

Compilación del programa de ejemplo

Para crear el programa de ejemplo de calculadora de escritorio, haga lo siguiente:

  1. Procese el archivo de gramática yacc utilizando el distintivo opcional -d (que informa al mandato yacc para crear un archivo que defina las señales utilizadas además del código fuente del lenguaje C):
    yacc -d calc.yacc
  2. Utilice el mandato ls para verificar que se han creado los archivos siguientes:
    y.tab.c
    El archivo fuente de lenguaje C que el mandato yacc ha creado para el analizador
    y.tab.h
    Un archivo de cabecera que contiene sentencias de definición para las señales utilizadas por el analizador
  3. Procese el archivo de especificación lex :
    lex calc.lex
  4. Utilice el mandato ls para verificar que se ha creado el archivo siguiente:
    lex.yy.c
    El archivo de origen de lenguaje C que el mandato lex ha creado para el analizador léxico
  5. Compile y enlace los dos archivos fuente de lenguaje C:
    cc y.tab.c lex.yy.c
  6. Utilice el mandato ls para verificar que se han creado los archivos siguientes:
    y.tab.o
    El archivo de objeto para el archivo de origen y.tab.c
    lex.yy.o
    El archivo de objeto para el archivo de origen lex.yy.c
    a.out
    El archivo de programa ejecutable
Para ejecutar el programa directamente desde el archivo a.out , escriba:
$ a.out

O

Para mover el programa a un archivo con un nombre más descriptivo, como en el ejemplo siguiente, y ejecutarlo, escriba:
$ mv a.out calculate
$ calculate
En cualquier caso, después de iniciar el programa, el cursor se mueve a la línea debajo de la$(indicador de mandatos). A continuación, especifique números y operadores como lo haría en una calculadora. Al pulsar la tecla Intro, el programa visualiza el resultado de la operación. Después de asignar un valor a una variable, como se indica a continuación, el cursor se mueve a la siguiente línea.
m=4 <enter>
_
Cuando utilice la variable en cálculos posteriores, tendrá el valor asignado:
m+5 <enter>
9
_

Código fuente del analizador

El ejemplo siguiente muestra el contenido del archivo calc.yacc . Este archivo tiene entradas en las tres secciones de un archivo de gramática yacc : declaraciones, reglas y 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);
}
El archivo contiene las secciones siguientes:
  • Sección Declaraciones. Esta sección contiene entradas que:
    • Incluir archivo de cabecera de E/S estándar
    • Definir variables globales
    • Defina ellistregla como el lugar para iniciar el proceso
    • Definir las señales utilizadas por el analizador
    • Definir los operadores y su prioridad
  • Sección de reglas. La sección de reglas define las reglas que analizan la corriente de entrada.
    • %start -Especifica que toda la entrada debe coincidir con stat.
    • %union -De forma predeterminada, los valores devueltos por las acciones y el analizador léxico son enteros. yacc también puede dar soporte a valores de otros tipos, incluidas las estructuras. Además, yacc realiza un seguimiento de los tipos e inserta los nombres de miembros de unión adecuados para que el analizador resultante se compruebe estrictamente. La pila de valores yacc se declara como una unión de los diversos tipos de valores deseados. El usuario declara la unión y asocia los nombres de miembro de unión a cada símbolo y símbolo no de terminal que tiene un valor. Cuando se hace referencia al valor a través de una construcción $$ o $n, yacc insertará automáticamente el nombre de unión adecuado, para que no se produzcan conversiones no deseadas.
    • %type -Hace uso de los miembros de la declaración %union y proporciona un tipo individual para los valores asociados a cada parte de la gramática.
    • %toksn -Lista las señales que proceden de la herramienta lex con su tipo.
  • Sección Programas. La sección de programas contiene las subrutinas siguientes. Puesto que estas subrutinas se incluyen en este archivo, no es necesario que utilice la biblioteca yacc al procesar este archivo.
    Subrutina Descripción
    Principal El programa principal necesario que llama a la subrutina yyparse para iniciar el programa.
    yyerror (s) Esta subrutina de manejo de errores sólo imprime un mensaje de error de sintaxis.
    aawrap La subrutina de conclusión que devuelve un valor de 1 cuando se produce el final de la entrada.

Código fuente del analizador léxico

Este archivo contiene sentencias de inclusión para entrada y salida estándar, así como para el archivo y.tab.h . Si utiliza el distintivo -d con el mandato yacc , el programa yacc genera ese archivo a partir de la información del archivo de gramática yacc . El archivo y.tab.h contiene definiciones para las señales que utiliza el programa analizador. Además, el archivo calc.lex contiene las reglas para generar estas señales a partir de la corriente de entrada. A continuación se muestra el contenido del archivo 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);
              }
%%