358 lines
6.4 KiB
Go
358 lines
6.4 KiB
Go
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)
|
|
}
|
|
}
|
|
|
|
}
|