lots of additions

- added Makefile for building
- added math constant support
- added many more calc operators and functions
- added more stack manipulation functions
This commit is contained in:
2023-10-30 19:13:24 +01:00
parent 3b48674f2b
commit 4ace2b4385
5 changed files with 286 additions and 7 deletions

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
@@ -14,6 +15,7 @@ type Calc struct {
batch bool
stdin bool
stack *Stack
history []string
completer readline.AutoCompleter
}
@@ -23,27 +25,57 @@ debug enable debug output
dump display the stack contents
clear clear the whole stack
shift remove the last element of the stack
history display calculation history
help show this message
Available operators:
basic operators: + - * /
`
Math operators:
^ power`
func NewCalc() *Calc {
c := Calc{stack: NewStack(), debug: false}
c.completer = readline.NewPrefixCompleter(
// commands
readline.PcItem("dump"),
readline.PcItem("reverse"),
readline.PcItem("debug"),
readline.PcItem("clear"),
readline.PcItem("batch"),
readline.PcItem("shift"),
readline.PcItem("undo"),
readline.PcItem("help"),
readline.PcItem("history"),
// ops
readline.PcItem("+"),
readline.PcItem("-"),
readline.PcItem("*"),
readline.PcItem("/"),
readline.PcItem("^"),
readline.PcItem("%"),
readline.PcItem("%-"),
readline.PcItem("%+"),
// constants
readline.PcItem("Pi"),
readline.PcItem("Phi"),
readline.PcItem("Sqrt2"),
readline.PcItem("SqrtE"),
readline.PcItem("SqrtPi"),
readline.PcItem("SqrtPhi"),
readline.PcItem("Ln2"),
readline.PcItem("Log2E"),
readline.PcItem("Ln10"),
readline.PcItem("Log10E"),
// math functions
readline.PcItem("sqrt"),
readline.PcItem("remainder"),
readline.PcItem("avg"),
readline.PcItem("median"),
)
return &c
@@ -65,7 +97,11 @@ func (c *Calc) ToggleStdin() {
func (c *Calc) Eval(line string) {
line = strings.TrimSpace(line)
space := regexp.MustCompile(`\s+`)
simple := regexp.MustCompile(`[\+\-\*\/]`)
simple := regexp.MustCompile(`^[\+\-\*\/]$`)
constants := []string{"E", "Pi", "Phi", "Sqrt2", "SqrtE", "SqrtPi",
"SqrtPhi", "Ln2", "Log2E", "Ln10", "Log10E"}
functions := []string{"sqrt", "remainder", "%", "%-", "%+"}
batch := []string{"median", "avg"}
if line == "" {
return
@@ -83,6 +119,22 @@ func (c *Calc) Eval(line string) {
continue
}
if contains(constants, item) {
c.stack.Backup()
c.stack.Push(const2num(item))
continue
}
if contains(functions, item) {
c.mathfunc(item)
continue
}
if contains(batch, item) {
c.batchfunc(item)
continue
}
switch item {
case "help":
fmt.Println(Help)
@@ -98,8 +150,17 @@ func (c *Calc) Eval(line string) {
case "shift":
c.stack.Backup()
c.stack.Shift()
case "reverse":
c.stack.Backup()
c.stack.Reverse()
case "undo":
c.stack.Restore()
case "history":
for _, entry := range c.history {
fmt.Println(entry)
}
case "^":
c.exp()
default:
fmt.Println("unknown command or operator!")
}
@@ -107,12 +168,18 @@ func (c *Calc) Eval(line string) {
}
}
func (c *Calc) Result() {
func (c *Calc) History(format string, args ...any) {
c.history = append(c.history, fmt.Sprintf(format, args...))
}
func (c *Calc) Result() float64 {
if !c.stdin {
fmt.Print("= ")
}
fmt.Println(c.stack.Last())
return c.stack.Last()
}
func (c *Calc) simple(op byte) {
@@ -146,10 +213,158 @@ func (c *Calc) simple(op byte) {
c.stack.Push(x)
c.History("%f %c %f = %f", a, op, b, x)
if !c.batch {
break
}
}
c.Result()
_ = c.Result()
}
func (c *Calc) exp() {
c.stack.Backup()
for c.stack.Len() > 1 {
b := c.stack.Pop()
a := c.stack.Pop()
x := math.Pow(a, b)
c.stack.Push(x)
c.History("%f ^ %f = %f", a, b, x)
if !c.batch {
break
}
}
_ = c.Result()
}
func (c *Calc) mathfunc(funcname string) {
// FIXME: split into 2 funcs, one working with 1 the other with 2
// args, saving Pop calls
c.stack.Backup()
for c.stack.Len() > 0 {
var x float64
switch funcname {
case "sqrt":
a := c.stack.Pop()
x = math.Sqrt(a)
c.History("sqrt(%f) = %f", a, x)
case "remainder":
b := c.stack.Pop()
a := c.stack.Pop()
x = math.Remainder(a, b)
c.History("remainderf(%f / %f) = %f", a, b, x)
case "%":
b := c.stack.Pop()
a := c.stack.Pop()
x = (a / 100) * b
c.History("%f percent of %f = %f", b, a, x)
case "%-":
b := c.stack.Pop()
a := c.stack.Pop()
x = a - ((a / 100) * b)
c.History("%f minus %f percent of %f = %f", a, b, a, x)
case "%+":
b := c.stack.Pop()
a := c.stack.Pop()
x = a + ((a / 100) * b)
c.History("%f plus %f percent of %f = %f", a, b, a, x)
}
c.stack.Push(x)
if !c.batch {
break
}
}
_ = c.Result()
}
func (c *Calc) batchfunc(funcname string) {
if !c.batch {
fmt.Println("error: only available in batch mode")
}
c.stack.Backup()
var x float64
count := c.stack.Len()
switch funcname {
case "median":
all := []float64{}
for c.stack.Len() > 0 {
all = append(all, c.stack.Pop())
}
middle := count / 2
x = all[middle]
c.History("median(all)")
case "avg":
var sum float64
for c.stack.Len() > 0 {
sum += c.stack.Pop()
}
x = sum / float64(count)
c.History("avg(all)")
}
c.stack.Push(x)
_ = c.Result()
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func const2num(name string) float64 {
switch name {
case "Pi":
return math.Pi
case "Phi":
return math.Phi
case "Sqrt2":
return math.Sqrt2
case "SqrtE":
return math.SqrtE
case "SqrtPi":
return math.SqrtPi
case "SqrtPhi":
return math.SqrtPhi
case "Ln2":
return math.Ln2
case "Log2E":
return math.Log2E
case "Ln10":
return math.Ln10
case "Log10E":
return math.Log10E
default:
return 0
}
}