Compare commits

2 Commits

Author SHA1 Message Date
28abd79961 bump version 2023-12-07 13:46:34 +01:00
dd14e7ec35 don't show shortcuts in help (clutters it) 2023-12-07 13:43:45 +01:00
26 changed files with 118 additions and 346 deletions

View File

@@ -51,17 +51,14 @@ install: buildlocal
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/ install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
clean: clean:
rm -rf $(tool) coverage.out testdata rm -rf $(tool) coverage.out
test: clean test:
go test ./... $(ARGS) go test -v ./...
testfuzzy: clean
go test -fuzz ./... $(ARGS)
singletest: singletest:
@echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v" @echo "Call like this: ''make singletest TEST=TestPrepareColumns"
go test -run $(TEST) $(ARGS) go test -run $(TEST)
cover-report: cover-report:
go test ./... -cover -coverprofile=coverage.out go test ./... -cover -coverprofile=coverage.out

88
calc.go
View File

@@ -222,12 +222,12 @@ func (c *Calc) Prompt() string {
} }
// the actual work horse, evaluate a line of calc command[s] // the actual work horse, evaluate a line of calc command[s]
func (c *Calc) Eval(line string) error { func (c *Calc) Eval(line string) {
// remove surrounding whitespace and comments, if any // remove surrounding whitespace and comments, if any
line = strings.TrimSpace(c.Comment.ReplaceAllString(line, "")) line = strings.TrimSpace(c.Comment.ReplaceAllString(line, ""))
if line == "" { if line == "" {
return nil return
} }
items := c.Space.Split(line, -1) items := c.Space.Split(line, -1)
@@ -239,25 +239,6 @@ func (c *Calc) Eval(line string) error {
c.notdone = false c.notdone = false
} }
if err := c.EvalItem(item); err != nil {
return err
}
}
if c.showstack && !c.stdin {
dots := ""
if c.stack.Len() > 5 {
dots = "... "
}
last := c.stack.Last(5)
fmt.Printf("stack: %s%s\n", dots, list2str(last))
}
return nil
}
func (c *Calc) EvalItem(item string) error {
num, err := strconv.ParseFloat(item, 64) num, err := strconv.ParseFloat(item, 64)
if err == nil { if err == nil {
@@ -270,42 +251,45 @@ func (c *Calc) EvalItem(item string) error {
if err == nil { if err == nil {
c.stack.Backup() c.stack.Backup()
c.stack.Push(float64(i)) c.stack.Push(float64(i))
return nil continue
} }
if contains(c.Constants, item) { if contains(c.Constants, item) {
// put the constant onto the stack // put the constant onto the stack
c.stack.Backup() c.stack.Backup()
c.stack.Push(const2num(item)) c.stack.Push(const2num(item))
return nil continue
} }
if exists(c.Funcalls, item) { if _, ok := c.Funcalls[item]; ok {
if err := c.DoFuncall(item); err != nil { if err := c.DoFuncall(item); err != nil {
return Error(err.Error()) fmt.Println(err)
} else { } else {
c.Result() c.Result()
} }
return nil continue
}
if exists(c.BatchFuncalls, item) {
if !c.batch {
return Error("only supported in batch mode")
} }
if c.batch {
if _, ok := c.BatchFuncalls[item]; ok {
if err := c.DoFuncall(item); err != nil { if err := c.DoFuncall(item); err != nil {
return Error(err.Error()) fmt.Println(err)
} else { } else {
c.Result() c.Result()
} }
return nil continue
}
} else {
if _, ok := c.BatchFuncalls[item]; ok {
fmt.Println("only supported in batch mode")
continue
}
} }
if contains(c.LuaFunctions, item) { if contains(c.LuaFunctions, item) {
// user provided custom lua functions // user provided custom lua functions
c.EvalLuaFunction(item) c.EvalLuaFunction(item)
return nil continue
} }
regmatches := c.Register.FindStringSubmatch(item) regmatches := c.Register.FindStringSubmatch(item)
@@ -316,29 +300,28 @@ func (c *Calc) EvalItem(item string) error {
case "<": case "<":
c.GetVar(regmatches[2]) c.GetVar(regmatches[2])
} }
return nil continue
} }
// internal commands // internal commands
// FIXME: propagate errors if _, ok := c.Commands[item]; ok {
if exists(c.Commands, item) {
c.Commands[item].Func(c) c.Commands[item].Func(c)
return nil continue
} }
if exists(c.ShowCommands, item) { if _, ok := c.ShowCommands[item]; ok {
c.ShowCommands[item].Func(c) c.ShowCommands[item].Func(c)
return nil continue
} }
if exists(c.StackCommands, item) { if _, ok := c.StackCommands[item]; ok {
c.StackCommands[item].Func(c) c.StackCommands[item].Func(c)
return nil continue
} }
if exists(c.SettingsCommands, item) { if _, ok := c.SettingsCommands[item]; ok {
c.SettingsCommands[item].Func(c) c.SettingsCommands[item].Func(c)
return nil continue
} }
switch item { switch item {
@@ -348,11 +331,20 @@ func (c *Calc) EvalItem(item string) error {
c.PrintHelp() c.PrintHelp()
default: default:
return Error("unknown command or operator") fmt.Println("unknown command or operator!")
}
} }
} }
return nil if c.showstack && !c.stdin {
dots := ""
if c.stack.Len() > 5 {
dots = "... "
}
last := c.stack.Last(5)
fmt.Printf("stack: %s%s\n", dots, list2str(last))
}
} }
// Execute a math function, check if it is defined just in case // Execute a math function, check if it is defined just in case
@@ -365,7 +357,7 @@ func (c *Calc) DoFuncall(funcname string) error {
} }
if function == nil { if function == nil {
return Error("function not defined but in completion list") panic("function not defined but in completion list")
} }
var args Numbers var args Numbers
@@ -515,7 +507,7 @@ func (c *Calc) PutVar(name string) {
} }
func (c *Calc) GetVar(name string) { func (c *Calc) GetVar(name string) {
if exists(c.Vars, name) { if _, ok := c.Vars[name]; ok {
c.Debug(fmt.Sprintf("retrieve %.2f from %s", c.Vars[name], name)) c.Debug(fmt.Sprintf("retrieve %.2f from %s", c.Vars[name], name))
c.stack.Backup() c.stack.Backup()
c.stack.Push(c.Vars[name]) c.stack.Push(c.Vars[name])

View File

@@ -19,8 +19,6 @@ package main
import ( import (
"fmt" "fmt"
"strconv"
"strings"
"testing" "testing"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
@@ -77,9 +75,7 @@ func TestCommentsAndWhitespace(t *testing.T) {
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
for _, line := range tt.cmd { for _, line := range tt.cmd {
if err := calc.Eval(line); err != nil { calc.Eval(line)
t.Errorf(err.Error())
}
} }
got := calc.stack.Last() got := calc.stack.Last()
@@ -292,9 +288,7 @@ func TestCalc(t *testing.T) {
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
calc.batch = tt.batch calc.batch = tt.batch
if err := calc.Eval(tt.cmd); err != nil { calc.Eval(tt.cmd)
t.Errorf(err.Error())
}
got := calc.Result() got := calc.Result()
calc.stack.Clear() calc.stack.Clear()
if got != tt.exp { if got != tt.exp {
@@ -356,60 +350,3 @@ func TestCalcLua(t *testing.T) {
}) })
} }
} }
func FuzzEval(f *testing.F) {
legal := []string{
"dump",
"showstack",
"help",
"Pi 31 *",
"SqrtE Pi /",
"55.5 yards-to-meters",
"2 4 +",
"7 8 batch sum",
"7 8 %-",
"7 8 clear",
"7 8 /",
"b",
"#444",
"<X",
}
for _, item := range legal {
f.Add(item)
}
calc := NewCalc()
var i int
f.Fuzz(func(t *testing.T, line string) {
t.Logf("Stack:\n%v\n", calc.stack.All())
if err := calc.EvalItem(line); err == nil {
t.Logf("given: <%s>", line)
// not corpus and empty?
if !contains(legal, line) && len(line) > 0 {
item := strings.TrimSpace(calc.Comment.ReplaceAllString(line, ""))
_, hexerr := fmt.Sscanf(item, "0x%x", &i)
// no comment?
if len(item) > 0 {
// no known command or function?
if _, err := strconv.ParseFloat(item, 64); err != nil {
if !contains(calc.Constants, item) &&
!exists(calc.Funcalls, item) &&
!exists(calc.BatchFuncalls, item) &&
!contains(calc.LuaFunctions, item) &&
!exists(calc.Commands, item) &&
!exists(calc.ShowCommands, item) &&
!exists(calc.SettingsCommands, item) &&
!exists(calc.StackCommands, item) &&
!calc.Register.MatchString(item) &&
item != "?" && item != "help" &&
hexerr != nil {
t.Errorf("Fuzzy input accepted: <%s>", line)
}
}
}
}
}
})
}

View File

@@ -457,20 +457,12 @@ func DefineFunctions() Funcalls {
"<": NewFuncall( "<": NewFuncall(
func(arg Numbers) R { func(arg Numbers) R {
// Shift by negative number provibited, so check it.
// Note that we check agains uint64 overflow as well here
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewR(0, errors.New("negative shift amount"))
}
return NewR(float64(int(arg[0])<<int(arg[1])), nil) return NewR(float64(int(arg[0])<<int(arg[1])), nil)
}, },
2), 2),
">": NewFuncall( ">": NewFuncall(
func(arg Numbers) R { func(arg Numbers) R {
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewR(0, errors.New("negative shift amount"))
}
return NewR(float64(int(arg[0])>>int(arg[1])), nil) return NewR(float64(int(arg[0])>>int(arg[1])), nil)
}, },
2), 2),

4
go.mod
View File

@@ -4,9 +4,7 @@ go 1.20
require ( require (
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/yuin/gopher-lua v1.1.0 // indirect github.com/yuin/gopher-lua v1.1.0 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
golang.org/x/tools v0.1.12 // indirect
) )

6
go.sum
View File

@@ -2,15 +2,9 @@ github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwys
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= 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/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/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= 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= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

40
main.go
View File

@@ -30,7 +30,7 @@ import (
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
) )
const VERSION string = "2.0.13" const VERSION string = "2.0.12"
const Usage string = `This is rpn, a reverse polish notation calculator cli. const Usage string = `This is rpn, a reverse polish notation calculator cli.
@@ -42,7 +42,6 @@ Options:
-s, --stack show last 5 items of the stack (off by default) -s, --stack show last 5 items of the stack (off by default)
-i --intermediate print intermediate results -i --intermediate print intermediate results
-m, --manual show manual -m, --manual show manual
-c, --config <file> load <file> containing LUA code
-v, --version show version -v, --version show version
-h, --help show help -h, --help show help
@@ -52,10 +51,6 @@ this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
Copyright (c) 2023 T.v.Dein` Copyright (c) 2023 T.v.Dein`
func main() { func main() {
os.Exit(Main())
}
func Main() int {
calc := NewCalc() calc := NewCalc()
showversion := false showversion := false
@@ -79,12 +74,12 @@ func Main() int {
if showversion { if showversion {
fmt.Printf("This is rpn version %s\n", VERSION) fmt.Printf("This is rpn version %s\n", VERSION)
return 0 return
} }
if showhelp { if showhelp {
fmt.Println(Usage) fmt.Println(Usage)
return 0 return
} }
if enabledebug { if enabledebug {
@@ -93,7 +88,7 @@ func Main() int {
if showmanual { if showmanual {
man() man()
return 0 os.Exit(0)
} }
// the lua state object is global, instanciate it early // the lua state object is global, instanciate it early
@@ -106,25 +101,14 @@ func Main() int {
luarunner := NewInterpreter(configfile, enabledebug) luarunner := NewInterpreter(configfile, enabledebug)
luarunner.InitLua() luarunner.InitLua()
calc.SetInt(luarunner) calc.SetInt(luarunner)
if calc.debug {
fmt.Println("loaded config")
}
} else {
if calc.debug {
fmt.Println(err)
}
} }
if len(flag.Args()) > 1 { if len(flag.Args()) > 1 {
// commandline calc operation, no readline etc needed // commandline calc operation, no readline etc needed
// called like rpn 2 2 + // called like rpn 2 2 +
calc.stdin = true calc.stdin = true
if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil { calc.Eval(strings.Join(flag.Args(), " "))
fmt.Println(err) return
return 1
}
return 0
} }
// interactive mode, need readline // interactive mode, need readline
@@ -157,10 +141,7 @@ func Main() int {
break break
} }
err = calc.Eval(line) calc.Eval(line)
if err != nil {
fmt.Println(err)
}
rl.SetPrompt(calc.Prompt()) rl.SetPrompt(calc.Prompt())
} }
@@ -169,13 +150,8 @@ func Main() int {
// echo 1 2 3 4 | rpn + // echo 1 2 3 4 | rpn +
// batch mode enabled automatically // batch mode enabled automatically
calc.batch = true calc.batch = true
if err = calc.Eval(flag.Args()[0]); err != nil { calc.Eval(flag.Args()[0])
fmt.Println(err)
return 1
} }
}
return 0
} }
func inputIsStdin() bool { func inputIsStdin() bool {

View File

@@ -1,20 +0,0 @@
package main
import (
"os"
"testing"
"github.com/rogpeppe/go-internal/testscript"
)
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"testrpn": Main,
}))
}
func TestRpn(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "t",
})
}

