summaryrefslogtreecommitdiff
path: root/appl/cmd/units.y
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/units.y')
-rw-r--r--appl/cmd/units.y771
1 files changed, 771 insertions, 0 deletions
diff --git a/appl/cmd/units.y b/appl/cmd/units.y
new file mode 100644
index 00000000..70284868
--- /dev/null
+++ b/appl/cmd/units.y
@@ -0,0 +1,771 @@
+%{
+#
+# 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);
+};
+
+%}
+%module Units
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+}
+
+%type <node> prog expr expr0 expr1 expr2 expr3 expr4
+
+%token <val> VAL
+%token <var> VAR
+%token <numb> SUP
+%%
+prog:
+ ':' VAR expr
+ {
+ f := $2.node.dim[0];
+ $2.node = $3.copy();
+ $2.node.dim[0] = 1;
+ if(f)
+ yyerror(sys->sprint("redefinition of %s", $2.name));
+ else if(vflag)
+ sys->print("%s\t%s\n", $2.name, $2.node.text());
+ }
+| ':' VAR '#'
+ {
+ for(i:=1; i<Ndim; i++)
+ if(fund[i] == nil)
+ break;
+ if(i >= Ndim) {
+ yyerror("too many dimensions");
+ i = Ndim-1;
+ }
+ fund[i] = $2;
+
+ f := $2.node.dim[0];
+ $2.node = Node.mk(1.0);
+ $2.node.dim[0] = 1;
+ $2.node.dim[i] = 1;
+ if(f)
+ yyerror(sys->sprint("redefinition of %s", $2.name));
+ else if(vflag)
+ sys->print("%s\t#\n", $2.name);
+ }
+| '?' expr
+ {
+ retnode1 = $2.copy();
+ }
+| '?'
+ {
+ retnode1 = Node.mk(1.0);
+ }
+
+expr:
+ expr4
+| expr '+' expr4
+ {
+ $$ = $1.add($3);
+ }
+| expr '-' expr4
+ {
+ $$ = $1.sub($3);
+ }
+
+expr4:
+ expr3
+| expr4 '*' expr3
+ {
+ $$ = $1.mul($3);
+ }
+| expr4 '/' expr3
+ {
+ $$ = $1.div($3);
+ }
+
+expr3:
+ expr2
+| expr3 expr2
+ {
+ $$ = $1.mul($2);
+ }
+
+expr2:
+ expr1
+| expr2 SUP
+ {
+ $$ = $1.xpn($2);
+ }
+| expr2 '^' expr1
+ {
+ for(i:=1; i<Ndim; i++)
+ if($3.dim[i]) {
+ yyerror("exponent has units");
+ $$ = $1;
+ break;
+ }
+ if(i >= Ndim) {
+ i = int $3.val;
+ if(real i != $3.val)
+ yyerror("exponent not integral");
+ $$ = $1.xpn(i);
+ }
+ }
+
+expr1:
+ expr0
+| expr1 '|' expr0
+ {
+ $$ = $1.div($3);
+ }
+
+expr0:
+ VAR
+ {
+ if($1.node.dim[0] == 0) {
+ yyerror(sys->sprint("undefined %s", $1.name));
+ $$ = Node.mk(1.0);
+ } else
+ $$ = $1.node.copy();
+ }
+| VAL
+ {
+ $$ = Node.mk($1);
+ }
+| '(' expr ')'
+ {
+ $$ = $2;
+ }
+%%
+
+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;
+}