Bison和flex计算器没有正确分配值

时间:2014-12-04 09:22:22

标签: c bison flex-lexer

我按照Bison文档示例编写了一个Bison-Flex计算器。当我尝试为变量赋值时,当值不包含其他变量时,它可以正常工作。例如,计算器在此操作后为变量分配正确的值:a = 2 + 3 + 5。但是,如果那时我尝试将a的值分配给另一个变量b,如b = a + 1,则计算器将a + 1的值存储在变量a中。如果我尝试在Bison代码中的分配规则中打印$ 1的名称,我在上一个例子中得到了,程序将变量a变为$ 1,而不是b。我也尝试启用调试选项,看起来Bison正在适当地应用规则和减少。

Bison文件,calculadora_bison.y:

%{
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "TablaSimbolos.h"
int yylex (void);
int yyerror (char *s);
%}

%union {
  float valor;
  NodoArbol * tptr;
};

%token <valor> NUM
%token <tptr> VAR FNCT 
%type <valor> exp

%right '='
%left '-' '+'
%left '*' '/'
%left NEG
%right '^'

%%
input: /* vac´ıo */
    | input line
;
line: '\n'
    | exp '\n' { printf ("\t%.10g\n", $1); }
    | exp ';' '\n' {  }
    | error '\n' { yyerrok; }
;
exp: NUM { $$ = $1;}
    | VAR { $$ = $1->valor.val;}
    | VAR '=' exp { $$ = $3; ActualizaNodoFloat($1->nombre, $3);}
    | FNCT '(' exp ')' { $$ = (*($1->valor.fnctptr))($3);}
    | exp '+' exp { $$ = $1 + $3; }
    | exp '-' exp { $$ = $1 - $3; }
    | exp '*' exp { $$ = $1 * $3; }
    | exp '/' exp { $$ = $1 / $3; }
    | '-' exp %prec NEG { $$ = -$2; }
    | exp '^' exp { $$ = pow ($1, $3); }
    | '(' exp ')' { $$ = $2; }
;
%%

struct init{
    char *fname;
    double (*fnct)();
};

struct init arith_fncts[] =
{
  { "atan", atan },
  { "cos",  cos  },
  { "exp",  exp  },
  { "ln",   log  },
  { "sin",  sin  },
  { "sqrt", sqrt },
  { 0, 0 },
};

int init_table(){
    int i;

    CrearArbol();

    for (i = 0; arith_fncts[i].fname != 0; i++){
        InsertarNodoFuncion(FNCT, arith_fncts[i].fname, arith_fncts[i].fnct);
    }

    return 0;
}

int main (){
    init_table ();
    yyparse ();
    return 0;
}

int yyerror (char *s){
    printf ("%s\n", s);
    return 0;
}

我的Flex代码,calculadora_flex.l:

%{
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "TablaSimbolos.h"
#include "calculadora_bison.tab.h"    

%}

ALNUM  [a-zA-Z][a-zA-Z0-9]*
DIGITO [0-9]*
FLOAT {DIGITO}*\.{DIGITO}*
OPERADORES [-+()=*^/\n;]

%%

{ALNUM} {
        NodoArbol s;
        s = buscaTS(yytext);                
        yylval.tptr = &s;
        return s.tipo;
        }

{FLOAT} {
        yylval.valor = atof(yytext);
        return NUM;
        }

{DIGITO} {      
        yylval.valor = atoi(yytext);
        return NUM;
        }

{OPERADORES} {          
            return (int)yytext[0];
            }


. { };

%%

对于符号表,我在C中使用BST。头文件是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct NodoArbolType{
        int tipo; //Componente léxico
        char * nombre; // Lexema
        union {
            float val; 
            double (*fnctptr)();
        } valor;
        struct NodoArbolType *izda; //Nodos hijo
        struct NodoArbolType *dcha;
    }NodoArbol;

void ActualizaNodoFloat(char * nombrenodo, float valor);

void CrearArbol();

NodoArbol BuscarArbol(char * nombre);

