Program Listing for File calc.hh

Return to documentation for file (calc.hh)

/*--------------------------------------------------------------------------*\
 |                                                                          |
 |  This program is free software; you can redistribute it and/or modify    |
 |  it under the terms of the GNU General Public License as published by    |
 |  the Free Software Foundation; either version 2, or (at your option)     |
 |  any later version.                                                      |
 |                                                                          |
 |  This program is distributed in the hope that it will be useful,         |
 |  but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 |  GNU General Public License for more details.                            |
 |                                                                          |
 |  You should have received a copy of the GNU General Public License       |
 |  along with this program; if not, write to the Free Software             |
 |  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               |
 |                                                                          |
 |  Copyright (C) 1999                                                      |
 |                                                                          |
 |            ___    ____  ___  __  __        ___    ____  ___  __  __      |
 |           /   \  /     /   \  \  /        /   \  /     /   \  \  /       |
 |          /____/ /__   /____/   \/        /____/ /__   /____/   \/        |
 |         /   \  /     /   \     /        /   \  /     /   \     /         |
 |        /____/ /____ /    /    /        /____/ /____ /    /    /          |
 |                                                                          |
 |      Enrico Bertolazzi                                                   |
 |      Dipartimento di Ingegneria Meccanica e Strutturale                  |
 |      Universita` degli Studi di Trento                                   |
 |      Via Mesiano 77, I-38050 Trento, Italy                               |
 |                                                                          |
\*--------------------------------------------------------------------------*/

#ifndef CALC_HH
#define CALC_HH

#include <cmath>

// standard includes I/O
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>

// STL lib
#include <string>
#include <map>

namespace calc_defs {

  using namespace ::std;

  template <typename T_type = double>
  class Calculator {

    // Calculator type
  public:

    typedef T_type              value_type;
    typedef value_type*         pointer;
    typedef const value_type*   const_pointer;
    typedef value_type&         reference;
    typedef const value_type&   const_reference;

    typedef Calculator<value_type> CALCULATOR;

    typedef value_type (*Func1)(value_type);
    typedef value_type (*Func2)(value_type,value_type);

    typedef map<string,Func1>      map_fun1;
    typedef map<string,Func2>      map_fun2;
    typedef map<string,value_type> map_real;

    typedef typename map_fun1::iterator       map_fun1_iterator;
    typedef typename map_fun1::const_iterator map_fun1_const_iterator;
    typedef typename map_fun2::iterator       map_fun2_iterator;
    typedef typename map_fun2::const_iterator map_fun2_const_iterator;
    typedef typename map_real::iterator       map_real_iterator;
    typedef typename map_real::const_iterator map_real_const_iterator;

  private:

    static value_type internal_abs (value_type x) { return x > 0 ? x : - x; }
    static value_type internal_pos (value_type x) { return x > 0 ? x : 0; }
    static value_type internal_neg (value_type x) { return x > 0 ? 0 : x; }
    static value_type internal_max (value_type a, value_type b) { return a > b ? a : b; }
    static value_type internal_min (value_type a, value_type b) { return a < b ? a : b; }

    map_fun1 unary_fun;
    map_fun2 binary_fun;
    map_real variables;

    typedef enum {
      Number, Variable, Parameter,
      Plus, Minus, Times, Divide, Power,
      OpenPar, ClosePar,
      Assign, Comma, Unrecognized,
      EndOfExpression, EndOfString
    } Token_Type;

    typedef enum {
      No_Error,
      Divide_By_Zero,
      Expected_OpenPar, Expected_ClosePar, Expected_Comma,
      Unknown_Variable, Bad_Position, Unknown_Error
    } ErrorCode;

  private:
    // error handling
    ErrorCode  error_found;

    // token management
    Token_Type token_type;
    string     token_string;
    value_type last_evaluated;

    // internal use
    char const * string_in;
    char const * ptr;

    char const & get(void)       { return *ptr++; }
    char const & see(void) const { return *ptr; }
    int          pos(void) const { return static_cast<int>(ptr - string_in); }

    void get_number( value_type & res, string const & s ) const {
      stringstream str(s);
      str >> res;
    }

    void to_string( value_type const & res, string & s ) const {
      stringstream str;
      str << res;
      s = str . str();
    }

    CALCULATOR const & operator = (CALCULATOR const &);
    // { init(); return *this; }

    Calculator(CALCULATOR const &);
    // { init(); }

  public:

    Calculator(void)
    : unary_fun()
    , binary_fun()
    , variables()
    , error_found()
    , token_type()
    , token_string()
    , last_evaluated(0)
    , string_in(0)
    , ptr(0)
    { init(); };

