// -*-go-*-
//
// Reverse Polish Notation Calculator
// Copyright (c) 2010 J.A. Roberts Tunney
// MIT License
//
// To compile:
//
//   ragel -Z -T0 -o rpn.go rpn.rl
//   go build -o rpn rpn.go
//   ./rpn
//
// To show a diagram of your state machine:
//
//   ragel -V -Z -p -o rpn.dot rpn.rl
//   xdot -Tpng -o rpn.png rpn.dot
//

package main

import (
  "errors"
	"fmt"
  "os"
	"strconv"
)

type stack struct {
	items []int
	count int
}

func (s *stack) pop() int {
	s.count--
	v := s.items[s.count]
	return v
}

func (s *stack) push(v int) {
	s.items[s.count] = v
	s.count++
}

func abs(v int) int {
	if v < 0 {
		v = -v
	}
	return v
}

%% machine rpn;
%% write data;

func rpn(data string) (res int, err error) {
	// p, pe, eof := 0, len(data), len(data)
	cs, p, pe := 0, 0, len(data)
	mark := 0
	st := &stack{items: make([]int, 128), count: 0}

	%%{
		action mark { mark = p }
		action push { x, _ := strconv.Atoi(data[mark:p]); st.push(x) }
		action add  { y, x := st.pop(), st.pop(); st.push(x + y) }
		action sub  { y, x := st.pop(), st.pop(); st.push(x - y) }
		action mul  { y, x := st.pop(), st.pop(); st.push(x * y) }
		action div  { y, x := st.pop(), st.pop(); st.push(x / y) }
		action abs  { st.push(abs(st.pop())) }
		action abba { st.push(666) }

		stuff  = digit+ >mark %push
		       | '+' @add
		       | '-' @sub
		       | '*' @mul
		       | '/' @div
		       | 'abs' %abs
		       | 'add' %add
		       | 'abba' %abba
		       ;

		main := ( space | stuff space )* ;

		write init;
		write exec;
	}%%

	if cs < rpn_first_final {
		if p == pe {
			return 0, errors.New("unexpected eof")
		} else {
			return 0, errors.New(fmt.Sprintf("error at position %d", p))
		}
	}

	if st.count == 0 {
		return 0, errors.New("rpn stack empty on result")
	}

	return st.pop(), nil
}

//////////////////////////////////////////////////////////////////////

type rpnTest struct {
	s string
	v int
}

var rpnTests = []rpnTest{
	rpnTest{"666\n", 666},
	rpnTest{"666 111\n", 111},
	rpnTest{"4 3 add\n", 7},
	rpnTest{"4 3 +\n", 7},
	rpnTest{"4 3 -\n", 1},
	rpnTest{"4 3 *\n", 12},
	rpnTest{"6 2 /\n", 3},
	rpnTest{"0 3 -\n", -3},
	rpnTest{"0 3 - abs\n", 3},
	rpnTest{" 2  2 + 3 - \n", 1},
	rpnTest{"10 7 3 2 * - +\n", 11},
	rpnTest{"abba abba add\n", 1332},
}

type rpnFailTest struct {
	s string
	e string
}

var rpnFailTests = []rpnFailTest{
	rpnFailTest{"\n", "rpn stack empty on result"},
}

func main() {
	rc := 0

	for _, test := range rpnTests {
		res, err := rpn(test.s)
		if err != nil {
			fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) %s\n", test.s, err)
			rc = 1
		} else if res != test.v {
			fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) -> %#v != %#v\n",
				test.s, res, test.v)
			rc = 1
		}
	}

	for _, test := range rpnFailTests {
		res, err := rpn(test.s)
		if err == nil {
			fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) -> %#v should fail: %#v\n",
				test.s, res, test.e)
		} else if err.Error() != test.e {
			fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) %#v should be %#v\n",
				test.s, err.Error(), test.e)
		}
	}

	os.Exit(rc)
}
