xref: /plan9-contrib/sys/src/cmd/units.y (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
1 %{
2 #include <u.h>
3 #include <libc.h>
4 #include <bio.h>
5 
6 enum
7 {
8 	Ndim	= 15,		/* number of dimensions */
9 	Nsym	= 40,		/* size of a name */
10 	Nvar	= 203,		/* hash table size */
11 	Maxe	= 695,		/* log of largest number */
12 };
13 
14 typedef	struct	Var	Var;
15 typedef	struct	Node	Node;
16 typedef	struct	Prefix	Prefix;
17 
18 struct	Node
19 {
20 	double	val;
21 	schar	dim[Ndim];
22 };
23 struct	Var
24 {
25 	Rune	name[Nsym];
26 	Node	node;
27 	Var*	link;
28 };
29 struct	Prefix
30 {
31 	double	val;
32 	Rune*	pname;
33 };
34 
35 char	buf[100];
36 int	digval;
37 Biobuf*	fi;
38 Biobuf	linebuf;
39 Var*	fund[Ndim];
40 Rune	line[1000];
41 ulong	lineno;
42 int	linep;
43 int	nerrors;
44 Node	one;
45 int	peekrune;
46 Node	retnode1;
47 Node	retnode2;
48 Node	retnode;
49 Rune	sym[Nsym];
50 Var*	vars[Nvar];
51 int	vflag;
52 
53 extern	void	add(Node*, Node*, Node*);
54 extern	void	div(Node*, Node*, Node*);
55 extern	int	specialcase(Node*, Node*, Node*);
56 extern	double	fadd(double, double);
57 extern	double	fdiv(double, double);
58 extern	double	fmul(double, double);
59 extern	int	gdigit(void*);
60 extern	Var*	lookup(int);
61 extern	void	main(int, char*[]);
62 extern	void	mul(Node*, Node*, Node*);
63 extern	void	ofile(void);
64 extern	double	pname(void);
65 extern	void	printdim(char*, int, int);
66 extern	int	ralpha(int);
67 extern	int	readline(void);
68 extern	void	sub(Node*, Node*, Node*);
69 extern	int	Ufmt(Fmt*);
70 extern	void	xpn(Node*, Node*, int);
71 extern	void	yyerror(char*, ...);
72 extern	int	yylex(void);
73 extern	int	yyparse(void);
74 
75 typedef	Node*	indnode;
76 #pragma	varargck	type	"U"	indnode
77 
78 %}
79 %union
80 {
81 	Node	node;
82 	Var*	var;
83 	int	numb;
84 	double	val;
85 }
86 
87 %type	<node>	prog expr expr0 expr1 expr2 expr3 expr4
88 
89 %token	<val>	VAL
90 %token	<var>	VAR
91 %token	<numb>	SUP
92 %%
93 prog:
94 	':' VAR expr
95 	{
96 		int f;
97 
98 		f = $2->node.dim[0];
99 		$2->node = $3;
100 		$2->node.dim[0] = 1;
101 		if(f)
102 			yyerror("redefinition of %S", $2->name);
103 		else
104 		if(vflag)
105 			print("%S\t%U\n", $2->name, &$2->node);
106 	}
107 |	':' VAR '#'
108 	{
109 		int f, i;
110 
111 		for(i=1; i<Ndim; i++)
112 			if(fund[i] == 0)
113 				break;
114 		if(i >= Ndim) {
115 			yyerror("too many dimensions");
116 			i = Ndim-1;
117 		}
118 		fund[i] = $2;
119 
120 		f = $2->node.dim[0];
121 		$2->node = one;
122 		$2->node.dim[0] = 1;
123 		$2->node.dim[i] = 1;
124 		if(f)
125 			yyerror("redefinition of %S", $2->name);
126 		else
127 		if(vflag)
128 			print("%S\t#\n", $2->name);
129 	}
130 |	'?' expr
131 	{
132 		retnode1 = $2;
133 	}
134 |	'?'
135 	{
136 		retnode1 = one;
137 	}
138 
139 expr:
140 	expr4
141 |	expr '+' expr4
142 	{
143 		add(&$$, &$1, &$3);
144 	}
145 |	expr '-' expr4
146 	{
147 		sub(&$$, &$1, &$3);
148 	}
149 
150 expr4:
151 	expr3
152 |	expr4 '*' expr3
153 	{
154 		mul(&$$, &$1, &$3);
155 	}
156 |	expr4 '/' expr3
157 	{
158 		div(&$$, &$1, &$3);
159 	}
160 
161 expr3:
162 	expr2
163 |	expr3 expr2
164 	{
165 		mul(&$$, &$1, &$2);
166 	}
167 
168 expr2:
169 	expr1
170 |	expr2 SUP
171 	{
172 		xpn(&$$, &$1, $2);
173 	}
174 |	expr2 '^' expr1
175 	{
176 		int i;
177 
178 		for(i=1; i<Ndim; i++)
179 			if($3.dim[i]) {
180 				yyerror("exponent has units");
181 				$$ = $1;
182 				break;
183 			}
184 		if(i >= Ndim) {
185 			i = $3.val;
186 			if(i != $3.val)
187 				yyerror("exponent not integral");
188 			xpn(&$$, &$1, i);
189 		}
190 	}
191 
192 expr1:
193 	expr0
194 |	expr1 '|' expr0
195 	{
196 		div(&$$, &$1, &$3);
197 	}
198 
199 expr0:
200 	VAR
201 	{
202 		if($1->node.dim[0] == 0) {
203 			yyerror("undefined %S", $1->name);
204 			$$ = one;
205 		} else
206 			$$ = $1->node;
207 	}
208 |	VAL
209 	{
210 		$$ = one;
211 		$$.val = $1;
212 	}
213 |	'(' expr ')'
214 	{
215 		$$ = $2;
216 	}
217 %%
218 
219 int
220 yylex(void)
221 {
222 	int c, i;
223 
224 	c = peekrune;
225 	peekrune = ' ';
226 
227 loop:
228 	if((c >= '0' && c <= '9') || c == '.')
229 		goto numb;
230 	if(ralpha(c))
231 		goto alpha;
232 	switch(c) {
233 	case ' ':
234 	case '\t':
235 		c = line[linep++];
236 		goto loop;
237 	case L'×':
238 		return '*';
239 	case L'÷':
240 		return '/';
241 	case L'¹':
242 	case L'ⁱ':
243 		yylval.numb = 1;
244 		return SUP;
245 	case L'²':
246 	case L'⁲':
247 		yylval.numb = 2;
248 		return SUP;
249 	case L'³':
250 	case L'⁳':
251 		yylval.numb = 3;
252 		return SUP;
253 	}
254 	return c;
255 
256 alpha:
257 	memset(sym, 0, sizeof(sym));
258 	for(i=0;; i++) {
259 		if(i < nelem(sym))
260 			sym[i] = c;
261 		c = line[linep++];
262 		if(!ralpha(c))
263 			break;
264 	}
265 	sym[nelem(sym)-1] = 0;
266 	peekrune = c;
267 	yylval.var = lookup(0);
268 	return VAR;
269 
270 numb:
271 	digval = c;
272 	yylval.val = charstod(gdigit, 0);
273 	return VAL;
274 }
275 
276 void
main(int argc,char * argv[])277 main(int argc, char *argv[])
278 {
279 	char *file;
280 
281 	ARGBEGIN {
282 	default:
283 		print("usage: units [-v] [file]\n");
284 		exits("usage");
285 	case 'v':
286 		vflag = 1;
287 		break;
288 	} ARGEND
289 
290 	file = "/lib/units";
291 	if(argc > 0)
292 		file = argv[0];
293 	fi = Bopen(file, OREAD);
294 	if(fi == 0) {
295 		print("cant open: %s\n", file);
296 		exits("open");
297 	}
298 	fmtinstall('U', Ufmt);
299 	one.val = 1;
300 
301 	/*
302 	 * read the 'units' file to
303 	 * develope a database
304 	 */
305 	lineno = 0;
306 	for(;;) {
307 		lineno++;
308 		if(readline())
309 			break;
310 		if(line[0] == 0 || line[0] == '/')
311 			continue;
312 		peekrune = ':';
313 		yyparse();
314 	}
315 
316 	/*
317 	 * read the console to
318 	 * print ratio of pairs
319 	 */
320 	Bterm(fi);
321 	fi = &linebuf;
322 	Binit(fi, 0, OREAD);
323 	lineno = 0;
324 	for(;;) {
325 		if(lineno & 1)
326 			print("you want: ");
327 		else
328 			print("you have: ");
329 		if(readline())
330 			break;
331 		peekrune = '?';
332 		nerrors = 0;
333 		yyparse();
334 		if(nerrors)
335 			continue;
336 		if(lineno & 1) {
337 			if(specialcase(&retnode, &retnode2, &retnode1))
338 				print("\tis %U\n", &retnode);
339 			else {
340 				div(&retnode, &retnode2, &retnode1);
341 				print("\t* %U\n", &retnode);
342 				div(&retnode, &retnode1, &retnode2);
343 				print("\t/ %U\n", &retnode);
344 			}
345 		} else
346 			retnode2 = retnode1;
347 		lineno++;
348 	}
349 	print("\n");
350 	exits(0);
351 }
352 
353 /*
354  * all characters that have some
355  * meaning. rest are usable as names
356  */
357 int
ralpha(int c)358 ralpha(int c)
359 {
360 	switch(c) {
361 	case 0:
362 	case '+':
363 	case '-':
364 	case '*':
365 	case '/':
366 	case '[':
367 	case ']':
368 	case '(':
369 	case ')':
370 	case '^':
371 	case ':':
372 	case '?':
373 	case ' ':
374 	case '\t':
375 	case '.':
376 	case '|':
377 	case '#':
378 	case L'¹':
379 	case L'ⁱ':
380 	case L'²':
381 	case L'⁲':
382 	case L'³':
383 	case L'⁳':
384 	case L'×':
385 	case L'÷':
386 		return 0;
387 	}
388 	return 1;
389 }
390 
391 int
gdigit(void *)392 gdigit(void*)
393 {
394 	int c;
395 
396 	c = digval;
397 	if(c) {
398 		digval = 0;
399 		return c;
400 	}
401 	c = line[linep++];
402 	peekrune = c;
403 	return c;
404 }
405 
406 void
yyerror(char * fmt,...)407 yyerror(char *fmt, ...)
408 {
409 	va_list arg;
410 
411 	/*
412 	 * hack to intercept message from yaccpar
413 	 */
414 	if(strcmp(fmt, "syntax error") == 0) {
415 		yyerror("syntax error, last name: %S", sym);
416 		return;
417 	}
418 	va_start(arg, fmt);
419 	vseprint(buf, buf+sizeof(buf), fmt, arg);
420 	va_end(arg);
421 	print("%ld: %S\n\t%s\n", lineno, line, buf);
422 	nerrors++;
423 	if(nerrors > 5) {
424 		print("too many errors\n");
425 		exits("errors");
426 	}
427 }
428 
429 void
add(Node * c,Node * a,Node * b)430 add(Node *c, Node *a, Node *b)
431 {
432 	int i, d;
433 
434 	for(i=0; i<Ndim; i++) {
435 		d = a->dim[i];
436 		c->dim[i] = d;
437 		if(d != b->dim[i])
438 			yyerror("add must be like units");
439 	}
440 	c->val = fadd(a->val, b->val);
441 }
442 
443 void
sub(Node * c,Node * a,Node * b)444 sub(Node *c, Node *a, Node *b)
445 {
446 	int i, d;
447 
448 	for(i=0; i<Ndim; i++) {
449 		d = a->dim[i];
450 		c->dim[i] = d;
451 		if(d != b->dim[i])
452 			yyerror("sub must be like units");
453 	}
454 	c->val = fadd(a->val, -b->val);
455 }
456 
457 void
mul(Node * c,Node * a,Node * b)458 mul(Node *c, Node *a, Node *b)
459 {
460 	int i;
461 
462 	for(i=0; i<Ndim; i++)
463 		c->dim[i] = a->dim[i] + b->dim[i];
464 	c->val = fmul(a->val, b->val);
465 }
466 
467 void
div(Node * c,Node * a,Node * b)468 div(Node *c, Node *a, Node *b)
469 {
470 	int i;
471 
472 	for(i=0; i<Ndim; i++)
473 		c->dim[i] = a->dim[i] - b->dim[i];
474 	c->val = fdiv(a->val, b->val);
475 }
476 
477 void
xpn(Node * c,Node * a,int b)478 xpn(Node *c, Node *a, int b)
479 {
480 	int i;
481 
482 	*c = one;
483 	if(b < 0) {
484 		b = -b;
485 		for(i=0; i<b; i++)
486 			div(c, c, a);
487 	} else
488 	for(i=0; i<b; i++)
489 		mul(c, c, a);
490 }
491 
492 int
specialcase(Node * c,Node * a,Node * b)493 specialcase(Node *c, Node *a, Node *b)
494 {
495 	int i, d, d1, d2;
496 
497 	d1 = 0;
498 	d2 = 0;
499 	for(i=1; i<Ndim; i++) {
500 		d = a->dim[i];
501 		if(d) {
502 			if(d != 1 || d1)
503 				return 0;
504 			d1 = i;
505 		}
506 		d = b->dim[i];
507 		if(d) {
508 			if(d != 1 || d2)
509 				return 0;
510 			d2 = i;
511 		}
512 	}
513 	if(d1 == 0 || d2 == 0)
514 		return 0;
515 
516 	if(memcmp(fund[d1]->name, L"°C", 3*sizeof(Rune)) == 0 &&
517 	   memcmp(fund[d2]->name, L"°F", 3*sizeof(Rune)) == 0 &&
518 	   b->val == 1) {
519 		memcpy(c->dim, b->dim, sizeof(c->dim));
520 		c->val = a->val * 9. / 5. + 32.;
521 		return 1;
522 	}
523 
524 	if(memcmp(fund[d1]->name, L"°F", 3*sizeof(Rune)) == 0 &&
525 	   memcmp(fund[d2]->name, L"°C", 3*sizeof(Rune)) == 0 &&
526 	   b->val == 1) {
527 		memcpy(c->dim, b->dim, sizeof(c->dim));
528 		c->val = (a->val - 32.) * 5. / 9.;
529 		return 1;
530 	}
531 	return 0;
532 }
533 
534 void
printdim(char * str,int d,int n)535 printdim(char *str, int d, int n)
536 {
537 	Var *v;
538 
539 	if(n) {
540 		v = fund[d];
541 		if(v)
542 			sprint(strchr(str, 0), " %S", v->name);
543 		else
544 			sprint(strchr(str, 0), " [%d]", d);
545 		switch(n) {
546 		case 1:
547 			break;
548 		case 2:
549 			strcat(str, "²");
550 			break;
551 		case 3:
552 			strcat(str, "³");
553 			break;
554 		default:
555 			sprint(strchr(str, 0), "^%d", n);
556 		}
557 	}
558 }
559 
560 int
Ufmt(Fmt * fp)561 Ufmt(Fmt *fp)
562 {
563 	char str[200];
564 	Node *n;
565 	int f, i, d;
566 
567 	n = va_arg(fp->args, Node*);
568 	sprint(str, "%g", n->val);
569 
570 	f = 0;
571 	for(i=1; i<Ndim; i++) {
572 		d = n->dim[i];
573 		if(d > 0)
574 			printdim(str, i, d);
575 		else
576 		if(d < 0)
577 			f = 1;
578 	}
579 
580 	if(f) {
581 		strcat(str, " /");
582 		for(i=1; i<Ndim; i++) {
583 			d = n->dim[i];
584 			if(d < 0)
585 				printdim(str, i, -d);
586 		}
587 	}
588 
589 	return fmtstrcpy(fp, str);
590 }
591 
592 int
readline(void)593 readline(void)
594 {
595 	int i, c;
596 
597 	linep = 0;
598 	for(i=0;; i++) {
599 		c = Bgetrune(fi);
600 		if(c < 0)
601 			return 1;
602 		if(c == '\n')
603 			break;
604 		if(i < nelem(line))
605 			line[i] = c;
606 	}
607 	if(i >= nelem(line))
608 		i = nelem(line)-1;
609 	line[i] = 0;
610 	return 0;
611 }
612 
613 Var*
lookup(int f)614 lookup(int f)
615 {
616 	int i;
617 	Var *v, *w;
618 	double p;
619 	ulong h;
620 
621 	h = 0;
622 	for(i=0; sym[i]; i++)
623 		h = h*13 + sym[i];
624 	h %= nelem(vars);
625 
626 	for(v=vars[h]; v; v=v->link)
627 		if(memcmp(sym, v->name, sizeof(sym)) == 0)
628 			return v;
629 	if(f)
630 		return 0;
631 	v = malloc(sizeof(*v));
632 	if(v == nil) {
633 		fprint(2, "out of memory\n");
634 		exits("mem");
635 	}
636 	memset(v, 0, sizeof(*v));
637 	memcpy(v->name, sym, sizeof(sym));
638 	v->link = vars[h];
639 	vars[h] = v;
640 
641 	p = 1;
642 	for(;;) {
643 		p = fmul(p, pname());
644 		if(p == 0)
645 			break;
646 		w = lookup(1);
647 		if(w) {
648 			v->node = w->node;
649 			v->node.val = fmul(v->node.val, p);
650 			break;
651 		}
652 	}
653 	return v;
654 }
655 
656 Prefix	prefix[] =
657 {
658 	1e-24,	L"yocto",
659 	1e-21,	L"zepto",
660 	1e-18,	L"atto",
661 	1e-15,	L"femto",
662 	1e-12,	L"pico",
663 	1e-9,	L"nano",
664 	1e-6,	L"micro",
665 	1e-6,	L"μ",
666 	1e-3,	L"milli",
667 	1e-2,	L"centi",
668 	1e-1,	L"deci",
669 	1e1,	L"deka",
670 	1e2,	L"hecta",
671 	1e2,	L"hecto",
672 	1e3,	L"kilo",
673 	1e6,	L"mega",
674 	1e6,	L"meg",
675 	1e9,	L"giga",
676 	1e12,	L"tera",
677 	1e15,	L"peta",
678 	1e18,	L"exa",
679 	1e21,	L"zetta",
680 	1e24,	L"yotta",
681 	0,	0
682 };
683 
684 double
pname(void)685 pname(void)
686 {
687 	Rune *p;
688 	int i, j, c;
689 
690 	/*
691 	 * rip off normal prefixs
692 	 */
693 	for(i=0; p=prefix[i].pname; i++) {
694 		for(j=0; c=p[j]; j++)
695 			if(c != sym[j])
696 				goto no;
697 		memmove(sym, sym+j, (Nsym-j)*sizeof(*sym));
698 		memset(sym+(Nsym-j), 0, j*sizeof(*sym));
699 		return prefix[i].val;
700 	no:;
701 	}
702 
703 	/*
704 	 * rip off 's' suffixes
705 	 */
706 	for(j=0; sym[j]; j++)
707 		;
708 	j--;
709 	/* j>1 is special hack to disallow ms finding m */
710 	if(j > 1 && sym[j] == 's') {
711 		sym[j] = 0;
712 		return 1;
713 	}
714 	return 0;
715 }
716 
717 /*
718  * careful floating point
719  */
720 double
fmul(double a,double b)721 fmul(double a, double b)
722 {
723 	double l;
724 
725 	if(a <= 0) {
726 		if(a == 0)
727 			return 0;
728 		l = log(-a);
729 	} else
730 		l = log(a);
731 
732 	if(b <= 0) {
733 		if(b == 0)
734 			return 0;
735 		l += log(-b);
736 	} else
737 		l += log(b);
738 
739 	if(l > Maxe) {
740 		yyerror("overflow in multiply");
741 		return 1;
742 	}
743 	if(l < -Maxe) {
744 		yyerror("underflow in multiply");
745 		return 0;
746 	}
747 	return a*b;
748 }
749 
750 double
fdiv(double a,double b)751 fdiv(double a, double b)
752 {
753 	double l;
754 
755 	if(a <= 0) {
756 		if(a == 0)
757 			return 0;
758 		l = log(-a);
759 	} else
760 		l = log(a);
761 
762 	if(b <= 0) {
763 		if(b == 0) {
764 			yyerror("division by zero");
765 			return 1;
766 		}
767 		l -= log(-b);
768 	} else
769 		l -= log(b);
770 
771 	if(l > Maxe) {
772 		yyerror("overflow in divide");
773 		return 1;
774 	}
775 	if(l < -Maxe) {
776 		yyerror("underflow in divide");
777 		return 0;
778 	}
779 	return a/b;
780 }
781 
782 double
fadd(double a,double b)783 fadd(double a, double b)
784 {
785 	return a + b;
786 }
787