3 Commits

Author SHA1 Message Date
a7fa0def04 weird windows panic 2023-12-07 14:06:11 +01:00
c9815e8ba3 not supported on windows! 2023-12-07 13:55:12 +01:00
d0376a63e3 added commandline and stdin tests using testscript 2023-12-07 13:42:41 +01:00
9 changed files with 116 additions and 222 deletions

View File

@@ -51,13 +51,10 @@ 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 ARGS=-v"

90
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])
@@ -528,10 +520,8 @@ func sortcommands(hash Commands) []string {
keys := make([]string, 0, len(hash)) keys := make([]string, 0, len(hash))
for key := range hash { for key := range hash {
if len(key) > 1 {
keys = append(keys, key) keys = append(keys, key)
} }
}
sort.Strings(keys) sort.Strings(keys)

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),

18
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.11"
const Usage string = `This is rpn, a reverse polish notation calculator cli. const Usage string = `This is rpn, a reverse polish notation calculator cli.
@@ -119,11 +119,7 @@ func Main() int {
// 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 1
}
return 0 return 0
} }
@@ -157,10 +153,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,10 +162,7 @@ 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 return 0

View File

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

View File

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

View File

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

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