// 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_":
//	go tool yacc -p "units_"

%{

// units.y
// example of a Go yacc program
// usage is
//	go tool yacc -p "units_" units.y (produces y.go)
//	go build -o units y.go
//	./units $GOROOT/src/cmd/yacc/units.txt
//	you have: c
//	you want: furlongs/fortnight
//		* 1.8026178e+12
//		/ 5.5474878e-13
//	you have:

package main

import (
	"flag"
	"fmt"
	"bufio"
	"os"
	"math"
	"strconv"
	"unicode/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 rune    // 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 {
			Errorf("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 {
			Errorf("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 {
			Errorf("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 rune
	var 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 int(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.ParseFloat(sym, 64)
	if err != nil {
		fmt.Printf("error converting %v\n", sym)
		f = 0
	}
	yylval.vval = f
	return VAL
}

func (UnitsLex) Error(s string) {
	Errorf("syntax error, last name: %v", sym)
}

func main() {
	var file string

	flag.BoolVar(&vflag, "v", false, "verbose")

	flag.Parse()

	if dir := os.Getenv("GOROOT"); dir != "" {
		file = dir + "/src/cmd/yacc/units.txt"
	}
	if flag.NArg() > 0 {
		file = flag.Arg(0)
	} else if file == "" {
		fmt.Fprintf(os.Stderr, "can not find data file units.txt; provide it as argument or set $GOROOT\n")
		os.Exit(1)
	}

	f, err := os.Open(file)
	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 rune) bool {
	switch c {
	case 0, '+', '-', '*', '/', '[', ']', '(', ')',
		'^', ':', '?', ' ', '\t', '.', '|', '#',
		'×', '÷', '¹', 'ⁱ', '²', '⁲', '³', '⁳':
		return false
	}
	return true
}

/*
 * number forming character
 */
func rdigit(c rune) bool {
	switch c {
	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
		'.', 'e', '+', '-':
		return true
	}
	return false
}

func Errorf(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 Error(s string) {
	Errorf("%s", s)
}

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() rune {
	var c rune
	var 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
	{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"},
}

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 {
			Errorf("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
}
