5 Commits

37 changed files with 480 additions and 1196 deletions

View File

@@ -7,7 +7,7 @@ assignees: TLINDEN
---
**Description**
**Describtion**
<!-- 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:
strategy:
matrix:
version: [1.22.1]
version: [1.21]
os: [ubuntu-latest, windows-latest, macos-latest]
name: Build
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go ${{ matrix.version }}
uses: actions/setup-go@v5
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '${{ matrix.version }}'
go-version: ${{ matrix.version }}
id: go
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: build
run: make buildlocal
run: go build
- name: test
run: make test
@@ -28,11 +28,9 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
- uses: actions/setup-go@v3
with:
go-version: 1.22
- uses: actions/checkout@v4
go-version: 1.21
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
skip-cache: true
uses: golangci/golangci-lint-action@v3

View File

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

349
calc.go
View File

@@ -1,5 +1,5 @@
/*
Copyright © 2023-2024 Thomas von Dein
Copyright © 2023 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@ package main
import (
"errors"
"fmt"
"math"
"regexp"
"sort"
"strconv"
@@ -36,8 +35,6 @@ type Calc struct {
showstack bool
intermediate bool
notdone bool // set to true as long as there are items left in the eval loop
precision int
stack *Stack
history []string
completer readline.AutoCompleter
@@ -65,11 +62,9 @@ const Help string = `
Operators:
basic operators: + - x * / ^ (* is an alias of x)
Bitwise operators: and or xor < (left shift) > (right shift)
Percent functions:
% percent
%- subtract percent
%- substract percent
%+ add percent
Math functions (see https://pkg.go.dev/math):
@@ -92,9 +87,8 @@ Register variables:
// commands, constants and operators, defined here to feed completion
// and our mode switch in Eval() dynamically
const (
Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
Precision int = 2
ShowStackLen int = 5
//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`
)
// That way we can add custom functions to completion
@@ -125,61 +119,54 @@ func (c *Calc) GetCompleteCustomFuncalls() func(string) []string {
}
for command := range c.SettingsCommands {
if len(command) > 1 {
completions = append(completions, command)
}
completions = append(completions, command)
}
for command := range c.ShowCommands {
if len(command) > 1 {
completions = append(completions, command)
}
completions = append(completions, command)
}
for command := range c.StackCommands {
if len(command) > 1 {
completions = append(completions, command)
}
completions = append(completions, command)
}
for command := range c.Commands {
if len(command) > 1 {
completions = append(completions, command)
}
completions = append(completions, command)
}
return completions
}
}
func NewCalc() *Calc {
calc := Calc{stack: NewStack(), debug: false, precision: Precision}
c := Calc{stack: NewStack(), debug: false}
calc.Funcalls = DefineFunctions()
calc.BatchFuncalls = DefineBatchFunctions()
calc.Vars = map[string]float64{}
c.Funcalls = DefineFunctions()
c.BatchFuncalls = DefineBatchFunctions()
c.Vars = map[string]float64{}
calc.completer = readline.NewPrefixCompleter(
c.completer = readline.NewPrefixCompleter(
// custom lua functions
readline.PcItemDynamic(GetCompleteCustomFunctions()),
readline.PcItemDynamic(calc.GetCompleteCustomFuncalls()),
readline.PcItemDynamic(c.GetCompleteCustomFuncalls()),
)
calc.Space = regexp.MustCompile(`\s+`)
calc.Comment = regexp.MustCompile(`#.*`) // ignore everything after #
calc.Register = regexp.MustCompile(`^([<>])([A-Z][A-Z0-9]*)`)
c.Space = regexp.MustCompile(`\s+`)
c.Comment = regexp.MustCompile(`#.*`) // ignore everything after #
c.Register = regexp.MustCompile(`^([<>])([A-Z][A-Z0-9]*)`)
// 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
func (c *Calc) SetInt(interpreter *Interpreter) {
c.interpreter = interpreter
func (c *Calc) SetInt(I *Interpreter) {
c.interpreter = I
for name := range LuaFuncs {
c.LuaFunctions = append(c.LuaFunctions, name)
@@ -206,31 +193,31 @@ func (c *Calc) ToggleShow() {
}
func (c *Calc) Prompt() string {
prompt := "\033[31m»\033[0m "
batch := ""
p := "\033[31m»\033[0m "
b := ""
if c.batch {
batch = "->batch"
b = "->batch"
}
debug := ""
revision := ""
d := ""
v := ""
if c.debug {
debug = "->debug"
revision = fmt.Sprintf("/rev%d", c.stack.rev)
d = "->debug"
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]
func (c *Calc) Eval(line string) error {
func (c *Calc) Eval(line string) {
// remove surrounding whitespace and comments, if any
line = strings.TrimSpace(c.Comment.ReplaceAllString(line, ""))
if line == "" {
return nil
return
}
items := c.Space.Split(line, -1)
@@ -242,135 +229,103 @@ func (c *Calc) Eval(line string) error {
c.notdone = false
}
if err := c.EvalItem(item); err != nil {
return err
num, err := strconv.ParseFloat(item, 64)
if err == nil {
c.stack.Backup()
c.stack.Push(num)
} else {
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
continue
}
if _, ok := c.Funcalls[item]; ok {
if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
}
if c.batch {
if _, ok := c.BatchFuncalls[item]; ok {
if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
}
} else {
if _, ok := c.BatchFuncalls[item]; ok {
fmt.Println("only supported in batch mode")
continue
}
}
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.EvalLuaFunction(item)
continue
}
regmatches := c.Register.FindStringSubmatch(item)
if len(regmatches) == 3 {
switch regmatches[1] {
case ">":
c.PutVar(regmatches[2])
case "<":
c.GetVar(regmatches[2])
}
continue
}
// internal commands
if _, ok := c.Commands[item]; ok {
c.Commands[item].Func(c)
continue
}
if _, ok := c.ShowCommands[item]; ok {
c.ShowCommands[item].Func(c)
continue
}
if _, ok := c.StackCommands[item]; ok {
c.StackCommands[item].Func(c)
continue
}
if _, ok := c.SettingsCommands[item]; ok {
c.SettingsCommands[item].Func(c)
continue
}
switch item {
case "?":
fallthrough
case "help":
c.PrintHelp()
default:
fmt.Println("unknown command or operator!")
}
}
}
if c.showstack && !c.stdin {
dots := ""
if c.stack.Len() > ShowStackLen {
if c.stack.Len() > 5 {
dots = "... "
}
last := c.stack.Last(ShowStackLen)
last := c.stack.Last(5)
fmt.Printf("stack: %s%s\n", dots, list2str(last))
}
return nil
}
func (c *Calc) EvalItem(item string) error {
num, err := strconv.ParseFloat(item, 64)
if err == nil {
c.stack.Backup()
c.stack.Push(num)
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())
}
c.Result()
return nil
}
if exists(c.BatchFuncalls, item) {
if !c.batch {
return Error("only supported in batch mode")
}
if err := c.DoFuncall(item); err != nil {
return Error(err.Error())
}
c.Result()
return nil
}
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.EvalLuaFunction(item)
return nil
}
regmatches := c.Register.FindStringSubmatch(item)
if len(regmatches) == 3 {
switch regmatches[1] {
case ">":
c.PutVar(regmatches[2])
case "<":
c.GetVar(regmatches[2])
}
return nil
}
// internal commands
// FIXME: propagate errors
if exists(c.Commands, item) {
c.Commands[item].Func(c)
return nil
}
if exists(c.ShowCommands, item) {
c.ShowCommands[item].Func(c)
return nil
}
if exists(c.StackCommands, item) {
c.StackCommands[item].Func(c)
return nil
}
if exists(c.SettingsCommands, item) {
c.SettingsCommands[item].Func(c)
return nil
}
switch item {
case "?":
fallthrough
case "help":
c.PrintHelp()
default:
return Error("unknown command or operator")
}
return nil
}
// Execute a math function, check if it is defined just in case
@@ -383,11 +338,10 @@ func (c *Calc) DoFuncall(funcname string) error {
}
if function == nil {
return Error("function not defined but in completion list")
panic("function not defined but in completion list")
}
var args Numbers
batch := false
if function.Expectargs == -1 {
@@ -409,11 +363,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!)
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
return funcresult.Err
return R.Err
}
// don't forget to backup!
@@ -429,11 +383,10 @@ func (c *Calc) DoFuncall(funcname string) error {
}
// save result
c.stack.Push(funcresult.Res)
c.stack.Push(R.Res)
// thanks a lot
c.SetHistory(funcname, args, funcresult.Res)
c.SetHistory(funcname, args, R.Res)
return nil
}
@@ -458,16 +411,7 @@ func (c *Calc) Result() float64 {
fmt.Print("= ")
}
result := c.stack.Last()[0]
truncated := math.Trunc(result)
precision := c.precision
if result == truncated {
precision = 0
}
format := fmt.Sprintf("%%.%df\n", precision)
fmt.Printf(format, result)
fmt.Println(c.stack.Last()[0])
}
return c.stack.Last()[0]
@@ -481,26 +425,24 @@ func (c *Calc) Debug(msg string) {
func (c *Calc) EvalLuaFunction(funcname string) {
// called from calc loop
var luaresult float64
var x float64
var err error
switch c.interpreter.FuncNumArgs(funcname) {
case 0:
fallthrough
case 1:
luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
case 2:
luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2))
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2))
case -1:
luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
default:
luaresult, err = 0, errors.New("invalid number of argument requested")
x, err = 0, errors.New("invalid number of argument requested")
}
if err != nil {
fmt.Println(err)
return
}
@@ -511,26 +453,24 @@ 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, luaresult)
c.History("%s(%f) = %f", funcname, a, x)
}
dopush = false
case 1:
a := c.stack.Pop()
c.History("%s(%f) = %f", funcname, a, luaresult)
c.History("%s(%f) = %f", funcname, a, x)
case 2:
a := 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:
c.stack.Clear()
c.History("%s(*) = %f", funcname, luaresult)
c.History("%s(*) = %f", funcname, x)
}
if dopush {
c.stack.Push(luaresult)
c.stack.Push(x)
}
c.Result()
@@ -548,7 +488,7 @@ func (c *Calc) PutVar(name string) {
}
func (c *Calc) GetVar(name string) {
if exists(c.Vars, name) {
if _, ok := c.Vars[name]; ok {
c.Debug(fmt.Sprintf("retrieve %.2f from %s", c.Vars[name], name))
c.stack.Backup()
c.stack.Push(c.Vars[name])
@@ -561,9 +501,7 @@ func sortcommands(hash Commands) []string {
keys := make([]string, 0, len(hash))
for key := range hash {
if len(key) > 1 {
keys = append(keys, key)
}
keys = append(keys, key)
}
sort.Strings(keys)
@@ -573,35 +511,27 @@ 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)
@@ -609,7 +539,6 @@ 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

@@ -19,8 +19,6 @@ package main
import (
"fmt"
"strconv"
"strings"
"testing"
lua "github.com/yuin/gopher-lua"
@@ -71,22 +69,20 @@ func TestCommentsAndWhitespace(t *testing.T) {
},
}
for _, test := range tests {
for _, tt := range tests {
testname := fmt.Sprintf("%s .(expect %.2f)",
test.name, test.exp)
tt.name, tt.exp)
t.Run(testname, func(t *testing.T) {
for _, line := range test.cmd {
if err := calc.Eval(line); err != nil {
t.Error(err.Error())
}
for _, line := range tt.cmd {
calc.Eval(line)
}
got := calc.stack.Last()
if len(got) > 0 {
if got[0] != test.exp {
if got[0] != tt.exp {
t.Errorf("parsing failed:\n+++ got: %f\n--- want: %f",
got, test.exp)
got, tt.exp)
}
}
@@ -94,6 +90,7 @@ func TestCommentsAndWhitespace(t *testing.T) {
t.Errorf("invalid stack size:\n+++ got: %d\n--- want: 1",
calc.stack.Len())
}
})
calc.stack.Clear()
@@ -244,61 +241,20 @@ func TestCalc(t *testing.T) {
cmd: `4 4 + undo *`,
exp: 16,
},
// bit tests
{
name: "bit and",
cmd: `1 3 and`,
exp: 1,
},
{
name: "bit or",
cmd: `1 3 or`,
exp: 3,
},
{
name: "bit xor",
cmd: `1 3 xor`,
exp: 2,
},
// converters
{
name: "inch-to-cm",
cmd: `111 inch-to-cm`,
exp: 281.94,
},
{
name: "gallons-to-liters",
cmd: `111 gallons-to-liters`,
exp: 420.135,
},
{
name: "meters-to-yards",
cmd: `111 meters-to-yards`,
exp: 1.2139107611548556,
},
{
name: "miles-to-kilometers",
cmd: `111 miles-to-kilometers`,
exp: 178.599,
},
}
for _, test := range tests {
for _, tt := range tests {
testname := fmt.Sprintf("cmd-%s-expect-%.2f",
test.name, test.exp)
tt.name, tt.exp)
t.Run(testname, func(t *testing.T) {
calc.batch = test.batch
if err := calc.Eval(test.cmd); err != nil {
t.Error(err.Error())
}
calc.batch = tt.batch
calc.Eval(tt.cmd)
got := calc.Result()
calc.stack.Clear()
if got != test.exp {
if got != tt.exp {
t.Errorf("calc failed:\n+++ got: %f\n--- want: %f",
got, test.exp)
got, tt.exp)
}
})
}
@@ -323,24 +279,23 @@ func TestCalcLua(t *testing.T) {
}
calc := NewCalc()
LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true})
defer LuaInterpreter.Close()
L = lua.NewState(lua.Options{SkipOpenLibs: true})
defer L.Close()
luarunner := NewInterpreter("example.lua", false)
luarunner.InitLua()
calc.SetInt(luarunner)
for _, test := range tests {
testname := fmt.Sprintf("lua-%s", test.function)
for _, tt := range tests {
testname := fmt.Sprintf("lua-%s", tt.function)
t.Run(testname, func(t *testing.T) {
calc.stack.Clear()
for _, item := range test.stack {
for _, item := range tt.stack {
calc.stack.Push(item)
}
calc.EvalLuaFunction(test.function)
calc.EvalLuaFunction(tt.function)
got := calc.stack.Last()
@@ -349,68 +304,10 @@ func TestCalcLua(t *testing.T) {
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",
test.function, got, test.exp)
tt.function, got, tt.exp)
}
})
}
}
func FuzzEval(f *testing.F) {
legal := []string{
"dump",
"showstack",
"help",
"Pi 31 *",
"SqrtE Pi /",
"55.5 yards-to-meters",
"2 4 +",
"7 8 batch sum",
"7 8 %-",
"7 8 clear",
"7 8 /",
"b",
"#444",
"<X",
}
for _, item := range legal {
f.Add(item)
}
calc := NewCalc()
var hexnum int
f.Fuzz(func(t *testing.T, line string) {
t.Logf("Stack:\n%v\n", calc.stack.All())
if err := calc.EvalItem(line); err == nil {
t.Logf("given: <%s>", line)
// not corpus and empty?
if !contains(legal, line) && len(line) > 0 {
item := strings.TrimSpace(calc.Comment.ReplaceAllString(line, ""))
_, hexerr := fmt.Sscanf(item, "0x%x", &hexnum)
// no comment?
if len(item) > 0 {
// no known command or function?
if _, err := strconv.ParseFloat(item, 64); err != nil {
if !contains(calc.Constants, item) &&
!exists(calc.Funcalls, item) &&
!exists(calc.BatchFuncalls, item) &&
!contains(calc.LuaFunctions, item) &&
!exists(calc.Commands, item) &&
!exists(calc.ShowCommands, item) &&
!exists(calc.SettingsCommands, item) &&
!exists(calc.StackCommands, item) &&
!calc.Register.MatchString(item) &&
item != "?" && item != "help" &&
hexerr != nil {
t.Errorf("Fuzzy input accepted: <%s>", line)
}
}
}
}
}
})
}

View File

@@ -18,12 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
type CommandFunction func(*Calc)
@@ -42,8 +38,9 @@ func NewCommand(help string, function CommandFunction) *Command {
}
}
func (c *Calc) SetSettingsCommands() Commands {
return Commands{
// define all management (that is: non calculation) commands
func (c *Calc) SetCommands() {
c.SettingsCommands = Commands{
// Toggles
"debug": NewCommand(
"toggle debugging",
@@ -88,10 +85,8 @@ func (c *Calc) SetSettingsCommands() Commands {
},
),
}
}
func (c *Calc) SetShowCommands() Commands {
return Commands{
c.ShowCommands = Commands{
// Display commands
"dump": NewCommand(
"display the stack contents",
@@ -122,20 +117,9 @@ func (c *Calc) SetShowCommands() Commands {
}
},
),
"hex": NewCommand(
"show last stack item in hex form (converted to int)",
func(c *Calc) {
if c.stack.Len() > 0 {
fmt.Printf("0x%x\n", int(c.stack.Last()[0]))
}
},
),
}
}
func (c *Calc) SetStackCommands() Commands {
return Commands{
c.StackCommands = Commands{
"clear": NewCommand(
"clear the whole stack",
func(c *Calc) {
@@ -162,7 +146,14 @@ func (c *Calc) SetStackCommands() Commands {
"swap": NewCommand(
"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(
@@ -174,21 +165,17 @@ func (c *Calc) SetStackCommands() Commands {
"dup": NewCommand(
"duplicate last stack item",
CommandDup,
),
"edit": NewCommand(
"edit the stack interactively",
CommandEdit,
func(c *Calc) {
item := c.stack.Last()
if len(item) == 1 {
c.stack.Backup()
c.stack.Push(item[0])
} else {
fmt.Println("stack empty")
}
},
),
}
}
// 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{
@@ -209,142 +196,6 @@ func (c *Calc) SetCommands() {
// aliases
c.Commands["quit"] = c.Commands["exit"]
c.SettingsCommands["d"] = c.SettingsCommands["debug"]
c.SettingsCommands["b"] = c.SettingsCommands["batch"]
c.SettingsCommands["s"] = c.SettingsCommands["showstack"]
c.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["p"] = c.ShowCommands["dump"]
c.ShowCommands["v"] = c.ShowCommands["vars"]
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)
}
c.SettingsCommands["undebug"] = c.SettingsCommands["nodebug"]
c.SettingsCommands["show"] = c.SettingsCommands["showstack"]
}

348
funcs.go
View File

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

15
go.mod
View File

@@ -1,15 +1,10 @@
module rpn
go 1.22
go 1.20
require (
github.com/chzyer/readline v1.5.1
github.com/rogpeppe/go-internal v1.13.1
github.com/spf13/pflag v1.0.5
github.com/yuin/gopher-lua v1.1.1
)
require (
golang.org/x/sys v0.21.0 // indirect
golang.org/x/tools v0.22.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/yuin/gopher-lua v1.1.0 // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
)

13
go.sum
View File

@@ -1,17 +1,10 @@
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
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/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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=

View File

@@ -29,8 +29,8 @@ type Interpreter struct {
script string
}
// LuaInterpreter is the lua interpreter, instantiated in main()
var LuaInterpreter *lua.LState
// LUA interpreter, instanciated in main()
var L *lua.LState
// holds a user provided lua function
type LuaFunction struct {
@@ -39,8 +39,8 @@ type LuaFunction struct {
numargs int
}
// LuaFuncs must be global since init() is being called from lua which
// doesn't have access to the interpreter instance
// 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 := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.NewFunction(pair.f),
if err := L.CallByParam(lua.P{
Fn: L.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 := LuaInterpreter.DoFile(i.script); err != nil {
if err := L.DoFile(i.script); err != nil {
panic(err)
}
// instantiate
// instanciate
LuaFuncs = map[string]LuaFunction{}
// 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()
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal("init"),
if err := L.CallByParam(lua.P{
Fn: L.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 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
// 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) {
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 := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal(funcname),
if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname),
NRet: 1,
Protect: true,
}, 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:
// 2 arg variant
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal(funcname),
if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname),
NRet: 1,
Protect: true,
}, 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:
// batch variant, use lua table as array
table := LuaInterpreter.NewTable()
tb := L.NewTable()
// put the whole stack into it
for _, item := range items {
table.Append(lua.LNumber(item))
tb.Append(lua.LNumber(item))
}
if err := LuaInterpreter.CallByParam(lua.P{
Fn: LuaInterpreter.GetGlobal(funcname),
if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname),
NRet: 1,
Protect: true,
}, table); err != nil {
return 0, fmt.Errorf("failed to exec lua func %s: %w", funcname, err)
}, tb); err != nil {
return 0, err
}
}
// get result and cast to float64
if res, ok := LuaInterpreter.Get(-1).(lua.LNumber); ok {
LuaInterpreter.Pop(1)
if res, ok := L.Get(-1).(lua.LNumber); ok {
L.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(lstate *lua.LState) int {
function := lstate.ToString(1)
numargs := lstate.ToInt(2)
help := lstate.ToString(3)
func register(L *lua.LState) int {
function := L.ToString(1)
numargs := L.ToInt(2)
help := L.ToString(3)
LuaFuncs[function] = LuaFunction{
name: function,

89
main.go
View File

@@ -1,5 +1,5 @@
/*
Copyright © 2023-2024 Thomas von Dein
Copyright © 2023 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -30,33 +30,27 @@ import (
lua "github.com/yuin/gopher-lua"
)
const VERSION string = "2.1.2"
const VERSION string = "2.0.9"
const Usage string = `This is rpn, a reverse polish notation calculator cli.
Usage: rpn [-bdvh] [<operator>]
Options:
-b, --batchmode enable batch mode
-d, --debug enable debug mode
-s, --stack show last 5 items of the stack (off by default)
-i --intermediate print intermediate results
-m, --manual show manual
-c, --config <file> load <file> containing LUA code
-p, --precision <int> floating point number precision (default 2)
-v, --version show version
-h, --help show help
-b, --batchmode enable batch mode
-d, --debug enable debug mode
-s, --stack show last 5 items of the stack (off by default)
-i --intermediate print intermediate results
-m, --manual show manual
-v, --version show version
-h, --help show help
When <operator> is given, batch mode ist automatically enabled. Use
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
Copyright (c) 2023-2024 T.v.Dein`
Copyright (c) 2023 T.v.Dein`
func main() {
os.Exit(Main())
}
func Main() int {
calc := NewCalc()
showversion := false
@@ -75,20 +69,17 @@ func Main() int {
flag.BoolVarP(&showmanual, "manual", "m", false, "show manual")
flag.StringVarP(&configfile, "config", "c",
os.Getenv("HOME")+"/.rpn.lua", "config file (lua format)")
flag.IntVarP(&calc.precision, "precision", "p", Precision, "floating point precision")
flag.Parse()
if showversion {
fmt.Printf("This is rpn version %s\n", VERSION)
return 0
return
}
if showhelp {
fmt.Println(Usage)
return 0
return
}
if enabledebug {
@@ -97,13 +88,12 @@ func Main() int {
if showmanual {
man()
return 0
os.Exit(0)
}
// the lua state object is global, instantiate it early
LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true})
defer LuaInterpreter.Close()
// the lua state object is global, instanciate it early
L = lua.NewState(lua.Options{SkipOpenLibs: true})
defer L.Close()
// our config file is interpreted as lua code, only functions can
// be defined, init() will be called by InitLua().
@@ -111,29 +101,18 @@ func Main() int {
luarunner := NewInterpreter(configfile, enabledebug)
luarunner.InitLua()
calc.SetInt(luarunner)
if calc.debug {
fmt.Println("loaded config")
}
} else if calc.debug {
fmt.Println(err)
}
if len(flag.Args()) > 1 {
// commandline calc operation, no readline etc needed
// called like rpn 2 2 +
calc.stdin = true
if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil {
fmt.Println(err)
return 1
}
return 0
calc.Eval(strings.Join(flag.Args(), " "))
return
}
// interactive mode, need readline
reader, err := readline.NewEx(&readline.Config{
rl, err := readline.NewEx(&readline.Config{
Prompt: calc.Prompt(),
HistoryFile: os.Getenv("HOME") + "/.rpn-history",
HistoryLimit: 500,
@@ -146,8 +125,8 @@ func Main() int {
if err != nil {
panic(err)
}
defer reader.Close()
reader.CaptureExitSignal()
defer rl.Close()
rl.CaptureExitSignal()
if inputIsStdin() {
// commands are coming on stdin, however we will still enter
@@ -157,17 +136,13 @@ func Main() int {
for {
// primary program repl
line, err := reader.Readline()
line, err := rl.Readline()
if err != nil {
break
}
err = calc.Eval(line)
if err != nil {
fmt.Println(err)
}
reader.SetPrompt(calc.Prompt())
calc.Eval(line)
rl.SetPrompt(calc.Prompt())
}
if len(flag.Args()) > 0 {
@@ -175,31 +150,23 @@ func Main() int {
// echo 1 2 3 4 | rpn +
// batch mode enabled automatically
calc.batch = true
if err = calc.Eval(flag.Args()[0]); err != nil {
fmt.Println(err)
return 1
}
calc.Eval(flag.Args()[0])
}
return 0
}
func inputIsStdin() bool {
stat, _ := os.Stdin.Stat()
return (stat.Mode() & os.ModeCharDevice) == 0
}
func man() {
var buf bytes.Buffer
man := exec.Command("less", "-")
buf.WriteString(manpage)
var b bytes.Buffer
b.Write([]byte(manpage))
man.Stdout = os.Stdout
man.Stdin = &buf
man.Stdin = &b
man.Stderr = os.Stderr
err := man.Run()

View File

@@ -1,20 +0,0 @@
package main
import (
"os"
"testing"
"github.com/rogpeppe/go-internal/testscript"
)
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"testrpn": Main,
}))
}
func TestRpn(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "t",
})
}

82
rpn.go
View File

@@ -8,15 +8,13 @@ SYNOPSIS
Usage: rpn [-bdvh] [<operator>]
Options:
-b, --batchmode enable batch mode
-d, --debug enable debug mode
-s, --stack show last 5 items of the stack (off by default)
-i --intermediate print intermediate results
-m, --manual show manual
-c, --config <file> load <file> containing LUA code
-p, --precision <int> floating point number precision (default 2)
-v, --version show version
-h, --help show help
-b, --batchmode enable batch mode
-d, --debug enable debug mode
-s, --stack show last 5 items of the stack (off by default)
-i --intermediate print intermediate results
-m, --manual show manual
-v, --version show version
-h, --help show help
When <operator> is given, batch mode ist automatically enabled. Use
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
@@ -107,9 +105,6 @@ DESCRIPTION
If the first parameter to rpn is a math operator or function, batch mode
is enabled automatically, see last example.
You can enter integers, floating point numbers (positive or negative) or
hex numbers (prefixed with 0x).
STACK MANIPULATION
There are lots of stack manipulation commands provided. The most
important one is undo which goes back to the stack before the last math
@@ -129,23 +124,15 @@ DESCRIPTION
Basic operators:
+ add
- subtract
- substract
/ divide
x multiply (alias: *)
^ power
Bitwise operators:
and bitwise and
or bitwise or
xor bitwise xor
< left shift
> right shift
Percent functions:
% percent
%- subtract percent
%- substract percent
%+ add percent
Batch functions:
@@ -163,44 +150,19 @@ DESCRIPTION
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
y1 copysign dim hypot
Conversion functions:
cm-to-inch
inch-to-cm
gallons-to-liters
liters-to-gallons
yards-to-meters
meters-to-yards
miles-to-kilometers
kilometers-to-miles
Configuration Commands:
Commands:
[no]batch toggle batch mode (nobatch turns it off)
[no]debug toggle debug output (nodebug turns it off)
[no]showstack show the last 5 items of the stack (noshowtack turns it off)
Show commands:
dump display the stack contents
hex show last stack item in hex form (converted to int)
history display calculation history
vars show list of variables
Stack manipulation commands:
clear clear the whole stack
shift remove the last element of the stack
reverse reverse the stack elements
swap exchange the last two stack elements
dup duplicate last stack item
undo undo last operation
edit edit the stack interactively using vi or $EDITOR
Other commands:
history display calculation history
help|? show this message
manual show manual
quit|exit|c-d|c-c exit program
Register variables:
@@ -210,17 +172,6 @@ DESCRIPTION
Refer to https://pkg.go.dev/math for details about those functions.
There are also a number of shortcuts for some commands available:
d debug
b batch
s showstack
h history
p dump (aka print)
v vars
c clear
u undo
INTERACTIVE REPL
While you can use rpn in the command-line, the best experience you'll
have is the interactive repl (read eval print loop). Just execute "rpn"
@@ -311,15 +262,6 @@ EXTENDING RPN USING LUA
So you can't open files, execute other programs or open a connection to
the outside!
CONFIGURATION
rpn can be configured via command line flags (see usage above). Most of
the flags are also available as interactive commands, such as "--batch"
has the same effect as the batch command.
The floating point number precision option "-p, --precision" however is
not available as interactive command, it MUST be configured on the
command line, if needed. The default precision is 2.
GETTING HELP
In interactive mode you can enter the help command (or ?) to get a short
help along with a list of all supported operators and functions.
@@ -339,7 +281,7 @@ LICENSE
This software is licensed under the GNU GENERAL PUBLIC LICENSE version
3.
Copyright (c) 2023-2024 by Thomas von Dein
Copyright (c) 2023 by Thomas von Dein
This software uses the following GO modules:

83
rpn.pod
View File

@@ -7,15 +7,13 @@ rpn - Programmable command-line calculator using reverse polish notation
Usage: rpn [-bdvh] [<operator>]
Options:
-b, --batchmode enable batch mode
-d, --debug enable debug mode
-s, --stack show last 5 items of the stack (off by default)
-i --intermediate print intermediate results
-m, --manual show manual
-c, --config <file> load <file> containing LUA code
-p, --precision <int> floating point number precision (default 2)
-v, --version show version
-h, --help show help
-b, --batchmode enable batch mode
-d, --debug enable debug mode
-s, --stack show last 5 items of the stack (off by default)
-i --intermediate print intermediate results
-m, --manual show manual
-v, --version show version
-h, --help show help
When <operator> is given, batch mode ist automatically enabled. Use
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
@@ -111,9 +109,6 @@ Example of batch mode usage:
If the first parameter to rpn is a math operator or function, batch
mode is enabled automatically, see last example.
You can enter integers, floating point numbers (positive or negative)
or hex numbers (prefixed with 0x).
=head2 STACK MANIPULATION
There are lots of stack manipulation commands provided. The most
@@ -136,23 +131,15 @@ stack.
Basic operators:
+ add
- subtract
- substract
/ divide
x multiply (alias: *)
^ power
Bitwise operators:
and bitwise and
or bitwise or
xor bitwise xor
< left shift
> right shift
Percent functions:
% percent
%- subtract percent
%- substract percent
%+ add percent
Batch functions:
@@ -170,44 +157,19 @@ Math functions:
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
y1 copysign dim hypot
Conversion functions:
cm-to-inch
inch-to-cm
gallons-to-liters
liters-to-gallons
yards-to-meters
meters-to-yards
miles-to-kilometers
kilometers-to-miles
Configuration Commands:
Commands:
[no]batch toggle batch mode (nobatch turns it off)
[no]debug toggle debug output (nodebug turns it off)
[no]showstack show the last 5 items of the stack (noshowtack turns it off)
Show commands:
dump display the stack contents
hex show last stack item in hex form (converted to int)
history display calculation history
vars show list of variables
Stack manipulation commands:
clear clear the whole stack
shift remove the last element of the stack
reverse reverse the stack elements
swap exchange the last two stack elements
dup duplicate last stack item
undo undo last operation
edit edit the stack interactively using vi or $EDITOR
Other commands:
history display calculation history
help|? show this message
manual show manual
quit|exit|c-d|c-c exit program
@@ -218,17 +180,6 @@ Register variables:
Refer to https://pkg.go.dev/math for details about those functions.
There are also a number of shortcuts for some commands available:
d debug
b batch
s showstack
h history
p dump (aka print)
v vars
c clear
u undo
=head1 INTERACTIVE REPL
While you can use rpn in the command-line, the best experience you'll
@@ -344,16 +295,6 @@ B<Please note, that io, networking and system stuff is not allowed
though. So you can't open files, execute other programs or open a
connection to the outside!>
=head1 CONFIGURATION
B<rpn> can be configured via command line flags (see usage
above). Most of the flags are also available as interactive commands,
such as C<--batch> has the same effect as the B<batch> command.
The floating point number precision option C<-p, --precision> however
is not available as interactive command, it MUST be configured on the
command line, if needed. The default precision is 2.
=head1 GETTING HELP
In interactive mode you can enter the B<help> command (or B<?>) to get
@@ -376,7 +317,7 @@ L<https://github.com/TLINDEN/rpnc/issues>.
This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3.
Copyright (c) 2023-2024 by Thomas von Dein
Copyright (c) 2023 by Thomas von Dein
This software uses the following GO modules:

BIN
rpnc.mp4

Binary file not shown.

View File

@@ -64,14 +64,14 @@ func (s *Stack) Bump() {
}
// append an item to the stack
func (s *Stack) Push(item float64) {
func (s *Stack) Push(x float64) {
s.mutex.Lock()
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.linklist.PushBack(item)
s.linklist.PushBack(x)
}
// 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.Bump()
return val.(float64)
}
@@ -124,33 +123,32 @@ func (s *Stack) Swap() {
return
}
prevA := s.linklist.Back()
s.linklist.Remove(prevA)
a := s.linklist.Back()
s.linklist.Remove(a)
prevB := s.linklist.Back()
s.linklist.Remove(prevB)
b := s.linklist.Back()
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(prevB.Value)
s.linklist.PushBack(a.Value)
s.linklist.PushBack(b.Value)
}
// Return the last num items from the stack w/o modifying it.
func (s *Stack) Last(num ...int) []float64 {
items := []float64{}
stacklen := s.Len()
i := s.Len()
count := 1
if len(num) > 0 {
count = num[0]
}
for e := s.linklist.Front(); e != nil; e = e.Next() {
if stacklen <= count {
if i <= count {
items = append(items, e.Value.(float64))
}
stacklen--
i--
}
return items
@@ -170,14 +168,12 @@ 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)
}
@@ -219,7 +215,6 @@ 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) {
stack := NewStack()
stack.Push(5)
got := stack.Pop()
s := NewStack()
s.Push(5)
got := s.Pop()
if got != 5.0 {
t.Errorf("pop failed:\n+++ got: %f\n--- want: %f",
got, 5.0)
}
if stack.Len() != 0 {
if s.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) {
stack := NewStack()
stack.Push(5)
stack.Push(5)
stack.Push(5)
stack.Pop()
s := NewStack()
s.Push(5)
s.Push(5)
s.Push(5)
s.Pop()
if stack.Len() != 2 {
if s.Len() != 2 {
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) {
t.Run("shift", func(t *testing.T) {
stack := NewStack()
stack.Shift()
s := NewStack()
s.Shift()
if stack.Len() != 0 {
if s.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) {
stack := NewStack()
stack.Push(5)
stack.Push(5)
stack.Push(5)
stack.Clear()
s := NewStack()
s.Push(5)
s.Push(5)
s.Push(5)
s.Clear()
if stack.Len() != 0 {
if s.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) {
stack := NewStack()
stack.Push(5)
got := stack.Last()
s := NewStack()
s.Push(5)
got := s.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 stack.Len() != 1 {
if s.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) {
stack := NewStack()
s := NewStack()
list := []float64{2, 4, 6, 8}
for _, item := range list {
stack.Push(item)
s.Push(item)
}
got := stack.All()
got := s.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 stack.Len() != len(list) {
if s.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) {
stack := NewStack()
stack.Push(5)
stack.Backup()
stack.Clear()
stack.Restore()
s := NewStack()
s.Push(5)
s.Backup()
s.Clear()
s.Restore()
if stack.Len() != 1 {
if s.Len() != 1 {
t.Errorf("stack not correctly restored()")
}
value := stack.Pop()
if value != 5.0 {
a := s.Pop()
if a != 5.0 {
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) {
t.Run("reverse", func(t *testing.T) {
stack := NewStack()
s := NewStack()
list := []float64{2, 4, 6}
reverse := []float64{6, 4, 2}
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) {
t.Errorf("all failed:\n+++ got: %d elements\n--- want: %d elements",

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
exec testrpn -p 4 2 3 /
stdout '0.6667\n'

View File

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

View File

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

View File

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

View File

@@ -1,16 +0,0 @@
exec testrpn -d -c test.lua 3 5 lower
stdout '3\n'
-- test.lua --
function lower(a,b)
if a < b then
return a
else
return b
end
end
function init()
-- expects 2 args
register("lower", 2, "lower")
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
-- simple function, return the lower number of the two operands
function lower(a,b)
if a < b then
return a
else
return b
end
end
function init()
-- expects 2 args
register("lower", 2, "lower")
end

19
util.go
View File

@@ -23,20 +23,19 @@ import (
"strings"
)
// find an item in a list, generic variant
func contains[E comparable](s []E, v E) bool {
for _, vs := range s {
if v == vs {
// find an item in a list
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// look if a key in a map exists, generic variant
func exists[K comparable, V any](m map[K]V, v K) bool {
if _, ok := m[v]; ok {
func exists(m map[string]interface{}, item string) bool {
// FIXME: try to use this for all cases
if _, ok := m[item]; ok {
return true
}
@@ -73,7 +72,3 @@ func const2num(name string) float64 {
func list2str(list Numbers) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]")
}
func Error(m string) error {
return fmt.Errorf("Error: %s", m)
}