View File

@@ -1,2 +0,0 @@
exec testrpn 1 2 dump
stdout 'Stack revision 2 .0x'

View File

@@ -1,2 +0,0 @@
! exec testrpn 1 2 dumb
stdout 'unknown command or operator'

View File

@@ -1,2 +0,0 @@
! exec testrpn 4 +
stdout 'stack doesn''t provide enough arguments'

View File

@@ -1,2 +0,0 @@
exec testrpn -d 44 55 *
stdout 'push to stack: 2420.00\n'

View File

@@ -1,2 +0,0 @@
! exec testrpn 100 50 50 - /
stdout 'division by null'

View File

@@ -1,16 +0,0 @@
exec testrpn -d -c test.lua 3 5 lower
stdout '3\n'
-- test.lua --
function lower(a,b)
if a < b then
return a
else
return b
end
end
function init()
-- expects 2 args
register("lower", 2, "lower")
end

View File

@@ -1,2 +0,0 @@
exec testrpn 44 55 *
stdout '2420\n'

View File

@@ -1,2 +0,0 @@
exec testrpn -m
stdout 'This software is licensed under the GNU GENERAL PUBLIC LICENSE'

View File

@@ -1,2 +0,0 @@
exec testrpn -h
stdout 'This is rpn'

View File

@@ -1,2 +0,0 @@
exec testrpn -v
stdout 'This is rpn version'

