plan9fox/sys/src/cmd/units.y
2012-02-23 16:58:53 +01:00

823 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%{
#include <u.h>
#include <libc.h>
#include <bio.h>
enum
{
Ndim = 15, /* number of dimensions */
Nsym = 40, /* size of a name */
Nvar = 203, /* hash table size */
Maxe = 695, /* log of largest number */
};
typedef struct Var Var;
typedef struct Node Node;
typedef struct Prefix Prefix;
struct Node
{
double val;
schar dim[Ndim];
};
struct Var
{
Rune name[Nsym];
Node node;
Var* link;
};
struct Prefix
{
double val;
Rune* pname;
};
char buf[100];
int digval;
Biobuf* fi;
Biobuf linebuf;
Var* fund[Ndim];
Rune line[1000];
ulong lineno;
int linep;
int nerrors;
Node one;
int peekrune;
Node retnode1;
Node retnode2;
Node retnode;
Rune sym[Nsym];
Var* vars[Nvar];
int vflag;
extern void add(Node*, Node*, Node*);
extern void div(Node*, Node*, Node*);
extern int specialcase(Node*, Node*, Node*);
extern double fadd(double, double);
extern double fdiv(double, double);
extern double fmul(double, double);
extern int gdigit(void*);
extern Var* lookup(int);
extern void main(int, char*[]);
extern void mul(Node*, Node*, Node*);
extern void ofile(void);
extern double pname(void);
extern void printdim(char*, int, int);
extern int ralpha(int);
extern int readline(void);
extern void sub(Node*, Node*, Node*);
extern int Ufmt(Fmt*);
extern void xpn(Node*, Node*, int);
extern void yyerror(char*, ...);
extern int yylex(void);
extern int yyparse(void);
typedef Node* indnode;
#pragma varargck type "U" indnode
%}
%union
{
Node node;
Var* var;
int numb;
double val;
}
%type <node> prog expr expr0 expr1 expr2 expr3 expr4
%token <val> VAL
%token <var> VAR
%token <numb> SUP
%%
prog:
':' VAR expr
{
int f;
f = $2->node.dim[0];
$2->node = $3;
$2->node.dim[0] = 1;
if(f)
yyerror("redefinition of %S", $2->name);
else
if(vflag)
print("%S\t%U\n", $2->name, &$2->node);
}
| ':' VAR '#'
{
int f, i;
for(i=1; i<Ndim; i++)
if(fund[i] == 0)
break;
if(i >= Ndim) {
yyerror("too many dimensions");
i = Ndim-1;
}
fund[i] = $2;
f = $2->node.dim[0];
$2->node = one;
$2->node.dim[0] = 1;
$2->node.dim[i] = 1;
if(f)
yyerror("redefinition of %S", $2->name);
else
if(vflag)
print("%S\t#\n", $2->name);
}
| '?' expr
{
retnode1 = $2;
}
| '?'
{
retnode1 = one;
}
expr:
expr4
| expr '+' expr4
{
add(&$$, &$1, &$3);
}
| expr '-' expr4
{
sub(&$$, &$1, &$3);
}
expr4:
expr3
| expr4 '*' expr3
{
mul(&$$, &$1, &$3);
}
| expr4 '/' expr3
{
div(&$$, &$1, &$3);
}
expr3:
expr2
| expr3 expr2
{
mul(&$$, &$1, &$2);
}
expr2:
expr1
| expr2 SUP
{
xpn(&$$, &$1, $2);
}
| expr2 '^' expr1
{
int i;
for(i=1; i<Ndim; i++)
if($3.dim[i]) {
yyerror("exponent has units");
$$ = $1;
break;
}
if(i >= Ndim) {
i = $3.val;
if(i != $3.val)
yyerror("exponent not integral");
xpn(&$$, &$1, i);
}
}
expr1:
expr0
| expr1 '|' expr0
{
div(&$$, &$1, &$3);
}
expr0:
VAR
{
if($1->node.dim[0] == 0) {
yyerror("undefined %S", $1->name);
$$ = one;
} else
$$ = $1->node;
}
| VAL
{
$$ = one;
$$.val = $1;
}
| '(' expr ')'
{
$$ = $2;
}
%%
int
yylex(void)
{
int c, i;
c = peekrune;
peekrune = ' ';
loop:
if((c >= '0' && c <= '9') || c == '.')
goto numb;
if(ralpha(c))
goto alpha;
switch(c) {
case ' ':
case '\t':
c = line[linep++];
goto loop;
case L'×':
return '*';
case L'÷':
return '/';
case L'¹':
yylval.numb = 1;
return SUP;
case L'²':
yylval.numb = 2;
return SUP;
case L'³':
yylval.numb = 3;
return SUP;
case L'⁴':
yylval.numb = 4;
return SUP;
case L'⁵':
yylval.numb = 5;
return SUP;
case L'⁶':
yylval.numb = 6;
return SUP;
case L'⁷':
yylval.numb = 7;
return SUP;
case L'⁸':
yylval.numb = 8;
return SUP;
case L'⁹':
yylval.numb = 9;
return SUP;
}
return c;
alpha:
memset(sym, 0, sizeof(sym));
for(i=0;; i++) {
if(i < nelem(sym))
sym[i] = c;
c = line[linep++];
if(!ralpha(c))
break;
}
sym[nelem(sym)-1] = 0;
peekrune = c;
yylval.var = lookup(0);
return VAR;
numb:
digval = c;
yylval.val = charstod(gdigit, 0);
return VAL;
}
void
main(int argc, char *argv[])
{
char *file;
ARGBEGIN {
default:
print("usage: units [-v] [file]\n");
exits("usage");
case 'v':
vflag = 1;
break;
} ARGEND
file = "/lib/units";
if(argc > 0)
file = argv[0];
fi = Bopen(file, OREAD);
if(fi == 0) {
print("cant open: %s\n", file);
exits("open");
}
fmtinstall('U', Ufmt);
one.val = 1;
/*
* read the 'units' file to
* develope a database
*/
lineno = 0;
for(;;) {
lineno++;
if(readline())
break;
if(line[0] == 0 || line[0] == '/')
continue;
peekrune = ':';
yyparse();
}
/*
* read the console to
* print ratio of pairs
*/
Bterm(fi);
fi = &linebuf;
Binit(fi, 0, OREAD);
lineno = 0;
for(;;) {
if(lineno & 1)
print("you want: ");
else
print("you have: ");
if(readline())
break;
peekrune = '?';
nerrors = 0;
yyparse();
if(nerrors)
continue;
if(lineno & 1) {
if(specialcase(&retnode, &retnode2, &retnode1))
print("\tis %U\n", &retnode);
else {
div(&retnode, &retnode2, &retnode1);
print("\t* %U\n", &retnode);
div(&retnode, &retnode1, &retnode2);
print("\t/ %U\n", &retnode);
}
} else
retnode2 = retnode1;
lineno++;
}
print("\n");
exits(0);
}
/*
* all characters that have some
* meaning. rest are usable as names
*/
int
ralpha(int c)
{
switch(c) {
case 0:
case '+':
case '-':
case '*':
case '/':
case '[':
case ']':
case '(':
case ')':
case '^':
case ':':
case '?':
case ' ':
case '\t':
case '.':
case '|':
case '#':
case L'¹':
case L'²':
case L'³':
case L'⁴':
case L'⁵':
case L'⁶':
case L'⁷':
case L'⁸':
case L'⁹':
case L'×':
case L'÷':
return 0;
}
return 1;
}
int
gdigit(void*)
{
int c;
c = digval;
if(c) {
digval = 0;
return c;
}
c = line[linep++];
peekrune = c;
return c;
}
void
yyerror(char *fmt, ...)
{
va_list arg;
/*
* hack to intercept message from yaccpar
*/
if(strcmp(fmt, "syntax error") == 0) {
yyerror("syntax error, last name: %S", sym);
return;
}
va_start(arg, fmt);
vseprint(buf, buf+sizeof(buf), fmt, arg);
va_end(arg);
print("%ld: %S\n\t%s\n", lineno, line, buf);
nerrors++;
if(nerrors > 5) {
print("too many errors\n");
exits("errors");
}
}
void
add(Node *c, Node *a, Node *b)
{
int i, d;
for(i=0; i<Ndim; i++) {
d = a->dim[i];
c->dim[i] = d;
if(d != b->dim[i])
yyerror("add must be like units");
}
c->val = fadd(a->val, b->val);
}
void
sub(Node *c, Node *a, Node *b)
{
int i, d;
for(i=0; i<Ndim; i++) {
d = a->dim[i];
c->dim[i] = d;
if(d != b->dim[i])
yyerror("sub must be like units");
}
c->val = fadd(a->val, -b->val);
}
void
mul(Node *c, Node *a, Node *b)
{
int i;
for(i=0; i<Ndim; i++)
c->dim[i] = a->dim[i] + b->dim[i];
c->val = fmul(a->val, b->val);
}
void
div(Node *c, Node *a, Node *b)
{
int i;
for(i=0; i<Ndim; i++)
c->dim[i] = a->dim[i] - b->dim[i];
c->val = fdiv(a->val, b->val);
}
void
xpn(Node *c, Node *a, int b)
{
int i;
*c = one;
if(b < 0) {
b = -b;
for(i=0; i<b; i++)
div(c, c, a);
} else
for(i=0; i<b; i++)
mul(c, c, a);
}
int
specialcase(Node *c, Node *a, Node *b)
{
int i, d, d1, d2;
d1 = 0;
d2 = 0;
for(i=1; i<Ndim; i++) {
d = a->dim[i];
if(d) {
if(d != 1 || d1)
return 0;
d1 = i;
}
d = b->dim[i];
if(d) {
if(d != 1 || d2)
return 0;
d2 = i;
}
}
if(d1 == 0 || d2 == 0)
return 0;
if(memcmp(fund[d1]->name, L"°C", 3*sizeof(Rune)) == 0 &&
memcmp(fund[d2]->name, L"°F", 3*sizeof(Rune)) == 0 &&
b->val == 1) {
memcpy(c->dim, b->dim, sizeof(c->dim));
c->val = a->val * 9. / 5. + 32.;
return 1;
}
if(memcmp(fund[d1]->name, L"°F", 3*sizeof(Rune)) == 0 &&
memcmp(fund[d2]->name, L"°C", 3*sizeof(Rune)) == 0 &&
b->val == 1) {
memcpy(c->dim, b->dim, sizeof(c->dim));
c->val = (a->val - 32.) * 5. / 9.;
return 1;
}
return 0;
}
void
printdim(char *str, int d, int n)
{
Var *v;
if(n) {
v = fund[d];
if(v)
sprint(strchr(str, 0), " %S", v->name);
else
sprint(strchr(str, 0), " [%d]", d);
switch(n) {
case 1:
break;
case 2:
strcat(str, "²");
break;
case 3:
strcat(str, "³");
break;
case 4:
strcat(str, "⁴");
break;
case 5:
strcat(str, "⁵");
break;
case 6:
strcat(str, "⁶");
break;
case 7:
strcat(str, "⁷");
break;
case 8:
strcat(str, "⁸");
break;
case 9:
strcat(str, "⁹");
break;
default:
sprint(strchr(str, 0), "^%d", n);
}
}
}
int
Ufmt(Fmt *fp)
{
char str[200];
Node *n;
int f, i, d;
n = va_arg(fp->args, Node*);
sprint(str, "%g", n->val);
f = 0;
for(i=1; i<Ndim; i++) {
d = n->dim[i];
if(d > 0)
printdim(str, i, d);
else
if(d < 0)
f = 1;
}
if(f) {
strcat(str, " /");
for(i=1; i<Ndim; i++) {
d = n->dim[i];
if(d < 0)
printdim(str, i, -d);
}
}
return fmtstrcpy(fp, str);
}
int
readline(void)
{
int i, c;
linep = 0;
for(i=0;; i++) {
c = Bgetrune(fi);
if(c < 0)
return 1;
if(c == '\n')
break;
if(i < nelem(line))
line[i] = c;
}
if(i >= nelem(line))
i = nelem(line)-1;
line[i] = 0;
return 0;
}
Var*
lookup(int f)
{
int i;
Var *v, *w;
double p;
ulong h;
h = 0;
for(i=0; sym[i]; i++)
h = h*13 + sym[i];
h %= nelem(vars);
for(v=vars[h]; v; v=v->link)
if(memcmp(sym, v->name, sizeof(sym)) == 0)
return v;
if(f)
return 0;
v = malloc(sizeof(*v));
if(v == nil) {
fprint(2, "out of memory\n");
exits("mem");
}
memset(v, 0, sizeof(*v));
memcpy(v->name, sym, sizeof(sym));
v->link = vars[h];
vars[h] = v;
p = 1;
for(;;) {
p = fmul(p, pname());
if(p == 0)
break;
w = lookup(1);
if(w) {
v->node = w->node;
v->node.val = fmul(v->node.val, p);
break;
}
}
return v;
}
Prefix prefix[] =
{
1e-24, L"yocto",
1e-21, L"zepto",
1e-18, L"atto",
1e-15, L"femto",
1e-12, L"pico",
1e-9, L"nano",
1e-6, L"micro",
1e-6, L"μ",
1e-3, L"milli",
1e-2, L"centi",
1e-1, L"deci",
1e1, L"deka",
1e2, L"hecta",
1e2, L"hecto",
1e3, L"kilo",
1e6, L"mega",
1e6, L"meg",
1e9, L"giga",
1e12, L"tera",
1e15, L"peta",
1e18, L"exa",
1e21, L"zetta",
1e24, L"yotta",
0, 0
};
double
pname(void)
{
Rune *p;
int i, j, c;
/*
* rip off normal prefixs
*/
for(i=0; p=prefix[i].pname; i++) {
for(j=0; c=p[j]; j++)
if(c != sym[j])
goto no;
memmove(sym, sym+j, (Nsym-j)*sizeof(*sym));
memset(sym+(Nsym-j), 0, j*sizeof(*sym));
return prefix[i].val;
no:;
}
/*
* rip off 's' suffixes
*/
for(j=0; sym[j]; j++)
;
j--;
/* j>1 is special hack to disallow ms finding m */
if(j > 1 && sym[j] == 's') {
sym[j] = 0;
return 1;
}
return 0;
}
/*
* careful floating point
*/
double
fmul(double a, double b)
{
double l;
if(a <= 0) {
if(a == 0)
return 0;
l = log(-a);
} else
l = log(a);
if(b <= 0) {
if(b == 0)
return 0;
l += log(-b);
} else
l += log(b);
if(l > Maxe) {
yyerror("overflow in multiply");
return 1;
}
if(l < -Maxe) {
yyerror("underflow in multiply");
return 0;
}
return a*b;
}
double
fdiv(double a, double b)
{
double l;
if(a <= 0) {
if(a == 0)
return 0;
l = log(-a);
} else
l = log(a);
if(b <= 0) {
if(b == 0) {
yyerror("division by zero");
return 1;
}
l -= log(-b);
} else
l -= log(b);
if(l > Maxe) {
yyerror("overflow in divide");
return 1;
}
if(l < -Maxe) {
yyerror("underflow in divide");
return 0;
}
return a/b;
}
double
fadd(double a, double b)
{
return a + b;
}