Vous êtes sur la page 1sur 20

5- Interrupciones

Caso de Estudio: Calculadora Cientfica Avanzada


La calculadora cientfica bsica desarrollada en la Prctica anterior es capaz de realizar operaciones de la forma a operacin b, por ejemplo: 4.5 + 9.43, es decir una sola operacin matemtica a la vez. En este Caso de Estudio se estudiar como hacer una calculadora que pueda resolver expresiones matemticas como: ((10 5) * 3) / (3.14 + 45)

El algoritmo utilizado para resolver una expresin de este tipo es llamado analizador sintctico (expression parsing), aqu estudiaremos una versin en particular llamada Analizador sintctico recursivo descendente (recursive-descendent parser). Este tipo de algoritmos analizan expresiones, aunque hay muchos tipos de expresiones, aqu trataremos slo con expresiones numricas. La explicacin de este algoritmo se tom de [20] . Las expresiones numricas que analizaremos estn compuestas por los siguientes smbolos: Nmeros Operadores: +, -, *, /, ^ Parentesis Donde el operador ^ lo usaremos para expoenciacin, ej. 2^3 = 8.

Estos smbolos pueden combinarse para formar expresiones respetando las reglas del algebra. Por ejemplo: 10 5.5 (100 5) * 13/3 2^3 + 32 Los operadores usados tienen la siguiente precedencia: Precendencia Mayor Operadores + - (unario) ^ */ + Ejemplo -3, +2.5 2^3 3.33*2, 1/3 2+3, 4-2

Menor

Cuando 2 operadores tienen la misma precedencia, se evalan de izquierda a derecha, por ejemplo si la operacin es 6/3*2, el resultado es 4.

Ing. Juan Ramon Terven Salinas

93

5- Interrupciones

Analizar expresiones
El analizar expresiones no es una tarea fcil, por ejemplo suponga la siguiente expresin: 10 2 * 3 Nosotros sabemos que el resultado de esta operacin es 4, si queremos hacer un programa que calcule esta operacin es sencillo. Pero Cmo hacer un programa que pueda resolver cualquier expresin arbitraria? Tal vez nos pueda surgir la idea de un algoritmo como el siguiente:

a = toma el primer operando while(haya operando presentes) { op = tomar operador b = tomar segundo operando a = a op b }

Este algoritmo toma el primer operando, el operador, y el segundo operando para realizar la primera operacin, luego toma el siguiente operador y operando para realizar la siguiente operacin, y as sucesivamente. Si usamos este algoritmo para resolver la expresin anterior, el resultado sera 24 en lugar de 4, ya que este algoritmo no toma en cuenta la precedencia de operadores. No podemos simplemente tomar los operandos y operadores de izquierda a derecha porque las reglas del algebra dictan que la multiplicacin debe efectuarse antes que la suma y la resta, el problema aumenta si agregamos parntesis, exponenciacin, operadores unarios, variables, etc.

Ing. Juan Ramon Terven Salinas

94

5- Interrupciones

Analizador Recursivo Descendente

Existen diversas formas de analizar una expresin, el algoritmo que usaremos es un analizador recursivo descendente. En este algoritmo, las expresiones son estructuras de datos recursivas, es decir expresiones que estn definidas en trminos de ellas mismas. Si por ejemplo las expresiones solamente pueden usar los operadores +, -, *, / y parntesis, entonces todas las expresiones se pueden definir con las siguientes reglas:
expresion termino[+ termino][ termino] termino factor[* factor ][/ factor ]
factor numero o (expresion)

Los corchetes indican un elemento opcional y significa produce. Estas reglas son llamadas Reglas de produccin de la expresin. Entonces las reglas se leen:

Expresin produce trmino ms trmino o termino menos trmino. Trmino produce factor veces factor o factor dividido entre factor. Factor produce nmero o una expresin entre parntesis.

Por ejemplo, la expresin: 10 + 5 * 4 Tiene 2 trminos: 10 y 5*4, el segundo trmino contiene 2 factores: 5 y 4, estos factores consisten de un nmero cada uno.

