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);
		}
	}
}