Universidad de Costa Rica
Escuela de Ciencias de la
Computación e Informática
Profesor Adolfo Di Mare
CI-1322
I Semestre 2010
[<=] [home] [<>] [\/] [=>]
CI-1322 Autómatas y compiladores

Tarea #2 [solución]

La calculadora de varios dígitos

      El programa de la Figura 1 es una calculadora que evalúa expresiones complejas, con paréntesis. Tiene la limitación de que trabaja con números de un solo dígito.

      Modifique este programa para que pueda procesar expresiones con números de varios dígitos. Limite su trabajo a los métodos "aparea()", "num()" y "Evaluar()". Use la clase "Token.h" de la Figura 2 y complete el método "yylex()" para obtener un programa que pueda leer de la línea de comandos valores numéricos que contengan separadores para miles y millones. También use funciones de utiliería como "atof()" para obtener el valor numérico de una hilera. Su programa debe procesar correctamente estos ejemplos:

      El caracter "_" nunca se usa como marcador de decimles. Como no siempre es posible saber si la coma o el punto son el marcador de decimales, su programa debe permitir indicar cuál se usa incluyendo un punto o una coma al principio:

// CLC.cpp  (C) adolfo@di-mare.com

/*  resultado
    Evalúa expresiones aritméticas simples en que los
    operandos son números del 0 al 9.
*/

#if defined(__BORLANDC__)  // Compilando con Borland C++
    #include <bool.h>      // Define bool para BC++ v3.1 o inferior
#endif

#include "Astring.h"       // class string

#include <iostream.h>
#include <cctype>


template <class T>
class Pila {
public:
    Pila() { _top = 0; }
    void   Push(T d);
    T      Pop();
    T      Top() { return _vec[_top-1]; }
private:
    enum { Capacidad = 132 };
    int    _top;            // tope de la pila
    T      _vec[Capacidad]; // vector para la pila
}; // Pila

template <class T>
inline void Pila<T>::Push(T d) {
    _vec[_top] = d;
    _top++;
}

template <class T>
inline T Pila<T>::Pop() {
    _top--;
    return _vec[_top];
}

typedef char Token; // OJO: Astring sólo funciona para "char"

class Calculadora {
public:
    Calculadora(const char* exp=0)    : _infijo  (exp)  { Trabaje(); }
    void operator = (const char* exp) { _infijo = exp;    Trabaje(); }
    long Evaluar();
                        //  expr ==>   term r1
private:                //  r1   ==> + term r1
    void expr();        //  r1   ==> - term r1 | ú
    void r1();          //
    void term();        //  term ==>   factor r2
    void r2();          //  r2   ==> * factor r2 | ú
    void factor();      //  r2   ==> / factor r2
    void num();         //
                        //  factor ==> ( expr ) | num
                        //
    void aparea(Token); //  num ==> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
    void Trabaje();
    void error(const char * msg);

private:
    Token  preAnalisis; // siguiente token

    Astring _infijo;    // hilera inicial
    Astring _posfijo;   // hilera resultado

    size_t  _cursor;    // posición en _infijo
};  // Calculadora


void Calculadora::Trabaje() {
/*  resultado
    Traduce a notación posfija la expresión almancenada
    en *this.
    - Para evaluarla, hay que invocar a Calculadora::Evaluar().
*/
/*  requiere
    - La expresión almacenada no debe tener errores de sintaxis.
*/
    _posfijo = "";
    if (_infijo == "") {    // No se digitó ninguna expresión
        return;
    }
    _cursor = 0;

    while (_infijo[_cursor] == ' ') { // ignora blancos al inicio
        _cursor++;
    }

    preAnalisis = _infijo[_cursor]; // inicializa preAnalisis
    expr();      // reconoce la expresión _infijo
} // Calculadora::Trabaje()

void Calculadora::error(const char * msg) {
/*  resultado
    Graba en "cout" un mensaje de error.
    - Indica la posición actual de proceso en al hilera de entrada.
*/
    cout << "ERROR(" << 1+_cursor << ")";
    if (msg != 0) {  // +1 porque _cursor comienza en 0
        if (msg[0] != 0) {
            cout << ": " << msg;
        }
    }
    cout << endl;
} // Calculadora::error()

void Calculadora::expr() {
//  expr ==> term r1
    term();
    r1();
} // Calculadora::expr()

void Calculadora::r1() {
//  r1 ==> + term r1
//  r1 ==> - term r1
//  r1 ==> ú

    if (preAnalisis == '+') {              //  r1 ==> + term r1
        aparea('+');
        term(); {{ _posfijo += '+'; }}
        r1();
    } else if (preAnalisis == '-') {       //  r1 ==> - term r1
        aparea('-');
        term(); {{ _posfijo += '-'; }}
        r1();
    } else { }                             //  r1 ==> ú
} // Calculadora::r1()

void Calculadora::term() {
//  term ==> factor r2
    factor();
    r2();
} // Calculadora::term()

