/* Unit based calculator */ %{ #define YYSTYPE UnitMeasure #include #include #include #include #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; }