mirror of
https://codeberg.org/scip/rpnc.git
synced 2025-12-17 12:31:04 +01:00
started work on porting it to golang
This commit is contained in:
155
go/calc.go
Normal file
155
go/calc.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Calc struct {
|
||||||
|
debug bool
|
||||||
|
batch bool
|
||||||
|
stdin bool
|
||||||
|
stack *Stack
|
||||||
|
completer readline.AutoCompleter
|
||||||
|
}
|
||||||
|
|
||||||
|
const Help string = `Available commands:
|
||||||
|
batch enable batch mode
|
||||||
|
debug enable debug output
|
||||||
|
dump display the stack contents
|
||||||
|
clear clear the whole stack
|
||||||
|
shift remove the last element of the stack
|
||||||
|
help show this message
|
||||||
|
|
||||||
|
Available operators:
|
||||||
|
basic operators: + - * /
|
||||||
|
`
|
||||||
|
|
||||||
|
func NewCalc() *Calc {
|
||||||
|
c := Calc{stack: NewStack(), debug: false}
|
||||||
|
|
||||||
|
c.completer = readline.NewPrefixCompleter(
|
||||||
|
readline.PcItem("dump"),
|
||||||
|
readline.PcItem("debug"),
|
||||||
|
readline.PcItem("clear"),
|
||||||
|
readline.PcItem("batch"),
|
||||||
|
readline.PcItem("shift"),
|
||||||
|
readline.PcItem("undo"),
|
||||||
|
readline.PcItem("help"),
|
||||||
|
readline.PcItem("+"),
|
||||||
|
readline.PcItem("-"),
|
||||||
|
readline.PcItem("*"),
|
||||||
|
readline.PcItem("/"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) ToggleDebug() {
|
||||||
|
c.debug = !c.debug
|
||||||
|
c.stack.ToggleDebug()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) ToggleBatch() {
|
||||||
|
c.batch = !c.batch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) ToggleStdin() {
|
||||||
|
c.stdin = !c.stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) Eval(line string) {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
simple := regexp.MustCompile(`[\+\-\*\/]`)
|
||||||
|
|
||||||
|
if line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range space.Split(line, -1) {
|
||||||
|
num, err := strconv.ParseFloat(item, 64)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Push(num)
|
||||||
|
} else {
|
||||||
|
if simple.MatchString(line) {
|
||||||
|
c.simple(item[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case "help":
|
||||||
|
fmt.Println(Help)
|
||||||
|
case "dump":
|
||||||
|
c.stack.Dump()
|
||||||
|
case "debug":
|
||||||
|
c.ToggleDebug()
|
||||||
|
case "batch":
|
||||||
|
c.ToggleBatch()
|
||||||
|
case "clear":
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Clear()
|
||||||
|
case "shift":
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Shift()
|
||||||
|
case "undo":
|
||||||
|
c.stack.Restore()
|
||||||
|
default:
|
||||||
|
fmt.Println("unknown command or operator!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) Result() {
|
||||||
|
if !c.stdin {
|
||||||
|
fmt.Print("= ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(c.stack.Last())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) simple(op byte) {
|
||||||
|
c.stack.Backup()
|
||||||
|
|
||||||
|
for c.stack.Len() > 1 {
|
||||||
|
b := c.stack.Pop()
|
||||||
|
a := c.stack.Pop()
|
||||||
|
var x float64
|
||||||
|
|
||||||
|
if c.debug {
|
||||||
|
fmt.Printf("DEBUG: evaluating: %.2f %c %.2f\n", a, op, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case '+':
|
||||||
|
x = a + b
|
||||||
|
case '-':
|
||||||
|
x = a - b
|
||||||
|
case '*':
|
||||||
|
x = a * b
|
||||||
|
case '/':
|
||||||
|
if b == 0 {
|
||||||
|
fmt.Println("error: division by null!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x = a / b
|
||||||
|
default:
|
||||||
|
panic("invalid operator!")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.stack.Push(x)
|
||||||
|
|
||||||
|
if !c.batch {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Result()
|
||||||
|
}
|
||||||
9
go/go.mod
Normal file
9
go/go.mod
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module rpn
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/chzyer/readline v1.5.1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
||||||
|
)
|
||||||
8
go/go.sum
Normal file
8
go/go.sum
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
|
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
||||||
|
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||||
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
||||||
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
93
go/main.go
Normal file
93
go/main.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
const VERSION string = "0.0.1"
|
||||||
|
|
||||||
|
const Usage string = `This is rpn, a reverse polish notation calculator cli.
|
||||||
|
|
||||||
|
Usage: rpn [-bdvh] [<operator>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-b, --batchmode enable batch mode
|
||||||
|
-d, --debug enable debug mode
|
||||||
|
-v, --version show version
|
||||||
|
-h, --help show help
|
||||||
|
|
||||||
|
When <operator> is given, batch mode ist automatically enabled. Use
|
||||||
|
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
|
||||||
|
|
||||||
|
Copyright (c) 2023 T.v.Dein
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
calc := NewCalc()
|
||||||
|
|
||||||
|
showversion := false
|
||||||
|
showhelp := false
|
||||||
|
enabledebug := false
|
||||||
|
|
||||||
|
flag.BoolVarP(&calc.batch, "batchmode", "b", false, "batch mode")
|
||||||
|
flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode")
|
||||||
|
flag.BoolVarP(&showversion, "version", "v", false, "show version")
|
||||||
|
flag.BoolVarP(&showhelp, "help", "h", false, "show usage")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if showversion {
|
||||||
|
fmt.Printf("This is rpn version %s\n", VERSION)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if showhelp {
|
||||||
|
fmt.Println(Usage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabledebug {
|
||||||
|
calc.ToggleDebug()
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := readline.NewEx(&readline.Config{
|
||||||
|
Prompt: "\033[31m»\033[0m ",
|
||||||
|
HistoryFile: os.Getenv("HOME") + ".rpn-history",
|
||||||
|
HistoryLimit: 500,
|
||||||
|
AutoComplete: calc.completer,
|
||||||
|
InterruptPrompt: "^C",
|
||||||
|
EOFPrompt: "exit",
|
||||||
|
HistorySearchFold: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer rl.Close()
|
||||||
|
rl.CaptureExitSignal()
|
||||||
|
|
||||||
|
if inputIsStdin() {
|
||||||
|
calc.ToggleStdin()
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := rl.Readline()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
calc.Eval(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(flag.Args()) > 0 {
|
||||||
|
calc.Eval(flag.Args()[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inputIsStdin() bool {
|
||||||
|
stat, _ := os.Stdin.Stat()
|
||||||
|
return (stat.Mode() & os.ModeCharDevice) == 0
|
||||||
|
}
|
||||||
132
go/stack.go
Normal file
132
go/stack.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stack struct {
|
||||||
|
dll list.List
|
||||||
|
backup list.List
|
||||||
|
debug bool
|
||||||
|
rev int
|
||||||
|
backuprev int
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStack() *Stack {
|
||||||
|
return &Stack{dll: list.List{}, backup: list.List{}, rev: 0, backuprev: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Debug(msg string) {
|
||||||
|
if s.debug {
|
||||||
|
fmt.Printf("DEBUG(%03d): %s\n", s.rev, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) ToggleDebug() {
|
||||||
|
s.debug = !s.debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Bump() {
|
||||||
|
s.rev++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Push(x float64) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
s.Debug(fmt.Sprintf(" push to stack: %.2f", x))
|
||||||
|
|
||||||
|
s.Bump()
|
||||||
|
s.dll.PushBack(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Pop() float64 {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.dll.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
tail := s.dll.Back()
|
||||||
|
val := tail.Value
|
||||||
|
s.dll.Remove(tail)
|
||||||
|
|
||||||
|
s.Debug(fmt.Sprintf("DEBUG: remove from stack: %.2f", val))
|
||||||
|
|
||||||
|
s.Bump()
|
||||||
|
return val.(float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Shift() {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.dll.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tail := s.dll.Back()
|
||||||
|
s.dll.Remove(tail)
|
||||||
|
|
||||||
|
s.Debug(fmt.Sprintf("DEBUG: remove from stack: %.2f", tail.Value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Last() float64 {
|
||||||
|
if s.dll.Back() == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.dll.Back().Value.(float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Dump() {
|
||||||
|
fmt.Printf("Stack revision %d (%p):\n", s.rev, &s.dll)
|
||||||
|
for e := s.dll.Front(); e != nil; e = e.Next() {
|
||||||
|
fmt.Println(e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.debug {
|
||||||
|
fmt.Printf("Backup stack revision %d (%p):\n", s.backuprev, &s.backup)
|
||||||
|
for e := s.backup.Front(); e != nil; e = e.Next() {
|
||||||
|
fmt.Println(e.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Clear() {
|
||||||
|
s.Debug("DEBUG: clearing stack")
|
||||||
|
|
||||||
|
s.dll = list.List{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Len() int {
|
||||||
|
return s.dll.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Backup() {
|
||||||
|
// we need clean the list and restore it from scratch each time we
|
||||||
|
// make a backup, because the elements in list.List{} are pointers
|
||||||
|
// and lead to unexpected results. The methid here works reliably
|
||||||
|
// at least.
|
||||||
|
s.backup = list.List{}
|
||||||
|
for e := s.dll.Front(); e != nil; e = e.Next() {
|
||||||
|
s.backup.PushBack(e.Value)
|
||||||
|
}
|
||||||
|
s.backuprev = s.rev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Restore() {
|
||||||
|
if s.rev == 0 {
|
||||||
|
fmt.Println("error: stack is empty.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Debug(fmt.Sprintf("restoring stack to revision %d", s.backuprev))
|
||||||
|
|
||||||
|
s.rev = s.backuprev
|
||||||
|
s.dll = s.backup
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user