Ahora la expresin: 14 * (7 3) Tiene 2 factores: 14 y (7 3), los cuales son un nmero y una expresin entre parntesis, la expresin entre parntesis contiene 2 trminos: 7 y 3.

Ing. Juan Ramon Terven Salinas

95

5- Interrupciones Para entender como funciona el algoritmo veremos como las reglas de produccin analizan la siguiente expresin: 9/3 (100 + 56) 1. Se obtiene el primer trmino, 9/3. 2. Se obtiene cada factor y se realiza la divisin. El resultado es 3. 3. Se obtiene el segundo trmino (100 + 56). En este punto, se analiza recursivamente la subexpresin. 4. Se obtiene cada trmino y se suman, el resultado es 156. 5. Regresa de la evaluacin recursiva del segundo trmino. 6. Resta 156 de 3. El resultado es -153.

Diseccionar una expresin


Antes de analizar una expresin debemos extraer sus componentes o tokens individuales que forman la expresin. Esta extraccin se realiza conforme se va analizando la expresin. En nuestro algoritmo usaremos 4 tipos de tokens: Ninguno, Delimitador, Numero y Error, estos tipos de tokens los definimos como constantes en el archivo parser.h como se muestra a continuacin.

// Tipos de tokens #define NONE_TK #define DELIMITER_TK #define NUMBER_TK #define ERR_TK

0 1 2 3

Para entender el proceso de tokenizacin veamos los tokens de la siguiente expresin: 9 / 100 (5 * 4.3) ^2

Token 9 / 100 ( 5 * 4.3 ) ^ 2


Ing. Juan Ramon Terven Salinas

Tipo de Token Nmero Delimitador Nmero Delimitador Delimitador Nmero Delimitador Nmero Delimitador Delimitador Nmero
96

5- Interrupciones Para extraer los tokens de una expresin usaremos una funcin que llamaremos

getToken la cual se muestra a continuacin.

Programa 5-9. Funcin para diseccionar una expresion /*************************************************************** FUNCIONES PARA DISECCIONAR UNA EXPRESION ***************************************************************/ // Obtiene el siguiente token void getToken(void) { short i, longitudExp; unsigned char punto; tokType = NONE_TK; token[0] = 0; // obtiene la longitud de la expresion longitudExp = strlen(expresion); // Revisa si es fin de expresion if(expIdx == longitudExp) { token[0] = EOE; return; } // Se brinca los espacios en blanco while(expIdx < longitudExp && expresion[expIdx]==' ') expIdx++; // Si los espacios en blanco terminan la expresion if(expIdx == longitudExp) { token[0] = EOE; return; } // Si es un delimitador +, -, *, /, ^, (, o ) if(isDelim(expresion[expIdx])) { token[0] = expresion[expIdx]; token[1] = 0; //fin de cadena expIdx++; tokType = DELIMITER_TK; } // Si es un numero else if((expresion[expIdx]>='0' && expresion[expIdx]<='9') || (expresion[expIdx] == '.')) { i=0; punto=0; //mientras sea numero while((expresion[expIdx]>='0' && expresion[expIdx]<='9') || (expresion[expIdx] == '.')) { // revisa que solo haya 1 punto if(expresion[expIdx] == '.' && punto==1)

Ing. Juan Ramon Terven Salinas

97

5- Interrupciones
{ token[0] = ERR_TK; return; } // Si es un punto, debe ser el ultimo else if(expresion[expIdx] == '.' && punto == 0) punto = 1; //indica que ya leyo un punto //carga el siguiente numero en la cadena token token[i] = expresion[expIdx]; expIdx++; i++; //Si ya no cabe en la cadena de expresion termina el ciclo if(expIdx >= EXP_SIZE) break; } //tipo de token tokType = NUMBER_TK; token[i] = 0; // fin de cadena } // Si es un caracter desconocido else token[0] = EOE; } // Regresa 1 si c es un delimitador int isDelim(char c) { char *ptr; char delimitadores[15] = " +-*/^()"; // busca el caracter en la cadena delimitadores ptr = strrchr(delimitadores, c); if (ptr != NULL) return 1; else return 0; }

Ing. Juan Ramon Terven Salinas

98

5- Interrupciones

Cdigo del analizador sintctico recursivo descendente

