V10/cmd/units/units.y
/*% mkcdate >cdate.h && yacc % && cyntax y.tab.c && cc -go # y.tab.c
* New units
*/
%{
#define NDIM 11
#define NUNIT 700
#define NSTRBUF 8192
struct unit{
char *name;
double coef;
int dim[NDIM];
}unit[NUNIT];
struct unit *dim[NDIM];
char strbuf[NSTRBUF];
char *strp=strbuf;
struct unit mul(), div(), pwr(), prim();
%}
%term NUMBER NAME SQUARE CUBE
%type<u> u unit NUMBER NAME
%union{
struct unit u;
}
%left SQUARE CUBE
%left '^'
%%
unit: {static struct unit u={0,1}; $$=u;}
| unit u {$$=mul($1, $2);}
| unit '/' u {$$=div($1, $3);}
u: NUMBER
| NAME
| '@' NUMBER {$$=prim($2);}
| '(' unit ')' {$$=$2;}
| SQUARE u {$$=pwr($2, 2.);}
| CUBE u {$$=pwr($2, 3.);}
| u '^' NUMBER {$$=pwr($1, $3.coef);}
%%
#include <stdio.h>
#include <math.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "cdate.h"
char *strchr();
char *lp, *line;
digit(c){ return '0'<=c && c<='9'; }
char idchar[256];
int nerror;
idcharfn(c){
switch(c){
case '\0':
case '*':
case '/':
case '@':
case '(':
case ')':
case '^':
case ' ':
case '\t':
case '\n':
return 0;
default:
return 1;
}
}
struct prefix{
int len;
double coef;
char *name;
}prefix[]={
0, 1e18, "exa",
0, 1e15, "peta",
0, 1e12, "tera",
0, 1e9, "giga",
0, 1e6, "mega",
0, 1e6, "meg",
0, 1e4, "myria",
0, 1e3, "kilo",
0, 1e2, "hekta",
0, 1e2, "hekto",
0, 1e1, "deka",
0, 1.5, "sesqui",
0, .5, "hemi",
0, .5, "demi",
0, .5, "semi",
0, 1e-1, "deci",
0, 1e-2, "centi",
0, 1e-3, "milli",
0, 1e-6, "micro",
0, 1e-9, "nano",
0, 1e-12, "pico",
0, 1e-15, "femto",
0, 1e-18, "atto",
/* Are these a good idea? */
0, 1e9, "G",
0, 1e6, "M",
0, 1e3, "k",
0, 1e-3, "m",
0, 1e-6, "u",
0, 1e-9, "n",
0, 1e-12, "p",
0, 0, 0
};
hash(s)
register char *s;
{
register i, j;
i=0;
for(j=0;*s;j++)
i+=*s++*j;
i%=NUNIT;
return i<0?i+NUNIT:i;
}
/*
* symbol table lookup subroutine: look for the name. If not found,
* try stripping prefixes.
*/
struct unit *look2(name)
char *name;
{
register char *s=name;
register i, j;
register struct prefix *p;
double coef=1;
static struct unit mul;
do{
i=j=hash(s);
do{
if(unit[j].name==0)
break;
if(strcmp(s, unit[j].name)==0){
if(coef==1)
return unit+j;
mul=unit[j];
mul.coef*=coef;
return &mul;
}
if(++j==NUNIT)
j=0;
}while(j!=i);
for(p=prefix;p->name;p++)
if(strncmp(s, p->name, p->len)==0){
coef*=p->coef;
s+=p->len;
if(*s=='\0'){ /* no unit, just prefixes */
mul.coef=coef;
for(i=0;i!=NDIM;i++)
mul.dim[i]=0;
return &mul;
}
break;
}
}while(p->name);
return 0;
}
/*
* Look for the unit with the given name.
* Perhaps deleting a trailing `s' will help.
*/
struct unit *lookup(name)
char *name;
{
register struct unit *u=look2(name);
if(u==0 && name[strlen(name)-1]=='s'){
name[strlen(name)-1]=0;
u=look2(name);
}
if(u==0){
fprintf(stderr, "Unknown unit %s\n", name);
nerror++;
}
return u;
}
char *copy(s)
register char *s;
{
char *strcpy();
register l=strlen(s)+1;
if(strp+l>&strbuf[NSTRBUF]){
fprintf(stderr, "Units: out of space (copy)\n");
exit(1);
}
strcpy(strp, s);
strp+=l;
return strp-l;
}
yylex(){
register char *s;
char token[512];
register digits, dot, i;
register struct unit *up;
while(*lp==' ' || *lp=='\t')
lp++;
if(*lp=='\0')
return EOF;
if(digit(*lp) || *lp=='-' || *lp=='.'){
s=token;
digits=0;
dot=0;
do{
if(digit(*lp))
digits++;
else if(*lp=='.')
dot++;
*s++=*lp++;
}while(digit(*lp) || *lp=='.');
if(!digits || dot>1)
yyerror("Bad number");
else if(*lp=='e' || *lp=='E'){
*s++=*lp++;
if(*lp=='+' || *lp=='-')
*s++=*lp++;
if(!digit(*lp))
yyerror("Bad number");
else{
do
*s++=*lp++;
while(digit(*lp));
}
}
*s='\0';
yylval.u.coef=atof(token);
for(i=0;i!=NDIM;i++)
yylval.u.dim[i]=0;
return NUMBER;
}
if(idchar[*lp]){
for(s=token;idchar[*s=*lp];s++,lp++);
*s='\0';
if(strcmp(token, "square")==0) return SQUARE;
if(strcmp(token, "sq")==0) return SQUARE;
if(strcmp(token, "cubic")==0) return CUBE;
if(strcmp(token, "cu")==0) return CUBE;
if(up=lookup(token))
yylval.u=*up;
else
yylval.u.coef=5551212.;
return NAME;
}
switch(*lp){
case '*':
case '/':
case '(':
case ')':
case '^':
case '@':
return *lp++;
case '\0':
return EOF;
default:
yyerror("Bad char");
return EOF;
}
}
conformable(u, v)
register struct unit *u, *v;
{
register i;
for(i=0;i!=NDIM;i++)
if(u->dim[i]!=v->dim[i])
return 0;
return 1;
}
char *dname(i){
static char v[]="%000";
if(dim[i])
return dim[i]->name;
sprintf(v, "%%%d", i);
return v;
}
punit(u)
register struct unit *u;
{
register i;
printf("\t%g", u->coef);
for(i=0;i!=NDIM;i++)
switch(u->dim[i]){
case 0: break;
case 1: printf(" %s", dname(i)); break;
default: printf(" %s^%d", dname(i), u->dim[i]); break;
}
putchar('\n');
}
yyerror(m)
char *m;
{
register char *s;
printf("%s\n", line);
for(s=line;s!=lp;s++)
putchar(*s=='\t'?'\t':' ');
printf("^\n%s\n", m);
nerror++;
}
struct unit mul(u, v)
struct unit u, v;
{
register i;
u.coef*=v.coef;
for(i=0;i!=NDIM;i++)
u.dim[i]+=v.dim[i];
return u;
}
struct unit div(u, v)
struct unit u, v;
{
register i;
u.coef/=v.coef;
for(i=0;i!=NDIM;i++)
u.dim[i]-=v.dim[i];
return u;
}
double huge = HUGE;
double zero = 0.;
struct unit pwr(u, f)
struct unit u;
double f;
{
register i;
if(f!=(int)f)
yyerror("Sorry, only integer powers");
errno = 0;
u.coef=pow(u.coef, f);
if(errno == EDOM)
zero/zero;
if(errno == ERANGE)
huge*huge;
for(i=0;i!=NDIM;i++)
u.dim[i]*=f;
return u;
}
struct unit prim(u)
struct unit u;
{
register d=u.coef;
if(d!=u.coef)
yyerror("Primitive unit must be integral");
else if(d<0 || NDIM<=u.coef)
yyerror("Primitive unit out of range");
else{
u.coef=1;
u.dim[d]=1;
}
return u;
}
/*
* readunits sucks, since it knows too many details about the
* difference between file1 and file2
*/
readunits(file1, file2)
char *file1, *file2;
{
register FILE *f;
char buf[512];
register char *s, *name;
register i, j, k, nunit;
int n;
struct stat ascii1, ascii2, bin;
sprintf(buf, "%s.bin", file1);
if(stat(file1, &ascii1)>=0 && stat(file2, &ascii2)>=0
&& stat(buf, &bin)>=0 && cdate<bin.st_mtime
&& ascii1.st_mtime<bin.st_mtime && ascii2.st_mtime<bin.st_mtime){
if((i=open(buf, 0))>=0
&& read(i, (char *)unit, sizeof unit)==sizeof unit
&& read(i, (char *)strbuf, sizeof strbuf)==sizeof strbuf
&& read(i, (char *)&n, sizeof n)==sizeof n
&& read(i, (char *)dim, sizeof dim)==sizeof dim){
strp=strbuf+n;
close(i);
return;
}
close(i);
for(i=0;i!=NUNIT;i++)
unit[i].name=0;
}
for(k=0;k!=2;k++){
f=fopen(k==0?file1:file2, "r");
if(f==0){
perror(k==0?file1:file2);
if(k==1)
break;
exit(1);
}
while(fgets(buf, sizeof buf, f)){
if((s=strchr(buf, '#')) || (s=strchr(buf, '\n')))
*s='\0';
for(name=buf;*name==' ' || *name=='\t';name++);
if(*name=='\0')
continue;
for(s=name;idchar[*s];s++);
if(*s!=' ' && *s!='\t'){
fprintf(stderr, "Bad unit `%s'\n", name);
nerror++;
continue;
}
*s++='\0';
line=lp=s;
if(yyparse())
continue;
i=j=hash(name);
do{
if(unit[j].name==0){
unit[j]=yyval.u;
unit[j].name=copy(name);
if(unit[j].coef==1){
nunit=0;
for(i=0;i!=NDIM;i++)
switch(unit[j].dim[i]){
case 1: nunit++; n=i; break;
case 0: break;
default: nunit=2; break;
}
if(nunit==1 && dim[n]==0)
dim[n]=unit+j;
}
goto Ok;
}
if(++j==NUNIT)
j=0;
}while(j!=i);
fprintf(stderr, "Units: out of space (NUNIT)\n");
exit(1);
Ok: ;
}
fclose(f);
}
sprintf(buf, "%s.bin", file1);
umask(0);
if((i=creat(buf, 0666))>=0){
n=strp-strbuf;
write(i, (char *)unit, sizeof unit);
write(i, (char *)strbuf, sizeof strbuf);
write(i, (char *)&n, sizeof n);
write(i, (char *)dim, sizeof dim);
close(i);
}
}
getunit(u, prompt, match)
struct unit *u;
char *prompt;
{
char buf[512];
register char *s;
Again:
do{
nerror=0;
printf("%s: ", prompt);
if(fgets(buf, sizeof buf, stdin)==0)
return 0;
if((s=strchr(buf, '#')) || (s=strchr(buf, '\n')))
*s='\0';
for(s=buf;*s==' ' || *s=='\t';s++);
if(match && *s=='?'){
matchunit();
goto Again;
}
line=lp=s;
}while(yyparse() || nerror);
*u=yyval.u;
return 1;
}
#include <setjmp.h>
jmp_buf jmp;
err(){
signal(8, err);
printf("Floating point error\n");
nerror++;
errno = 0;
longjmp(jmp, 0);
}
struct unit have, want;
matchunit(){
register struct unit *u;
for(u=unit;u!=&unit[NUNIT];u++)
if(u->name && conformable(&have, u))
printf("%g %s\n", have.coef/u->coef, u->name);
}
main(){
register i;
for(i=0;prefix[i].name;i++)
prefix[i].len=strlen(prefix[i].name);
for(i=0;i!=256;i++)
idchar[i]=idcharfn(i);
setjmp(jmp);
signal(8, err);
readunits("/usr/lib/Units", "/usr/lib/Monetary.Units");
while(getunit(&have, "You have", 0) && getunit(&want, "You want", 1)){
if(conformable(&have, &want))
printf("* %g\n/ %g\n",
have.coef/want.coef, want.coef/have.coef);
else{
printf("conformability\n");
punit(&have);
punit(&want);
}
}
}