/* Unit based calculator */

%{
   #define YYSTYPE UnitMeasure
   #include <math.h>
   #include <ctype.h>
   #include <string.h>
   #include <stdio.h>

   #define MAX(x,y) ((x) > (y) ? (x) : (y))
   #define MIN(x,y) ((x) < (y) ? (x) : (y))

   typedef struct _unitmeasure UnitMeasure;

   struct _unitmeasure {
     double val;
     int    unit;
     int    dim;
   };

   double unit_factors[] = { 1.0, 1.0,  100, 1000,   10, 39.37, 3.281,};
   char    *unit_names[] = { "",  "m", "cm", "mm", "dm",  "in",  "ft",};
   
   void          unitprint  (UnitMeasure arg);
   UnitMeasure   unitsum    (UnitMeasure arg1, UnitMeasure arg2);
   UnitMeasure   unitnegate (UnitMeasure arg1);
   UnitMeasure   unitmul    (UnitMeasure arg1, UnitMeasure arg2);
   UnitMeasure   unitdiv    (UnitMeasure arg1, UnitMeasure arg2);

   int           yyerror    (char *s);
   int           yylex      (void);
%}

%token NUM
%token UNIT
%token ERR

%% /* Grammar rules and actions follow */

input:    /* empty */
        | input line
        ;

line:     '\n'
        | term '\n'         { unitprint ($1); }
        ;

term:     expr              { $$ = $1; }
        | expr '+' term     { $$ = unitsum ($1, $3); }
        | expr '-' term     { $$ = unitsum ($1, unitnegate ($3)); }
        ;

expr:     factor            { $$ = $1 ; }
        | factor  '*' expr  { $$ = unitmul ($1, $3); }
        | factor  '/' expr  { $$ = unitdiv ($1, $3); }
        ;

factor:   UNIT              { $$.val = 1.0; $$.unit = $1.unit; $$.dim = 1; }
        | NUM               { $$.val = $1.val; $$.unit = 0; $$.dim = 0; }
        | NUM UNIT          { $$.val = $1.val; $$.unit = $2.unit; $$.dim = 1; }
        | '-' NUM           { $$ = unitnegate ($1); $$.unit = 0; $$.dim = 0; }
        | '-' NUM UNIT      { $$ = unitnegate ($1); $$.unit = $3.unit; $$.dim = 1; }
        | '(' term ')'      { $$ = $2; }
        ;

%%


void
unitprint (UnitMeasure arg)
{
  switch (arg.dim)
    {
      case 0:
        printf ("Result: %g\n", arg.val);
        break;
      case 1:
        printf ("Result: %g %s\n", arg.val, unit_names [arg.unit]);
        break;
      default:
        printf ("Result: %g %s^%d\n", arg.val, unit_names [arg.unit], arg.dim);
        break;
    }
}


UnitMeasure
unitsum (UnitMeasure arg1, UnitMeasure arg2)
{
  UnitMeasure ret;

  if (arg1.dim != arg2.dim && arg1.dim * arg2.dim != 0)
    {
      fprintf (stderr, "dimension mismatch!\n");
      arg2.dim = MAX (arg1.dim, arg2.dim);
    }

  /* dimensionless terms inherit the dimension from the other operand */
  if (arg1.dim == 0)
    {
      arg1.dim  = arg2.dim;
      arg1.unit = arg2.unit;
    }

  if (arg2.dim == 0)
    {
      arg2.dim  = arg1.dim;
      arg2.unit = arg1.unit;
    }

  ret.unit = arg1.unit;
  ret.val  = arg1.val + arg2.val * pow (unit_factors[arg1.unit] / unit_factors[arg2.unit], arg1.dim);
  ret.dim  = arg1.dim;

  return ret;
}


UnitMeasure
unitnegate (UnitMeasure arg1)
{
  UnitMeasure ret;

  ret.unit =   arg1.unit;
  ret.val  = - arg1.val;
  ret.dim  =   arg1.dim;

  return ret;
}


UnitMeasure
unitmul (UnitMeasure arg1, UnitMeasure arg2)
{
  UnitMeasure ret;

  /* inherit the unit for dimensionless values */
  if (arg1.dim == 0)
    arg1.unit = arg2.unit;

  if (arg2.dim == 0)
    arg2.unit = arg1.unit;

  ret.unit = arg1.unit ? arg1.unit : arg2.unit;
  ret.val  = arg1.val * arg2.val * pow (unit_factors[arg1.unit] / unit_factors[arg2.unit], arg2.dim);
  ret.dim  = arg1.dim + arg2.dim;

  return ret;
}


UnitMeasure
unitdiv (UnitMeasure arg1, UnitMeasure arg2)
{
  if (arg2.val == 0)
    {
      fprintf (stderr, "division by zero!\n");
      return arg1;
    }
  
  arg2.val = 1.0 / arg2.val;
  arg2.dim *= -1;

  return unitmul (arg1, arg2);
}


/* Lexical analyzer scans for units or for numbers and returns them.
 * and the token NUM, or the ASCII character read if not a number. Skips
 * all blanks and tabs, returns 0 for EOF. */

int
yylex (void)
{
  int i, c;
  char unitname[10]; 

  while ((c = getchar ()) == ' ' || c == '\t'); /* skip white space */

  if (c == '.' || isdigit (c))
    { /* process numbers */
      ungetc (c, stdin);
      scanf ("%lf", &yylval.val);
      return NUM;
    }

  if (isalpha (c))
    {
      i = 0;
      do
        {
          unitname[i] = c;
          i++;
          c = getchar ();
        }
      while (i < 9 && isalpha (c));
      unitname[i] = '\0';
      ungetc (c, stdin);

      for (i = 1; i < 7; i++)
        {
          if (!strcmp (unitname, unit_names[i]))
	    {
              yylval.unit = i;
              return UNIT;
	    }
	}
     
      return ERR;
    }

  if (c == EOF)
    {
      return 0; /* return end-of-file */
    }

  return c; /* return single chars */
}


int
yyerror (char *s)
{
   printf("%s\n", s);
   return 1;
}


int
main (int argc, char *argv[])
{
  yyparse ();
  return 0;
}