    ~Calculator(void) { };

    void init(void);

    bool parse( char const * str );
    bool parse( string const & str ) { return parse(str.c_str()); }

    void
    parse_file(
      char const * name,
      bool         show_err = false,
      ostream &    stream = cerr
    );

    void
    parse_file(
      string const & name,
      bool           show_err = false,
      ostream &      stream = cerr
    ) {
      parse_file( name.c_str(), show_err, stream);
    }

    void report_error( ostream & s );

    const_reference get_value() const { return last_evaluated; };

    void set( string const & name, value_type val );
    void set( char const * name, value_type val ) { set(string(name),val); }

    bool
    drop( string const & name ) {
      map_real_const_iterator ii = variables.find(name);
      bool ex = ii != variables.end();
      if ( ex ) variables . erase( ii );
      return ex;
      }

    bool drop( char const * name ) { return drop(string(name)); }

    bool exist(string const & name) const
    { return variables.find(name) != variables.end(); }
    bool exist(char const name[]) const { return exist(string(name)); }

    value_type get( string const & name, bool & ok );
    value_type get( char const * name, bool & ok ) { return get(string(name),ok); }

    void print( ostream & s ) const;

    void
    set_unary_fun( char const * f_name, Func1 f_ptr )
    { unary_fun[f_name] = f_ptr; }

    void
    set_unary_fun( string const & f_name, Func1 f_ptr )
    { unary_fun[f_name] = f_ptr; }

    void
    set_binary_fun( char const * f_name, Func2 f_ptr )
    { binary_fun[f_name] = f_ptr; }

    void
    set_binary_fun( string const & f_name, Func2 f_ptr )
    { binary_fun[f_name] = f_ptr; }

    bool no_error() const { return No_Error == error_found; }

    map_real const & variables_map() const { return variables; }

    void
    variables_merge( map_real const & ee_vars ) {
      for ( map_real_const_iterator ii = ee_vars . begin();
            ii != ee_vars . end(); ++ii ) {
        variables[ii -> first] = ii -> second;
      }
    }

  private:

    value_type G0(void);
    value_type G1(void);
    value_type G2(void);
    value_type G3(void);
    value_type G4(void);
    value_type G5(void);
    void Next_Token(void);

  };

  template <typename T_type>
  inline
  void
  Calculator<T_type>::init() {
    last_evaluated      = 0;

    unary_fun["abs"]    = internal_abs;
    unary_fun["pos"]    = internal_pos;
    unary_fun["neg"]    = internal_neg;

    unary_fun["cos"]    = cos;
    unary_fun["sin"]    = sin;
    unary_fun["tan"]    = tan;

    unary_fun["acos"]   = acos;
    unary_fun["asin"]   = asin;
    unary_fun["atan"]   = atan;

    unary_fun["cosh"]   = cosh;
    unary_fun["sinh"]   = sinh;
    unary_fun["tanh"]   = tanh;

    unary_fun["exp"]    = exp;
    unary_fun["log"]    = log;
    unary_fun["log10"]  = log10;
    unary_fun["sqrt"]   = sqrt;
    unary_fun["ceil"]   = ceil;
    unary_fun["floor"]  = floor;

    binary_fun["atan2"] = atan2;
    binary_fun["pow"]   = pow;
    binary_fun["max"]   = internal_max;
    binary_fun["min"]   = internal_min;

    // predefined variables
    variables["pi"]     = 3.14159265358979323846;
    variables["e"]      = 2.71828182845904523536;
  }

  template <typename T_type>
  void
  Calculator<T_type>::set( string const & name, value_type val ) {
    int pos = name . find('@');
    if ( pos != string::npos ) {
      // split variable in 2
      string var1 = name . substr(0,pos);
      string var2 = name . substr(pos+1);

      map_real_const_iterator ii = variables . find(var2);
      if ( ii != variables . end() ) {
        // build variable
        to_string( ii -> second, var2 );
        var1 += var2;
        variables[var1] = val;
      } else {
        token_string = var2;
        throw Unknown_Variable;
      }
    } else {
      variables[name] = val;
    }
  }

  template <typename T_type>
  typename Calculator<T_type>::value_type
  Calculator<T_type>::get( string const & name, bool & ok ) {
    int pos = name . find('@');
    string var1, var2;
    if ( pos != string::npos ) {
      // split variable in 2
      var1 = name . substr(0,pos);
      var2 = name . substr(pos+1);
      map_real_const_iterator ii = variables . find(var2);
      if ( ii != variables . end() ) {
        // build variable
        to_string( ii -> second, var2 );
        var1 += var2;
      } else {
        token_string = var2;
        throw Unknown_Variable;
      }
    } else {
      var1 = name;
    }
    map_real_const_iterator ii = variables . find(var1);
    ok = ii != variables . end();
    if ( ok ) return ii -> second;
    else      return 0;
  }

