ref: 01338076d1a3610858f41fcbcdc7e46fb1109b77
dir: /appl/cmd/units.b/
implement Units; #line 2 "units.y" # # subject to the Lucent Public License 1.02 # include "sys.m"; sys: Sys; include "draw.m"; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "math.m"; math: Math; include "arg.m"; Ndim: con 15; # number of dimensions Nvar: con 203; # hash table size Maxe: con 695.0; # log of largest number Node: adt { val: real; dim: array of int; # [Ndim] schar mk: fn(v: real): Node; text: fn(n: self Node): string; add: fn(a: self Node, b: Node): Node; sub: fn(a: self Node, b: Node): Node; mul: fn(a: self Node, b: Node): Node; div: fn(a: self Node, b: Node): Node; xpn: fn(a: self Node, b: int): Node; copy: fn(a: self Node): Node; }; Var: adt { name: string; node: Node; }; Prefix: adt { val: real; pname: string; }; digval := 0; fi: ref Iobuf; fund := array[Ndim] of ref Var; line: string; lineno := 0; linep := 0; nerrors := 0; peekrune := 0; retnode1: Node; retnode2: Node; retnode: Node; sym: string; vars := array[Nvar] of list of ref Var; vflag := 0; YYSTYPE: adt { node: Node; var: ref Var; numb: int; val: real; }; YYLEX: adt { lval: YYSTYPE; lex: fn(l: self ref YYLEX): int; error: fn(l: self ref YYLEX, msg: string); }; Units: module { init: fn(nil: ref Draw->Context, args: list of string); VAL: con 57346; VAR: con 57347; SUP: con 57348; }; YYEOFCODE: con 1; YYERRCODE: con 2; YYMAXDEPTH: con 200; #line 203 "units.y" init(nil: ref Draw->Context, args: list of string) { sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; math = load Math Math->PATH; arg := load Arg Arg->PATH; arg->init(args); arg->setusage("units [-v] [file]"); while((o := arg->opt()) != 0) case o { 'v' => vflag = 1; * => arg->usage(); } args = arg->argv(); arg = nil; file := "/lib/units"; if(args != nil) file = hd args; fi = bufio->open(file, Sys->OREAD); if(fi == nil) { sys->fprint(sys->fildes(2), "units: cannot open %s: %r\n", file); raise "fail:open"; } lex := ref YYLEX; # # read the 'units' file to # develop a database # lineno = 0; for(;;) { lineno++; if(readline()) break; if(len line == 0 || line[0] == '/') continue; peekrune = ':'; yyparse(lex); } # # read the console to # print ratio of pairs # fi = bufio->fopen(sys->fildes(0), Sys->OREAD); lineno = 0; for(;;) { if(lineno & 1) sys->print("you want: "); else sys->print("you have: "); if(readline()) break; peekrune = '?'; nerrors = 0; yyparse(lex); if(nerrors) continue; if(lineno & 1) { isspcl: int; (isspcl, retnode) = specialcase(retnode2, retnode1); if(isspcl) sys->print("\tis %s\n", retnode.text()); else { retnode = retnode2.div(retnode1); sys->print("\t* %s\n", retnode.text()); retnode = retnode1.div(retnode2); sys->print("\t/ %s\n", retnode.text()); } } else retnode2 = retnode1.copy(); lineno++; } sys->print("\n"); } YYLEX.lex(lex: self ref YYLEX): int { c := peekrune; peekrune = ' '; while(c == ' ' || c == '\t'){ if(linep >= len line) return 0; # -1? c = line[linep++]; } case c { '0' to '9' or '.' => digval = c; (lex.lval.val, peekrune) = readreal(gdigit, lex); return VAL; '×' => return '*'; '÷' => return '/'; '¹' or 'ⁱ' => lex.lval.numb = 1; return SUP; '²' or '' => lex.lval.numb = 2; return SUP; '³' or '' => lex.lval.numb = 3; return SUP; * => if(ralpha(c)){ sym = ""; for(i:=0;; i++) { sym[i] = c; if(linep >= len line){ c = ' '; break; } c = line[linep++]; if(!ralpha(c)) break; } peekrune = c; lex.lval.var = lookup(0); return VAR; } } return c; } # # all characters that have some # meaning. rest are usable as names # ralpha(c: int): int { case c { 0 or '+' or '-' or '*' or '/' or '[' or ']' or '(' or ')' or '^' or ':' or '?' or ' ' or '\t' or '.' or '|' or '#' or '¹' or 'ⁱ' or '²' or '' or '³' or '' or '×' or '÷' => return 0; } return 1; } gdigit(nil: ref YYLEX): int { c := digval; if(c) { digval = 0; return c; } if(linep >= len line) return 0; return line[linep++]; } YYLEX.error(lex: self ref YYLEX, s: string) { # # hack to intercept message from yaccpar # if(s == "syntax error") { lex.error(sys->sprint("syntax error, last name: %s", sym)); return; } sys->print("%d: %s\n\t%s\n", lineno, line, s); nerrors++; if(nerrors > 5) { sys->print("too many errors\n"); raise "fail:errors"; } } yyerror(s: string) { l := ref YYLEX; l.error(s); } Node.mk(v: real): Node { return (v, array[Ndim] of {* => 0}); } Node.add(a: self Node, b: Node): Node { c := Node.mk(fadd(a.val, b.val)); for(i:=0; i<Ndim; i++) { d := a.dim[i]; c.dim[i] = d; if(d != b.dim[i]) yyerror("add must be like units"); } return c; } Node.sub(a: self Node, b: Node): Node { c := Node.mk(fadd(a.val, -b.val)); for(i:=0; i<Ndim; i++) { d := a.dim[i]; c.dim[i] = d; if(d != b.dim[i]) yyerror("sub must be like units"); } return c; } Node.mul(a: self Node, b: Node): Node { c := Node.mk(fmul(a.val, b.val)); for(i:=0; i<Ndim; i++) c.dim[i] = a.dim[i] + b.dim[i]; return c; } Node.div(a: self Node, b: Node): Node { c := Node.mk(fdiv(a.val, b.val)); for(i:=0; i<Ndim; i++) c.dim[i] = a.dim[i] - b.dim[i]; return c; } Node.xpn(a: self Node, b: int): Node { c := Node.mk(1.0); if(b < 0) { b = -b; for(i:=0; i<b; i++) c = c.div(a); } else for(i:=0; i<b; i++) c = c.mul(a); return c; } Node.copy(a: self Node): Node { c := Node.mk(a.val); c.dim[0:] = a.dim; return c; } specialcase(a, b: Node): (int, Node) { c := Node.mk(0.0); d1 := 0; d2 := 0; for(i:=1; i<Ndim; i++) { d := a.dim[i]; if(d) { if(d != 1 || d1) return (0, c); d1 = i; } d = b.dim[i]; if(d) { if(d != 1 || d2) return (0, c); d2 = i; } } if(d1 == 0 || d2 == 0) return (0, c); if(fund[d1].name == "°C" && fund[d2].name == "°F" && b.val == 1.0) { c = b.copy(); c.val = a.val * 9. / 5. + 32.; return (1, c); } if(fund[d1].name == "°F" && fund[d2].name == "°C" && b.val == 1.0) { c = b.copy(); c.val = (a.val - 32.) * 5. / 9.; return (1, c); } return (0, c); } printdim(d: int, n: int): string { s := ""; if(n) { v := fund[d]; if(v != nil) s += " "+v.name; else s += sys->sprint(" [%d]", d); case n { 1 => ; 2 => s += "²"; 3 => s += "³"; 4 => s += "⁴"; * => s += sys->sprint("^%d", n); } } return s; } Node.text(n: self Node): string { str := sys->sprint("%.7g", n.val); f := 0; for(i:=1; i<len n.dim; i++) { d := n.dim[i]; if(d > 0) str += printdim(i, d); else if(d < 0) f = 1; } if(f) { str += " /"; for(i=1; i<len n.dim; i++) { d := n.dim[i]; if(d < 0) str += printdim(i, -d); } } return str; } readline(): int { linep = 0; line = ""; for(i:=0;; i++) { c := fi.getc(); if(c < 0) return 1; if(c == '\n') return 0; line[i] = c; } } lookup(f: int): ref Var { h := 0; for(i:=0; i < len sym; i++) h = h*13 + sym[i]; if(h < 0) h ^= int 16r80000000; h %= len vars; for(vl:=vars[h]; vl != nil; vl = tl vl) if((hd vl).name == sym) return hd vl; if(f) return nil; v := ref Var(sym, Node.mk(0.0)); vars[h] = v :: vars[h]; p := 1.0; for(;;) { p = fmul(p, pname()); if(p == 0.0) break; w := lookup(1); if(w != nil) { v.node = w.node.copy(); v.node.val = fmul(v.node.val, p); break; } } return v; } prefix: array of Prefix = array[] of { (1e-24, "yocto"), (1e-21, "zepto"), (1e-18, "atto"), (1e-15, "femto"), (1e-12, "pico"), (1e-9, "nano"), (1e-6, "micro"), (1e-6, "μ"), (1e-3, "milli"), (1e-2, "centi"), (1e-1, "deci"), (1e1, "deka"), (1e2, "hecta"), (1e2, "hecto"), (1e3, "kilo"), (1e6, "mega"), (1e6, "meg"), (1e9, "giga"), (1e12, "tera"), (1e15, "peta"), (1e18, "exa"), (1e21, "zetta"), (1e24, "yotta") }; pname(): real { # # rip off normal prefices # Pref: for(i:=0; i < len prefix; i++) { p := prefix[i].pname; for(j:=0; j < len p; j++) if(j >= len sym || p[j] != sym[j]) continue Pref; sym = sym[j:]; return prefix[i].val; } # # rip off 's' suffixes # for(j:=0; j < len sym; j++) ; j--; # j>1 is special hack to disallow ms finding m if(j > 1 && sym[j] == 's') { sym = sym[0:j]; return 1.0; } return 0.0; } # # reads a floating-point number # readreal[T](f: ref fn(t: T): int, vp: T): (real, int) { s := ""; c := f(vp); while(c == ' ' || c == '\t') c = f(vp); if(c == '-' || c == '+'){ s[len s] = c; c = f(vp); } start := len s; while(c >= '0' && c <= '9'){ s[len s] = c; c = f(vp); } if(c == '.'){ s[len s] = c; c = f(vp); while(c >= '0' && c <= '9'){ s[len s] = c; c = f(vp); } } if(len s > start && (c == 'e' || c == 'E')){ s[len s] = c; c = f(vp); if(c == '-' || c == '+'){ s[len s] = c; c = f(vp); } while(c >= '0' && c <= '9'){ s[len s] = c; c = f(vp); } } return (real s, c); } # # careful floating point # fmul(a, b: real): real { l: real; if(a <= 0.0) { if(a == 0.0) return 0.0; l = math->log(-a); } else l = math->log(a); if(b <= 0.0) { if(b == 0.0) return 0.0; l += math->log(-b); } else l += math->log(b); if(l > Maxe) { yyerror("overflow in multiply"); return 1.0; } if(l < -Maxe) { yyerror("underflow in multiply"); return 0.0; } return a*b; } fdiv(a, b: real): real { l: real; if(a <= 0.0) { if(a == 0.0) return 0.0; l = math->log(-a); } else l = math->log(a); if(b <= 0.0) { if(b == 0.0) { yyerror("division by zero"); return 1.0; } l -= math->log(-b); } else l -= math->log(b); if(l > Maxe) { yyerror("overflow in divide"); return 1.0; } if(l < -Maxe) { yyerror("underflow in divide"); return 0.0; } return a/b; } fadd(a, b: real): real { return a + b; } yyexca := array[] of {-1, 1, 1, -1, -2, 0, }; YYNPROD: con 21; YYPRIVATE: con 57344; yytoknames: array of string; yystates: array of string; yydebug: con 0; YYLAST: con 41; yyact := array[] of { 8, 10, 7, 9, 16, 17, 12, 11, 20, 21, 15, 31, 23, 6, 4, 12, 11, 22, 13, 5, 1, 27, 28, 0, 14, 30, 29, 13, 20, 20, 25, 26, 0, 24, 18, 19, 16, 17, 2, 0, 3, }; yypact := array[] of { 31,-1000, 9, 11, 2, 26, 22, 11, 3, -3, -1000,-1000,-1000, 11, 26,-1000, 11, 11, 11, 11, 3,-1000, 11, 11, -6, 22, 22, 11, 11, -3, -1000,-1000, }; yypgo := array[] of { 0, 20, 19, 1, 3, 0, 2, 13, }; yyr1 := array[] of { 0, 1, 1, 1, 1, 2, 2, 2, 7, 7, 7, 6, 6, 5, 5, 5, 4, 4, 3, 3, 3, }; yyr2 := array[] of { 0, 3, 3, 2, 1, 1, 3, 3, 1, 3, 3, 1, 2, 1, 2, 3, 1, 3, 1, 1, 3, }; yychk := array[] of { -1000, -1, 7, 9, 5, -2, -7, -6, -5, -4, -3, 5, 4, 16, -2, 8, 10, 11, 12, 13, -5, 6, 14, 15, -2, -7, -7, -6, -6, -4, -3, 17, }; yydef := array[] of { 0, -2, 0, 4, 0, 3, 5, 8, 11, 13, 16, 18, 19, 0, 1, 2, 0, 0, 0, 0, 12, 14, 0, 0, 0, 6, 7, 9, 10, 15, 17, 20, }; yytok1 := array[] of { 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 3, 3, 3, 3, 16, 17, 12, 10, 3, 11, 3, 13, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 3, 3, 3, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 14, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 15, }; yytok2 := array[] of { 2, 3, 4, 5, 6, }; yytok3 := array[] of { 0 }; YYSys: module { FD: adt { fd: int; }; fildes: fn(fd: int): ref FD; fprint: fn(fd: ref FD, s: string, *): int; }; yysys: YYSys; yystderr: ref YYSys->FD; YYFLAG: con -1000; # parser for yacc output yytokname(yyc: int): string { if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) return yytoknames[yyc-1]; return "<"+string yyc+">"; } yystatname(yys: int): string { if(yys >= 0 && yys < len yystates && yystates[yys] != nil) return yystates[yys]; return "<"+string yys+">\n"; } yylex1(yylex: ref YYLEX): int { c : int; yychar := yylex.lex(); if(yychar <= 0) c = yytok1[0]; else if(yychar < len yytok1) c = yytok1[yychar]; else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) c = yytok2[yychar-YYPRIVATE]; else{ n := len yytok3; c = 0; for(i := 0; i < n; i+=2) { if(yytok3[i+0] == yychar) { c = yytok3[i+1]; break; } } if(c == 0) c = yytok2[1]; # unknown char } if(yydebug >= 3) yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); return c; } YYS: adt { yyv: YYSTYPE; yys: int; }; yyparse(yylex: ref YYLEX): int { if(yydebug >= 1 && yysys == nil) { yysys = load YYSys "$Sys"; yystderr = yysys->fildes(2); } yys := array[YYMAXDEPTH] of YYS; yyval: YYSTYPE; yystate := 0; yychar := -1; yynerrs := 0; # number of errors yyerrflag := 0; # error recovery flag yyp := -1; yyn := 0; yystack: for(;;){ # put a state and value onto the stack if(yydebug >= 4) yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); yyp++; if(yyp >= len yys) yys = (array[len yys * 2] of YYS)[0:] = yys; yys[yyp].yys = yystate; yys[yyp].yyv = yyval; for(;;){ yyn = yypact[yystate]; if(yyn > YYFLAG) { # simple state if(yychar < 0) yychar = yylex1(yylex); yyn += yychar; if(yyn >= 0 && yyn < YYLAST) { yyn = yyact[yyn]; if(yychk[yyn] == yychar) { # valid shift yychar = -1; yyp++; if(yyp >= len yys) yys = (array[len yys * 2] of YYS)[0:] = yys; yystate = yyn; yys[yyp].yys = yystate; yys[yyp].yyv = yylex.lval; if(yyerrflag > 0) yyerrflag--; if(yydebug >= 4) yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); continue; } } } # default state action yyn = yydef[yystate]; if(yyn == -2) { if(yychar < 0) yychar = yylex1(yylex); # look through exception table for(yyxi:=0;; yyxi+=2) if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) break; for(yyxi += 2;; yyxi += 2) { yyn = yyexca[yyxi]; if(yyn < 0 || yyn == yychar) break; } yyn = yyexca[yyxi+1]; if(yyn < 0){ yyn = 0; break yystack; } } if(yyn != 0) break; # error ... attempt to resume parsing if(yyerrflag == 0) { # brand new error yylex.error("syntax error"); yynerrs++; if(yydebug >= 1) { yysys->fprint(yystderr, "%s", yystatname(yystate)); yysys->fprint(yystderr, "saw %s\n", yytokname(yychar)); } } if(yyerrflag != 3) { # incompletely recovered error ... try again yyerrflag = 3; # find a state where "error" is a legal shift action while(yyp >= 0) { yyn = yypact[yys[yyp].yys] + YYERRCODE; if(yyn >= 0 && yyn < YYLAST) { yystate = yyact[yyn]; # simulate a shift of "error" if(yychk[yystate] == YYERRCODE) continue yystack; } # the current yyp has no shift onn "error", pop stack if(yydebug >= 2) yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n", yys[yyp].yys, yys[yyp-1].yys ); yyp--; } # there is no state on the stack with an error shift ... abort yyn = 1; break yystack; } # no shift yet; clobber input char if(yydebug >= 2) yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar)); if(yychar == YYEOFCODE) { yyn = 1; break yystack; } yychar = -1; # try again in the same state } # reduction by production yyn if(yydebug >= 2) yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); yypt := yyp; yyp -= yyr2[yyn]; # yyval = yys[yyp+1].yyv; yym := yyn; # consult goto table to find next state yyn = yyr1[yyn]; yyg := yypgo[yyn]; yyj := yyg + yys[yyp].yys + 1; if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) yystate = yyact[yyg]; case yym { 1=> #line 90 "units.y" { f := yys[yypt-1].yyv.var.node.dim[0]; yys[yypt-1].yyv.var.node = yys[yypt-0].yyv.node.copy(); yys[yypt-1].yyv.var.node.dim[0] = 1; if(f) yyerror(sys->sprint("redefinition of %s", yys[yypt-1].yyv.var.name)); else if(vflag) sys->print("%s\t%s\n", yys[yypt-1].yyv.var.name, yys[yypt-1].yyv.var.node.text()); } 2=> #line 100 "units.y" { for(i:=1; i<Ndim; i++) if(fund[i] == nil) break; if(i >= Ndim) { yyerror("too many dimensions"); i = Ndim-1; } fund[i] = yys[yypt-1].yyv.var; f := yys[yypt-1].yyv.var.node.dim[0]; yys[yypt-1].yyv.var.node = Node.mk(1.0); yys[yypt-1].yyv.var.node.dim[0] = 1; yys[yypt-1].yyv.var.node.dim[i] = 1; if(f) yyerror(sys->sprint("redefinition of %s", yys[yypt-1].yyv.var.name)); else if(vflag) sys->print("%s\t#\n", yys[yypt-1].yyv.var.name); } 3=> #line 120 "units.y" { retnode1 = yys[yypt-0].yyv.node.copy(); } 4=> #line 124 "units.y" { retnode1 = Node.mk(1.0); } 5=> yyval.node = yys[yyp+1].yyv.node; 6=> #line 131 "units.y" { yyval.node = yys[yypt-2].yyv.node.add(yys[yypt-0].yyv.node); } 7=> #line 135 "units.y" { yyval.node = yys[yypt-2].yyv.node.sub(yys[yypt-0].yyv.node); } 8=> yyval.node = yys[yyp+1].yyv.node; 9=> #line 142 "units.y" { yyval.node = yys[yypt-2].yyv.node.mul(yys[yypt-0].yyv.node); } 10=> #line 146 "units.y" { yyval.node = yys[yypt-2].yyv.node.div(yys[yypt-0].yyv.node); } 11=> yyval.node = yys[yyp+1].yyv.node; 12=> #line 153 "units.y" { yyval.node = yys[yypt-1].yyv.node.mul(yys[yypt-0].yyv.node); } 13=> yyval.node = yys[yyp+1].yyv.node; 14=> #line 160 "units.y" { yyval.node = yys[yypt-1].yyv.node.xpn(yys[yypt-0].yyv.numb); } 15=> #line 164 "units.y" { for(i:=1; i<Ndim; i++) if(yys[yypt-0].yyv.node.dim[i]) { yyerror("exponent has units"); yyval.node = yys[yypt-2].yyv.node; break; } if(i >= Ndim) { i = int yys[yypt-0].yyv.node.val; if(real i != yys[yypt-0].yyv.node.val) yyerror("exponent not integral"); yyval.node = yys[yypt-2].yyv.node.xpn(i); } } 16=> yyval.node = yys[yyp+1].yyv.node; 17=> #line 182 "units.y" { yyval.node = yys[yypt-2].yyv.node.div(yys[yypt-0].yyv.node); } 18=> #line 188 "units.y" { if(yys[yypt-0].yyv.var.node.dim[0] == 0) { yyerror(sys->sprint("undefined %s", yys[yypt-0].yyv.var.name)); yyval.node = Node.mk(1.0); } else yyval.node = yys[yypt-0].yyv.var.node.copy(); } 19=> #line 196 "units.y" { yyval.node = Node.mk(yys[yypt-0].yyv.val); } 20=> #line 200 "units.y" { yyval.node = yys[yypt-1].yyv.node; } } } return yyn; }