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