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