11 Commits

Author SHA1 Message Date
T.v.Dein
e81be12b19 Merge pull request #31 from TLINDEN/internal/fuzzytesting
* reorganized Eval() to return errors and call EvalItem() on each item
* fix negative shift amount error, found with fuzzy testing :)
* added fuzzy testing
2023-12-08 18:43:10 +01:00
222dc3a734 added fuzzy testing 2023-12-08 18:37:59 +01:00
49e01565b9 catch exec errors 2023-12-08 18:37:35 +01:00
e4a8af9b5b fix negative shift amount error, found with fuzzy testing :) 2023-12-08 18:36:33 +01:00
ac9d08d6fc reorganized Eval() return errors and call EvalItem() on each item 2023-12-08 18:35:56 +01:00
T.v.Dein
cb774b3b80 added commandline and stdin tests using testscript (#28)
* added commandline and stdin tests using testscript

---------

Co-authored-by: Thomas von Dein <tom@vondein.org>
2023-12-07 14:09:42 +01:00
T.v.Dein
846b3e63fc don't show shortcuts in help (clutters it) (#27)
* don't show shortcuts in help (clutters it)

* bump version

---------

Co-authored-by: Thomas von Dein <tom@vondein.org>
2023-12-07 13:47:32 +01:00
T.v.Dein
5557ad5f99 use generics for contains() and add generic exists() (#29)
Co-authored-by: Thomas von Dein <tom@vondein.org>
2023-12-07 13:47:04 +01:00
T.v.Dein
d2db420837 Merge pull request #25 from TLINDEN/doc/fix-formating
fix pod formatting (fixes #24)
2023-12-05 20:09:42 +01:00
T.v.Dein
b4f53d2dd6 Merge pull request #26 from TLINDEN/feature/add-shortcuts
added a couple of command shortcuts
2023-12-05 20:06:05 +01:00
4c6caa7114 fix pod formatting (fixes #24) 2023-12-04 18:03:58 +01:00
28 changed files with 372 additions and 129 deletions

View File

@@ -51,14 +51,17 @@ 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 rm -rf $(tool) coverage.out testdata
test: test: clean
go test -v ./... go test ./... $(ARGS)
testfuzzy: clean
go test -fuzz ./... $(ARGS)
singletest: singletest:
@echo "Call like this: ''make singletest TEST=TestPrepareColumns" @echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v"
go test -run $(TEST) go test -run $(TEST) $(ARGS)
cover-report: cover-report:
go test ./... -cover -coverprofile=coverage.out go test ./... -cover -coverprofile=coverage.out

206
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) { func (c *Calc) Eval(line string) error {
// 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 return nil
} }
items := c.Space.Split(line, -1) items := c.Space.Split(line, -1)
@@ -239,100 +239,8 @@ func (c *Calc) Eval(line string) {
c.notdone = false c.notdone = false
} }
num, err := strconv.ParseFloat(item, 64) if err := c.EvalItem(item); err != nil {
return err
if err == nil {
c.stack.Backup()
c.stack.Push(num)
} else {
// try hex
var i int
_, err := fmt.Sscanf(item, "0x%x", &i)
if err == nil {
c.stack.Backup()
c.stack.Push(float64(i))
continue
}
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
continue
}
if _, ok := c.Funcalls[item]; ok {
if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
}
if c.batch {
if _, ok := c.BatchFuncalls[item]; ok {
if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
}
} else {
if _, ok := c.BatchFuncalls[item]; ok {
fmt.Println("only supported in batch mode")
continue
}
}
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.EvalLuaFunction(item)
continue
}
regmatches := c.Register.FindStringSubmatch(item)
if len(regmatches) == 3 {
switch regmatches[1] {
case ">":
c.PutVar(regmatches[2])
case "<":
c.GetVar(regmatches[2])
}
continue
}
// internal commands
if _, ok := c.Commands[item]; ok {
c.Commands[item].Func(c)
continue
}
if _, ok := c.ShowCommands[item]; ok {
c.ShowCommands[item].Func(c)
continue
}
if _, ok := c.StackCommands[item]; ok {
c.StackCommands[item].Func(c)
continue
}
if _, ok := c.SettingsCommands[item]; ok {
c.SettingsCommands[item].Func(c)
continue
}
switch item {
case "?":
fallthrough
case "help":
c.PrintHelp()
default:
fmt.Println("unknown command or operator!")
}
} }
} }
@@ -345,6 +253,106 @@ func (c *Calc) Eval(line string) {
last := c.stack.Last(5) last := c.stack.Last(5)
fmt.Printf("stack: %s%s\n", dots, list2str(last)) fmt.Printf("stack: %s%s\n", dots, list2str(last))
} }
return nil
}
func (c *Calc) EvalItem(item string) error {
num, err := strconv.ParseFloat(item, 64)
if err == nil {
c.stack.Backup()
c.stack.Push(num)
} else {
// try hex
var i int
_, err := fmt.Sscanf(item, "0x%x", &i)
if err == nil {
c.stack.Backup()
c.stack.Push(float64(i))
return nil
}
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
return nil
}
if exists(c.Funcalls, item) {
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
} else {
c.Result()
}
return nil
}
if exists(c.BatchFuncalls, item) {
if !c.batch {
return Error("only supported in batch mode")
}
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
} else {
c.Result()
}
return nil
}
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.EvalLuaFunction(item)
return nil
}
regmatches := c.Register.FindStringSubmatch(item)
if len(regmatches) == 3 {
switch regmatches[1] {
case ">":
c.PutVar(regmatches[2])
case "<":
c.GetVar(regmatches[2])
}
return nil
}
// internal commands
// FIXME: propagate errors
if exists(c.Commands, item) {
c.Commands[item].Func(c)
return nil
}
if exists(c.ShowCommands, item) {
c.ShowCommands[item].Func(c)
return nil
}
if exists(c.StackCommands, item) {
c.StackCommands[item].Func(c)
return nil
}
if exists(c.SettingsCommands, item) {
c.SettingsCommands[item].Func(c)
return nil
}
switch item {
case "?":
fallthrough
case "help":
c.PrintHelp()
default:
return Error("unknown command or operator")
}
}
return nil
} }
// Execute a math function, check if it is defined just in case // Execute a math function, check if it is defined just in case
@@ -357,7 +365,7 @@ func (c *Calc) DoFuncall(funcname string) error {
} }
if function == nil { if function == nil {
panic("function not defined but in completion list") return Error("function not defined but in completion list")
} }
var args Numbers var args Numbers
@@ -507,7 +515,7 @@ func (c *Calc) PutVar(name string) {
} }
func (c *Calc) GetVar(name string) { func (c *Calc) GetVar(name string) {
if _, ok := c.Vars[name]; ok { if exists(c.Vars, name) {
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])
@@ -520,8 +528,10 @@ 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,6 +19,8 @@ package main
import ( import (
"fmt" "fmt"
"strconv"
"strings"
"testing" "testing"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
@@ -75,7 +77,9 @@ 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 {
calc.Eval(line) if err := calc.Eval(line); err != nil {
t.Errorf(err.Error())
}
} }
got := calc.stack.Last() got := calc.stack.Last()
@@ -288,7 +292,9 @@ 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
calc.Eval(tt.cmd) if err := calc.Eval(tt.cmd); err != nil {
t.Errorf(err.Error())
}
got := calc.Result() got := calc.Result()
calc.stack.Clear() calc.stack.Clear()
if got != tt.exp { if got != tt.exp {
@@ -350,3 +356,60 @@ 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,12 +457,20 @@ 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,7 +4,9 @@ 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-20220310020820-b874c991c1a5 // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/tools v0.1.12 // indirect
) )

6
go.sum
View File

@@ -2,9 +2,15 @@ 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.11" const VERSION string = "2.0.13"
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,6 +42,7 @@ 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
@@ -51,6 +52,10 @@ 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
@@ -74,12 +79,12 @@ func main() {
if showversion { if showversion {
fmt.Printf("This is rpn version %s\n", VERSION) fmt.Printf("This is rpn version %s\n", VERSION)
return return 0
} }
if showhelp { if showhelp {
fmt.Println(Usage) fmt.Println(Usage)
return return 0
} }
if enabledebug { if enabledebug {
@@ -88,7 +93,7 @@ func main() {
if showmanual { if showmanual {
man() man()
os.Exit(0) return 0
} }
// the lua state object is global, instanciate it early // the lua state object is global, instanciate it early
@@ -101,14 +106,25 @@ func main() {
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
calc.Eval(strings.Join(flag.Args(), " ")) if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil {
return fmt.Println(err)
return 1
}
return 0
} }
// interactive mode, need readline // interactive mode, need readline
@@ -141,7 +157,10 @@ func main() {
break break
} }
calc.Eval(line) err = calc.Eval(line)
if err != nil {
fmt.Println(err)
}
rl.SetPrompt(calc.Prompt()) rl.SetPrompt(calc.Prompt())
} }
@@ -150,10 +169,15 @@ func main() {
// 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
calc.Eval(flag.Args()[0]) if err = calc.Eval(flag.Args()[0]); err != nil {
fmt.Println(err)
return 1
} }
} }
return 0
}
func inputIsStdin() bool { func inputIsStdin() bool {
stat, _ := os.Stdin.Stat() stat, _ := os.Stdin.Stat()
return (stat.Mode() & os.ModeCharDevice) == 0 return (stat.Mode() & os.ModeCharDevice) == 0

20
main_test.go Normal file
View File

@@ -0,0 +1,20 @@
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",
})
}

