Skip to content

Example: Calculator with Complex Numbers

Ronald Franco edited this page Dec 21, 2018 · 1 revision

This example exemplifies the ability to use flow variables that are not of a primitive type, in this case complex numbers. The example employs the FlexTokenizer class to tokenize input. To enable flow variables to be complex numbers, a ComplexNum class was created. The example overrides the insertion operator of the TokenStream class to extract and store the semantic value of a given token, representing a complex number, in an out-flow variable properly.

The ComplexNum class overrides the arithmetic operators and they are used on ComplexNum flow variables in the semantic actions of the AFG. The result is a clean looking AFG that isn’t cluttered with complicated semantic actions.

Flex Specification:

%{
#define NUM 2
%}

num    [0-9]*([0-9]|\.[0-9]|[0-9]\.)[0-9]*
%option noyywrap

%%
{num}j?         { return NUM; }
[-+*/=()\n]     { return *yytext; }
.               /* do nothing */
%%

Driver Program:

#include <iostream>
#include <cstring>
#include <cmath>
#include <sstream>

#include "parser.h"
#include "flextokenizer.h"
#include "tokenstream.h"

#define NUM_TOK_CODE 2

class ComplexNum {
 
  friend std::ostream& operator<<(std::ostream & out, ComplexNum a)
  {
    out << a.real;
    if (a.imag < 0)
      out << " - ";
    else if (a.imag > 0)
      out << " + ";
    if (a.imag != 0)
      out << std::abs(a.imag) << "j";
    return out;
  }

  public:
    /* constructors */
    ComplexNum()
      : 
        real(0),
        imag(0)
    { }
    ComplexNum(float r, float i = 0) 
      : 
        real(r),
        imag(i)
    { }
    /* arithmetic operator overloads */
    ComplexNum operator*(ComplexNum & arg)
    {
      ComplexNum result;
      result.real = (real * arg.real) + (-1 * imag * arg.imag);
      result.imag = (real * arg.imag) + (imag * arg.real);
      return result;
    }
    ComplexNum operator*=(ComplexNum & arg)
    {
      *this = *this * arg;
      return *this;
    }
    ComplexNum operator/(ComplexNum & arg)
    {
      ComplexNum result;
      result.real = (real * arg.real + imag * arg.imag) 
        / (arg.real * arg.real + arg.imag * arg.imag * -1);
      result.imag = (imag * arg.real - real * arg.imag)
			  / (arg.real * arg.real + arg.imag * arg.imag * -1);
      return result;
    }
    ComplexNum operator/=(ComplexNum & arg)
    {
      *this = *this / arg;
      return *this;
    }
    ComplexNum operator+(ComplexNum & arg)
    {
      ComplexNum result;
      result.real = real + arg.real;
      result.imag = imag + arg.imag;
      return result;
    }
    ComplexNum operator+=(ComplexNum & arg)
    {
      *this = *this + arg;
      return *this;
    }
    ComplexNum operator-(ComplexNum & arg)
    {
      ComplexNum result;
      result.real = real - arg.real;
      result.imag = imag - arg.imag;
      return result;
    }
    ComplexNum operator-=(ComplexNum & arg)
    {
      *this = *this - arg;
      return *this;
    }
    /* member data */
    double real;
    double imag;
};

/* overload extraction operator of TokenStream class to allow the semantic
 * values of tokens to be properly stored into ComplexNum flow variables */
TokenStream<ComplexNum> & operator>>(TokenStream<ComplexNum> & is, ComplexNum & out)
{
  std::string tok = is.get_text();

  if (tok.back() == 'j')
  {
    /* token is imaginary */
    tok.pop_back();
    std::stringstream tok_stream(tok);
    out.real = 0;
    tok_stream >> out.imag;
  } else {
    /* token is real */
    std::stringstream tok_stream(tok);
    tok_stream >> out.real;
    out.imag = 0;
  }
  
  return is;
}

int main()
{
  /* complex number calculator example */

  /* create nonterminals line, expr, fact, and term. create terminal num */
  Parser<ComplexNum> line, expr, fact, term, num(NUM_TOK_CODE);

  /* create ComplexNum flow variables */
  ComplexNum a, b;
 
  /* AFG */ 
  line>>a = expr>>a & '\n';
  
  expr>>a = term>>a & *( '+' & term>>b & [&]{ a += b; } 
      | '-' & term>>b & [&]{ a -= b; });
  
  term>>a = fact>>a & *( '*' & fact>>b & [&]{ a *= b; } 
      | '/' & fact>>b & [&]{ a /= b; } );
  
  fact>>a = '(' & expr>>a & ')' | num>>a;

  /* FlexTokenizer will use stdin */
  FlexTokenizer tokens;

  /* maintain current position */
  size_t pos = 0;

  while (true)
  {
    /* user prompt */
    std::cout << "============================================================";
    std::cout << "\nA calculator that supports:";
    std::cout << "\n\t- integers, doubles, and complex numbers";
    std::cout << "\n\t- +, -, *, /";
    std::cout << "\n\t- parenthesis";
    std::cout << "\nFormat: <expression>";
    std::cout << "\nExamples:\n\t1) 4 + 3j * 6\n\t2) (5 + 2j) * (2 - 3j)\n";
    std::cout << "============================================================";

    std::cout << "\nGive me a mathematical expression.";
    std::cout << "\nCtrl + C to quit: ";

    /* begin parsing */
    if (line.parse(&tokens,&pos))
    {
      std::cout << "Expression computed succesfully!\nResult: " << a << "\n" << std::endl;
      a = ComplexNum();
    }
    else
    {
      std::cout << "Expression computation failed\n" << std::endl;
    }

    /* clear input */
    tokens.clear();
    pos = 0;
  }
  return 0;
}
Clone this wiki locally