3 Commits

Author SHA1 Message Date
ed7ab15a1e do error checking 2023-12-04 13:52:09 +01:00
91fac6d160 added help comment 2023-12-04 13:44:19 +01:00
a33a76bb06 add edit feature 2023-12-04 13:35:40 +01:00
29 changed files with 135 additions and 417 deletions

View File

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

98
calc.go
View File

@@ -121,28 +121,20 @@ func (c *Calc) GetCompleteCustomFuncalls() func(string) []string {
}
for command := range c.SettingsCommands {
if len(command) > 1 {
completions = append(completions, command)
}
}
for command := range c.ShowCommands {
if len(command) > 1 {
completions = append(completions, command)
}
}
for command := range c.StackCommands {
if len(command) > 1 {
completions = append(completions, command)
}
}
for command := range c.Commands {
if len(command) > 1 {
completions = append(completions, command)
}
}
return completions
}
@@ -222,12 +214,12 @@ func (c *Calc) Prompt() string {
}
// 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
line = strings.TrimSpace(c.Comment.ReplaceAllString(line, ""))
if line == "" {
return nil
return
}
items := c.Space.Split(line, -1)
@@ -239,25 +231,6 @@ func (c *Calc) Eval(line string) error {
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)
if err == nil {
@@ -270,42 +243,45 @@ func (c *Calc) EvalItem(item string) error {
if err == nil {
c.stack.Backup()
c.stack.Push(float64(i))
return nil
continue
}
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
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 {
return Error(err.Error())
fmt.Println(err)
} else {
c.Result()
}
return nil
}
if exists(c.BatchFuncalls, item) {
if !c.batch {
return Error("only supported in batch mode")
continue
}
if c.batch {
if _, ok := c.BatchFuncalls[item]; ok {
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
fmt.Println(err)
} else {
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) {
// user provided custom lua functions
c.EvalLuaFunction(item)
return nil
continue
}
regmatches := c.Register.FindStringSubmatch(item)
@@ -316,29 +292,28 @@ func (c *Calc) EvalItem(item string) error {
case "<":
c.GetVar(regmatches[2])
}
return nil
continue
}
// internal commands
// FIXME: propagate errors
if exists(c.Commands, item) {
if _, ok := c.Commands[item]; ok {
c.Commands[item].Func(c)
return nil
continue
}
if exists(c.ShowCommands, item) {
if _, ok := c.ShowCommands[item]; ok {
c.ShowCommands[item].Func(c)
return nil
continue
}
if exists(c.StackCommands, item) {
if _, ok := c.StackCommands[item]; ok {
c.StackCommands[item].Func(c)
return nil
continue
}
if exists(c.SettingsCommands, item) {
if _, ok := c.SettingsCommands[item]; ok {
c.SettingsCommands[item].Func(c)
return nil
continue
}
switch item {
@@ -348,11 +323,20 @@ func (c *Calc) EvalItem(item string) error {
c.PrintHelp()
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
@@ -365,7 +349,7 @@ func (c *Calc) DoFuncall(funcname string) error {
}
if function == nil {
return Error("function not defined but in completion list")
panic("function not defined but in completion list")
}
var args Numbers
@@ -515,7 +499,7 @@ func (c *Calc) PutVar(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.stack.Backup()
c.stack.Push(c.Vars[name])
@@ -528,10 +512,8 @@ func sortcommands(hash Commands) []string {
keys := make([]string, 0, len(hash))
for key := range hash {
if len(key) > 1 {
keys = append(keys, key)
}
}
sort.Strings(keys)

View File

@@ -19,8 +19,6 @@ package main
import (
"fmt"
"strconv"
"strings"
"testing"
lua "github.com/yuin/gopher-lua"
@@ -77,9 +75,7 @@ func TestCommentsAndWhitespace(t *testing.T) {
t.Run(testname, func(t *testing.T) {
for _, line := range tt.cmd {
if err := calc.Eval(line); err != nil {
t.Errorf(err.Error())
}
calc.Eval(line)
}
got := calc.stack.Last()
@@ -292,9 +288,7 @@ func TestCalc(t *testing.T) {
t.Run(testname, func(t *testing.T) {
calc.batch = tt.batch
if err := calc.Eval(tt.cmd); err != nil {
t.Errorf(err.Error())
}
calc.Eval(tt.cmd)
got := calc.Result()
calc.stack.Clear()
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

@@ -305,15 +305,6 @@ func (c *Calc) SetCommands() {
// aliases
c.Commands["quit"] = c.Commands["exit"]
c.SettingsCommands["d"] = c.SettingsCommands["debug"]
c.SettingsCommands["b"] = c.SettingsCommands["batch"]
c.SettingsCommands["s"] = c.SettingsCommands["showstack"]
c.ShowCommands["h"] = c.ShowCommands["history"]
c.ShowCommands["p"] = c.ShowCommands["dump"]
c.ShowCommands["v"] = c.ShowCommands["vars"]
c.StackCommands["c"] = c.StackCommands["clear"]
c.StackCommands["u"] = c.StackCommands["undo"]
c.SettingsCommands["undebug"] = c.SettingsCommands["nodebug"]
c.SettingsCommands["show"] = c.SettingsCommands["showstack"]
}

View File

@@ -457,20 +457,12 @@ func DefineFunctions() Funcalls {
"<": NewFuncall(
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)
},
2),
">": NewFuncall(
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)
},
2),

4
go.mod
View File

@@ -4,9 +4,7 @@ go 1.20
require (
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/yuin/gopher-lua v1.1.0 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // 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/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
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/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"
)
const VERSION string = "2.0.13"
const VERSION string = "2.0.11"
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)
-i --intermediate print intermediate results
-m, --manual show manual
-c, --config <file> load <file> containing LUA code
-v, --version show version
-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`
func main() {
os.Exit(Main())
}
func Main() int {
calc := NewCalc()
showversion := false
@@ -79,12 +74,12 @@ func Main() int {
if showversion {
fmt.Printf("This is rpn version %s\n", VERSION)
return 0
return
}
if showhelp {
fmt.Println(Usage)
return 0
return
}
if enabledebug {
@@ -93,7 +88,7 @@ func Main() int {
if showmanual {
man()
return 0
os.Exit(0)
}
// the lua state object is global, instanciate it early
@@ -106,25 +101,14 @@ func Main() int {
luarunner := NewInterpreter(configfile, enabledebug)
luarunner.InitLua()
calc.SetInt(luarunner)
if calc.debug {
fmt.Println("loaded config")
}
} else {
if calc.debug {
fmt.Println(err)
}
}
if len(flag.Args()) > 1 {
// commandline calc operation, no readline etc needed
// called like rpn 2 2 +
calc.stdin = true
if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil {
fmt.Println(err)
return 1
}
return 0
calc.Eval(strings.Join(flag.Args(), " "))
return
}
// interactive mode, need readline
@@ -157,10 +141,7 @@ func Main() int {
break
}
err = calc.Eval(line)
if err != nil {
fmt.Println(err)
}
calc.Eval(line)
rl.SetPrompt(calc.Prompt())
}
@@ -169,13 +150,8 @@ func Main() int {
// echo 1 2 3 4 | rpn +
// batch mode enabled automatically
calc.batch = true
if err = calc.Eval(flag.Args()[0]); err != nil {
fmt.Println(err)
return 1
calc.Eval(flag.Args()[0])
}
}
return 0
}
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",
})
}

39
rpn.go
View File

@@ -178,27 +178,17 @@ DESCRIPTION
[no]debug toggle debug output (nodebug 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 hex show last stack item
in hex form (converted to int) history display calculation history vars
show list of variables
dump display the stack contents
hex show last stack item 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
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
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
Other commands: help|? show this message manual show manual
quit|exit|c-d|c-c exit program
Register variables:
@@ -208,17 +198,6 @@ DESCRIPTION
Refer to https://pkg.go.dev/math for details about those functions.
There are also a number of shortcuts for some commands available:
d debug
b batch
s showstack
h history
p dump (aka print)
v vars
c clear
u undo
INTERACTIVE REPL
While you can use rpn in the command-line, the best experience you'll
have is the interactive repl (read eval print loop). Just execute "rpn"

14
rpn.pod
View File

@@ -186,14 +186,12 @@ Configuration Commands:
[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 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 the last element of the stack
reverse reverse the stack elements
@@ -203,7 +201,6 @@ Stack manipulation commands:
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
@@ -216,17 +213,6 @@ Register variables:
Refer to https://pkg.go.dev/math for details about those functions.
There are also a number of shortcuts for some commands available:
d debug
b batch
s showstack
h history
p dump (aka print)
v vars
c clear
u undo
=head1 INTERACTIVE REPL
While you can use rpn in the command-line, the best experience you'll

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"
)
// find an item in a list, generic variant
func contains[E comparable](s []E, v E) bool {
for _, vs := range s {
if v == vs {
// find an item in a list
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
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 {
switch name {
case "Pi":
@@ -71,7 +63,3 @@ func const2num(name string) float64 {
func list2str(list Numbers) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]")
}
func Error(m string) error {
return fmt.Errorf("Error: %s!", m)
}