View File

@@ -1,4 +0,0 @@
exec echo 1 2 3 4 5 batch median
stdin stdout
exec testrpn
[unix] stdout '3\n'

View File

@@ -1,4 +0,0 @@
exec echo 1 2 3 4 5
stdin stdout
[unix] exec testrpn median
[unix] stdout '3\n'

View File

@@ -1,4 +0,0 @@
exec echo 10 10 +
stdin stdout
exec testrpn
[unix] stdout '20\n'

View File

@@ -1,6 +0,0 @@
stdin input.txt
exec testrpn
[unix] stdout 'Available configuration commands'
-- input.txt --
?

View File

@@ -1,13 +0,0 @@
stdin input.txt
exec testrpn
[unix] stdout '28\n'
-- input.txt --
10
10
+
>SUM
clear
8
<SUM
+

View File

@@ -1,4 +0,0 @@
exec echo 1 2 3 4 5 median
stdin stdout
exec testrpn -b
[unix] stdout '3\n'

View File

@@ -1,13 +0,0 @@
-- simple function, return the lower number of the two operands
function lower(a,b)
if a < b then
return a
else
return b
end
end
function init()
-- expects 2 args
register("lower", 2, "lower")
end

20
util.go
View File

@@ -23,24 +23,16 @@ import (
"strings" "strings"
) )
// find an item in a list, generic variant // find an item in a list
func contains[E comparable](s []E, v E) bool { func contains(s []string, e string) bool {
for _, vs := range s { for _, a := range s {
if v == vs { if a == e {
return true return true
} }
} }
return false return false
} }
// look if a key in a map exists, generic variant
func exists[K comparable, V any](m map[K]V, v K) bool {
if _, ok := m[v]; ok {
return true
}
return false
}
func const2num(name string) float64 { func const2num(name string) float64 {
switch name { switch name {
case "Pi": case "Pi":
@@ -71,7 +63,3 @@ func const2num(name string) float64 {
func list2str(list Numbers) string { func list2str(list Numbers) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]") return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]")
} }
func Error(m string) error {
return fmt.Errorf("Error: %s!", m)
}