28
rpn.go
View File

@@ -178,17 +178,27 @@ DESCRIPTION
[no]debug toggle debug output (nodebug turns it off) [no]debug toggle debug output (nodebug turns it off)
[no]showstack show the last 5 items of the stack (noshowtack turns it off) [no]showstack show the last 5 items of the stack (noshowtack turns it off)
Show commands: dump display the stack contents hex show last stack item Show commands:
in hex form (converted to int) history display calculation history vars
show list of variables
Stack manipulation commands: clear clear the whole stack shift remove dump display the stack contents
the last element of the stack reverse reverse the stack elements swap hex show last stack item in hex form (converted to int)
exchange the last two stack elements dup duplicate last stack item undo history display calculation history
undo last operation edit edit the stack interactively using vi or vars show list of variables
$EDITOR
Other commands: help|? show this message manual show manual Stack manipulation commands:
clear clear the whole stack
shift remove the last element of the stack
reverse reverse the stack elements
swap exchange the last two stack elements
dup duplicate last stack item
undo undo last operation
edit edit the stack interactively using vi or $EDITOR
Other commands:
help|? show this message
manual show manual
quit|exit|c-d|c-c exit program quit|exit|c-d|c-c exit program
Register variables: Register variables:

View File

@@ -186,12 +186,14 @@ Configuration Commands:
[no]showstack show the last 5 items of the stack (noshowtack turns it off) [no]showstack show the last 5 items of the stack (noshowtack turns it off)
Show commands: Show commands:
dump display the stack contents dump display the stack contents
hex show last stack item in hex form (converted to int) hex show last stack item in hex form (converted to int)
history display calculation history history display calculation history
vars show list of variables vars show list of variables
Stack manipulation commands: Stack manipulation commands:
clear clear the whole stack clear clear the whole stack
shift remove the last element of the stack shift remove the last element of the stack
reverse reverse the stack elements reverse reverse the stack elements
@@ -201,6 +203,7 @@ Stack manipulation commands:
edit edit the stack interactively using vi or $EDITOR edit edit the stack interactively using vi or $EDITOR
Other commands: Other commands:
help|? show this message help|? show this message
manual show manual manual show manual
quit|exit|c-d|c-c exit program quit|exit|c-d|c-c exit program

2
t/cmdline-command.txtar Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

16
t/cmdlinecalc-lua.txtar Normal file
View File

@@ -0,0 +1,16 @@
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

2
t/cmdlinecalc.txtar Normal file
View File

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

2
t/getman.txtar Normal file
View File

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

2
t/getusage.txtar Normal file
View File

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

2
t/getversion.txtar Normal file
View File

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

4
t/stdin-batch-cmd.txtar Normal file
View File

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

4
t/stdin-batch.txtar Normal file
View File

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

4
t/stdin-calc.txtar Normal file
View File

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

6
t/stdin-help.txtar Normal file
View File

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

13
t/stdin-use-vars.txtar Normal file
View File

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

View File

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

13
t/test.lua Normal file
View File

@@ -0,0 +1,13 @@
-- 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,16 +23,24 @@ import (
"strings" "strings"
) )
// find an item in a list // find an item in a list, generic variant
func contains(s []string, e string) bool { func contains[E comparable](s []E, v E) bool {
for _, a := range s { for _, vs := range s {
if a == e { if v == vs {
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":
@@ -63,3 +71,7 @@ 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)
}