Una vez que sabemos como diseccionar una expresin estamos listos para analizar la expresin. Durante el anlisis de la expresin se detectan errores en la expresin, tales como falta de parntesis, error matemtico, error de sintaxis, etc. Estos tipos de errores los definimos en el archivo parser.h. A continuacin se muestra el cdigo completo del archivo parser.h.

Programa 5-10. parser.h

#ifndef _PARSER_H #define _PARSER_H // Tipos de tokens #define NONE_TK #define DELIMITER_TK #define NUMBER_TK #define ERR_TK // Tipos de errores #define NO_ERR #define SYNTAX_ERR #define PARENTESIS_ERR #define NOEXP_ERR #define MATH_ERR 0 1 2 3 0 1 2 3 4

// Token que indica fin-de-expresion #define EOE 0 // Tamano maximo de la expresion #define EXP_SIZE 16 void setErrorId(char); char getErrorId(void); char* gerErrorStr(char); double evaluate(char*); double evalExp1(void); double evalExp2(void); double evalExp3(void); double evalExp4(void); double evalExp5(void); double atom(void); void getToken(void); int isDelim(char); #endif

Ing. Juan Ramon Terven Salinas

99

5- Interrupciones La constante EXP_SIZE define el tamao mximo de la expresin; se escogi un valor de 16 para limitar el tamao de la expresin al tamao de una fila de la pantalla. Si se desea analizar expresiones mas largas solo aumente este nmero. Las funciones que realizan el algoritmo del analizador sintctico son las siguientes:

double double double double double double double

evaluate(char*); evalExp1(void); evalExp2(void); evalExp3(void); evalExp4(void); evalExp5(void); atom(void);

A continuacin se muestra el cdigo completo del archivo parser.c, en el cual se encuentran las funciones que realizan el algoritmo de analizar la expresin

Programa 5-11. Archivo parser.c #include <p32xxxx.h> #include <string.h> #include <stdlib.h> #include <math.h> #include "parser.h" char expresion[EXP_SIZE]; // int expIdx; // char errorId = 0; // unsigned char token[EXP_SIZE int tokType; // //Descripcion de error char err[5][20]= { "No error", "Syntax error", "Err parentesis", "No Expresion", "Error Math" }; /*************************************************************** FUNCIONES PARA MANEJOS DE ERRORES ***************************************************************/ //Asigna el cdigo de error void setErrorId(char error) { errorId = error; } //Regresa el cdigo de error char getErrorId(void) { return errorId; } cadena de expresion indice actual de la expresion ID del error actual + 1]; // token actual contiene el tipo de token

Ing. Juan Ramon Terven Salinas

100

5- Interrupciones
//Regresa la descripcion del error char* gerErrorStr(char error) { if(error<5) return err[error]; else return "no error"; } /*************************************************************** FUNCIONES DEL ANALIZADOR SINTACTICO RECURSIVO ***************************************************************/ // Punto de entrada del analizador sintactico double evaluate(char* expstr) { double result; // Copia la expresion en cadena expresion strcpy(expresion,expstr); expIdx = 0; getToken(); //Si el token es EOE if(token[0] == EOE) { // no hay expresion presente setErrorId(NOEXP_ERR); result = 0.0; } //Si token es de tipo error else if(token[0] == ERR_TK) { //Asigna el error setErrorId(SYNTAX_ERR); result = 0.0; } else // Analiza y evalua la expresion result = evalExp1(); //Revisa que la expresion termine con EOE if(token[0] != EOE) { setErrorId(SYNTAX_ERR); result = 0.0; } return result; }// fin evaluate // Suma o resta dos terminos (menor precedencia) double evalExp1(void) { char op; double result; double partialResult; result = evalExp2(); op = token[0];

Ing. Juan Ramon Terven Salinas

101