NodoArbol buscaTS(char * nombre);

int InsertarNodoEntero(NodoArbol *newNode);

NodoArbol InsertarNodo(int Key, char * cA);

int InsertarNodoFloat(int Key, char * cA, float val);

int InsertarNodoFuncion(int Key, char * cA, double (*fnct)());

void EliminarArbol();

void ImprimirArbol90grados();

void ImprimirArbolConValores();

int EliminarNodo(char * nombre);

.c文件:

#include "TablaSimbolos.h"
#include "calculadora_bison.tab.h"
//Declaramos el nodo como estático para que solo pueda ser accedido desde aquí
static NodoArbol *raiz;

/**
 * Función que limpia recursivamente a partir de un nodo
 */
static void LimpiarArbol(NodoArbol *T){
    if(T==NULL) return; 
    if(T->izda != NULL) LimpiarArbol(T->izda); //Si sus hijos no son NULL, también
                                               //se limpian
    if(T->dcha != NULL) LimpiarArbol(T->dcha); 
    free(T); //Libera la memoria del puntero al nodo
    return;
}


/*
 * Función que crea un árbol a partir del nodo raíz
 */
void CrearArbol(){
    LimpiarArbol(raiz); //Primero libera el nodo raíz por si existía un árbol previo
    raiz = NULL;
    return;
}

/**
 * Función que determina si un árbol no tiene ningún nodo 
 */
int esVacio(){
    return(raiz==NULL);
}

/*
 * Función que busca en un árbol un nodo a partir de un string que correspondería
 * al campo nombre de los nodos
 */
NodoArbol BuscarArbol(char * nombre){
    NodoArbol *temp;

    temp = raiz; //Puntero auxiliar

    //Mientras temp no sea NULL (haya llegado al final del árbol) o no se 
    //encuentre el string nombre
    while((temp != NULL) && (strcmp(nombre, temp->nombre)!=0)){        
        if(strcmp(nombre, temp->nombre)<0) //Si el string es "menor" que el 
                                        //nombre del nodo a examinar (por ejemplo
                                        // function<while), sigue examinando por
                                        //el nodo hijo izquierdo
            temp = temp->izda;  
        else if(strcmp(nombre, temp->nombre)>0) //Análogamente al caso anterior
                                        //pero cuando sea mayor
            temp = temp->dcha;        
    }
    if(temp == NULL){ //Si el nombre no se ha encontrado, devuelve error
        temp = (NodoArbol *)malloc(sizeof(NodoArbol));
        temp->tipo = -1;
    }
    return(*temp);
}

/**
 * Función que inserta un NodoArbol en el árbol
 */
int InsertarNodoEntero(NodoArbol *nuevoNodo){
    NodoArbol *temp;
    NodoArbol *anterior;

    temp = raiz;
    anterior = NULL;

    //Busca la posición en la que se insertará el nodo
    while((temp != NULL) && (strcmp(nuevoNodo->nombre, temp->nombre)!=0)){
        anterior = temp;
        if(strcmp(nuevoNodo->nombre, temp->nombre)<0)
            temp = temp->izda;
        else if(strcmp(nuevoNodo->nombre, temp->nombre)>0)
            temp = temp->dcha;
    }

    //Añadimos el nodo al árbol
    if(anterior == NULL) //Si anterior==NULL, el nodo es el primero en introducirse
        raiz = nuevoNodo;

    else {
        //Si el nombre de nuevoNodo < anterior->nombre, lo introducimos a la izquierda
        if(strcmp(nuevoNodo->nombre, anterior->nombre)<0)
            anterior->izda = nuevoNodo;
        //En el caso contrario, lo introducimos a la izquierda
        else if(strcmp(nuevoNodo->nombre, anterior->nombre)>0)
            anterior->dcha = nuevoNodo;
    }
   return(nuevoNodo->tipo);
}

/**
 * Función que inserta un nodo en el árbol a partir de su valor y su nombre
 */
