| // Derived from Plan 9's /sys/src/cmd/units.y |
| // http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/units.y |
| // |
| // Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights Reserved. |
| // Portions Copyright 2009 The Go Authors. All Rights Reserved. |
| // Distributed under the terms of the Lucent Public License Version 1.02 |
| // See http://plan9.bell-labs.com/plan9/license.html |
| |
| // Generate parser with prefix "units_": |
| // goyacc -p "units_" |
| |
| %{ |
| |
| // units.y |
| // example of a goyacc program |
| // usage is |
| // goyacc units.y (produces y.go) |
| // 6g y.go |
| // 6l y.6 |
| // ./6.out $GOROOT/src/cmd/goyacc/units |
| // you have: c |
| // you want: furlongs/fortnight |
| // * 1.8026178e+12 |
| // / 5.5474878e-13 |
| // you have: |
| |
| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| "bufio" |
| "os" |
| "math" |
| "strconv" |
| "utf8" |
| ) |
| |
| const ( |
| Ndim = 15 // number of dimensions |
| Maxe = 695 // log of largest number |
| ) |
| |
| type Node struct { |
| vval float64 |
| dim [Ndim]int8 |
| } |
| |
| type Var struct { |
| name string |
| node Node |
| } |
| |
| var fi *bufio.Reader // input |
| var fund [Ndim]*Var // names of fundamental units |
| var line string // current input line |
| var lineno int // current input line number |
| var linep int // index to next rune in unput |
| var nerrors int // error count |
| var one Node // constant one |
| var peekrune int // backup runt from input |
| var retnode1 Node |
| var retnode2 Node |
| var retnode Node |
| var sym string |
| var vflag bool |
| |
| %} |
| |
| %union |
| { |
| node Node; |
| vvar *Var; |
| numb int; |
| vval float64; |
| } |
| |
| %type <node> prog expr expr0 expr1 expr2 expr3 expr4 |
| |
| %token <vval> VAL |
| %token <vvar> VAR |
| %token <numb> SUP |
| %% |
| prog: |
| ':' VAR expr |
| { |
| var f int; |
| |
| f = int($2.node.dim[0]); |
| $2.node = $3; |
| $2.node.dim[0] = 1; |
| if f != 0 { |
| Error("redefinition of %v", $2.name); |
| } else |
| if vflag { |
| fmt.Printf("%v\t%v\n", $2.name, &$2.node); |
| } |
| } |
| | ':' VAR '#' |
| { |
| var f, i int; |
| |
| for i=1; i<Ndim; i++ { |
| if fund[i] == nil { |
| break; |
| } |
| } |
| if i >= Ndim { |
| Error("too many dimensions"); |
| i = Ndim-1; |
| } |
| fund[i] = $2; |
| |
| f = int($2.node.dim[0]); |
| $2.node = one; |
| $2.node.dim[0] = 1; |
| $2.node.dim[i] = 1; |
| if f != 0 { |
| Error("redefinition of %v", $2.name); |
| } else |
| if vflag { |
| fmt.Printf("%v\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 |
| { |
| var i int; |
| |
| for i=1; i<Ndim; i++ { |
| if $3.dim[i] != 0 { |
| Error("exponent has units"); |
| $$ = $1; |
| break; |
| } |
| } |
| if i >= Ndim { |
| i = int($3.vval); |
| if float64(i) != $3.vval { |
| Error("exponent not integral"); |
| } |
| xpn(&$$, &$1, i); |
| } |
| } |
| |
| expr1: |
| expr0 |
| | expr1 '|' expr0 |
| { |
| div(&$$, &$1, &$3); |
| } |
| |
| expr0: |
| VAR |
| { |
| if $1.node.dim[0] == 0 { |
| Error("undefined %v", $1.name); |
| $$ = one; |
| } else |
| $$ = $1.node; |
| } |
| | VAL |
| { |
| $$ = one; |
| $$.vval = $1; |
| } |
| | '(' expr ')' |
| { |
| $$ = $2; |
| } |
| %% |
| |
| type UnitsLex int |
| |
| func (UnitsLex) Lex(yylval *units_SymType) int { |
| var c, i int |
| |
| c = peekrune |
| peekrune = ' ' |
| |
| loop: |
| if (c >= '0' && c <= '9') || c == '.' { |
| goto numb |
| } |
| if ralpha(c) { |
| goto alpha |
| } |
| switch c { |
| case ' ', '\t': |
| c = getrune() |
| goto loop |
| case '×': |
| return '*' |
| case '÷': |
| return '/' |
| case '¹', 'ⁱ': |
| yylval.numb = 1 |
| return SUP |
| case '²', '': |
| yylval.numb = 2 |
| return SUP |
| case '³', '': |
| yylval.numb = 3 |
| return SUP |
| } |
| return c |
| |
| alpha: |
| sym = "" |
| for i = 0; ; i++ { |
| sym += string(c) |
| c = getrune() |
| if !ralpha(c) { |
| break |
| } |
| } |
| peekrune = c |
| yylval.vvar = lookup(0) |
| return VAR |
| |
| numb: |
| sym = "" |
| for i = 0; ; i++ { |
| sym += string(c) |
| c = getrune() |
| if !rdigit(c) { |
| break |
| } |
| } |
| peekrune = c |
| f, err := strconv.Atof64(sym) |
| if err != nil { |
| fmt.Printf("error converting %v\n", sym) |
| f = 0 |
| } |
| yylval.vval = f |
| return VAL |
| } |
| |
| func (UnitsLex) Error(s string) { |
| Error("syntax error, last name: %v", sym) |
| } |
| |
| func main() { |
| var file string |
| |
| flag.BoolVar(&vflag, "v", false, "verbose") |
| |
| flag.Parse() |
| |
| file = os.Getenv("GOROOT") + "/src/cmd/goyacc/units.txt" |
| if flag.NArg() > 0 { |
| file = flag.Arg(0) |
| } |
| |
| f, err := os.Open(file, os.O_RDONLY, 0) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "error opening %v: %v\n", file, err) |
| os.Exit(1) |
| } |
| fi = bufio.NewReader(f) |
| |
| one.vval = 1 |
| |
| /* |
| * read the 'units' file to |
| * develope a database |
| */ |
| lineno = 0 |
| for { |
| lineno++ |
| if readline() { |
| break |
| } |
| if len(line) == 0 || line[0] == '/' { |
| continue |
| } |
| peekrune = ':' |
| units_Parse(UnitsLex(0)) |
| } |
| |
| /* |
| * read the console to |
| * print ratio of pairs |
| */ |
| fi = bufio.NewReader(os.NewFile(0, "stdin")) |
| |
| lineno = 0 |
| for { |
| if (lineno & 1) != 0 { |
| fmt.Printf("you want: ") |
| } else { |
| fmt.Printf("you have: ") |
| } |
| if readline() { |
| break |
| } |
| peekrune = '?' |
| nerrors = 0 |
| units_Parse(UnitsLex(0)) |
| if nerrors != 0 { |
| continue |
| } |
| if (lineno & 1) != 0 { |
| if specialcase(&retnode, &retnode2, &retnode1) { |
| fmt.Printf("\tis %v\n", &retnode) |
| } else { |
| div(&retnode, &retnode2, &retnode1) |
| fmt.Printf("\t* %v\n", &retnode) |
| div(&retnode, &retnode1, &retnode2) |
| fmt.Printf("\t/ %v\n", &retnode) |
| } |
| } else { |
| retnode2 = retnode1 |
| } |
| lineno++ |
| } |
| fmt.Printf("\n") |
| os.Exit(0) |
| } |
| |
| /* |
| * all characters that have some |
| * meaning. rest are usable as names |
| */ |
| func ralpha(c int) bool { |
| switch c { |
| case 0, '+', '-', '*', '/', '[', ']', '(', ')', |
| '^', ':', '?', ' ', '\t', '.', '|', '#', |
| '×', '÷', '¹', 'ⁱ', '²', '', '³', '': |
| return false |
| } |
| return true |
| } |
| |
| /* |
| * number forming character |
| */ |
| func rdigit(c int) bool { |
| switch c { |
| case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', |
| '.', 'e', '+', '-': |
| return true |
| } |
| return false |
| } |
| |
| func Error(s string, v ...interface{}) { |
| fmt.Printf("%v: %v\n\t", lineno, line) |
| fmt.Printf(s, v...) |
| fmt.Printf("\n") |
| |
| nerrors++ |
| if nerrors > 5 { |
| fmt.Printf("too many errors\n") |
| os.Exit(1) |
| } |
| } |
| |
| func add(c, a, b *Node) { |
| var i int |
| var d int8 |
| |
| for i = 0; i < Ndim; i++ { |
| d = a.dim[i] |
| c.dim[i] = d |
| if d != b.dim[i] { |
| Error("add must be like units") |
| } |
| } |
| c.vval = fadd(a.vval, b.vval) |
| } |
| |
| func sub(c, a, b *Node) { |
| var i int |
| var d int8 |
| |
| for i = 0; i < Ndim; i++ { |
| d = a.dim[i] |
| c.dim[i] = d |
| if d != b.dim[i] { |
| Error("sub must be like units") |
| } |
| } |
| c.vval = fadd(a.vval, -b.vval) |
| } |
| |
| func mul(c, a, b *Node) { |
| var i int |
| |
| for i = 0; i < Ndim; i++ { |
| c.dim[i] = a.dim[i] + b.dim[i] |
| } |
| c.vval = fmul(a.vval, b.vval) |
| } |
| |
| func div(c, a, b *Node) { |
| var i int |
| |
| for i = 0; i < Ndim; i++ { |
| c.dim[i] = a.dim[i] - b.dim[i] |
| } |
| c.vval = fdiv(a.vval, b.vval) |
| } |
| |
| func xpn(c, a *Node, b int) { |
| var i int |
| |
| *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) |
| } |
| } |
| } |
| |
| func specialcase(c, a, b *Node) bool { |
| var i int |
| var d, d1, d2 int8 |
| |
| d1 = 0 |
| d2 = 0 |
| for i = 1; i < Ndim; i++ { |
| d = a.dim[i] |
| if d != 0 { |
| if d != 1 || d1 != 0 { |
| return false |
| } |
| d1 = int8(i) |
| } |
| d = b.dim[i] |
| if d != 0 { |
| if d != 1 || d2 != 0 { |
| return false |
| } |
| d2 = int8(i) |
| } |
| } |
| if d1 == 0 || d2 == 0 { |
| return false |
| } |
| |
| if fund[d1].name == "°C" && fund[d2].name == "°F" && |
| b.vval == 1 { |
| for ll := 0; ll < len(c.dim); ll++ { |
| c.dim[ll] = b.dim[ll] |
| } |
| c.vval = a.vval*9./5. + 32. |
| return true |
| } |
| |
| if fund[d1].name == "°F" && fund[d2].name == "°C" && |
| b.vval == 1 { |
| for ll := 0; ll < len(c.dim); ll++ { |
| c.dim[ll] = b.dim[ll] |
| } |
| c.vval = (a.vval - 32.) * 5. / 9. |
| return true |
| } |
| return false |
| } |
| |
| func printdim(str string, d, n int) string { |
| var v *Var |
| |
| if n != 0 { |
| v = fund[d] |
| if v != nil { |
| str += fmt.Sprintf("%v", v.name) |
| } else { |
| str += fmt.Sprintf("[%d]", d) |
| } |
| switch n { |
| case 1: |
| break |
| case 2: |
| str += "²" |
| case 3: |
| str += "³" |
| default: |
| str += fmt.Sprintf("^%d", n) |
| } |
| } |
| return str |
| } |
| |
| func (n Node) String() string { |
| var str string |
| var f, i, d int |
| |
| str = fmt.Sprintf("%.7e ", n.vval) |
| |
| f = 0 |
| for i = 1; i < Ndim; i++ { |
| d = int(n.dim[i]) |
| if d > 0 { |
| str = printdim(str, i, d) |
| } else if d < 0 { |
| f = 1 |
| } |
| } |
| |
| if f != 0 { |
| str += " /" |
| for i = 1; i < Ndim; i++ { |
| d = int(n.dim[i]) |
| if d < 0 { |
| str = printdim(str, i, -d) |
| } |
| } |
| } |
| |
| return str |
| } |
| |
| func (v *Var) String() string { |
| var str string |
| str = fmt.Sprintf("%v %v", v.name, v.node) |
| return str |
| } |
| |
| func readline() bool { |
| s, err := fi.ReadString('\n') |
| if err != nil { |
| return true |
| } |
| line = s |
| linep = 0 |
| return false |
| } |
| |
| func getrune() int { |
| var c, n int |
| |
| if linep >= len(line) { |
| return 0 |
| } |
| c, n = utf8.DecodeRuneInString(line[linep:len(line)]) |
| linep += n |
| if c == '\n' { |
| c = 0 |
| } |
| return c |
| } |
| |
| var symmap = make(map[string]*Var) // symbol table |
| |
| func lookup(f int) *Var { |
| var p float64 |
| var w *Var |
| |
| v, ok := symmap[sym] |
| if ok { |
| return v |
| } |
| if f != 0 { |
| return nil |
| } |
| v = new(Var) |
| v.name = sym |
| symmap[sym] = v |
| |
| p = 1 |
| for { |
| p = fmul(p, pname()) |
| if p == 0 { |
| break |
| } |
| w = lookup(1) |
| if w != nil { |
| v.node = w.node |
| v.node.vval = fmul(v.node.vval, p) |
| break |
| } |
| } |
| return v |
| } |
| |
| type Prefix struct { |
| vval float64 |
| name string |
| } |
| |
| var prefix = []Prefix{ // prefix table |
| Prefix{1e-24, "yocto"}, |
| Prefix{1e-21, "zepto"}, |
| Prefix{1e-18, "atto"}, |
| Prefix{1e-15, "femto"}, |
| Prefix{1e-12, "pico"}, |
| Prefix{1e-9, "nano"}, |
| Prefix{1e-6, "micro"}, |
| Prefix{1e-6, "μ"}, |
| Prefix{1e-3, "milli"}, |
| Prefix{1e-2, "centi"}, |
| Prefix{1e-1, "deci"}, |
| Prefix{1e1, "deka"}, |
| Prefix{1e2, "hecta"}, |
| Prefix{1e2, "hecto"}, |
| Prefix{1e3, "kilo"}, |
| Prefix{1e6, "mega"}, |
| Prefix{1e6, "meg"}, |
| Prefix{1e9, "giga"}, |
| Prefix{1e12, "tera"}, |
| Prefix{1e15, "peta"}, |
| Prefix{1e18, "exa"}, |
| Prefix{1e21, "zetta"}, |
| Prefix{1e24, "yotta"}, |
| } |
| |
| func pname() float64 { |
| var i, j, n int |
| var s string |
| |
| /* |
| * rip off normal prefixs |
| */ |
| n = len(sym) |
| for i = 0; i < len(prefix); i++ { |
| s = prefix[i].name |
| j = len(s) |
| if j < n && sym[0:j] == s { |
| sym = sym[j:n] |
| return prefix[i].vval |
| } |
| } |
| |
| /* |
| * rip off 's' suffixes |
| */ |
| if n > 2 && sym[n-1] == 's' { |
| sym = sym[0 : n-1] |
| return 1 |
| } |
| |
| return 0 |
| } |
| |
| |
| // careful multiplication |
| // exponents (log) are checked before multiply |
| func fmul(a, b float64) float64 { |
| var l float64 |
| |
| if b <= 0 { |
| if b == 0 { |
| return 0 |
| } |
| l = math.Log(-b) |
| } else { |
| l = math.Log(b) |
| } |
| |
| if a <= 0 { |
| if a == 0 { |
| return 0 |
| } |
| l += math.Log(-a) |
| } else { |
| l += math.Log(a) |
| } |
| |
| if l > Maxe { |
| Error("overflow in multiply") |
| return 1 |
| } |
| if l < -Maxe { |
| Error("underflow in multiply") |
| return 0 |
| } |
| return a * b |
| } |
| |
| // careful division |
| // exponents (log) are checked before divide |
| func fdiv(a, b float64) float64 { |
| var l float64 |
| |
| if b <= 0 { |
| if b == 0 { |
| Error("division by zero: %v %v", a, b) |
| return 1 |
| } |
| l = math.Log(-b) |
| } else { |
| l = math.Log(b) |
| } |
| |
| if a <= 0 { |
| if a == 0 { |
| return 0 |
| } |
| l -= math.Log(-a) |
| } else { |
| l -= math.Log(a) |
| } |
| |
| if l < -Maxe { |
| Error("overflow in divide") |
| return 1 |
| } |
| if l > Maxe { |
| Error("underflow in divide") |
| return 0 |
| } |
| return a / b |
| } |
| |
| func fadd(a, b float64) float64 { |
| return a + b |
| } |