5- Interrupciones
while(op == '+' || op == '-') { getToken(); partialResult = evalExp2(); switch(op) { case '-': result = result - partialResult; break; case '+': result = result + partialResult; break; } op = token[0]; } return result; }// fin evalExp1 // Multiplica o divide 2 factores (mayor precedencia que + y -) double evalExp2(void) { char op; double result; double partialResult; result = evalExp3(); op = token[0]; while(op == '*' || op == '/') { getToken(); partialResult = evalExp3(); switch(op) { case '*': result = result * partialResult; break; case '/': if(partialResult == 0.0) setErrorId(MATH_ERR); result = result / partialResult; break; } op = token[0]; } return result; }//fin evalExp2 // Procesa un exponente (mayor precedencia que * y /) double evalExp3(void) { double result; double partialResult; int t; char op; result = evalExp4();

Ing. Juan Ramon Terven Salinas

102

5- Interrupciones

op = token[0]; if(op == '^') { getToken(); partialResult = evalExp3(); if(partialResult == 0.0) result = 1.0; else result = pow(result,partialResult); } return result; }//evalExp3 // Evalua operadores unarios + o - (mayor precedencia que ^) double evalExp4(void) { double result; char op; op = 0; if((tokType == DELIMITER_TK && token[0] == '+') || token[0]=='-') { op = token[0]; getToken(); } result = evalExp5(); if(op == '-') result = -result; return result; }//evalExp4 // Procesa una expresion en parentesis (tienen la mayor precedencia) double evalExp5(void) { double result; if(token[0]=='(') { getToken(); result = evalExp2(); if(token[0] != ')') { setErrorId(PARENTESIS_ERR); return 0.0; } getToken(); } else result = atom(); return result; }//evalExp5

Ing. Juan Ramon Terven Salinas

103

5- Interrupciones

// Obtiene un factor (nmero) double atom(void) { double result = 0.0; char* end; if(tokType == NUMBER_TK) { result = strtod(token, &end); getToken(); } else setErrorId(SYNTAX_ERR); return result; }// fin atom /*************************************************************** FUNCIONES PARA DISECCIONAR UNA EXPRESION ***************************************************************/ // Obtiene el siguiente token void getToken(void) { short i, longitudExp; unsigned char punto; tokType = NONE_TK; token[0] = 0; // obtiene la longitud de la expresion longitudExp = strlen(expresion); // Revisa si es fin de expresion if(expIdx == longitudExp) { token[0] = EOE; return; } // Se brinca los espacios en blanco while(expIdx < longitudExp && expresion[expIdx]==' ') expIdx++; // Si los espacios en blanco terminan la expresion if(expIdx == longitudExp) { token[0] = EOE; return; } // Si es un operador +, -, *, /, ^, (, o ) if(isDelim(expresion[expIdx])) { token[0] = expresion[expIdx]; token[1] = 0; //fin de cadena expIdx++; tokType = DELIMITER_TK; } // Si es un numero

Ing. Juan Ramon Terven Salinas

104