NodoArbol InsertarNodo(int valor, char *cA){
    NodoArbol *nuevoNodo;

    nuevoNodo = (NodoArbol *)malloc(sizeof(NodoArbol));
    nuevoNodo->tipo = valor;    
    nuevoNodo->nombre = (char*)malloc(sizeof(char) * strlen(cA));    
    strcpy(nuevoNodo->nombre, cA);
    nuevoNodo->valor.val=0;
    nuevoNodo->izda = nuevoNodo->dcha = NULL;
    InsertarNodoEntero(nuevoNodo);
    return(*nuevoNodo);
}

int InsertarNodoFloat(int Key, char * cA, float val){
    NodoArbol *nuevoNodo;

    nuevoNodo = (NodoArbol *)malloc(sizeof(NodoArbol));
    nuevoNodo->tipo = Key;
    nuevoNodo->valor.val = val;
    nuevoNodo->nombre = (char*)malloc(sizeof(char) * strlen(cA));    
    strcpy(nuevoNodo->nombre, cA);
    nuevoNodo->izda = nuevoNodo->dcha = NULL;    
    return(InsertarNodoEntero(nuevoNodo));
}

int InsertarNodoFuncion(int Key, char * cA, double (*fnct)()){
    NodoArbol *nuevoNodo;

    nuevoNodo = (NodoArbol *)malloc(sizeof(NodoArbol));
    nuevoNodo->tipo = Key;
    nuevoNodo->valor.fnctptr = fnct;
    nuevoNodo->nombre = (char*)malloc(sizeof(char) * strlen(cA));    
    strcpy(nuevoNodo->nombre, cA);
    nuevoNodo->izda = nuevoNodo->dcha = NULL;    
    return(InsertarNodoEntero(nuevoNodo));    
}

/*
 * Función que busca un nodo a partir del nombre y devuelve su valor. Si no lo
 * encuentra, inserta el nodo y devuelve el valor del nuevo nodo.
 */

NodoArbol buscaTS(char * nombre){
    NodoArbol id;   
    id=BuscarArbol(nombre);   
    if(id.tipo==-1){
        id=InsertarNodo(VAR, nombre);
    }    
    return id;
}

void ActualizaNodoFloat(char * nombreNodo, float valor){
    //printf("en actualizarNodoFloat, nombreNodo = %s \n", nombreNodo);

    EliminarNodo(nombreNodo);
    InsertarNodoFloat(VAR, nombreNodo, valor);

    //NodoArbol nodo = BuscarArbol(nombreNodo);
    //printf("en buscar dentro de actualizaNodo%f", nodo.valor.val);
}


/*
 * Función que elimina un árbol desde la raíz, liberando la memoria ocupada por él
 */
void EliminarArbol(){
    LimpiarArbol(raiz);
}

void ImprimirNodoConValores(NodoArbol * nodo){
    if(nodo->izda!=NULL){
        ImprimirNodoConValores(nodo->izda);
    }
    printf("%d - %s", nodo->tipo, nodo->nombre);
    if(nodo->tipo==VAR){
        printf(" --> %f \n", nodo->valor.val);
    }else{
        printf("\n");
    }
    if(nodo->dcha!=NULL){
        ImprimirNodoConValores(nodo->dcha);
    }
}

void ImprimirArbolConValores(){
    ImprimirNodoConValores(raiz);
}