  template <typename T_type>
  bool
  Calculator<T_type>::parse( char const * str ) {
    string_in = ptr = str;
    error_found = No_Error;
    try {
      do { Next_Token(); } while ( token_type == EndOfExpression );
      while ( token_type != EndOfString && error_found == No_Error ) {
        last_evaluated = G0();
        while ( token_type == EndOfExpression ) Next_Token();
      }
    }
    catch (ErrorCode err) {
      error_found = err;
    }
    catch (...) {
      error_found = Unknown_Error;
    }
    return error_found != No_Error;
  }

  template <typename T_type>
  void
  Calculator<T_type>::parse_file(
    char const * name,
    bool const   show_err,
    ostream &    stream_error
  ) {
    string        line;
    unsigned long nline = 0;
    ifstream      stream;
    stream . open(name);
    if ( stream.is_open() ) {
      while ( stream.good() ) {
        ++nline;
        getline( stream, line);
        if ( parse(line.c_str()) && show_err ) {
          stream_error << "in file '" << name
                       << "' on line " << nline
                       << " found an error\n";
          report_error(stream_error);
        }
      }
      stream . close();
    } else {
      if ( show_err ) {
        stream_error << "ERROR in opening file '" << name << "'\n";
      }
    }
  }

  template <typename T_type>
  void
  Calculator<T_type>::report_error( ostream & s ) {
    switch (error_found) {
    case No_Error:
      s << "No error found\n";
      return;

    case Divide_By_Zero:
      s << "divide by 0\n";
      break;

    case Expected_OpenPar:
      s << "expect ``('' found ``" << token_string << "''\n";
      break;

    case Expected_ClosePar:
      s << "expect ``)'' found ``" << token_string << "''\n";
      break;

    case Expected_Comma:
      s << "expect ``,'' found ``" << token_string << "''\n";
      break;

    case Unknown_Variable:
      s << "unknown variable: " << token_string << "\n";
      break;

    case Bad_Position:
      s << "bad position for token: ``" << token_string << "''\n";
      break;

    case Unknown_Error:
      s << "Unknown error for token: ``" << token_string << "''\n";
      break;
    }
    s << "\t: " << string_in << "\n"
      << "\t: " << setfill('-') << setw(pos()-1) << "^\n";
  }

  template <typename T_type>
  void
  Calculator<T_type>::print( ostream & s ) const {
    map_real_const_iterator ii;
    map_fun1_const_iterator f1;
    map_fun2_const_iterator f2;

    s << "\nUNARY FUNCTIONS\n";
    for ( f1 = unary_fun . begin(); f1 != unary_fun . end(); ++f1 )
      s << f1 -> first << ", ";

    s << "\n\nBINARY FUNCTIONS\n";
    for ( f2 = binary_fun . begin(); f2 != binary_fun . end(); ++f2 )
      s << f2 -> first << ", ";

    s << "\n\nVARIABLES\n";
    for ( ii = variables . begin(); ii != variables . end(); ++ii )
      s << ii -> first << " = " << ii -> second << "\n";

    s << "END LIST\n";
  }

  template <typename T_type>
  typename Calculator<T_type>::value_type
  Calculator<T_type>::G0() {

    if ( token_type == EndOfExpression || token_type == EndOfString ) return 0;

    char const * const bf_ptr = ptr; // save pointer
    string     name  = token_string;
    Token_Type token = token_type;

    if ( token_type == Variable ) {
      Next_Token();
      if ( token_type == Assign ) { // handle assign
        Next_Token();              // eat assign
        value_type res = G1();
        set(name,res);
        return res; // assign value
      }
    }

    ptr = bf_ptr; // restore pointer
    token_string = name;
    token_type   = token;

    return G1(); // handle immediate evaluation
  }

  // handle binary + and -
  template <typename T_type>
  typename Calculator<T_type>::value_type
  Calculator<T_type>::G1() {
    value_type lval = G2(), rval;
    while ( token_type == Plus || token_type == Minus ) {
      bool do_plus = token_type == Plus;
      Next_Token();
      rval = G2();
      if ( do_plus ) lval += rval;
      else           lval -= rval;
    };
    return lval;
  }