5- Interrupciones
else if((expresion[expIdx]>='0' && expresion[expIdx]<='9') || (expresion[expIdx] == '.')) { i=0; punto=0; //mientras sea numero while((expresion[expIdx]>='0' && expresion[expIdx]<='9') || (expresion[expIdx] == '.')) { // revisa que solo haya 1 punto if(expresion[expIdx] == '.' && punto==1) { token[0] = ERR_TK; return; } // Si es un punto, debe ser el ultimo else if(expresion[expIdx] == '.' && punto == 0) punto = 1; //indica que ya leyo un punto //carga el siguiente numero en la cadena token token[i] = expresion[expIdx]; expIdx++; i++; //Si ya no cabe en la cadena de expresion termina el ciclo if(expIdx >= EXP_SIZE) break; } //tipo de token tokType = NUMBER_TK; token[i] = 0; // fin de cadena } // Si es un caracter desconocido else token[0] = EOE; } // Regresa 1 si c es un delimitador int isDelim(char c) { char *ptr; char delimitadores[15] = " +-*/^()"; // busca el caracter en la cadena delimitadores ptr = strrchr(delimitadores, c); if (ptr != NULL) return 1; else return 0; }

Ing. Juan Ramon Terven Salinas

105

5- Interrupciones

Probar el analizador sintctico


Para probar el analizador sintctico usaremos el siguiente programa y lo simularemos usando MPLAB SIM y la ventana de Watch.

#include <p32xxxx.h> #include "../parser.h" //Bits de configuracion #pragma config POSCMOD = HS, FNOSC = PRIPLL, FPLLMUL = MUL_20 #pragma config FPLLIDIV = DIV_2, FPLLODIV = DIV_1 #pragma config FPBDIV = DIV_1, FWDTEN = OFF, UPLLEN = ON #pragma config UPLLIDIV = DIV_2, FVBUSONIO = ON, FUSBIDIO = ON #pragma config FSOSCEN = OFF, CP = OFF, FCKSM = CSECMD int main(void) { double resultado; char expresion[] = "5+3*2"; resultado = evaluate(expresion); while(1); return 0; }

Pruebe el funcionamiento del algoritmo con diferentes expresiones. Empiece con expresiones sencillas y vaya aumentando la complejidad observando su funcionamiento, por ejemplo:

char expresion[] = "5+3*2"; char expresion[] = "(5+3)*2"; char expresion[] = "5+3*2/3"; char expresion[] = "5+3*2^3"; char expresion[] = "5+3*2^-3/4"; char expresion[] = "9/100(5*4.3)^2";

Ing. Juan Ramon Terven Salinas

106

5- Interrupciones

EJEMPLO 6. Calculadora Avanzada


El siguiente ejemplo demuestra el uso del Analizador sintctico recursivo descendente para construir una calculadora capaz de resolver expresiones matemticas de hasta 16 caracteres (un rengln del display).

Figura 5-9. Diagrama de la Calculadora Avanzada

Para este ejemplo se requieren agregar las siguientes libreras, de las cuales solo la librera teclado.h sufri modificaciones con respecto a la versin orginal para considerar el nuevo esquema del teclado y los operadores activados con shift.
#include #include #include #include #include #include #include #include <p32xxxx.h> <stdlib.h> <string.h> <stdio.h> "../../librerias/LCD/alpha_lcd.h" "../../librerias/retardos/retardos.h" "../teclado.h" "../parser.h"

Ing. Juan Ramon Terven Salinas

107

5- Interrupciones A continuacin se muestran los nicos cambios realizados al archivo teclado.c.

Programa 5-12. Cambios en teclado.c

En el inicio de la funcin cambie lo siguiente: char teclas[16] ={'1','2','3','+', '4','5','6','-', '7','8','9','*', 'S','0','.','='}; char shift_key[16]={'1','2','3','(', '4','5','6',')', '7','8','9','/', 'S','0','^','C'}; En la function leeTecla se cambia el parmetro de entrada ya que ahora recibe un dato char que indica si hay shift o no. Y se cambia la parte final de la decodificacin de la tecla. int leeTecla(char shift) { // Decodifica la tecla if(tecla < 16) if(shift) return shift_key[tecla]; else return teclas[tecla]; else return -1; }//fin de funcin leeTecla Como cambi el parmetro de entrada, tambin debe cambiar el prototipo de la funcin en el archivo teclado.h

A continuacin se muestra el cdigo fuente de la calculadora avanzada.

Programa 5-13. Calculadora Avanzada #include <p32xxxx.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include "../../librerias/LCD/alpha_lcd.h" #include "../../librerias/retardos/retardos.h" #include "../teclado.h" #include "../parser.h"

Ing. Juan Ramon Terven Salinas

108

5- Interrupciones
//Bits de configuracion #pragma config POSCMOD = HS, FNOSC = PRIPLL, FPLLMUL = MUL_20 #pragma config FPLLIDIV = DIV_2, FPLLODIV = DIV_1 #pragma config FPBDIV = DIV_1, FWDTEN = OFF, UPLLEN = ON #pragma config UPLLIDIV = DIV_2, FVBUSONIO = ON, FUSBIDIO = ON #pragma config FSOSCEN = OFF, CP = OFF, FCKSM = CSECMD //Variables Globales char strExpresion[EXP_SIZE+1] = {0}; char strResultado[10]; short indiceExp; double resultado; char shift; //Programa principal int main(void) { AD1PCFG = 0xFFFF; // configura LCD openLCD(); //Escribe "0" en renglon 1 setDDRamAddr(0x0); putsLCD("0"); setDDRamAddr(0x0); //Activa el teclado matricial openTeclado(); //Inicia variables indiceExp = 0; resultado = 0; strcpy(strExpresion,"0"); shift = 0; //Ciclo principal while(1) { //Solo espera a que se pulse una tecla } return 0; } //RUTINA DE INTERRUPCIN DE CHANGE NOTIFICATION //Se produce cuando se pulsa cualquier tecla //1. Decodifica la tecla pulsada //2. Si se pulsa la tecla S cambia el valor de shift //3. Si se pulsa = ejecuta la expresion, muestra el resultado y reinicia valores //4. Si se pulsa C limpia todo //5. Si se pulsa otra tecla agrega el smbolo a la expresion void __ISR( _CHANGE_NOTICE_VECTOR, ipl1) CNInterruptHandler( void) { char tecla, error; retardoms(5); tecla = leeTecla(shift); if(tecla != -1) // si hay tecla valida

// configura pines AN como digitales

Ing. Juan Ramon Terven Salinas

109

5- Interrupciones
{ switch(tecla) { //Si se puls la tecla 'S' case 'S': shift = 1 - shift; //Si esta seleccionado el shift parpadea LCD if(shift) writeCmdLCD(BLINK_ON & CURSOR_OFF); else writeCmdLCD(BLINK_OFF & CURSOR_OFF); break; //Si se puls la tecla '=' case '=': { //ejecuta la expresion resultado = evaluate(strExpresion); error = getErrorId(); //Si ocurrio un error en la expresion if(error) { //lo muestra en renglon2 setDDRamAddr(0x40); putsLCD(gerErrorStr(error)); //Limpia el error setErrorId(NO_ERR); //limpia resultado resultado = 0; } else { //convierte resultado a cadena sprintf(strResultado,"%f",resultado); //lo muestra en renglon2 setDDRamAddr(0x40); putsLCD(strResultado); } //reinicia la cadena operando strcpy(strExpresion,"0"); indiceExp = 0; //coloca cursor en primer renglon setDDRamAddr(0x0); break; //Si se puls la tecla 'C' case 'C': //limpia cadena operando indiceExp = 0; strcpy(strExpresion,"0"); //limpia resultado resultado = 0; //Quita el shift shift = 0; writeCmdLCD(BLINK_OFF & CURSOR_OFF);

Ing. Juan Ramon Terven Salinas

110

5- Interrupciones

//limpia pantalla setDDRamAddr(0x0); putsLCD("0 "); setDDRamAddr(0x40); putsLCD(" "); //Regresa cursor a la posicion inicial setDDRamAddr(0x0); break; //De lo contrario si se puls cualquier otra tecla default: //Si inicia una expresion if(indiceExp == 0) { //limpia pantalla setDDRamAddr(0x0); putsLCD("0 "); setDDRamAddr(0x40); putsLCD(" "); setDDRamAddr(0x0); } //si aun caben digitos en la expresion... if(indiceExp <= EXP_SIZE) { //almacena el smbolo en la cadena operando strExpresion[indiceExp++] = tecla; strExpresion[indiceExp] = 0; //fin de cadena //lo muestra en LCD putcLCD(tecla); } } } while(leeTecla(shift)!=-1); //espera a que se suelte la tecla retardoms(5); } //Limpia la bandera de interrupcin IFS1bits.CNIF = 0; }

Ing. Juan Ramon Terven Salinas

111

5- Interrupciones

PRACTICA 4. Calculadora Cientfica Avanzada


Agregar funcionalidad a la calculadora avanzada del ejemplo anterior, para que sea capaz de ejecutar expresiones matemticas que incluyan las operaciones mostradas en la Figura 5-10.

Figura 5-10. Calculadora Cientfica Avanzada

Por cada operacin nueva que se le agregue a la calculadora se agrega un punto al valor de la prctica. Las operaciones nuevas son: 1. seno 2. coseno 3. tangente 4. seno-1 5. coseno-1 6. tangente-1 7. logaritmo base 10 8. logaritmo natura 9. ex 10. DEL (para borrar, similar a backspace)

Ing. Juan Ramon Terven Salinas

112

Vous aimerez peut-être aussi