Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

%{
#include <u.h>
#include <libc.h>
#include <bio.h>

enum
{
        Ndim    = 15,           /* number of dimensions */
        Nsym    = 40,           /* size of a name */
        Nvar    = 203,          /* hash table size */
        Maxe    = 695,          /* log of largest number */
};

typedef struct  Var     Var;
typedef struct  Node    Node;
typedef struct  Prefix  Prefix;

struct  Node
{
        double  val;
        schar   dim[Ndim];
};
struct  Var
{
        Rune    name[Nsym];
        Node    node;
        Var*    link;
};
struct  Prefix
{
        double  val;
        Rune*   pname;
};

char    buf[100];
int     digval;
Biobuf* fi;
Biobuf  linebuf;
Var*    fund[Ndim];
Rune    line[1000];
ulong   lineno;
int     linep;
int     nerrors;
Node    one;
int     peekrune;
Node    retnode1;
Node    retnode2;
Node    retnode;
Rune    sym[Nsym];
Var*    vars[Nvar];
int     vflag;

extern  void    add(Node*, Node*, Node*);
extern  void    div(Node*, Node*, Node*);
extern  int     specialcase(Node*, Node*, Node*);
extern  double  fadd(double, double);
extern  double  fdiv(double, double);
extern  double  fmul(double, double);
extern  int     gdigit(void*);
extern  Var*    lookup(int);
extern  void    main(int, char*[]);
extern  void    mul(Node*, Node*, Node*);
extern  void    ofile(void);
extern  double  pname(void);
extern  void    printdim(char*, int, int);
extern  int     ralpha(int);
extern  int     readline(void);
extern  void    sub(Node*, Node*, Node*);
extern  int     Ufmt(Fmt*);
extern  void    xpn(Node*, Node*, int);
extern  void    yyerror(char*, ...);
extern  int     yylex(void);
extern  int     yyparse(void);

typedef Node*   indnode;
#pragma varargck        type    "U"     indnode

%}
%union
{
        Node    node;
        Var*    var;
        int     numb;
        double  val;
}

%type   <node>  prog expr expr0 expr1 expr2 expr3 expr4

%token  <val>   VAL
%token  <var>   VAR
%token  <numb>  SUP
%%
prog:
        ':' VAR expr
        {
                int f;

                f = $2->node.dim[0];
                $2->node = $3;
                $2->node.dim[0] = 1;
                if(f)
                        yyerror("redefinition of %S", $2->name);
                else
                if(vflag)
                        print("%S\t%U\n", $2->name, &$2->node);
        }
|       ':' VAR '#'
        {
                int f, i;

                for(i=1; i<Ndim; i++)
                        if(fund[i] == 0)
                                break;
                if(i >= Ndim) {
                        yyerror("too many dimensions");
                        i = Ndim-1;
                }
                fund[i] = $2;

                f = $2->node.dim[0];
                $2->node = one;
                $2->node.dim[0] = 1;
                $2->node.dim[i] = 1;
                if(f)
                        yyerror("redefinition of %S", $2->name);
                else
                if(vflag)
                        print("%S\t#\n", $2->name);
        }
|       '?' expr
        {
                retnode1 = $2;
        }
|       '?'
        {
                retnode1 = one;
        }

expr:
        expr4
|       expr '+' expr4
        {
                add(&$$, &$1, &$3);
        }
|       expr '-' expr4
        {
                sub(&$$, &$1, &$3);
        }

expr4:
        expr3
|       expr4 '*' expr3
        {
                mul(&$$, &$1, &$3);
        }
|       expr4 '/' expr3
        {
                div(&$$, &$1, &$3);
        }

expr3:
        expr2
|       expr3 expr2
        {
                mul(&$$, &$1, &$2);
        }

expr2:
        expr1
|       expr2 SUP
        {
                xpn(&$$, &$1, $2);
        }
|       expr2 '^' expr1
        {
                int i;

                for(i=1; i<Ndim; i++)
                        if($3.dim[i]) {
                                yyerror("exponent has units");
                                $$ = $1;
                                break;
                        }
                if(i >= Ndim) {
                        i = $3.val;
                        if(i != $3.val)
                                yyerror("exponent not integral");
                        xpn(&$$, &$1, i);
                }
        }