  // handles * and /
  template <typename T_type>
  typename Calculator<T_type>::value_type
  Calculator<T_type>::G2() {
    value_type lval = G3(), rval;
    while ( token_type == Times || token_type == Divide ) {
      bool do_times = token_type == Times;
      Next_Token();
      rval = G3();
      if ( do_times ) lval *= rval;
      else {
        if ( rval == 0 ) throw Divide_By_Zero;
        lval /= rval;
      }
    };
    return lval;
  }

  // handles ^ operator
  template <typename T_type>
  typename Calculator<T_type>::value_type
  Calculator<T_type>::G3() {
    value_type lval = G4();
    if ( token_type == Power )
      { Next_Token(); lval = pow(lval,G4()); }
    return lval;
  }

  // handles any unary + or - signs
  template <typename T_type>
  typename Calculator<T_type>::value_type
  Calculator<T_type>::G4() {
    if ( token_type == Minus ) { Next_Token(); return - G5(); }
    if ( token_type == Plus  ) Next_Token();
    return G5();
  }

  // handles numbers, variables, functions and parentesis
  template <typename T_type>
  typename Calculator<T_type>::value_type
  Calculator<T_type>::G5() {
    if ( token_type == OpenPar ) { // handle ( ... )
      Next_Token(); // eat (
      value_type val = G0();
      if ( token_type != ClosePar ) throw Expected_ClosePar;
      Next_Token(); // eat )
      return val;
    }

    if ( token_type == Number ) {
      value_type val;
      get_number(val,token_string);
      Next_Token();
      return val;
    }

    if ( token_type == Variable ) {

      bool ok;
      value_type res = get( token_string, ok );
      if ( ok ) {
        Next_Token();
        return res;
      }

      map_fun1_iterator f1 = unary_fun . find(token_string);
      if ( f1 != unary_fun . end() ) {
        Next_Token(); // expect (
        if ( token_type != OpenPar ) throw Expected_OpenPar;
        Next_Token(); // eat (
        value_type v1 = G0();
        if ( token_type != ClosePar ) throw Expected_ClosePar;
        Next_Token(); // eat )
        return f1 -> second(v1);
      }

      map_fun2_iterator f2 = binary_fun . find(token_string);
      if ( f2 != binary_fun . end() ) {
        Next_Token(); // expect (
        if ( token_type != OpenPar ) throw Expected_OpenPar;
        Next_Token(); // eat (
        value_type v1 = G0();
        if ( token_type != Comma ) throw Expected_Comma;
        Next_Token(); // eat ,
        value_type v2 = G0();
        if ( token_type != ClosePar ) throw Expected_ClosePar;
        Next_Token(); // eat )
        return f2 -> second(v1,v2);
      }

      throw Unknown_Variable;
    }
    throw Bad_Position;
  }

  template <typename T_type>
  void
  Calculator<T_type>::Next_Token() { // eat separators
    token_type   = EndOfExpression;
    token_string = "";

    // EAT SEPARATORS
    while ( isspace(see()) ) get();

    // SKIP COMMENTS
    if ( see() == '#' ) {
      while ( see() != '\n' && see() != '\0' ) get();
      return;
    }

    if ( isalpha(see()) ) {
      token_type = Variable;
      do { token_string += get(); } while ( isalnum(see()) || see() == '_' || see() == '@' );
      return;
    }

    if ( isdigit(see()) ) {
      token_type = Number;
      do { token_string += get(); } while ( isdigit(see()) );
      if ( see() == '.' ) {
        do { token_string += get(); } while ( isdigit(see()) );
      }
      if ( see() == 'e' || see() == 'E' ) {
        token_string += get();
        if ( see() == '+' || see() == '-' ) token_string += get();
        if ( isdigit(see()) ) {
          do { token_string += get(); } while ( isdigit(see()) );
        } else {
          token_type = Unrecognized;
        }
      }
      return;
    }

    token_string = get();
    switch (token_string[0]) {
    case '+' : token_type = Plus;
               break;
    case '-' : token_type = Minus;
               break;
    case '*' : token_type = Times;
               break;
    case '/' : token_type = Divide;
               break;
    case '^' : token_type = Power;
               break;
    case '(' : token_type = OpenPar;
               break;
    case ')' : token_type = ClosePar;
               break;
    case '=' : token_type = Assign;
               break;
    case ',' : token_type = Comma;
               break;
    case '\0': token_type   = EndOfString;
               token_string = "EndOfString";
               break;
    case ';' : token_type   = EndOfExpression;
               token_string = "EndOfExpression";
               break;
    default  : token_type   = Unrecognized;
               token_string = "Unrecognized";
               break;
    }
  }

  // end class Calculator

} // end namespace


namespace calc_load {
  using calc_defs::Calculator;
}

#endif

// end of file: calc.hh