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

54
go/Makefile Normal file
View File

@@ -0,0 +1,54 @@
# Copyright © 2023 Thomas von Dein
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# no need to modify anything below
tool = rpn
VERSION = $(shell grep VERSION main.go | head -1 | cut -d '"' -f2)
archs = darwin freebsd linux windows
PREFIX = /usr/local
UID = root
GID = 0
all: buildlocal
buildlocal:
CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o $(tool)
install: buildlocal
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
clean:
rm -rf $(tool) coverage.out
test:
go test -v ./...
bash t/test.sh
singletest:
@echo "Call like this: ''make singletest TEST=TestPrepareColumns MOD=lib"
go test -run $(TEST) github.com/tlinden/rpn/$(MOD)
cover-report:
go test ./... -cover -coverprofile=coverage.out
go tool cover -html=coverage.out
goupdate:
go get -t -u=patch ./...

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
}
}

View File

@@ -55,7 +55,7 @@ func main() {
rl, err := readline.NewEx(&readline.Config{
Prompt: "\033[31m»\033[0m ",
HistoryFile: os.Getenv("HOME") + ".rpn-history",
HistoryFile: os.Getenv("HOME") + "/.rpn-history",
HistoryLimit: 500,
AutoComplete: calc.completer,
InterruptPrompt: "^C",

BIN
go/rpn Executable file

Binary file not shown.

View File

@@ -55,7 +55,7 @@ func (s *Stack) Pop() float64 {
val := tail.Value
s.dll.Remove(tail)
s.Debug(fmt.Sprintf("DEBUG: remove from stack: %.2f", val))
s.Debug(fmt.Sprintf("remove from stack: %.2f", val))
s.Bump()
return val.(float64)
@@ -72,7 +72,7 @@ func (s *Stack) Shift() {
tail := s.dll.Back()
s.dll.Remove(tail)
s.Debug(fmt.Sprintf("DEBUG: remove from stack: %.2f", tail.Value))
s.Debug(fmt.Sprintf("remove from stack: %.2f", tail.Value))
}
func (s *Stack) Last() float64 {
@@ -130,3 +130,13 @@ func (s *Stack) Restore() {
s.rev = s.backuprev
s.dll = s.backup
}
func (s *Stack) Reverse() {
newstack := list.List{}
for e := s.dll.Front(); e != nil; e = e.Next() {
newstack.PushFront(e.Value)
}
s.dll = newstack
}