// 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	<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;
	}
%%

func
Lex() 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", sym);
		f = 0;
	}
	yylval.vval = f;
	return VAL;
}

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.Printf("error opening %v: %v", 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 = ':';
		Parse();
	}

	/*
	 * 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;
		Parse();
		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 ...)
{

	/*
	 * hack to intercept message from yaccpar
	 */
	if s == "syntax error" {
		Error("syntax error, last name: %v", sym);
		return;
	}
	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;
}
