diff --git a/go/Makefile b/go/Makefile new file mode 100644 index 0000000..d00774f --- /dev/null +++ b/go/Makefile @@ -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 . + + +# +# 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 ./... diff --git a/go/calc.go b/go/calc.go index 98c10f6..e0e67d4 100644 --- a/go/calc.go +++ b/go/calc.go @@ -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 + } } diff --git a/go/main.go b/go/main.go index a82bb39..40a35e0 100644 --- a/go/main.go +++ b/go/main.go @@ -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", diff --git a/go/rpn b/go/rpn new file mode 100755 index 0000000..7ee1857 Binary files /dev/null and b/go/rpn differ diff --git a/go/stack.go b/go/stack.go index 562ec89..8ed84d8 100644 --- a/go/stack.go +++ b/go/stack.go @@ -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 +}