void Calculadora::r2() {
//  r2 ==> * factor r2
//  r2 ==> / factor r2
//  r2 ==> ú

    if (preAnalisis == '*') {              //  r2 ==> * factor r2
        aparea('*');
        factor(); {{ _posfijo += '*'; }}
        r2();
    } else if (preAnalisis == '/') {       //  r2 ==> / factor r2
        aparea('/');
        factor(); {{ _posfijo += '/'; }}
        r2();
    } else { }                             //  r2 ==> ú
} // Calculadora::r2()

void Calculadora::factor() {
//  factor ==> ( expr )
//  factor ==>   num

    if (preAnalisis == '(') {              //  factor ==> ( expr )
        aparea('(');
        expr();
        aparea(')');
    } else if (isdigit(preAnalisis)) {     //  factor ==>   num
        num();
    } else {
        error("El factor no es dígito ni '('");
    }
} // Calculadora::factor()

void Calculadora::num() {
//  num ==> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

    if (isdigit(preAnalisis)) {
        {{ _posfijo += preAnalisis; }}
        aparea(preAnalisis);
    } else {
        error("El token no es dígito");
    }
} // Calculadora::num()

void Calculadora::aparea(Token ch) {
/*  resultado
    Consume el terminal, y avanza al siguiente lexema.
    - Si "ch" no coincide con el valor de "preAnalisis"
      emite un error.
*/
    if (preAnalisis != ch) {
        error("Token inesperado");
    }

    preAnalisis = _infijo[++_cursor];
    while (_infijo[_cursor] == ' ') { // ignora blancos
        preAnalisis = _infijo[++_cursor];
    }
} // Calculadora::aparea()

long Calculadora::Evaluar() {
/*  resultado
    Evalúa la expresión contenida en "*this".
*/
    Pila<long> P;       // pila usada para evaluar _posfijo
    size_t len = strlen(_posfijo.c_str());
    if (len==0) {
        return 0;
    }

    for (size_t i=0; i < len; ++i) {  // recorre toda la expresión
        long op1, op2;
        if (isdigit(_posfijo[i])) {
            // si es un dígito lo mete en la pila
            P.Push( _posfijo[i] - '0');
        } else if (_posfijo[i] == '+') { // Si es +, saca los operandos
            op1 = P.Pop();               // de la pila y los suma
            op2 = P.Pop();
            P.Push(op2 + op1); // mete el resultado intermedio en la pila
        } else if (_posfijo[i] == '-') { // Si es - resta
            op1 = P.Pop();
            op2 = P.Pop();
            P.Push(op2 - op1);  // lo mete en la pila
        } else if (_posfijo[i] == '*') {
            op1 = P.Pop();
            op2 = P.Pop();
            P.Push(op2 * op1);
        } else if (_posfijo[i] == '/') {
            op1 = P.Pop();
            op2 = P.Pop();
            if (op1 != 0) { // para no dividir entre 0
                P.Push(op2 / op1);
            } else {
                P.Push(0);
                error("División por cero");
            }
        }
    }
    return P.Pop();
} // Calculadora::Evaluar()

int main() {
    char str[200];
    Calculadora C;
    long        V;

    cout << endl << endl;
    strcpy(str, "(5 * 3)");
    C = str;
    V = C.Evaluar();
    cout << V  << " == " << str << endl;

    strcpy(str, "(1 + 2) * (3 - 4 - 5)");
    C = str;
    V = C.Evaluar();
    cout << V  << " == " << str << endl;

    strcpy(str, "(( (((((1 + 2))))) * ((((3 - 4 - 5)))) ))" );
    C = str;
    V = C.Evaluar();
    cout << V  << " == " << str << endl;

    strcpy(str, "1 / ( 3 - (2+1) )");
    C = str;

    cout << str << " == " << endl;
    V = C.Evaluar();
    cout << "== " << V  << endl;

    strcpy(str, ")" );
    cout << str << endl;
    C = str;

    strcpy(str, "1++" );
    cout << str << endl;
    C = str;

    strcpy(str, "1+2*)" );
    cout << str << endl;
    C = str;

    strcpy(str, "(1++" );
    cout << str << endl;
    C = str;

    strcpy(str, "(x +" );
    cout << str << endl;
    C = str;

    return 0;
} // main()

// EOF: CLC.cpp
Figura 1

// Token.h    (C) 2010 adolfo@di-mare.com

/** \file  Token.h
    \brief Ejemplo de una analizador léxico yylex() implementado a mano
    para la gramática de la calculadora. En la práctica es
    necesario reprogramar \c yylex() para que reconozca un conjunto
    de tokens y lexemas diferente.
*/

#ifndef Token_h
#define Token_h

#include <string>
#include <cstdlib> // strlen(), etc.

using std::string;

#include <iostream>
#include <cctype>
#include <climits>         // CHAR_MAX

