%{ #include #include #include 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 prog expr expr0 expr1 expr2 expr3 expr4 %token VAL %token VAR %token 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) { 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 = $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; idim[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; idim[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; idim[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; idim[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; idim[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; idim[i]; if(d > 0) printdim(str, i, d); else if(d < 0) f = 1; } if(f) { strcat(str, " /"); for(i=1; idim[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; }