expr1:
        expr0
|       expr1 '|' expr0
        {
                div(&$$, &$1, &$3);
        }

expr0:
        VAR
        {
                if($1->node.dim[0] == 0) {
                        yyerror("undefined %S", $1->name);
                        $$ = one;
                } else
                        $$ = $1->node;
        }
|       VAL
        {
                $$ = one;
                $$.val = $1;
        }
|       '(' expr ')'
        {
                $$ = $2;
        }
%%

int
yylex(void)
{
        int c, i;

        c = peekrune;
        peekrune = ' ';

loop:
        if((c >= '0' && c <= '9') || c == '.')
                goto numb;
        if(ralpha(c))
                goto alpha;
        switch(c) {
        case ' ':
        case '\t':
                c = line[linep++];
                goto loop;
        case L'×':
                return '*';
        case L'÷':
                return '/';
        case L'¹':
        case L'ⁱ':
                yylval.numb = 1;
                return SUP;
        case L'²':
        case L'⁲':
                yylval.numb = 2;
                return SUP;
        case L'³':
        case L'⁳':
                yylval.numb = 3;
                return SUP;
        }
        return c;

alpha:
        memset(sym, 0, sizeof(sym));
        for(i=0;; i++) {
                if(i < nelem(sym))
                        sym[i] = c;
                c = line[linep++];
                if(!ralpha(c))
                        break;
        }
        sym[nelem(sym)-1] = 0;
        peekrune = c;
        yylval.var = lookup(0);
        return VAR;

numb:
        digval = c;
        yylval.val = charstod(gdigit, 0);
        return VAL;
}

void
main(int argc, char *argv[])
{
        char *file;

        ARGBEGIN {
        default:
                print("usage: units [-v] [file]\n");
                exits("usage");
        case 'v':
                vflag = 1;
                break;
        } ARGEND

        file = "/lib/units";
        if(argc > 0)
                file = argv[0];
        fi = Bopen(file, OREAD);
        if(fi == 0) {
                print("cant open: %s\n", file);
                exits("open");
        }
        fmtinstall('U', Ufmt);
        one.val = 1;

        /*
         * read the 'units' file to
         * develope a database
         */
        lineno = 0;
        for(;;) {
                lineno++;
                if(readline())
                        break;
                if(line[0] == 0 || line[0] == '/')
                        continue;
                peekrune = ':';
                yyparse();
        }

        /*
         * read the console to
         * print ratio of pairs
         */
        Bterm(fi);
        fi = &linebuf;
        Binit(fi, 0, OREAD);
        lineno = 0;
        for(;;) {
                if(lineno & 1)
                        print("you want: ");
                else
                        print("you have: ");
                if(readline())
                        break;
                peekrune = '?';
                nerrors = 0;
                yyparse();
                if(nerrors)
                        continue;
                if(lineno & 1) {
                        if(specialcase(&retnode, &retnode2, &retnode1))
                                print("\tis %U\n", &retnode);
                        else {
                                div(&retnode, &retnode2, &retnode1);
                                print("\t* %U\n", &retnode);
                                div(&retnode, &retnode1, &retnode2);
                                print("\t/ %U\n", &retnode);
                        }
                } else
                        retnode2 = retnode1;
                lineno++;
        }
        print("\n");
        exits(0);
}

/*
 * all characters that have some
 * meaning. rest are usable as names
 */
int
ralpha(int c)
{
        switch(c) {
        case 0:
        case '+':
        case '-':
        case '*':
        case '/':
        case '[':
        case ']':
        case '(':
        case ')':
        case '^':
        case ':':
        case '?':
        case ' ':
        case '\t':
        case '.':
        case '|':
        case '#':
        case L'¹':
        case L'ⁱ':
        case L'²':
        case L'⁲':
        case L'³':
        case L'⁳':
        case L'×':
        case L'÷':
                return 0;
        }
        return 1;
}

