blob: 2127179fac2c2e5617388b89389e1d5a55f2534b [file] [log] [blame]
%{
#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;
char* name;
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;
#define div unitsdiv
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 0xd7:
return 0x2a;
case 0xf7:
return 0x2f;
case 0xb9:
case 0x2071:
yylval.numb = 1;
return SUP;
case 0xb2:
case 0x2072:
yylval.numb = 2;
return SUP;
case 0xb3:
case 0x2073:
yylval.numb = 3;
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 = fmtcharstod(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 = unsharp("#9/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 0xb9:
case 0x2071:
case 0xb2:
case 0x2072:
case 0xb3:
case 0x2073:
case 0xd7:
case 0xf7:
return 0;
}
return 1;
}
int
gdigit(void *v)
{
int c;
USED(v);
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;
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, "yocto", 0,
1e-21, "zepto", 0,
1e-18, "atto", 0,
1e-15, "femto", 0,
1e-12, "pico", 0,
1e-9, "nano", 0,
1e-6, "micro", 0,
1e-6, "μ", 0,
1e-3, "milli", 0,
1e-2, "centi", 0,
1e-1, "deci", 0,
1e1, "deka", 0,
1e2, "hecta", 0,
1e2, "hecto", 0,
1e3, "kilo", 0,
1e6, "mega", 0,
1e6, "meg", 0,
1e9, "giga", 0,
1e12, "tera", 0,
1e15, "peta", 0,
1e18, "exa", 0,
1e21, "zetta", 0,
1e24, "yotta", 0,
0, 0, 0,
};
double
pname(void)
{
Rune *p;
int i, j, c;
/*
* rip off normal prefixs
*/
if(prefix[0].pname == nil){
for(i=0; prefix[i].name; i++)
prefix[i].pname = runesmprint("%s", prefix[i].name);
}
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;
}