fix linter errors

This commit is contained in:
2024-01-26 13:08:15 +01:00
parent 6a2a501e48
commit c144658a53
12 changed files with 599 additions and 513 deletions

View File

@@ -59,6 +59,14 @@ test: clean
testfuzzy: clean
go test -fuzz ./... $(ARGS)
testlint: test lint
lint:
golangci-lint run
lint-full:
golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,godox,dupword,forcetypeassert,goerr113,gomnd
singletest:
@echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v"
go test -run $(TEST) $(ARGS)

259
calc.go
View File

@@ -69,7 +69,7 @@ Bitwise operators: and or xor < (left shift) > (right shift)
Percent functions:
% percent
%- substract percent
%- subtract percent
%+ add percent
Math functions (see https://pkg.go.dev/math):
@@ -92,9 +92,9 @@ Register variables:
// commands, constants and operators, defined here to feed completion
// and our mode switch in Eval() dynamically
const (
//Commands string = `dump reverse clear shift undo help history manual exit quit swap debug undebug nodebug batch nobatch showstack noshowstack vars`
Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
Precision int = 2
Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
Precision int = 2
ShowStackLen int = 5
)
// That way we can add custom functions to completion
@@ -150,37 +150,36 @@ func (c *Calc) GetCompleteCustomFuncalls() func(string) []string {
return completions
}
}
func NewCalc() *Calc {
c := Calc{stack: NewStack(), debug: false, precision: Precision}
calc := Calc{stack: NewStack(), debug: false, precision: Precision}
c.Funcalls = DefineFunctions()
c.BatchFuncalls = DefineBatchFunctions()
c.Vars = map[string]float64{}
calc.Funcalls = DefineFunctions()
calc.BatchFuncalls = DefineBatchFunctions()
calc.Vars = map[string]float64{}
c.completer = readline.NewPrefixCompleter(
calc.completer = readline.NewPrefixCompleter(
// custom lua functions
readline.PcItemDynamic(GetCompleteCustomFunctions()),
readline.PcItemDynamic(c.GetCompleteCustomFuncalls()),
readline.PcItemDynamic(calc.GetCompleteCustomFuncalls()),
)
c.Space = regexp.MustCompile(`\s+`)
c.Comment = regexp.MustCompile(`#.*`) // ignore everything after #
c.Register = regexp.MustCompile(`^([<>])([A-Z][A-Z0-9]*)`)
calc.Space = regexp.MustCompile(`\s+`)
calc.Comment = regexp.MustCompile(`#.*`) // ignore everything after #
calc.Register = regexp.MustCompile(`^([<>])([A-Z][A-Z0-9]*)`)
// pre-calculate mode switching arrays
c.Constants = strings.Split(Constants, " ")
calc.Constants = strings.Split(Constants, " ")
c.SetCommands()
calc.SetCommands()
return &c
return &calc
}
// setup the interpreter, called from main(), import lua functions
func (c *Calc) SetInt(I *Interpreter) {
c.interpreter = I
func (c *Calc) SetInt(interpreter *Interpreter) {
c.interpreter = interpreter
for name := range LuaFuncs {
c.LuaFunctions = append(c.LuaFunctions, name)
@@ -207,22 +206,22 @@ func (c *Calc) ToggleShow() {
}
func (c *Calc) Prompt() string {
p := "\033[31m»\033[0m "
b := ""
prompt := "\033[31m»\033[0m "
batch := ""
if c.batch {
b = "->batch"
batch = "->batch"
}
d := ""
v := ""
debug := ""
revision := ""
if c.debug {
d = "->debug"
v = fmt.Sprintf("/rev%d", c.stack.rev)
debug = "->debug"
revision = fmt.Sprintf("/rev%d", c.stack.rev)
}
return fmt.Sprintf("rpn%s%s [%d%s]%s", b, d, c.stack.Len(), v, p)
return fmt.Sprintf("rpn%s%s [%d%s]%s", batch, debug, c.stack.Len(), revision, prompt)
}
// the actual work horse, evaluate a line of calc command[s]
@@ -251,10 +250,12 @@ func (c *Calc) Eval(line string) error {
if c.showstack && !c.stdin {
dots := ""
if c.stack.Len() > 5 {
if c.stack.Len() > ShowStackLen {
dots = "... "
}
last := c.stack.Last(5)
last := c.stack.Last(ShowStackLen)
fmt.Printf("stack: %s%s\n", dots, list2str(last))
}
@@ -267,93 +268,106 @@ func (c *Calc) EvalItem(item string) error {
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
return nil
}
// 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())
}
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
return nil
c.Result()
return nil
}
if exists(c.BatchFuncalls, item) {
if !c.batch {
return Error("only supported in batch mode")
}
if exists(c.Funcalls, item) {
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
} else {
c.Result()
}
return nil
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
}
if exists(c.BatchFuncalls, item) {
if !c.batch {
return Error("only supported in batch mode")
}
c.Result()
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
} else {
c.Result()
}
return nil
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])
}
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.EvalLuaFunction(item)
return nil
}
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)
// internal commands
// FIXME: propagate errors
if exists(c.Commands, item) {
c.Commands[item].Func(c)
return nil
}
return nil
}
if exists(c.ShowCommands, item) {
c.ShowCommands[item].Func(c)
return nil
}
if exists(c.ShowCommands, item) {
c.ShowCommands[item].Func(c)
if exists(c.StackCommands, item) {
c.StackCommands[item].Func(c)
return nil
}
return nil
}
if exists(c.SettingsCommands, item) {
c.SettingsCommands[item].Func(c)
return nil
}
if exists(c.StackCommands, item) {
c.StackCommands[item].Func(c)
switch item {
case "?":
fallthrough
case "help":
c.PrintHelp()
return nil
}
default:
return Error("unknown command or operator")
}
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
@@ -373,6 +387,7 @@ func (c *Calc) DoFuncall(funcname string) error {
}
var args Numbers
batch := false
if function.Expectargs == -1 {
@@ -394,11 +409,11 @@ func (c *Calc) DoFuncall(funcname string) error {
// the actual lambda call, so to say. We provide a slice of
// the requested size, fetched from the stack (but not popped
// yet!)
R := function.Func(args)
funcresult := function.Func(args)
if R.Err != nil {
if funcresult.Err != nil {
// leave the stack untouched in case of any error
return R.Err
return funcresult.Err
}
// don't forget to backup!
@@ -414,10 +429,11 @@ func (c *Calc) DoFuncall(funcname string) error {
}
// save result
c.stack.Push(R.Res)
c.stack.Push(funcresult.Res)
// thanks a lot
c.SetHistory(funcname, args, R.Res)
c.SetHistory(funcname, args, funcresult.Res)
return nil
}
@@ -465,24 +481,26 @@ func (c *Calc) Debug(msg string) {
func (c *Calc) EvalLuaFunction(funcname string) {
// called from calc loop
var x float64
var luaresult float64
var err error
switch c.interpreter.FuncNumArgs(funcname) {
case 0:
fallthrough
case 1:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
case 2:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2))
luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2))
case -1:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
default:
x, err = 0, errors.New("invalid number of argument requested")
luaresult, err = 0, errors.New("invalid number of argument requested")
}
if err != nil {
fmt.Println(err)
return
}
@@ -493,24 +511,26 @@ func (c *Calc) EvalLuaFunction(funcname string) {
switch c.interpreter.FuncNumArgs(funcname) {
case 0:
a := c.stack.Last()
if len(a) == 1 {
c.History("%s(%f) = %f", funcname, a, x)
c.History("%s(%f) = %f", funcname, a, luaresult)
}
dopush = false
case 1:
a := c.stack.Pop()
c.History("%s(%f) = %f", funcname, a, x)
c.History("%s(%f) = %f", funcname, a, luaresult)
case 2:
a := c.stack.Pop()
b := c.stack.Pop()
c.History("%s(%f,%f) = %f", funcname, a, b, x)
c.History("%s(%f,%f) = %f", funcname, a, b, luaresult)
case -1:
c.stack.Clear()
c.History("%s(*) = %f", funcname, x)
c.History("%s(*) = %f", funcname, luaresult)
}
if dopush {
c.stack.Push(x)
c.stack.Push(luaresult)
}
c.Result()
@@ -553,27 +573,35 @@ func sortcommands(hash Commands) []string {
func (c *Calc) PrintHelp() {
fmt.Println("Available configuration commands:")
for _, name := range sortcommands(c.SettingsCommands) {
fmt.Printf("%-20s %s\n", name, c.SettingsCommands[name].Help)
}
fmt.Println()
fmt.Println("Available show commands:")
for _, name := range sortcommands(c.ShowCommands) {
fmt.Printf("%-20s %s\n", name, c.ShowCommands[name].Help)
}
fmt.Println()
fmt.Println("Available stack manipulation commands:")
for _, name := range sortcommands(c.StackCommands) {
fmt.Printf("%-20s %s\n", name, c.StackCommands[name].Help)
}
fmt.Println()
fmt.Println("Other commands:")
for _, name := range sortcommands(c.Commands) {
fmt.Printf("%-20s %s\n", name, c.Commands[name].Help)
}
fmt.Println()
fmt.Println(Help)
@@ -581,6 +609,7 @@ func (c *Calc) PrintHelp() {
// append lua functions, if any
if len(LuaFuncs) > 0 {
fmt.Println("Lua functions:")
for name, function := range LuaFuncs {
fmt.Printf("%-20s %s\n", name, function.help)
}

View File

@@ -71,12 +71,12 @@ func TestCommentsAndWhitespace(t *testing.T) {
},
}
for _, tt := range tests {
for _, test := range tests {
testname := fmt.Sprintf("%s .(expect %.2f)",
tt.name, tt.exp)
test.name, test.exp)
t.Run(testname, func(t *testing.T) {
for _, line := range tt.cmd {
for _, line := range test.cmd {
if err := calc.Eval(line); err != nil {
t.Errorf(err.Error())
}
@@ -84,9 +84,9 @@ func TestCommentsAndWhitespace(t *testing.T) {
got := calc.stack.Last()
if len(got) > 0 {
if got[0] != tt.exp {
if got[0] != test.exp {
t.Errorf("parsing failed:\n+++ got: %f\n--- want: %f",
got, tt.exp)
got, test.exp)
}
}
@@ -94,7 +94,6 @@ func TestCommentsAndWhitespace(t *testing.T) {
t.Errorf("invalid stack size:\n+++ got: %d\n--- want: 1",
calc.stack.Len())
}
})
calc.stack.Clear()
@@ -286,20 +285,20 @@ func TestCalc(t *testing.T) {
},
}
for _, tt := range tests {
for _, test := range tests {
testname := fmt.Sprintf("cmd-%s-expect-%.2f",
tt.name, tt.exp)
test.name, test.exp)
t.Run(testname, func(t *testing.T) {
calc.batch = tt.batch
if err := calc.Eval(tt.cmd); err != nil {
calc.batch = test.batch
if err := calc.Eval(test.cmd); err != nil {
t.Errorf(err.Error())
}
got := calc.Result()
calc.stack.Clear()
if got != tt.exp {
if got != test.exp {
t.Errorf("calc failed:\n+++ got: %f\n--- want: %f",
got, tt.exp)
got, test.exp)
}
})
}
@@ -324,23 +323,24 @@ func TestCalcLua(t *testing.T) {
}
calc := NewCalc()
L = lua.NewState(lua.Options{SkipOpenLibs: true})
defer L.Close()
LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true})
defer LuaInterpreter.Close()
luarunner := NewInterpreter("example.lua", false)
luarunner.InitLua()
calc.SetInt(luarunner)
for _, tt := range tests {
testname := fmt.Sprintf("lua-%s", tt.function)
for _, test := range tests {
testname := fmt.Sprintf("lua-%s", test.function)
t.Run(testname, func(t *testing.T) {
calc.stack.Clear()
for _, item := range tt.stack {
for _, item := range test.stack {
calc.stack.Push(item)
}
calc.EvalLuaFunction(tt.function)
calc.EvalLuaFunction(test.function)
got := calc.stack.Last()
@@ -349,9 +349,9 @@ func TestCalcLua(t *testing.T) {
calc.stack.Len())
}
if got[0] != tt.exp {
if got[0] != test.exp {
t.Errorf("lua function %s failed:\n+++ got: %f\n--- want: %f",
tt.function, got, tt.exp)
test.function, got, test.exp)
}
})
}
@@ -380,7 +380,8 @@ func FuzzEval(f *testing.F) {
}
calc := NewCalc()
var i int
var hexnum int
f.Fuzz(func(t *testing.T, line string) {
t.Logf("Stack:\n%v\n", calc.stack.All())
@@ -389,7 +390,7 @@ func FuzzEval(f *testing.F) {
// 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)
_, hexerr := fmt.Sscanf(item, "0x%x", &hexnum)
// no comment?
if len(item) > 0 {
// no known command or function?

View File

@@ -42,9 +42,8 @@ func NewCommand(help string, function CommandFunction) *Command {
}
}
// define all management (that is: non calculation) commands
func (c *Calc) SetCommands() {
c.SettingsCommands = Commands{
func (c *Calc) SetSettingsCommands() Commands {
return Commands{
// Toggles
"debug": NewCommand(
"toggle debugging",
@@ -89,8 +88,10 @@ func (c *Calc) SetCommands() {
},
),
}
}
c.ShowCommands = Commands{
func (c *Calc) SetShowCommands() Commands {
return Commands{
// Display commands
"dump": NewCommand(
"display the stack contents",
@@ -131,8 +132,10 @@ func (c *Calc) SetCommands() {
},
),
}
}
c.StackCommands = Commands{
func (c *Calc) SetStackCommands() Commands {
return Commands{
"clear": NewCommand(
"clear the whole stack",
func(c *Calc) {
@@ -159,14 +162,7 @@ func (c *Calc) SetCommands() {
"swap": NewCommand(
"exchange the last two elements",
func(c *Calc) {
if c.stack.Len() < 2 {
fmt.Println("stack too small, can't swap")
} else {
c.stack.Backup()
c.stack.Swap()
}
},
CommandSwap,
),
"undo": NewCommand(
@@ -178,113 +174,21 @@ func (c *Calc) SetCommands() {
"dup": NewCommand(
"duplicate last stack item",
func(c *Calc) {
item := c.stack.Last()
if len(item) == 1 {
c.stack.Backup()
c.stack.Push(item[0])
} else {
fmt.Println("stack empty")
}
},
CommandDup,
),
"edit": NewCommand(
"edit the stack interactively",
func(c *Calc) {
if c.stack.Len() == 0 {
fmt.Println("empty stack")
return
}
c.stack.Backup()
// put the stack contents into a tmp file
tmp, err := os.CreateTemp("", "stack")
if err != nil {
fmt.Println(err)
return
}
defer os.Remove(tmp.Name())
comment := `# add or remove numbers as you wish.
# each number must be on its own line.
# numbers must be floating point formatted.
`
_, err = tmp.WriteString(comment)
if err != nil {
fmt.Println(err)
return
}
for _, item := range c.stack.All() {
_, err = fmt.Fprintf(tmp, "%f\n", item)
if err != nil {
fmt.Println(err)
return
}
}
tmp.Close()
// determine which editor to use
editor := "vi"
enveditor, present := os.LookupEnv("EDITOR")
if present {
if editor != "" {
if _, err := os.Stat(editor); err == nil {
editor = enveditor
}
}
}
// execute editor with our tmp file containing current stack
cmd := exec.Command(editor, tmp.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Println("could not run editor command: ", err)
return
}
// read the file back in
modified, err := os.Open(tmp.Name())
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer modified.Close()
// reset the stack
c.stack.Clear()
// and put the new contents (if legit) back onto the stack
scanner := bufio.NewScanner(modified)
for scanner.Scan() {
line := strings.TrimSpace(c.Comment.ReplaceAllString(scanner.Text(), ""))
if line == "" {
continue
}
num, err := strconv.ParseFloat(line, 64)
if err != nil {
fmt.Printf("%s is not a floating point number!\n", line)
continue
}
c.stack.Push(num)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading from file:", err)
}
},
CommandEdit,
),
}
}
// define all management (that is: non calculation) commands
func (c *Calc) SetCommands() {
c.SettingsCommands = c.SetSettingsCommands()
c.ShowCommands = c.SetShowCommands()
c.StackCommands = c.SetStackCommands()
// general commands
c.Commands = Commands{
@@ -317,3 +221,126 @@ func (c *Calc) SetCommands() {
c.StackCommands["c"] = c.StackCommands["clear"]
c.StackCommands["u"] = c.StackCommands["undo"]
}
// added to the command map:
func CommandSwap(c *Calc) {
if c.stack.Len() < 2 {
fmt.Println("stack too small, can't swap")
} else {
c.stack.Backup()
c.stack.Swap()
}
}
func CommandDup(c *Calc) {
item := c.stack.Last()
if len(item) == 1 {
c.stack.Backup()
c.stack.Push(item[0])
} else {
fmt.Println("stack empty")
}
}
func CommandEdit(calc *Calc) {
if calc.stack.Len() == 0 {
fmt.Println("empty stack")
return
}
calc.stack.Backup()
// put the stack contents into a tmp file
tmp, err := os.CreateTemp("", "stack")
if err != nil {
fmt.Println(err)
return
}
defer os.Remove(tmp.Name())
comment := `# add or remove numbers as you wish.
# each number must be on its own line.
# numbers must be floating point formatted.
`
_, err = tmp.WriteString(comment)
if err != nil {
fmt.Println(err)
return
}
for _, item := range calc.stack.All() {
_, err = fmt.Fprintf(tmp, "%f\n", item)
if err != nil {
fmt.Println(err)
return
}
}
tmp.Close()
// determine which editor to use
editor := "vi"
enveditor, present := os.LookupEnv("EDITOR")
if present {
if editor != "" {
if _, err := os.Stat(editor); err == nil {
editor = enveditor
}
}
}
// execute editor with our tmp file containing current stack
cmd := exec.Command(editor, tmp.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Println("could not run editor command: ", err)
return
}
// read the file back in
modified, err := os.Open(tmp.Name())
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer modified.Close()
// reset the stack
calc.stack.Clear()
// and put the new contents (if legit) back onto the stack
scanner := bufio.NewScanner(modified)
for scanner.Scan() {
line := strings.TrimSpace(calc.Comment.ReplaceAllString(scanner.Text(), ""))
if line == "" {
continue
}
num, err := strconv.ParseFloat(line, 64)
if err != nil {
fmt.Printf("%s is not a floating point number!\n", line)
continue
}
calc.stack.Push(num)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading from file:", err)
}
}

319
funcs.go
View File

@@ -22,14 +22,14 @@ import (
"math"
)
type R struct {
type Result struct {
Res float64
Err error
}
type Numbers []float64
type Function func(Numbers) R
type Function func(Numbers) Result
// every function we are able to call must be of type Funcall, which
// needs to specify how many numbers it expects and the actual go
@@ -64,446 +64,450 @@ func NewFuncall(function Function, expectargs ...int) *Funcall {
}
// Convenience function, create new result
func NewR(n float64, e error) R {
return R{Res: n, Err: e}
func NewResult(n float64, e error) Result {
return Result{Res: n, Err: e}
}
// the actual functions, called once during initialization.
func DefineFunctions() Funcalls {
f := map[string]*Funcall{
funcmap := map[string]*Funcall{
// simple operators, they all expect 2 args
"+": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]+arg[1], nil)
func(arg Numbers) Result {
return NewResult(arg[0]+arg[1], nil)
},
),
"-": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]-arg[1], nil)
func(arg Numbers) Result {
return NewResult(arg[0]-arg[1], nil)
},
),
"x": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*arg[1], nil)
func(arg Numbers) Result {
return NewResult(arg[0]*arg[1], nil)
},
),
"/": NewFuncall(
func(arg Numbers) R {
func(arg Numbers) Result {
if arg[1] == 0 {
return NewR(0, errors.New("division by null"))
return NewResult(0, errors.New("division by null"))
}
return NewR(arg[0]/arg[1], nil)
return NewResult(arg[0]/arg[1], nil)
},
),
"^": NewFuncall(
func(arg Numbers) R {
return NewR(math.Pow(arg[0], arg[1]), nil)
func(arg Numbers) Result {
return NewResult(math.Pow(arg[0], arg[1]), nil)
},
),
"%": NewFuncall(
func(arg Numbers) R {
return NewR((arg[0]/100)*arg[1], nil)
func(arg Numbers) Result {
return NewResult((arg[0]/100)*arg[1], nil)
},
),
"%-": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]-((arg[0]/100)*arg[1]), nil)
func(arg Numbers) Result {
return NewResult(arg[0]-((arg[0]/100)*arg[1]), nil)
},
),
"%+": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]+((arg[0]/100)*arg[1]), nil)
func(arg Numbers) Result {
return NewResult(arg[0]+((arg[0]/100)*arg[1]), nil)
},
),
"mod": NewFuncall(
func(arg Numbers) R {
return NewR(math.Remainder(arg[0], arg[1]), nil)
func(arg Numbers) Result {
return NewResult(math.Remainder(arg[0], arg[1]), nil)
},
),
"sqrt": NewFuncall(
func(arg Numbers) R {
return NewR(math.Sqrt(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Sqrt(arg[0]), nil)
},
1),
"abs": NewFuncall(
func(arg Numbers) R {
return NewR(math.Abs(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Abs(arg[0]), nil)
},
1),
"acos": NewFuncall(
func(arg Numbers) R {
return NewR(math.Acos(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Acos(arg[0]), nil)
},
1),
"acosh": NewFuncall(
func(arg Numbers) R {
return NewR(math.Acosh(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Acosh(arg[0]), nil)
},
1),
"asin": NewFuncall(
func(arg Numbers) R {
return NewR(math.Asin(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Asin(arg[0]), nil)
},
1),
"asinh": NewFuncall(
func(arg Numbers) R {
return NewR(math.Asinh(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Asinh(arg[0]), nil)
},
1),
"atan": NewFuncall(
func(arg Numbers) R {
return NewR(math.Atan(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Atan(arg[0]), nil)
},
1),
"atan2": NewFuncall(
func(arg Numbers) R {
return NewR(math.Atan2(arg[0], arg[1]), nil)
func(arg Numbers) Result {
return NewResult(math.Atan2(arg[0], arg[1]), nil)
},
2),
"atanh": NewFuncall(
func(arg Numbers) R {
return NewR(math.Atanh(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Atanh(arg[0]), nil)
},
1),
"cbrt": NewFuncall(
func(arg Numbers) R {
return NewR(math.Cbrt(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Cbrt(arg[0]), nil)
},
1),
"ceil": NewFuncall(
func(arg Numbers) R {
return NewR(math.Ceil(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Ceil(arg[0]), nil)
},
1),
"cos": NewFuncall(
func(arg Numbers) R {
return NewR(math.Cos(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Cos(arg[0]), nil)
},
1),
"cosh": NewFuncall(
func(arg Numbers) R {
return NewR(math.Cosh(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Cosh(arg[0]), nil)
},
1),
"erf": NewFuncall(
func(arg Numbers) R {
return NewR(math.Erf(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Erf(arg[0]), nil)
},
1),
"erfc": NewFuncall(
func(arg Numbers) R {
return NewR(math.Erfc(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Erfc(arg[0]), nil)
},
1),
"erfcinv": NewFuncall(
func(arg Numbers) R {
return NewR(math.Erfcinv(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Erfcinv(arg[0]), nil)
},
1),
"erfinv": NewFuncall(
func(arg Numbers) R {
return NewR(math.Erfinv(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Erfinv(arg[0]), nil)
},
1),
"exp": NewFuncall(
func(arg Numbers) R {
return NewR(math.Exp(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Exp(arg[0]), nil)
},
1),
"exp2": NewFuncall(
func(arg Numbers) R {
return NewR(math.Exp2(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Exp2(arg[0]), nil)
},
1),
"expm1": NewFuncall(
func(arg Numbers) R {
return NewR(math.Expm1(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Expm1(arg[0]), nil)
},
1),
"floor": NewFuncall(
func(arg Numbers) R {
return NewR(math.Floor(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Floor(arg[0]), nil)
},
1),
"gamma": NewFuncall(
func(arg Numbers) R {
return NewR(math.Gamma(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Gamma(arg[0]), nil)
},
1),
"ilogb": NewFuncall(
func(arg Numbers) R {
return NewR(float64(math.Ilogb(arg[0])), nil)
func(arg Numbers) Result {
return NewResult(float64(math.Ilogb(arg[0])), nil)
},
1),
"j0": NewFuncall(
func(arg Numbers) R {
return NewR(math.J0(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.J0(arg[0]), nil)
},
1),
"j1": NewFuncall(
func(arg Numbers) R {
return NewR(math.J1(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.J1(arg[0]), nil)
},
1),
"log": NewFuncall(
func(arg Numbers) R {
return NewR(math.Log(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Log(arg[0]), nil)
},
1),
"log10": NewFuncall(
func(arg Numbers) R {
return NewR(math.Log10(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Log10(arg[0]), nil)
},
1),
"log1p": NewFuncall(
func(arg Numbers) R {
return NewR(math.Log1p(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Log1p(arg[0]), nil)
},
1),
"log2": NewFuncall(
func(arg Numbers) R {
return NewR(math.Log2(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Log2(arg[0]), nil)
},
1),
"logb": NewFuncall(
func(arg Numbers) R {
return NewR(math.Logb(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Logb(arg[0]), nil)
},
1),
"pow": NewFuncall(
func(arg Numbers) R {
return NewR(math.Pow(arg[0], arg[1]), nil)
func(arg Numbers) Result {
return NewResult(math.Pow(arg[0], arg[1]), nil)
},
2),
"round": NewFuncall(
func(arg Numbers) R {
return NewR(math.Round(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Round(arg[0]), nil)
},
1),
"roundtoeven": NewFuncall(
func(arg Numbers) R {
return NewR(math.RoundToEven(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.RoundToEven(arg[0]), nil)
},
1),
"sin": NewFuncall(
func(arg Numbers) R {
return NewR(math.Sin(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Sin(arg[0]), nil)
},
1),
"sinh": NewFuncall(
func(arg Numbers) R {
return NewR(math.Sinh(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Sinh(arg[0]), nil)
},
1),
"tan": NewFuncall(
func(arg Numbers) R {
return NewR(math.Tan(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Tan(arg[0]), nil)
},
1),
"tanh": NewFuncall(
func(arg Numbers) R {
return NewR(math.Tanh(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Tanh(arg[0]), nil)
},
1),
"trunc": NewFuncall(
func(arg Numbers) R {
return NewR(math.Trunc(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Trunc(arg[0]), nil)
},
1),
"y0": NewFuncall(
func(arg Numbers) R {
return NewR(math.Y0(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Y0(arg[0]), nil)
},
1),
"y1": NewFuncall(
func(arg Numbers) R {
return NewR(math.Y1(arg[0]), nil)
func(arg Numbers) Result {
return NewResult(math.Y1(arg[0]), nil)
},
1),
"copysign": NewFuncall(
func(arg Numbers) R {
return NewR(math.Copysign(arg[0], arg[1]), nil)
func(arg Numbers) Result {
return NewResult(math.Copysign(arg[0], arg[1]), nil)
},
2),
"dim": NewFuncall(
func(arg Numbers) R {
return NewR(math.Dim(arg[0], arg[1]), nil)
func(arg Numbers) Result {
return NewResult(math.Dim(arg[0], arg[1]), nil)
},
2),
"hypot": NewFuncall(
func(arg Numbers) R {
return NewR(math.Hypot(arg[0], arg[1]), nil)
func(arg Numbers) Result {
return NewResult(math.Hypot(arg[0], arg[1]), nil)
},
2),
// converters of all kinds
"cm-to-inch": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/2.54, nil)
func(arg Numbers) Result {
return NewResult(arg[0]/2.54, nil)
},
1),
"inch-to-cm": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*2.54, nil)
func(arg Numbers) Result {
return NewResult(arg[0]*2.54, nil)
},
1),
"gallons-to-liters": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*3.785, nil)
func(arg Numbers) Result {
return NewResult(arg[0]*3.785, nil)
},
1),
"liters-to-gallons": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/3.785, nil)
func(arg Numbers) Result {
return NewResult(arg[0]/3.785, nil)
},
1),
"yards-to-meters": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*91.44, nil)
func(arg Numbers) Result {
return NewResult(arg[0]*91.44, nil)
},
1),
"meters-to-yards": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/91.44, nil)
func(arg Numbers) Result {
return NewResult(arg[0]/91.44, nil)
},
1),
"miles-to-kilometers": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*1.609, nil)
func(arg Numbers) Result {
return NewResult(arg[0]*1.609, nil)
},
1),
"kilometers-to-miles": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/1.609, nil)
func(arg Numbers) Result {
return NewResult(arg[0]/1.609, nil)
},
1),
"or": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])|int(arg[1])), nil)
func(arg Numbers) Result {
return NewResult(float64(int(arg[0])|int(arg[1])), nil)
},
2),
"and": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])&int(arg[1])), nil)
func(arg Numbers) Result {
return NewResult(float64(int(arg[0])&int(arg[1])), nil)
},
2),
"xor": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])^int(arg[1])), nil)
func(arg Numbers) Result {
return NewResult(float64(int(arg[0])^int(arg[1])), nil)
},
2),
"<": NewFuncall(
func(arg Numbers) R {
func(arg Numbers) Result {
// Shift by negative number provibited, so check it.
// Note that we check agains uint64 overflow as well here
// Note that we check against uint64 overflow as well here
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewR(0, errors.New("negative shift amount"))
return NewResult(0, errors.New("negative shift amount"))
}
return NewR(float64(int(arg[0])<<int(arg[1])), nil)
return NewResult(float64(int(arg[0])<<int(arg[1])), nil)
},
2),
">": NewFuncall(
func(arg Numbers) R {
func(arg Numbers) Result {
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewR(0, errors.New("negative shift amount"))
return NewResult(0, errors.New("negative shift amount"))
}
return NewR(float64(int(arg[0])>>int(arg[1])), nil)
return NewResult(float64(int(arg[0])>>int(arg[1])), nil)
},
2),
}
// aliases
f["*"] = f["x"]
f["remainder"] = f["mod"]
funcmap["*"] = funcmap["x"]
funcmap["remainder"] = funcmap["mod"]
return f
return funcmap
}
func DefineBatchFunctions() Funcalls {
f := map[string]*Funcall{
funcmap := map[string]*Funcall{
"median": NewFuncall(
func(args Numbers) R {
func(args Numbers) Result {
middle := len(args) / 2
return NewR(args[middle], nil)
return NewResult(args[middle], nil)
},
-1),
"mean": NewFuncall(
func(args Numbers) R {
func(args Numbers) Result {
var sum float64
for _, item := range args {
sum += item
}
return NewR(sum/float64(len(args)), nil)
return NewResult(sum/float64(len(args)), nil)
},
-1),
"min": NewFuncall(
func(args Numbers) R {
func(args Numbers) Result {
var min float64
min, args = args[0], args[1:]
for _, item := range args {
@@ -511,12 +515,13 @@ func DefineBatchFunctions() Funcalls {
min = item
}
}
return NewR(min, nil)
return NewResult(min, nil)
},
-1),
"max": NewFuncall(
func(args Numbers) R {
func(args Numbers) Result {
var max float64
max, args = args[0], args[1:]
for _, item := range args {
@@ -524,24 +529,26 @@ func DefineBatchFunctions() Funcalls {
max = item
}
}
return NewR(max, nil)
return NewResult(max, nil)
},
-1),
"sum": NewFuncall(
func(args Numbers) R {
func(args Numbers) Result {
var sum float64
for _, item := range args {
sum += item
}
return NewR(sum, nil)
return NewResult(sum, nil)
},
-1),
}
// aliases
f["+"] = f["sum"]
f["avg"] = f["mean"]
funcmap["+"] = funcmap["sum"]
funcmap["avg"] = funcmap["mean"]
return f
return funcmap
}

View File

@@ -29,8 +29,8 @@ type Interpreter struct {
script string
}
// LUA interpreter, instanciated in main()
var L *lua.LState
// LuaInterpreter is the lua interpreter, instantiated in main()
var LuaInterpreter *lua.LState
// holds a user provided lua function
type LuaFunction struct {
@@ -39,8 +39,8 @@ type LuaFunction struct {
numargs int
}
// must be global since init() is being called from lua which doesn't
// have access to the interpreter instance
// LuaFuncs must be global since init() is being called from lua which
// doesn't have access to the interpreter instance
var LuaFuncs map[string]LuaFunction
func NewInterpreter(script string, debug bool) *Interpreter {
@@ -61,8 +61,8 @@ func (i *Interpreter) InitLua() {
{lua.DebugLibName, lua.OpenDebug},
{lua.MathLibName, lua.OpenMath},
} {
if err := L.CallByParam(lua.P{
Fn: L.NewFunction(pair.f),
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.NewFunction(pair.f),
NRet: 0,
Protect: true,
}, lua.LString(pair.n)); err != nil {
@@ -71,19 +71,19 @@ func (i *Interpreter) InitLua() {
}
// load the lua config (which we expect to contain init() and math functions)
if err := L.DoFile(i.script); err != nil {
if err := LuaInterpreter.DoFile(i.script); err != nil {
panic(err)
}
// instanciate
// instantiate
LuaFuncs = map[string]LuaFunction{}
// that way the user can call register(...) from lua inside init()
L.SetGlobal("register", L.NewFunction(register))
LuaInterpreter.SetGlobal("register", LuaInterpreter.NewFunction(register))
// actually call init()
if err := L.CallByParam(lua.P{
Fn: L.GetGlobal("init"),
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal("init"),
NRet: 0,
Protect: true,
}); err != nil {
@@ -108,9 +108,9 @@ func (i *Interpreter) FuncNumArgs(name string) int {
// arguments. 1 uses the last item of the stack, 2 the last two and -1
// all items (which translates to batch mode)
//
// The items array will be provded by calc.Eval(), these are
// The items array will be provided by calc.Eval(), these are
// non-popped stack items. So the items will only removed from the
// stack when the lua function execution is successfull.
// stack when the lua function execution is successful.
func (i *Interpreter) CallLuaFunc(funcname string, items []float64) (float64, error) {
i.Debug(fmt.Sprintf("calling lua func %s() with %d args",
funcname, LuaFuncs[funcname].numargs))
@@ -120,44 +120,44 @@ func (i *Interpreter) CallLuaFunc(funcname string, items []float64) (float64, er
fallthrough
case 1:
// 1 arg variant
if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname),
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal(funcname),
NRet: 1,
Protect: true,
}, lua.LNumber(items[0])); err != nil {
fmt.Println(err)
return 0, err
return 0, fmt.Errorf("failed to exec lua func %s: %w", funcname, err)
}
case 2:
// 2 arg variant
if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname),
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal(funcname),
NRet: 1,
Protect: true,
}, lua.LNumber(items[0]), lua.LNumber(items[1])); err != nil {
return 0, err
return 0, fmt.Errorf("failed to exec lua func %s: %w", funcname, err)
}
case -1:
// batch variant, use lua table as array
tb := L.NewTable()
table := LuaInterpreter.NewTable()
// put the whole stack into it
for _, item := range items {
tb.Append(lua.LNumber(item))
table.Append(lua.LNumber(item))
}
if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname),
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal(funcname),
NRet: 1,
Protect: true,
}, tb); err != nil {
return 0, err
}, table); err != nil {
return 0, fmt.Errorf("failed to exec lua func %s: %w", funcname, err)
}
}
// get result and cast to float64
if res, ok := L.Get(-1).(lua.LNumber); ok {
L.Pop(1)
if res, ok := LuaInterpreter.Get(-1).(lua.LNumber); ok {
LuaInterpreter.Pop(1)
return float64(res), nil
}
@@ -167,10 +167,10 @@ func (i *Interpreter) CallLuaFunc(funcname string, items []float64) (float64, er
// called from lua to register a math function numargs may be 1, 2 or
// -1, it denotes the number of items from the stack requested by the
// lua function. -1 means batch mode, that is all items
func register(L *lua.LState) int {
function := L.ToString(1)
numargs := L.ToInt(2)
help := L.ToString(3)
func register(lstate *lua.LState) int {
function := lstate.ToString(1)
numargs := lstate.ToInt(2)
help := lstate.ToString(3)
LuaFuncs[function] = LuaFunction{
name: function,

37
main.go
View File

@@ -81,11 +81,13 @@ func Main() int {
if showversion {
fmt.Printf("This is rpn version %s\n", VERSION)
return 0
}
if showhelp {
fmt.Println(Usage)
return 0
}
@@ -95,12 +97,13 @@ func Main() int {
if showmanual {
man()
return 0
}
// the lua state object is global, instanciate it early
L = lua.NewState(lua.Options{SkipOpenLibs: true})
defer L.Close()
// the lua state object is global, instantiate it early
LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true})
defer LuaInterpreter.Close()
// our config file is interpreted as lua code, only functions can
// be defined, init() will be called by InitLua().
@@ -108,13 +111,12 @@ 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)
}
} else if calc.debug {
fmt.Println(err)
}
if len(flag.Args()) > 1 {
@@ -123,6 +125,7 @@ func Main() int {
calc.stdin = true
if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil {
fmt.Println(err)
return 1
}
@@ -130,7 +133,7 @@ func Main() int {
}
// interactive mode, need readline
rl, err := readline.NewEx(&readline.Config{
reader, err := readline.NewEx(&readline.Config{
Prompt: calc.Prompt(),
HistoryFile: os.Getenv("HOME") + "/.rpn-history",
HistoryLimit: 500,
@@ -143,8 +146,8 @@ func Main() int {
if err != nil {
panic(err)
}
defer rl.Close()
rl.CaptureExitSignal()
defer reader.Close()
reader.CaptureExitSignal()
if inputIsStdin() {
// commands are coming on stdin, however we will still enter
@@ -154,7 +157,7 @@ func Main() int {
for {
// primary program repl
line, err := rl.Readline()
line, err := reader.Readline()
if err != nil {
break
}
@@ -163,7 +166,8 @@ func Main() int {
if err != nil {
fmt.Println(err)
}
rl.SetPrompt(calc.Prompt())
reader.SetPrompt(calc.Prompt())
}
if len(flag.Args()) > 0 {
@@ -173,6 +177,7 @@ func Main() int {
calc.batch = true
if err = calc.Eval(flag.Args()[0]); err != nil {
fmt.Println(err)
return 1
}
}
@@ -182,17 +187,19 @@ func Main() int {
func inputIsStdin() bool {
stat, _ := os.Stdin.Stat()
return (stat.Mode() & os.ModeCharDevice) == 0
}
func man() {
var buf bytes.Buffer
man := exec.Command("less", "-")
var b bytes.Buffer
b.Write([]byte(manpage))
buf.WriteString(manpage)
man.Stdout = os.Stdout
man.Stdin = &b
man.Stdin = &buf
man.Stderr = os.Stderr
err := man.Run()

4
rpn.go
View File

@@ -129,7 +129,7 @@ DESCRIPTION
Basic operators:
+ add
- substract
- subtract
/ divide
x multiply (alias: *)
^ power
@@ -145,7 +145,7 @@ DESCRIPTION
Percent functions:
% percent
%- substract percent
%- subtract percent
%+ add percent
Batch functions:

View File

@@ -136,7 +136,7 @@ stack.
Basic operators:
+ add
- substract
- subtract
/ divide
x multiply (alias: *)
^ power
@@ -152,7 +152,7 @@ Bitwise operators:
Percent functions:
% percent
%- substract percent
%- subtract percent
%+ add percent
Batch functions:

View File

@@ -64,14 +64,14 @@ func (s *Stack) Bump() {
}
// append an item to the stack
func (s *Stack) Push(x float64) {
func (s *Stack) Push(item float64) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.Debug(fmt.Sprintf(" push to stack: %.2f", x))
s.Debug(fmt.Sprintf(" push to stack: %.2f", item))
s.Bump()
s.linklist.PushBack(x)
s.linklist.PushBack(item)
}
// remove and return an item from the stack
@@ -90,6 +90,7 @@ func (s *Stack) Pop() float64 {
s.Debug(fmt.Sprintf(" remove from stack: %.2f", val))
s.Bump()
return val.(float64)
}
@@ -123,32 +124,33 @@ func (s *Stack) Swap() {
return
}
a := s.linklist.Back()
s.linklist.Remove(a)
prevA := s.linklist.Back()
s.linklist.Remove(prevA)
b := s.linklist.Back()
s.linklist.Remove(b)
prevB := s.linklist.Back()
s.linklist.Remove(prevB)
s.Debug(fmt.Sprintf("swapping %.2f with %.2f", b.Value, a.Value))
s.Debug(fmt.Sprintf("swapping %.2f with %.2f", prevB.Value, prevA.Value))
s.linklist.PushBack(a.Value)
s.linklist.PushBack(b.Value)
s.linklist.PushBack(prevA.Value)
s.linklist.PushBack(prevB.Value)
}
// Return the last num items from the stack w/o modifying it.
func (s *Stack) Last(num ...int) []float64 {
items := []float64{}
i := s.Len()
stacklen := s.Len()
count := 1
if len(num) > 0 {
count = num[0]
}
for e := s.linklist.Front(); e != nil; e = e.Next() {
if i <= count {
if stacklen <= count {
items = append(items, e.Value.(float64))
}
i--
stacklen--
}
return items
@@ -168,12 +170,14 @@ func (s *Stack) All() []float64 {
// dump the stack to stdout, including backup if debug is enabled
func (s *Stack) Dump() {
fmt.Printf("Stack revision %d (%p):\n", s.rev, &s.linklist)
for e := s.linklist.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)
}
@@ -215,6 +219,7 @@ func (s *Stack) Restore() {
if s.rev == 0 {
fmt.Println("error: stack is empty.")
return
}

View File

@@ -35,16 +35,16 @@ func TestPush(t *testing.T) {
func TestPop(t *testing.T) {
t.Run("pop", func(t *testing.T) {
s := NewStack()
s.Push(5)
got := s.Pop()
stack := NewStack()
stack.Push(5)
got := stack.Pop()
if got != 5.0 {
t.Errorf("pop failed:\n+++ got: %f\n--- want: %f",
got, 5.0)
}
if s.Len() != 0 {
if stack.Len() != 0 {
t.Errorf("stack not empty after pop()")
}
})
@@ -52,25 +52,25 @@ func TestPop(t *testing.T) {
func TestPops(t *testing.T) {
t.Run("pops", func(t *testing.T) {
s := NewStack()
s.Push(5)
s.Push(5)
s.Push(5)
s.Pop()
stack := NewStack()
stack.Push(5)
stack.Push(5)
stack.Push(5)
stack.Pop()
if s.Len() != 2 {
if stack.Len() != 2 {
t.Errorf("stack len not correct after pop:\n+++ got: %d\n--- want: %d",
s.Len(), 2)
stack.Len(), 2)
}
})
}
func TestShift(t *testing.T) {
t.Run("shift", func(t *testing.T) {
s := NewStack()
s.Shift()
stack := NewStack()
stack.Shift()
if s.Len() != 0 {
if stack.Len() != 0 {
t.Errorf("stack not empty after shift()")
}
})
@@ -78,13 +78,13 @@ func TestShift(t *testing.T) {
func TestClear(t *testing.T) {
t.Run("clear", func(t *testing.T) {
s := NewStack()
s.Push(5)
s.Push(5)
s.Push(5)
s.Clear()
stack := NewStack()
stack.Push(5)
stack.Push(5)
stack.Push(5)
stack.Clear()
if s.Len() != 0 {
if stack.Len() != 0 {
t.Errorf("stack not empty after clear()")
}
})
@@ -92,9 +92,9 @@ func TestClear(t *testing.T) {
func TestLast(t *testing.T) {
t.Run("last", func(t *testing.T) {
s := NewStack()
s.Push(5)
got := s.Last()
stack := NewStack()
stack.Push(5)
got := stack.Last()
if len(got) != 1 {
t.Errorf("last failed:\n+++ got: %d elements\n--- want: %d elements",
@@ -106,7 +106,7 @@ func TestLast(t *testing.T) {
got, 5.0)
}
if s.Len() != 1 {
if stack.Len() != 1 {
t.Errorf("stack modified after last()")
}
})
@@ -114,14 +114,14 @@ func TestLast(t *testing.T) {
func TestAll(t *testing.T) {
t.Run("all", func(t *testing.T) {
s := NewStack()
stack := NewStack()
list := []float64{2, 4, 6, 8}
for _, item := range list {
s.Push(item)
stack.Push(item)
}
got := s.All()
got := stack.All()
if len(got) != len(list) {
t.Errorf("all failed:\n+++ got: %d elements\n--- want: %d elements",
@@ -135,7 +135,7 @@ func TestAll(t *testing.T) {
}
}
if s.Len() != len(list) {
if stack.Len() != len(list) {
t.Errorf("stack modified after last()")
}
})
@@ -143,37 +143,37 @@ func TestAll(t *testing.T) {
func TestBackupRestore(t *testing.T) {
t.Run("shift", func(t *testing.T) {
s := NewStack()
s.Push(5)
s.Backup()
s.Clear()
s.Restore()
stack := NewStack()
stack.Push(5)
stack.Backup()
stack.Clear()
stack.Restore()
if s.Len() != 1 {
if stack.Len() != 1 {
t.Errorf("stack not correctly restored()")
}
a := s.Pop()
if a != 5.0 {
value := stack.Pop()
if value != 5.0 {
t.Errorf("stack not identical to old revision:\n+++ got: %f\n--- want: %f",
a, 5.0)
value, 5.0)
}
})
}
func TestReverse(t *testing.T) {
t.Run("reverse", func(t *testing.T) {
s := NewStack()
stack := NewStack()
list := []float64{2, 4, 6}
reverse := []float64{6, 4, 2}
for _, item := range list {
s.Push(item)
stack.Push(item)
}
s.Reverse()
stack.Reverse()
got := s.All()
got := stack.All()
if len(got) != len(list) {
t.Errorf("all failed:\n+++ got: %d elements\n--- want: %d elements",

View File

@@ -30,6 +30,7 @@ func contains[E comparable](s []E, v E) bool {
return true
}
}
return false
}
@@ -38,6 +39,7 @@ func exists[K comparable, V any](m map[K]V, v K) bool {
if _, ok := m[v]; ok {
return true
}
return false
}
@@ -73,5 +75,5 @@ func list2str(list Numbers) string {
}
func Error(m string) error {
return fmt.Errorf("Error: %s!", m)
return fmt.Errorf("Error: %s", m)
}