我按照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;
}
}
}
答案 0 :(得分:0)
这与野牛或弯曲无关。
最后一个问题出现在{ALNUM}
的弹性动作中;特别是突出显示的行:
{ALNUM} {
NodoArbol s;
s = buscaTS(yytext);
yylval.tptr = &s;
return s.tipo;
}
此时,s
是一个局部变量(代码块的本地变量),这意味着它的生命周期以return
语句结束。因此,分配给yylval.tptr
的地址是悬空指针,它指向的地址完全不可预测。 (这是未定义的行为,因此后果可能会更加严重。)
但是,真的,buscaTS
返回整个NodoArbol
的副本有点奇怪。通常,二叉树查找会返回指向树节点的指针,因为二进制搜索树节点没有理由四处移动。
不幸的是,在您的设计中,节点确实会移动,因为当您修改与符号关联的值时,您删除符号的节点然后重新创建它,而不是仅仅更改关联的值。 IMO,这完全没有必要且非常低效,而且它也使得符号表设计更加困难。如果你改变了,那么你可以从BuscarArbol
(以及因此从BuscaTS
)返回一个节点的地址,然后你可以使用返回的指针来更新符号的值这是必要的。
我注意到了其他一些看你代码的事情:
当BuscarArbol
找不到符号时,它mallocs
一个新节点,然后返回该节点的副本。新分配的节点永远不会进入树中,并且永远不会被释放。如果更改BuscarArbol
以返回指针,它可以返回指向新分配节点的指针,让调用者负责将节点插入树中或释放它。或者它可以返回NULL
而不是伪造类型为-1
的节点。 (这是我的偏好,FWIW。)
插入新节点的各种方式有很多代码重复。我建议使用一个带有名称的例程并插入一个具有该名称的新节点,并返回指向新节点的指针。然后,调用者可以修复符号类型和值。
此外,当您使用名称创建新节点时,请复制名称,如下所示:
nuevoNodo->nombre = (char*)malloc(sizeof(char) * strlen(cA));
strcpy(nuevoNodo->nombre, cA);
这是一个缓冲区溢出,因为C中的字符串是NUL终止的,因此需要比字符串长度多一个字节。因此strcpy
将在分配结束后覆盖该字节,这是未定义的行为。
这就是我注意到的。我没有详细检查代码。