int
gdigit(void*)
{
        int c;

        c = digval;
        if(c) {
                digval = 0;
                return c;
        }
        c = line[linep++];
        peekrune = c;
        return c;
}

void
yyerror(char *fmt, ...)
{
        va_list arg;

        /*
         * hack to intercept message from yaccpar
         */
        if(strcmp(fmt, "syntax error") == 0) {
                yyerror("syntax error, last name: %S", sym);
                return;
        }
        va_start(arg, fmt);
        vseprint(buf, buf+sizeof(buf), fmt, arg);
        va_end(arg);
        print("%ld: %S\n\t%s\n", lineno, line, buf);
        nerrors++;
        if(nerrors > 5) {
                print("too many errors\n");
                exits("errors");
        }
}

void
add(Node *c, Node *a, Node *b)
{
        int i, d;

        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");
        }
        c->val = fadd(a->val, b->val);
}

void
sub(Node *c, Node *a, Node *b)
{
        int i, d;

        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");
        }
        c->val = fadd(a->val, -b->val);
}

void
mul(Node *c, Node *a, Node *b)
{
        int i;

        for(i=0; i<Ndim; i++)
                c->dim[i] = a->dim[i] + b->dim[i];
        c->val = fmul(a->val, b->val);
}

void
div(Node *c, Node *a, Node *b)
{
        int i;

        for(i=0; i<Ndim; i++)
                c->dim[i] = a->dim[i] - b->dim[i];
        c->val = fdiv(a->val, b->val);
}

void
xpn(Node *c, Node *a, int b)
{
        int i;

        *c = one;
        if(b < 0) {
                b = -b;
                for(i=0; i<b; i++)
                        div(c, c, a);
        } else
        for(i=0; i<b; i++)
                mul(c, c, a);
}

int
specialcase(Node *c, Node *a, Node *b)
{
        int i, d, d1, d2;

        d1 = 0;
        d2 = 0;
        for(i=1; i<Ndim; i++) {
                d = a->dim[i];
                if(d) {
                        if(d != 1 || d1)
                                return 0;
                        d1 = i;
                }
                d = b->dim[i];
                if(d) {
                        if(d != 1 || d2)
                                return 0;
                        d2 = i;
                }
        }
        if(d1 == 0 || d2 == 0)
                return 0;

        if(memcmp(fund[d1]->name, L"°C", 3*sizeof(Rune)) == 0 &&
           memcmp(fund[d2]->name, L"°F", 3*sizeof(Rune)) == 0 &&
           b->val == 1) {
                memcpy(c->dim, b->dim, sizeof(c->dim));
                c->val = a->val * 9. / 5. + 32.;
                return 1;
        }

        if(memcmp(fund[d1]->name, L"°F", 3*sizeof(Rune)) == 0 &&
           memcmp(fund[d2]->name, L"°C", 3*sizeof(Rune)) == 0 &&
           b->val == 1) {
                memcpy(c->dim, b->dim, sizeof(c->dim));
                c->val = (a->val - 32.) * 5. / 9.;
                return 1;
        }
        return 0;
}

void
printdim(char *str, int d, int n)
{
        Var *v;

        if(n) {
                v = fund[d];
                if(v)
                        sprint(strchr(str, 0), " %S", v->name);
                else
                        sprint(strchr(str, 0), " [%d]", d);
                switch(n) {
                case 1:
                        break;
                case 2:
                        strcat(str, "²");
                        break;
                case 3:
                        strcat(str, "³");
                        break;
                default:
                        sprint(strchr(str, 0), "^%d", n);
                }
        }
}

int
Ufmt(Fmt *fp)
{
        char str[200];
        Node *n;
        int f, i, d;

        n = va_arg(fp->args, Node*);
        sprint(str, "%g", n->val);

        f = 0;
        for(i=1; i<Ndim; i++) {
                d = n->dim[i];
                if(d > 0)
                        printdim(str, i, d);
                else
                if(d < 0)
                        f = 1;
        }

        if(f) {
                strcat(str, " /");
                for(i=1; i<Ndim; i++) {
                        d = n->dim[i];
                        if(d < 0)
                                printdim(str, i, -d);
                }
        }

        return fmtstrcpy(fp, str);
}