/// Contiene la pareja <code> <Token, Lexema> </code> que retorna \c yylex().
class Token {
private:
    int    m_codigo; ///< \c Token::Codigo. Valor numérico que representa al token.
    string m_lexema; ///< Lexema asociado al código.
    int  m_fl;       ///< \c first_line.   Línea que contiene la primera letra del token.
    int  m_fc;       ///< \c first_column. Columna en la que aparece la primera letra del token.
    int  m_ll;       ///< \c last_line.    Línea que contiene la última letra del token.
    int  m_lc;       ///< \c last_column.  Columna en la que aparece la última letra del token.
public:
    enum Codigo {
        NUM = 256+99+1-32,  // podría ser otro, mientras no "choque"
                            // con otros valores para tokens
        ERROR  = 666, FIN  = 999
    };
    Token()       { *this = 0; } ///< Constructor.
    Token(char c) { *this = c; } ///< Constructor para una letra.
    Token(int  c) { *this = c; } ///< Constructor sinónimo de <code> Token(char c) </code>.
    Token(const Token& o) { *this = o; } ///< Constructor de copia.
    const string& lexema() const { return m_lexema;  } ///< Retorna el lexema del token.
    const int     num()    const { return m_codigo;  }  ///< Código numérico del token.
    const int     token () const { return num(); }      ///< Sinónimo de \c num().
    const char*   Nombre() const { return Nombre(m_codigo); } ///< Nombre del token.
    static const char* Nombre(int);
    void   operator=( int c ); /// Asignación a partir de un entero o de una letra.
    Token& operator=( const Token& );
    void   set( int c, const Astring& lexema, int, int, int, int);
    void   loc( int &, int &, int &, int &) const;
}; // Token

/// Analizador léxico simple usado para tokenizar las letras de una hilera.
class Anlizador_Lexico {
private:
    const char * m_str; ///< Hilera grandota que contiene todas las letras.
    int m_idx; ///< Indica la siguiente posición en \c m_str a reconocer.
    int m_len; ///< Longitud de \c m_str.
public:
    /// Constructor por defecto.
    Anlizador_Lexico() : m_str(""),m_idx(0),m_len(0) { }
    ~Anlizador_Lexico() { } ///< Destructor.
    /// Constructor para usar \c str.
    Anlizador_Lexico( const char* str ) : { set(str); }
    /// Prepara a \* this para que use \c str.
    void set( const char* str ) : { m_str = str; m_idx = 0; m_len=strlen(m_str); }
    /// Retorna el siguiente token del analizador léxico.
    int yylex( Token & tk ); //<== Esto es lo que falta de implementar !!!
};


/// Deja el lexema como la hilera nula a menos que \c "c" sea un caracter válido.
inline void Token::operator=( int c ) {
    m_codigo =  c;
    if ( (0 < c) && (c <= CHAR_MAX) ) {
        m_lexema = c;
    } else {
        m_lexema = "";
    }
    m_fl = m_fc = m_ll = m_lc = 1;
}

inline void Token::set( int c, const Astring& lexema,
                        int fl=1, int fc=1, int ll=1, int lc=1 ) {
    m_codigo = c;
    m_lexema = lexema;
    m_fl = fl;
    m_fc = fc;
    m_ll = ll;
    m_lc = lc;
}

/// Retorna la posición del token.
/// - \c fl==first_line.   Línea que contiene la primera letra del token.
/// - \c fc==first_column. Columna en la que aparece la primera letra del token.
/// - \c ll==last_line.    Línea que contiene la última letra del token.
/// - \c lc==last_column.  Columna en la que aparece la última letra del token.
inline void Token::loc( int &fl, int &fc, int &ll, int &lc) const {
    fl = m_fl;
    fc = m_fc;
    ll = m_ll;
    lc = m_lc;
}

/// Copia.
inline Token& Token::operator=( const Token& o ) {
    m_codigo = o.m_codigo;
    m_lexema = o.m_lexema;
    m_fl = o._fl;
    m_fc = o._fc;
    m_ll = o._ll;
    m_lc = o._lc;

    return *this;
}

/// ¿ l == r ?
inline operator == (const Token& l, const Token& r) {
    return l.token() == r.token() && l.lexema() == r.lexema();
}

/// ¿ l != r ?
inline operator != (const Token& l, const Token& r) {
    return ! (l == r);
}

/// Retorna el nombre del token cuyo valor numérico es \c num.
const char* Token::Nombre(int num) {
    if      (num == '+')             return "+";
    else if (num == '-')             return "-";
    else if (num == '*')             return "*";
    else if (num == '/')             return "/";
    else if (num == Token::NUM)      return "NUM";
    else if (num == Token::FIN)      return "FIN";
    else                             return "ERROR";
} // Token::Nombre()

#endif // Token_h

// EOF: Token.h
Figura 2

      Entregue su tarea por correo electrónico, como lo hizo anteriormente.

[mailto:] Entrega de Tareas

Tiempo de entrega: 1 semana
Modalidad: En parejas

Soluciones

[mailto:] Adolfo Di Mare <adolfo@di-mare.com>.
Copyright © 2010
Derechos de autor reservados © 2010
[home] <> [/\]