// 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 %{ // 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 prog expr expr0 expr1 expr2 expr3 expr4 %token VAL %token VAR %token 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 { 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 = 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 *yySymType) 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 = ':' yyParse(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 yyParse(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 }