int EliminarNodo(char * nombre){
    NodoArbol * anterior;
    NodoArbol *temp;
    NodoArbol *delParent;    // Parent of node to delete
    NodoArbol *delNode;      // Node to delete

    temp = raiz;
    anterior = NULL;

    //Encontramos el nodo a eliminar
    while((temp != NULL) && (strcmp(temp->nombre, nombre)!=0)){
        anterior = temp;
        if(strcmp(nombre, temp->nombre)<0)
            temp = temp->izda;
        else if(strcmp(nombre, temp->nombre)>0)
            temp = temp->dcha;
    }

    if(temp == NULL){ // No se encontró el nodo    
        printf("Key not found. Nothing deleted. El nombre es %s\n", nombre);
        return 0;
    }else{
        if(temp == raiz){ //Eliminamos la raíz
            delNode = raiz;
            delParent = NULL; 
        }else{
            delNode = temp;
            delParent = anterior;
        }
    }

    // Case 1: Deleting node with no children or one child 
    if(delNode->dcha == NULL){
        if(delParent == NULL)    // If deleting the root    
        {
            raiz = delNode->izda;
            free(delNode);
            return 1;
        }
        else
        {
            if(delParent->izda == delNode)
                delParent->izda = delNode->izda;
            else
                delParent->dcha = delNode->izda;
            free(delNode);
            return 1;
        }
    }
    else // There is at least one child 
    {
        if(delNode->izda == NULL)    // Only 1 child and it is on the right
        {
            if(delParent == NULL)    // If deleting the root    
            {
                raiz = delNode->dcha;
                free(delNode);
                return 1;
            }
            else
            {
                if(delParent->izda == delNode)
                    delParent->izda = delNode->dcha;
                else
                    delParent->dcha = delNode->dcha;
                free(delNode);
                return 1;
            }
        }
        else // Case 2: Deleting node with two children 
        {                          
            temp = delNode->izda;
            anterior = delNode;
            while(temp->dcha != NULL)
            {
                anterior = temp;
                temp = temp->dcha;
            }
            // Copy the replacement values into the node to be deleted 
            delNode->tipo = temp->tipo;
            strcpy(delNode->nombre, temp->nombre);

            // Remove the replacement node from the tree 
            if(anterior == delNode)
                anterior->izda = temp->izda;
            else
                anterior->dcha = temp->izda;
            free(temp);
            return 1;
        }
    }
}

1 个答案:

答案 0 :(得分:0)

这与野牛或弯曲无关。

最后一个问题出现在{ALNUM}的弹性动作中;特别是突出显示的行:

{ALNUM} {
        NodoArbol s;
        s = buscaTS(yytext);                
        yylval.tptr = &s;
        return s.tipo;
        }

此时,s是一个局部变量(代码块的本地变量),这意味着它的生命周期以return语句结束。因此,分配给yylval.tptr的地址是悬空指针,它指向的地址完全不可预测。 (这是未定义的行为,因此后果可能会更加严重。)

但是,真的,buscaTS返回整个NodoArbol的副本有点奇怪。通常,二叉树查找会返回指向树节点的指针,因为二进制搜索树节点没有理由四处移动。

不幸的是,在您的设计中,节点确实会移动,因为当您修改与符号关联的值时,您删除符号的节点然后重新创建它,而不是仅仅更改关联的值。 IMO,这完全没有必要且非常低效,而且它也使得符号表设计更加困难。如果你改变了,那么你可以从BuscarArbol(以及因此从BuscaTS)返回一个节点的地址,然后你可以使用返回的指针来更新符号的值这是必要的。

我注意到了其他一些看你代码的事情:

  1. BuscarArbol找不到符号时,它mallocs一个新节点,然后返回该节点的副本。新分配的节点永远不会进入树中,并且永远不会被释放。如果更改BuscarArbol以返回指针,它可以返回指向新分配节点的指针,让调用者负责将节点插入树中或释放它。或者它可以返回NULL而不是伪造类型为-1的节点。 (这是我的偏好,FWIW。)

  2. 插入新节点的各种方式有很多代码重复。我建议使用一个带有名称的例程并插入一个具有该名称的新节点,并返回指向新节点的指针。然后,调用者可以修复符号类型和值。

  3. 此外,当您使用名称创建新节点时,请复制名称,如下所示:

    nuevoNodo->nombre = (char*)malloc(sizeof(char) * strlen(cA));    
    strcpy(nuevoNodo->nombre, cA);
    

    这是一个缓冲区溢出,因为C中的字符串是NUL终止的,因此需要比字符串长度多一个字节。因此strcpy将在分配结束后覆盖该字节,这是未定义的行为

  4. 这就是我注意到的。我没有详细检查代码。