package main import ( "flag" "fmt" "io/ioutil" "math" "os" "strconv" "strings" "tildegit.org/sloum/nums/qline" "tildegit.org/sloum/nums/termios" ) const ( add int = iota sub mul div pow rnd flr cil abs inv sqt ) type stack struct { ptr int data [100]float64 } func (s *stack) Push(v float64) { if s.ptr >= 99 { PrintError("Stack overflow") s.Clear() return } s.ptr++ s.data[s.ptr] = v } func (s *stack) Pop() (float64, error) { if s.ptr < 0 { PrintError("Stack underflow") s.Clear() return 0.0, fmt.Errorf("Stack underflow") } s.ptr-- return s.data[s.ptr+1], nil } func (s *stack) Clear() { s.ptr = -1 } func (s stack) Len() int { return s.ptr+1 } func (s *stack) Add() { n2, err := s.Pop() if err != nil { return } n1, err := s.Pop() if err != nil { return } s.Push(n1+n2) } func (s *stack) Sub() { n2, err := s.Pop() if err != nil { return } n1, err := s.Pop() if err != nil { return } s.Push(n1-n2) } func (s *stack) Mul() { n2, err := s.Pop() if err != nil { return } n1, err := s.Pop() if err != nil { return } s.Push(n1*n2) } func (s *stack) Div() { n2, err := s.Pop() if err != nil { return } n1, err := s.Pop() if err != nil { return } s.Push(n1/n2) } func (s *stack) Pow() { exp, err := s.Pop() if err != nil { return } base, err := s.Pop() if err != nil { return } s.Push(math.Pow(base, exp)) } func (s *stack) Floor() { n, err := s.Pop() if err != nil { return } s.Push(math.Floor(n)) } func (s *stack) Ceil() { n, err := s.Pop() if err != nil { return } s.Push(math.Ceil(n)) } func (s *stack) Round() { decimals, err := s.Pop() if err != nil { return } num, err := s.Pop() if err != nil { return } s.Push(math.Round(num*math.Pow(10, decimals)) / math.Pow(10, decimals)) } func (s *stack) Abs() { n, err := s.Pop() if err != nil { return } if n < 0 { s.Push(n*-1) } else { s.Push(n) } } func (s *stack) Inv() { n, err := s.Pop() if err != nil { return } s.Push(n*-1) } func (s *stack) Sqt() { n, err := s.Pop() if err != nil { return } s.Push(math.Sqrt(n)) } func (s *stack) Dup() { val, err := s.Pop() if err != nil { return } s.Push(val) s.Push(val) } func (s *stack) Ovr() { tos, err := s.Pop() if err != nil { return } cpy, err := s.Pop() if err != nil { return } s.Push(cpy) s.Push(tos) s.Push(cpy) } func (s *stack) Swp() { tos, err := s.Pop() if err != nil { return } newTos, err := s.Pop() if err != nil { return } s.Push(tos) s.Push(newTos) } func (s *stack) Drp() { s.Pop() } func (s stack) TOS() string { if s.ptr < 0 { return "0" } return strconv.FormatFloat(s.data[s.ptr], 'f', -1, 64) } func PrintError(s string) { fmt.Fprintf(os.Stderr, "\033[1;91m[Err] %s,\033[0m\n", s) } func PrintOperatorHelp() { txt := `# General This calculator uses reverse polish notation. As such, the operators come after the operands. For example: 5 7 + 1 - The output of the above would be '11' (using infix notation this would be written ' 5 + 7 - 1'). For more information on reverse polish notation see: https://en.wikipedia.org/wiki/Reverse_Polish_notation The output of a calculation will always be the top of the stack. If there is more than one value on the stack at the end of a calculation a warning will be output to stderr. # Command List The following commands all have a three character version and a single character version. Either one is valid. They are displayed as a comma separated values (single character version first) followed by a colon and a description of the operator. The operators are case insensitive. +, add: Adds the top two values on the stack -, sub: Subtracts the top stack value from the value underneath it /, div: Divides the top two stack values with the top of stack being the divisor and the value below it on the stack being the dividend *, mul: Multiplies the top two values on the stack ^, pow: Raises the value underneath top of stack to the power of top of stack @, rnd: Rounds the value underneath top of stack to the number of decimal places represented by top of stack. If top of stack is not a whole number an error will result >, cil: Performs a ceiling function on the top of stack (rounds it up to the next whole number) <, flr: Performs a floor function on the top of stack (rounds it down to the next whole number) |, abs: Takes the top value from the stack and leaves its absolute value !, inv: Multiplies top of stack by -1 V, sqt: Takes the top value from the stack and leaves its square root The following commands operate on the stack, rather than as numerical operations: x, swp: Swap the position of the top two values on the stack #, dup: Duplicate/copy the value on top of the stack such that the copy is now above the original on the stack _, clr: Clear the stack (wipe out all values and start over) ., drp: Drop the top value on the stack (remove it and throw it away) ?, help: Prints this help message ` fmt.Fprintf(os.Stderr, "%s\033\n", txt) } func processWord(w string, s *stack) { num, err := strconv.ParseFloat(w, 64) if err == nil { s.Push(num) return } w = strings.ToLower(w) switch w { case "+", "add": s.Add() case "-", "sub": s.Sub() case "*", "mul": s.Mul() case "/", "div": s.Div() case "^", "pow": s.Pow() case "@", "rnd": s.Round() case ">", "cil": s.Ceil() case "<", "flr": s.Floor() case "|", "abs": s.Abs() case "!", "inv": s.Inv() case "V", "sqt": s.Sqt() case "#", "dup": s.Dup() case "x", "swp": s.Swp() case "_", "clr": s.Clear() case ".", "drp": s.Drp() case "?", "help": PrintOperatorHelp() s.Clear() default: PrintError(fmt.Sprintf("Unknown operator: %q", w)) s.Clear() } } func main() { pipe := flag.Bool("p", false, "Take input from a pipe") flag.Parse() s := stack{-1, [100]float64{}} if !*pipe { termios.SetInitialTermios() defer termios.Restore() termios.SetCharMode() for { var cols, _ = termios.GetWindowSize() ln := qline.GetInput("", "", cols) f := strings.Fields(ln) if len(f) == 0 { break } fmt.Print("\n") for _, val := range f { processWord(val, &s) } fmt.Fprintf(os.Stderr, "%v\n", s.data[:s.ptr+1]) } } else { b, err := ioutil.ReadAll(os.Stdin) if err != nil { fmt.Fprint(os.Stderr, "Unable to read from pipe") } f := strings.Fields(string(b)) for _, val := range f { processWord(val, &s) } if s.ptr >= 0 { fmt.Fprintf(os.Stderr, "%v\n", s.data[s.ptr]) } else { os.Exit(1) } } }