int
readline(void)
{
        int i, c;

        linep = 0;
        for(i=0;; i++) {
                c = Bgetrune(fi);
                if(c < 0)
                        return 1;
                if(c == '\n')
                        break;
                if(i < nelem(line))
                        line[i] = c;
        }
        if(i >= nelem(line))
                i = nelem(line)-1;
        line[i] = 0;
        return 0;
}

Var*
lookup(int f)
{
        int i;
        Var *v, *w;
        double p;
        ulong h;

        h = 0;
        for(i=0; sym[i]; i++)
                h = h*13 + sym[i];
        h %= nelem(vars);

        for(v=vars[h]; v; v=v->link)
                if(memcmp(sym, v->name, sizeof(sym)) == 0)
                        return v;
        if(f)
                return 0;
        v = malloc(sizeof(*v));
        if(v == nil) {
                fprint(2, "out of memory\n");
                exits("mem");
        }
        memset(v, 0, sizeof(*v));
        memcpy(v->name, sym, sizeof(sym));
        v->link = vars[h];
        vars[h] = v;

        p = 1;
        for(;;) {
                p = fmul(p, pname());
                if(p == 0)
                        break;
                w = lookup(1);
                if(w) {
                        v->node = w->node;
                        v->node.val = fmul(v->node.val, p);
                        break;
                }
        }
        return v;
}

Prefix  prefix[] =
{
        1e-24,  L"yocto",
        1e-21,  L"zepto",
        1e-18,  L"atto",
        1e-15,  L"femto",
        1e-12,  L"pico",
        1e-9,   L"nano",
        1e-6,   L"micro",
        1e-6,   L"μ",
        1e-3,   L"milli",
        1e-2,   L"centi",
        1e-1,   L"deci",
        1e1,    L"deka",
        1e2,    L"hecta",
        1e2,    L"hecto",
        1e3,    L"kilo",
        1e6,    L"mega",
        1e6,    L"meg",
        1e9,    L"giga",
        1e12,   L"tera",
        1e15,   L"peta",
        1e18,   L"exa",
        1e21,   L"zetta",
        1e24,   L"yotta",
        0,      0
};

double
pname(void)
{
        Rune *p;
        int i, j, c;

        /*
         * rip off normal prefixs
         */
        for(i=0; p=prefix[i].pname; i++) {
                for(j=0; c=p[j]; j++)
                        if(c != sym[j])
                                goto no;
                memmove(sym, sym+j, (Nsym-j)*sizeof(*sym));
                memset(sym+(Nsym-j), 0, j*sizeof(*sym));
                return prefix[i].val;
        no:;
        }

        /*
         * rip off 's' suffixes
         */
        for(j=0; sym[j]; j++)
                ;
        j--;
        /* j>1 is special hack to disallow ms finding m */
        if(j > 1 && sym[j] == 's') {
                sym[j] = 0;
                return 1;
        }
        return 0;
}

/*
 * careful floating point
 */
double
fmul(double a, double b)
{
        double l;

        if(a <= 0) {
                if(a == 0)
                        return 0;
                l = log(-a);
        } else
                l = log(a);

        if(b <= 0) {
                if(b == 0)
                        return 0;
                l += log(-b);
        } else
                l += log(b);

        if(l > Maxe) {
                yyerror("overflow in multiply");
                return 1;
        }
        if(l < -Maxe) {
                yyerror("underflow in multiply");
                return 0;
        }
        return a*b;
}

double
fdiv(double a, double b)
{
        double l;

        if(a <= 0) {
                if(a == 0)
                        return 0;
                l = log(-a);
        } else
                l = log(a);

        if(b <= 0) {
                if(b == 0) {
                        yyerror("division by zero");
                        return 1;
                }
                l -= log(-b);
        } else
                l -= log(b);

        if(l > Maxe) {
                yyerror("overflow in divide");
                return 1;
        }
        if(l < -Maxe) {
                yyerror("underflow in divide");
                return 0;
        }
        return a/b;
}

double
fadd(double a, double b)
{
        return a + b;
}