1 Commits

19 changed files with 546 additions and 667 deletions

View File

@@ -7,7 +7,7 @@ assignees: TLINDEN
--- ---
**Description** **Describtion**
<!-- Please provide a clear and concise description of the issue: --> <!-- Please provide a clear and concise description of the issue: -->

View File

@@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

View File

@@ -4,22 +4,22 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
version: [1.22.1] version: [1.21]
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
name: Build name: Build
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Set up Go ${{ matrix.version }} - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '${{ matrix.version }}' go-version: ${{ matrix.version }}
id: go id: go
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: build - name: build
run: make buildlocal run: go build
- name: test - name: test
run: make test run: make test
@@ -28,11 +28,9 @@ jobs:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v3
with: with:
go-version: 1.22 go-version: 1.21
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v6 uses: golangci/golangci-lint-action@v3
with:
skip-cache: true

View File

@@ -59,14 +59,6 @@ test: clean
testfuzzy: clean testfuzzy: clean
go test -fuzz ./... $(ARGS) 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: singletest:
@echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v" @echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v"
go test -run $(TEST) $(ARGS) go test -run $(TEST) $(ARGS)

136
calc.go
View File

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

View File

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

View File

@@ -42,8 +42,9 @@ func NewCommand(help string, function CommandFunction) *Command {
} }
} }
func (c *Calc) SetSettingsCommands() Commands { // define all management (that is: non calculation) commands
return Commands{ func (c *Calc) SetCommands() {
c.SettingsCommands = Commands{
// Toggles // Toggles
"debug": NewCommand( "debug": NewCommand(
"toggle debugging", "toggle debugging",
@@ -88,10 +89,8 @@ func (c *Calc) SetSettingsCommands() Commands {
}, },
), ),
} }
}
func (c *Calc) SetShowCommands() Commands { c.ShowCommands = Commands{
return Commands{
// Display commands // Display commands
"dump": NewCommand( "dump": NewCommand(
"display the stack contents", "display the stack contents",
@@ -132,10 +131,8 @@ func (c *Calc) SetShowCommands() Commands {
}, },
), ),
} }
}
func (c *Calc) SetStackCommands() Commands { c.StackCommands = Commands{
return Commands{
"clear": NewCommand( "clear": NewCommand(
"clear the whole stack", "clear the whole stack",
func(c *Calc) { func(c *Calc) {
@@ -162,7 +159,14 @@ func (c *Calc) SetStackCommands() Commands {
"swap": NewCommand( "swap": NewCommand(
"exchange the last two elements", "exchange the last two elements",
CommandSwap, func(c *Calc) {
if c.stack.Len() < 2 {
fmt.Println("stack too small, can't swap")
} else {
c.stack.Backup()
c.stack.Swap()
}
},
), ),
"undo": NewCommand( "undo": NewCommand(
@@ -174,21 +178,113 @@ func (c *Calc) SetStackCommands() Commands {
"dup": NewCommand( "dup": NewCommand(
"duplicate last stack item", "duplicate last stack item",
CommandDup, func(c *Calc) {
item := c.stack.Last()
if len(item) == 1 {
c.stack.Backup()
c.stack.Push(item[0])
} else {
fmt.Println("stack empty")
}
},
), ),
"edit": NewCommand( "edit": NewCommand(
"edit the stack interactively", "edit the stack interactively",
CommandEdit, 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)
}
},
), ),
} }
}
// 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 // general commands
c.Commands = Commands{ c.Commands = Commands{
@@ -214,10 +310,6 @@ func (c *Calc) SetCommands() {
c.SettingsCommands["b"] = c.SettingsCommands["batch"] c.SettingsCommands["b"] = c.SettingsCommands["batch"]
c.SettingsCommands["s"] = c.SettingsCommands["showstack"] c.SettingsCommands["s"] = c.SettingsCommands["showstack"]
c.SettingsCommands["togglebatch"] = c.SettingsCommands["batch"]
c.SettingsCommands["toggledebug"] = c.SettingsCommands["debug"]
c.SettingsCommands["toggleshowstack"] = c.SettingsCommands["showstack"]
c.ShowCommands["h"] = c.ShowCommands["history"] c.ShowCommands["h"] = c.ShowCommands["history"]
c.ShowCommands["p"] = c.ShowCommands["dump"] c.ShowCommands["p"] = c.ShowCommands["dump"]
c.ShowCommands["v"] = c.ShowCommands["vars"] c.ShowCommands["v"] = c.ShowCommands["vars"]
@@ -225,126 +317,3 @@ func (c *Calc) SetCommands() {
c.StackCommands["c"] = c.StackCommands["clear"] c.StackCommands["c"] = c.StackCommands["clear"]
c.StackCommands["u"] = c.StackCommands["undo"] 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" "math"
) )
type Result struct { type R struct {
Res float64 Res float64
Err error Err error
} }
type Numbers []float64 type Numbers []float64
type Function func(Numbers) Result type Function func(Numbers) R
// every function we are able to call must be of type Funcall, which // 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 // needs to specify how many numbers it expects and the actual go
@@ -64,450 +64,446 @@ func NewFuncall(function Function, expectargs ...int) *Funcall {
} }
// Convenience function, create new result // Convenience function, create new result
func NewResult(n float64, e error) Result { func NewR(n float64, e error) R {
return Result{Res: n, Err: e} return R{Res: n, Err: e}
} }
// the actual functions, called once during initialization. // the actual functions, called once during initialization.
func DefineFunctions() Funcalls { func DefineFunctions() Funcalls {
funcmap := map[string]*Funcall{ f := map[string]*Funcall{
// simple operators, they all expect 2 args // simple operators, they all expect 2 args
"+": NewFuncall( "+": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]+arg[1], nil) return NewR(arg[0]+arg[1], nil)
}, },
), ),
"-": NewFuncall( "-": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]-arg[1], nil) return NewR(arg[0]-arg[1], nil)
}, },
), ),
"x": NewFuncall( "x": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]*arg[1], nil) return NewR(arg[0]*arg[1], nil)
}, },
), ),
"/": NewFuncall( "/": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
if arg[1] == 0 { if arg[1] == 0 {
return NewResult(0, errors.New("division by null")) return NewR(0, errors.New("division by null"))
} }
return NewResult(arg[0]/arg[1], nil) return NewR(arg[0]/arg[1], nil)
}, },
), ),
"^": NewFuncall( "^": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Pow(arg[0], arg[1]), nil) return NewR(math.Pow(arg[0], arg[1]), nil)
}, },
), ),
"%": NewFuncall( "%": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult((arg[0]/100)*arg[1], nil) return NewR((arg[0]/100)*arg[1], nil)
}, },
), ),
"%-": NewFuncall( "%-": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]-((arg[0]/100)*arg[1]), nil) return NewR(arg[0]-((arg[0]/100)*arg[1]), nil)
}, },
), ),
"%+": NewFuncall( "%+": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]+((arg[0]/100)*arg[1]), nil) return NewR(arg[0]+((arg[0]/100)*arg[1]), nil)
}, },
), ),
"mod": NewFuncall( "mod": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Remainder(arg[0], arg[1]), nil) return NewR(math.Remainder(arg[0], arg[1]), nil)
}, },
), ),
"sqrt": NewFuncall( "sqrt": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Sqrt(arg[0]), nil) return NewR(math.Sqrt(arg[0]), nil)
}, },
1), 1),
"abs": NewFuncall( "abs": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Abs(arg[0]), nil) return NewR(math.Abs(arg[0]), nil)
}, },
1), 1),
"acos": NewFuncall( "acos": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Acos(arg[0]), nil) return NewR(math.Acos(arg[0]), nil)
}, },
1), 1),
"acosh": NewFuncall( "acosh": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Acosh(arg[0]), nil) return NewR(math.Acosh(arg[0]), nil)
}, },
1), 1),
"asin": NewFuncall( "asin": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Asin(arg[0]), nil) return NewR(math.Asin(arg[0]), nil)
}, },
1), 1),
"asinh": NewFuncall( "asinh": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Asinh(arg[0]), nil) return NewR(math.Asinh(arg[0]), nil)
}, },
1), 1),
"atan": NewFuncall( "atan": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Atan(arg[0]), nil) return NewR(math.Atan(arg[0]), nil)
}, },
1), 1),
"atan2": NewFuncall( "atan2": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Atan2(arg[0], arg[1]), nil) return NewR(math.Atan2(arg[0], arg[1]), nil)
}, },
2), 2),
"atanh": NewFuncall( "atanh": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Atanh(arg[0]), nil) return NewR(math.Atanh(arg[0]), nil)
}, },
1), 1),
"cbrt": NewFuncall( "cbrt": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Cbrt(arg[0]), nil) return NewR(math.Cbrt(arg[0]), nil)
}, },
1), 1),
"ceil": NewFuncall( "ceil": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Ceil(arg[0]), nil) return NewR(math.Ceil(arg[0]), nil)
}, },
1), 1),
"cos": NewFuncall( "cos": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Cos(arg[0]), nil) return NewR(math.Cos(arg[0]), nil)
}, },
1), 1),
"cosh": NewFuncall( "cosh": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Cosh(arg[0]), nil) return NewR(math.Cosh(arg[0]), nil)
}, },
1), 1),
"erf": NewFuncall( "erf": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Erf(arg[0]), nil) return NewR(math.Erf(arg[0]), nil)
}, },
1), 1),
"erfc": NewFuncall( "erfc": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Erfc(arg[0]), nil) return NewR(math.Erfc(arg[0]), nil)
}, },
1), 1),
"erfcinv": NewFuncall( "erfcinv": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Erfcinv(arg[0]), nil) return NewR(math.Erfcinv(arg[0]), nil)
}, },
1), 1),
"erfinv": NewFuncall( "erfinv": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Erfinv(arg[0]), nil) return NewR(math.Erfinv(arg[0]), nil)
}, },
1), 1),
"exp": NewFuncall( "exp": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Exp(arg[0]), nil) return NewR(math.Exp(arg[0]), nil)
}, },
1), 1),
"exp2": NewFuncall( "exp2": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Exp2(arg[0]), nil) return NewR(math.Exp2(arg[0]), nil)
}, },
1), 1),
"expm1": NewFuncall( "expm1": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Expm1(arg[0]), nil) return NewR(math.Expm1(arg[0]), nil)
}, },
1), 1),
"floor": NewFuncall( "floor": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Floor(arg[0]), nil) return NewR(math.Floor(arg[0]), nil)
}, },
1), 1),
"gamma": NewFuncall( "gamma": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Gamma(arg[0]), nil) return NewR(math.Gamma(arg[0]), nil)
}, },
1), 1),
"ilogb": NewFuncall( "ilogb": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(float64(math.Ilogb(arg[0])), nil) return NewR(float64(math.Ilogb(arg[0])), nil)
}, },
1), 1),
"j0": NewFuncall( "j0": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.J0(arg[0]), nil) return NewR(math.J0(arg[0]), nil)
}, },
1), 1),
"j1": NewFuncall( "j1": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.J1(arg[0]), nil) return NewR(math.J1(arg[0]), nil)
}, },
1), 1),
"log": NewFuncall( "log": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Log(arg[0]), nil) return NewR(math.Log(arg[0]), nil)
}, },
1), 1),
"log10": NewFuncall( "log10": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Log10(arg[0]), nil) return NewR(math.Log10(arg[0]), nil)
}, },
1), 1),
"log1p": NewFuncall( "log1p": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Log1p(arg[0]), nil) return NewR(math.Log1p(arg[0]), nil)
}, },
1), 1),
"log2": NewFuncall( "log2": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Log2(arg[0]), nil) return NewR(math.Log2(arg[0]), nil)
}, },
1), 1),
"logb": NewFuncall( "logb": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Logb(arg[0]), nil) return NewR(math.Logb(arg[0]), nil)
}, },
1), 1),
"pow": NewFuncall( "pow": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Pow(arg[0], arg[1]), nil) return NewR(math.Pow(arg[0], arg[1]), nil)
}, },
2), 2),
"round": NewFuncall( "round": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Round(arg[0]), nil) return NewR(math.Round(arg[0]), nil)
}, },
1), 1),
"roundtoeven": NewFuncall( "roundtoeven": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.RoundToEven(arg[0]), nil) return NewR(math.RoundToEven(arg[0]), nil)
}, },
1), 1),
"sin": NewFuncall( "sin": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Sin(arg[0]), nil) return NewR(math.Sin(arg[0]), nil)
}, },
1), 1),
"sinh": NewFuncall( "sinh": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Sinh(arg[0]), nil) return NewR(math.Sinh(arg[0]), nil)
}, },
1), 1),
"tan": NewFuncall( "tan": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Tan(arg[0]), nil) return NewR(math.Tan(arg[0]), nil)
}, },
1), 1),
"tanh": NewFuncall( "tanh": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Tanh(arg[0]), nil) return NewR(math.Tanh(arg[0]), nil)
}, },
1), 1),
"trunc": NewFuncall( "trunc": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Trunc(arg[0]), nil) return NewR(math.Trunc(arg[0]), nil)
}, },
1), 1),
"y0": NewFuncall( "y0": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Y0(arg[0]), nil) return NewR(math.Y0(arg[0]), nil)
}, },
1), 1),
"y1": NewFuncall( "y1": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Y1(arg[0]), nil) return NewR(math.Y1(arg[0]), nil)
}, },
1), 1),
"copysign": NewFuncall( "copysign": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Copysign(arg[0], arg[1]), nil) return NewR(math.Copysign(arg[0], arg[1]), nil)
}, },
2), 2),
"dim": NewFuncall( "dim": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Dim(arg[0], arg[1]), nil) return NewR(math.Dim(arg[0], arg[1]), nil)
}, },
2), 2),
"hypot": NewFuncall( "hypot": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(math.Hypot(arg[0], arg[1]), nil) return NewR(math.Hypot(arg[0], arg[1]), nil)
}, },
2), 2),
// converters of all kinds // converters of all kinds
"cm-to-inch": NewFuncall( "cm-to-inch": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]/2.54, nil) return NewR(arg[0]/2.54, nil)
}, },
1), 1),
"inch-to-cm": NewFuncall( "inch-to-cm": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]*2.54, nil) return NewR(arg[0]*2.54, nil)
}, },
1), 1),
"gallons-to-liters": NewFuncall( "gallons-to-liters": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]*3.785, nil) return NewR(arg[0]*3.785, nil)
}, },
1), 1),
"liters-to-gallons": NewFuncall( "liters-to-gallons": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]/3.785, nil) return NewR(arg[0]/3.785, nil)
}, },
1), 1),
"yards-to-meters": NewFuncall( "yards-to-meters": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]*91.44, nil) return NewR(arg[0]*91.44, nil)
}, },
1), 1),
"meters-to-yards": NewFuncall( "meters-to-yards": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]/91.44, nil) return NewR(arg[0]/91.44, nil)
}, },
1), 1),
"miles-to-kilometers": NewFuncall( "miles-to-kilometers": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]*1.609, nil) return NewR(arg[0]*1.609, nil)
}, },
1), 1),
"kilometers-to-miles": NewFuncall( "kilometers-to-miles": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(arg[0]/1.609, nil) return NewR(arg[0]/1.609, nil)
}, },
1), 1),
"or": NewFuncall( "or": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(float64(int(arg[0])|int(arg[1])), nil) return NewR(float64(int(arg[0])|int(arg[1])), nil)
}, },
2), 2),
"and": NewFuncall( "and": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(float64(int(arg[0])&int(arg[1])), nil) return NewR(float64(int(arg[0])&int(arg[1])), nil)
}, },
2), 2),
"xor": NewFuncall( "xor": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
return NewResult(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) Result { func(arg Numbers) R {
// Shift by negative number provibited, so check it. // Shift by negative number provibited, so check it.
// Note that we check against uint64 overflow as well here // Note that we check agains uint64 overflow as well here
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 { if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewResult(0, errors.New("negative shift amount")) return NewR(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), 2),
">": NewFuncall( ">": NewFuncall(
func(arg Numbers) Result { func(arg Numbers) R {
if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 { if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 {
return NewResult(0, errors.New("negative shift amount")) return NewR(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), 2),
} }
// aliases // aliases
funcmap["*"] = funcmap["x"] f["*"] = f["x"]
funcmap["remainder"] = funcmap["mod"] f["remainder"] = f["mod"]
return funcmap return f
} }
func DefineBatchFunctions() Funcalls { func DefineBatchFunctions() Funcalls {
funcmap := map[string]*Funcall{ f := map[string]*Funcall{
"median": NewFuncall( "median": NewFuncall(
func(args Numbers) Result { func(args Numbers) R {
middle := len(args) / 2 middle := len(args) / 2
return NewR(args[middle], nil)
return NewResult(args[middle], nil)
}, },
-1), -1),
"mean": NewFuncall( "mean": NewFuncall(
func(args Numbers) Result { func(args Numbers) R {
var sum float64 var sum float64
for _, item := range args { for _, item := range args {
sum += item sum += item
} }
return NewR(sum/float64(len(args)), nil)
return NewResult(sum/float64(len(args)), nil)
}, },
-1), -1),
"min": NewFuncall( "min": NewFuncall(
func(args Numbers) Result { func(args Numbers) R {
var min float64 var min float64
min, args = args[0], args[1:] min, args = args[0], args[1:]
for _, item := range args { for _, item := range args {
@@ -515,13 +511,12 @@ func DefineBatchFunctions() Funcalls {
min = item min = item
} }
} }
return NewR(min, nil)
return NewResult(min, nil)
}, },
-1), -1),
"max": NewFuncall( "max": NewFuncall(
func(args Numbers) Result { func(args Numbers) R {
var max float64 var max float64
max, args = args[0], args[1:] max, args = args[0], args[1:]
for _, item := range args { for _, item := range args {
@@ -529,26 +524,24 @@ func DefineBatchFunctions() Funcalls {
max = item max = item
} }
} }
return NewR(max, nil)
return NewResult(max, nil)
}, },
-1), -1),
"sum": NewFuncall( "sum": NewFuncall(
func(args Numbers) Result { func(args Numbers) R {
var sum float64 var sum float64
for _, item := range args { for _, item := range args {
sum += item sum += item
} }
return NewR(sum, nil)
return NewResult(sum, nil)
}, },
-1), -1),
} }
// aliases // aliases
funcmap["+"] = funcmap["sum"] f["+"] = f["sum"]
funcmap["avg"] = funcmap["mean"] f["avg"] = f["mean"]
return funcmap return f
} }

17
go.mod
View File

@@ -1,15 +1,12 @@
module rpn module rpn
go 1.22 go 1.20
require ( require (
github.com/chzyer/readline v1.5.1 github.com/chzyer/readline v1.5.1 // indirect
github.com/rogpeppe/go-internal v1.13.1 github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5 // indirect
github.com/yuin/gopher-lua v1.1.1 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
require (
golang.org/x/sys v0.21.0 // indirect
golang.org/x/tools v0.22.0 // indirect
) )

19
go.sum
View File

@@ -1,17 +1,16 @@
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
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 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 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.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
github.com/yuin/gopher-lua v1.1.1/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

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

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

7
rpn.go
View File

@@ -108,8 +108,7 @@ DESCRIPTION
is enabled automatically, see last example. is enabled automatically, see last example.
You can enter integers, floating point numbers (positive or negative) or You can enter integers, floating point numbers (positive or negative) or
hex numbers (prefixed with 0x). Time values in hh::mm format are hex numbers (prefixed with 0x).
possible as well.
STACK MANIPULATION STACK MANIPULATION
There are lots of stack manipulation commands provided. The most There are lots of stack manipulation commands provided. The most
@@ -130,7 +129,7 @@ DESCRIPTION
Basic operators: Basic operators:
+ add + add
- subtract - substract
/ divide / divide
x multiply (alias: *) x multiply (alias: *)
^ power ^ power
@@ -146,7 +145,7 @@ DESCRIPTION
Percent functions: Percent functions:
% percent % percent
%- subtract percent %- substract percent
%+ add percent %+ add percent
Batch functions: Batch functions:

View File

@@ -112,8 +112,7 @@ If the first parameter to rpn is a math operator or function, batch
mode is enabled automatically, see last example. mode is enabled automatically, see last example.
You can enter integers, floating point numbers (positive or negative) You can enter integers, floating point numbers (positive or negative)
or hex numbers (prefixed with 0x). Time values in hh::mm format are or hex numbers (prefixed with 0x).
possible as well.
=head2 STACK MANIPULATION =head2 STACK MANIPULATION
@@ -137,7 +136,7 @@ stack.
Basic operators: Basic operators:
+ add + add
- subtract - substract
/ divide / divide
x multiply (alias: *) x multiply (alias: *)
^ power ^ power
@@ -153,7 +152,7 @@ Bitwise operators:
Percent functions: Percent functions:
% percent % percent
%- subtract percent %- substract percent
%+ add percent %+ add percent
Batch functions: Batch functions:

BIN
rpnc.mp4

Binary file not shown.

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
exec testrpn 09:55 4:15 -
stdout '5.67\n'

View File

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