blob: 5d3f9aca2439150e397689123efcf06883213e81 [file] [log] [blame]
// 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
}