22 Commits

Author SHA1 Message Date
28abd79961 bump version 2023-12-07 13:46:34 +01:00
dd14e7ec35 don't show shortcuts in help (clutters it) 2023-12-07 13:43:45 +01:00
T.v.Dein
d2db420837 Merge pull request #25 from TLINDEN/doc/fix-formating
fix pod formatting (fixes #24)
2023-12-05 20:09:42 +01:00
T.v.Dein
b4f53d2dd6 Merge pull request #26 from TLINDEN/feature/add-shortcuts
added a couple of command shortcuts
2023-12-05 20:06:05 +01:00
ec4d86f727 added a couple of shortcuts 2023-12-05 20:01:32 +01:00
4c6caa7114 fix pod formatting (fixes #24) 2023-12-04 18:03:58 +01:00
T.v.Dein
252e7eb8d9 Feature/edit stack command (#23)
* add edit feature
* bump version
2023-12-04 13:53:18 +01:00
T.v.Dein
416c163d94 Merge pull request #22 from TLINDEN/doc/improve
add converter docs
2023-11-14 20:01:19 +01:00
d93fbe33dc add converter docs 2023-11-14 20:00:09 +01:00
T.v.Dein
59241932e0 Feature/add converters and bitwise ops (#20)
* added:

- converters
- bitwise operators
- hex input and output support
2023-11-13 15:51:07 +01:00
T.v.Dein
127483eea1 Merge pull request #19 from TLINDEN/revert/exists
revert exists(), unused
2023-11-12 20:41:58 +01:00
4846691c46 revert exists(), unused 2023-11-12 20:41:06 +01:00
T.v.Dein
2f56761bf1 Feature/commands (#18)
* re-organized command structure
* added 'dup' command
2023-11-12 20:29:10 +01:00
T.v.Dein
0782b0920b Merge pull request #17 from TLINDEN/feature/add-disable-swiches
added no versions of batch debug and showstack toggles
2023-11-10 15:44:07 +01:00
40c4cf0e45 added no versions of batch debug and showstack toggles 2023-11-10 15:38:05 +01:00
T.v.Dein
b13fbc63e3 Merge pull request #16 from TLINDEN/internal/add-lua-tests
Internal/add lua tests
2023-11-09 18:49:32 +01:00
bacbfcc517 added debug output to Backup() 2023-11-09 18:47:31 +01:00
b91e024569 added more tests 2023-11-09 18:34:38 +01:00
a6f8a0fdbe renamed luafunc() 2023-11-09 18:34:29 +01:00
7b656c492a fix reverse and backup&restore 2023-11-09 18:34:07 +01:00
7d0443ce4b bump version 2023-11-09 18:34:00 +01:00
T.v.Dein
a964a99f3d Fix/lua no funcs known (#15)
* lua fixes:

- fix lua function calling, didn't work in the last
releases (regression)
- add lua funcs which don't modify the stack (for converters etc)
- added better lua examples
2023-11-08 19:03:37 +01:00
12 changed files with 882 additions and 126 deletions

View File

@@ -225,10 +225,15 @@ the `register()` function to register your functions to the
calculator. This function takes these parameters: calculator. This function takes these parameters:
- function name - function name
- number of arguments expected (1,2 or -1 allowed), -1 means batch - number of arguments expected (see below)
mode
- help text - help text
Number of expected arguments can be:
- 0: expect 1 argument but do NOT modify the stack
- 1-n: do a singular calculation
- -1: batch mode work with all numbers on the stack
Please [refer to the lua language Please [refer to the lua language
reference](https://www.lua.org/manual/5.4/) for more details about reference](https://www.lua.org/manual/5.4/) for more details about
LUA. LUA.

220
calc.go
View File

@@ -20,8 +20,8 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
@@ -48,27 +48,22 @@ type Calc struct {
Funcalls Funcalls Funcalls Funcalls
BatchFuncalls Funcalls BatchFuncalls Funcalls
// different kinds of commands, displays nicer in help output
StackCommands Commands
SettingsCommands Commands
ShowCommands Commands
Commands Commands
Vars map[string]float64 Vars map[string]float64
} }
// help for lua functions will be added dynamically // help for lua functions will be added dynamically
const Help string = `Available commands: const Help string = `
batch toggle batch mode
debug toggle debug output
show show the last 5 items of the stack
dump display the stack contents
clear clear the whole stack
shift remove the last element of the stack
reverse reverse the stack elements
swap exchange the last two elements
vars show list of variables
history display calculation history
help|? show this message
quit|exit|c-d|c-c exit program
Operators: Operators:
basic operators: + - x * / ^ (* is an alias of x) basic operators: + - x * / ^ (* is an alias of x)
Bitwise operators: and or xor < (left shift) > (right shift)
Percent functions: Percent functions:
% percent % percent
%- substract percent %- substract percent
@@ -94,7 +89,7 @@ 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 debug undebug clear batch shift undo help history manual exit quit swap show vars` //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`
) )
@@ -107,7 +102,6 @@ func GetCompleteCustomFunctions() func(string) []string {
completions = append(completions, luafunc) completions = append(completions, luafunc)
} }
completions = append(completions, strings.Split(Commands, " ")...)
completions = append(completions, strings.Split(Constants, " ")...) completions = append(completions, strings.Split(Constants, " ")...)
return completions return completions
@@ -126,6 +120,30 @@ func (c *Calc) GetCompleteCustomFuncalls() func(string) []string {
completions = append(completions, function) completions = append(completions, function)
} }
for command := range c.SettingsCommands {
if len(command) > 1 {
completions = append(completions, command)
}
}
for command := range c.ShowCommands {
if len(command) > 1 {
completions = append(completions, command)
}
}
for command := range c.StackCommands {
if len(command) > 1 {
completions = append(completions, command)
}
}
for command := range c.Commands {
if len(command) > 1 {
completions = append(completions, command)
}
}
return completions return completions
} }
@@ -151,16 +169,18 @@ func NewCalc() *Calc {
// pre-calculate mode switching arrays // pre-calculate mode switching arrays
c.Constants = strings.Split(Constants, " ") c.Constants = strings.Split(Constants, " ")
for name := range LuaFuncs { c.SetCommands()
c.LuaFunctions = append(c.LuaFunctions, name)
}
return &c return &c
} }
// setup the interpreter, called from main() // setup the interpreter, called from main(), import lua functions
func (c *Calc) SetInt(I *Interpreter) { func (c *Calc) SetInt(I *Interpreter) {
c.interpreter = I c.interpreter = I
for name := range LuaFuncs {
c.LuaFunctions = append(c.LuaFunctions, name)
}
} }
func (c *Calc) ToggleDebug() { func (c *Calc) ToggleDebug() {
@@ -225,6 +245,15 @@ func (c *Calc) Eval(line string) {
c.stack.Backup() c.stack.Backup()
c.stack.Push(num) c.stack.Push(num)
} else { } else {
// try hex
var i int
_, err := fmt.Sscanf(item, "0x%x", &i)
if err == nil {
c.stack.Backup()
c.stack.Push(float64(i))
continue
}
if contains(c.Constants, item) { if contains(c.Constants, item) {
// put the constant onto the stack // put the constant onto the stack
c.stack.Backup() c.stack.Backup()
@@ -259,7 +288,7 @@ func (c *Calc) Eval(line string) {
if contains(c.LuaFunctions, item) { if contains(c.LuaFunctions, item) {
// user provided custom lua functions // user provided custom lua functions
c.luafunc(item) c.EvalLuaFunction(item)
continue continue
} }
@@ -274,65 +303,32 @@ func (c *Calc) Eval(line string) {
continue continue
} }
// management commands // 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 { switch item {
case "?": case "?":
fallthrough fallthrough
case "help": case "help":
fmt.Println(Help) c.PrintHelp()
if len(LuaFuncs) > 0 {
fmt.Println("Lua functions:")
for name, function := range LuaFuncs {
fmt.Printf("%-20s %s\n", name, function.help)
}
}
case "dump":
c.stack.Dump()
case "debug":
c.ToggleDebug()
case "undebug":
c.debug = false
case "batch":
c.ToggleBatch()
case "clear":
c.stack.Backup()
c.stack.Clear()
case "shift":
c.stack.Backup()
c.stack.Shift()
case "reverse":
c.stack.Backup()
c.stack.Reverse()
case "swap":
if c.stack.Len() < 2 {
fmt.Println("stack too small, can't swap")
} else {
c.stack.Backup()
c.stack.Swap()
}
case "undo":
c.stack.Restore()
case "history":
for _, entry := range c.history {
fmt.Println(entry)
}
case "show":
c.ToggleShow()
case "exit":
fallthrough
case "quit":
os.Exit(0)
case "manual":
man()
case "vars":
if len(c.Vars) > 0 {
fmt.Printf("%-20s %s\n", "VARIABLE", "VALUE")
for k, v := range c.Vars {
fmt.Printf("%-20s -> %.2f\n", k, v)
}
} else {
fmt.Println("no vars registered")
}
default: default:
fmt.Println("unknown command or operator!") fmt.Println("unknown command or operator!")
@@ -393,6 +389,10 @@ func (c *Calc) DoFuncall(funcname string) error {
return R.Err return R.Err
} }
// don't forget to backup!
c.stack.Backup()
// "pop"
if batch { if batch {
// get rid of stack // get rid of stack
c.stack.Clear() c.stack.Clear()
@@ -442,12 +442,14 @@ func (c *Calc) Debug(msg string) {
} }
} }
func (c *Calc) luafunc(funcname string) { func (c *Calc) EvalLuaFunction(funcname string) {
// called from calc loop // called from calc loop
var x float64 var x float64
var err error var err error
switch c.interpreter.FuncNumArgs(funcname) { switch c.interpreter.FuncNumArgs(funcname) {
case 0:
fallthrough
case 1: case 1:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last()) x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
case 2: case 2:
@@ -465,7 +467,15 @@ func (c *Calc) luafunc(funcname string) {
c.stack.Backup() c.stack.Backup()
dopush := true
switch c.interpreter.FuncNumArgs(funcname) { switch c.interpreter.FuncNumArgs(funcname) {
case 0:
a := c.stack.Last()
if len(a) == 1 {
c.History("%s(%f) = %f", funcname, a, x)
}
dopush = false
case 1: case 1:
a := c.stack.Pop() a := c.stack.Pop()
c.History("%s(%f) = %f", funcname, a, x) c.History("%s(%f) = %f", funcname, a, x)
@@ -478,7 +488,9 @@ func (c *Calc) luafunc(funcname string) {
c.History("%s(*) = %f", funcname, x) c.History("%s(*) = %f", funcname, x)
} }
c.stack.Push(x) if dopush {
c.stack.Push(x)
}
c.Result() c.Result()
} }
@@ -503,3 +515,53 @@ func (c *Calc) GetVar(name string) {
fmt.Println("variable doesn't exist") fmt.Println("variable doesn't exist")
} }
} }
func sortcommands(hash Commands) []string {
keys := make([]string, 0, len(hash))
for key := range hash {
if len(key) > 1 {
keys = append(keys, key)
}
}
sort.Strings(keys)
return keys
}
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)
// 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

@@ -20,6 +20,8 @@ package main
import ( import (
"fmt" "fmt"
"testing" "testing"
lua "github.com/yuin/gopher-lua"
) )
func TestCommentsAndWhitespace(t *testing.T) { func TestCommentsAndWhitespace(t *testing.T) {
@@ -104,6 +106,7 @@ func TestCalc(t *testing.T) {
exp float64 exp float64
batch bool batch bool
}{ }{
// ops
{ {
name: "plus", name: "plus",
cmd: `15 15 +`, cmd: `15 15 +`,
@@ -144,6 +147,8 @@ func TestCalc(t *testing.T) {
cmd: `400 20 %+`, cmd: `400 20 %+`,
exp: 480, exp: 480,
}, },
// math tests
{ {
name: "mod", name: "mod",
cmd: `9 2 mod`, cmd: `9 2 mod`,
@@ -164,6 +169,20 @@ func TestCalc(t *testing.T) {
cmd: `6 4 dim`, cmd: `6 4 dim`,
exp: 2, exp: 2,
}, },
// constants tests
{
name: "pitimes2",
cmd: `Pi 2 *`,
exp: 6.283185307179586,
},
{
name: "pi+sqrt2",
cmd: `Pi Sqrt2 +`,
exp: 4.555806215962888,
},
// batch tests
{ {
name: "batch-sum", name: "batch-sum",
cmd: `2 2 2 2 sum`, cmd: `2 2 2 2 sum`,
@@ -194,6 +213,73 @@ func TestCalc(t *testing.T) {
exp: 5, exp: 5,
batch: true, batch: true,
}, },
// stack tests
{
name: "use-vars",
cmd: `10 >TEN clear 5 <TEN *`,
exp: 50,
},
{
name: "reverse",
cmd: `100 500 reverse -`,
exp: 400,
},
{
name: "swap",
cmd: `2 16 swap /`,
exp: 8,
},
{
name: "clear batch",
cmd: "1 1 1 1 1 clear 1 1 sum",
exp: 2,
batch: true,
},
{
name: "undo",
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 _, tt := range tests { for _, tt := range tests {
@@ -212,3 +298,55 @@ func TestCalc(t *testing.T) {
}) })
} }
} }
func TestCalcLua(t *testing.T) {
var tests = []struct {
function string
stack []float64
exp float64
}{
{
function: "lower",
stack: []float64{5, 6},
exp: 5.0,
},
{
function: "parallelresistance",
stack: []float64{100, 200, 300},
exp: 54.54545454545455,
},
}
calc := NewCalc()
L = lua.NewState(lua.Options{SkipOpenLibs: true})
defer L.Close()
luarunner := NewInterpreter("example.lua", false)
luarunner.InitLua()
calc.SetInt(luarunner)
for _, tt := range tests {
testname := fmt.Sprintf("lua-%s", tt.function)
t.Run(testname, func(t *testing.T) {
calc.stack.Clear()
for _, item := range tt.stack {
calc.stack.Push(item)
}
calc.EvalLuaFunction(tt.function)
got := calc.stack.Last()
if calc.stack.Len() != 1 {
t.Errorf("invalid stack size:\n+++ got: %d\n--- want: 1",
calc.stack.Len())
}
if got[0] != tt.exp {
t.Errorf("lua function %s failed:\n+++ got: %f\n--- want: %f",
tt.function, got, tt.exp)
}
})
}
}

319
command.go Normal file
View File

@@ -0,0 +1,319 @@
/*
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
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)
type Command struct {
Help string
Func CommandFunction
}
type Commands map[string]*Command
func NewCommand(help string, function CommandFunction) *Command {
return &Command{
Help: help,
Func: function,
}
}
// define all management (that is: non calculation) commands
func (c *Calc) SetCommands() {
c.SettingsCommands = Commands{
// Toggles
"debug": NewCommand(
"toggle debugging",
func(c *Calc) {
c.ToggleDebug()
},
),
"nodebug": NewCommand(
"disable debugging",
func(c *Calc) {
c.debug = false
c.stack.debug = false
},
),
"batch": NewCommand(
"toggle batch mode",
func(c *Calc) {
c.ToggleBatch()
},
),
"nobatch": NewCommand(
"disable batch mode",
func(c *Calc) {
c.batch = false
},
),
"showstack": NewCommand(
"toggle show last 5 items of the stack",
func(c *Calc) {
c.ToggleShow()
},
),
"noshowstack": NewCommand(
"disable display of the stack",
func(c *Calc) {
c.showstack = false
},
),
}
c.ShowCommands = Commands{
// Display commands
"dump": NewCommand(
"display the stack contents",
func(c *Calc) {
c.stack.Dump()
},
),
"history": NewCommand(
"display calculation history",
func(c *Calc) {
for _, entry := range c.history {
fmt.Println(entry)
}
},
),
"vars": NewCommand(
"show list of variables",
func(c *Calc) {
if len(c.Vars) > 0 {
fmt.Printf("%-20s %s\n", "VARIABLE", "VALUE")
for k, v := range c.Vars {
fmt.Printf("%-20s -> %.2f\n", k, v)
}
} else {
fmt.Println("no vars registered")
}
},
),
"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]))
}
},
),
}
c.StackCommands = Commands{
"clear": NewCommand(
"clear the whole stack",
func(c *Calc) {
c.stack.Backup()
c.stack.Clear()
},
),
"shift": NewCommand(
"remove the last element of the stack",
func(c *Calc) {
c.stack.Backup()
c.stack.Shift()
},
),
"reverse": NewCommand(
"reverse the stack elements",
func(c *Calc) {
c.stack.Backup()
c.stack.Reverse()
},
),
"swap": NewCommand(
"exchange the last two elements",
func(c *Calc) {
if c.stack.Len() < 2 {
fmt.Println("stack too small, can't swap")
} else {
c.stack.Backup()
c.stack.Swap()
}
},
),
"undo": NewCommand(
"undo last operation",
func(c *Calc) {
c.stack.Restore()
},
),
"dup": NewCommand(
"duplicate last stack item",
func(c *Calc) {
item := c.stack.Last()
if len(item) == 1 {
c.stack.Backup()
c.stack.Push(item[0])
} else {
fmt.Println("stack empty")
}
},
),
"edit": NewCommand(
"edit the stack interactively",
func(c *Calc) {
if c.stack.Len() == 0 {
fmt.Println("empty stack")
return
}
c.stack.Backup()
// put the stack contents into a tmp file
tmp, err := os.CreateTemp("", "stack")
if err != nil {
fmt.Println(err)
return
}
defer os.Remove(tmp.Name())
comment := `# add or remove numbers as you wish.
# each number must be on its own line.
# numbers must be floating point formatted.
`
_, err = tmp.WriteString(comment)
if err != nil {
fmt.Println(err)
return
}
for _, item := range c.stack.All() {
_, err = fmt.Fprintf(tmp, "%f\n", item)
if err != nil {
fmt.Println(err)
return
}
}
tmp.Close()
// determine which editor to use
editor := "vi"
enveditor, present := os.LookupEnv("EDITOR")
if present {
if editor != "" {
if _, err := os.Stat(editor); err == nil {
editor = enveditor
}
}
}
// execute editor with our tmp file containing current stack
cmd := exec.Command(editor, tmp.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Println("could not run editor command: ", err)
return
}
// read the file back in
modified, err := os.Open(tmp.Name())
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer modified.Close()
// reset the stack
c.stack.Clear()
// and put the new contents (if legit) back onto the stack
scanner := bufio.NewScanner(modified)
for scanner.Scan() {
line := strings.TrimSpace(c.Comment.ReplaceAllString(scanner.Text(), ""))
if line == "" {
continue
}
num, err := strconv.ParseFloat(line, 64)
if err != nil {
fmt.Printf("%s is not a floating point number!\n", line)
continue
}
c.stack.Push(num)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading from file:", err)
}
},
),
}
// general commands
c.Commands = Commands{
"exit": NewCommand(
"exit program",
func(c *Calc) {
os.Exit(0)
},
),
"manual": NewCommand(
"show manual",
func(c *Calc) {
man()
},
),
}
// aliases
c.Commands["quit"] = c.Commands["exit"]
c.SettingsCommands["d"] = c.SettingsCommands["debug"]
c.SettingsCommands["b"] = c.SettingsCommands["batch"]
c.SettingsCommands["s"] = c.SettingsCommands["showstack"]
c.ShowCommands["h"] = c.ShowCommands["history"]
c.ShowCommands["p"] = c.ShowCommands["dump"]
c.ShowCommands["v"] = c.ShowCommands["vars"]
c.StackCommands["c"] = c.StackCommands["clear"]
c.StackCommands["u"] = c.StackCommands["undo"]
}

38
example.lua Normal file
View File

@@ -0,0 +1,38 @@
-- 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
-- calculate parallel resistance. Batch function (registered with -1,
-- see below). Takes a table as parameter.
--
-- Formula: 1/( (1/R1) + (1/R2) + ...)
function parallelresistance(list)
sumres = 0
for i, value in ipairs(list) do
sumres = sumres + 1 / value
end
return 1 / sumres
end
-- converter example
function inch2centimeter(inches)
return inches * 2.54
end
function init()
-- expects 2 args
register("lower", 2, "lower")
-- expects a list of all numbers on the stack, batch mode
register("parallelresistance", -1, "parallel resistance")
-- expects 1 arg, but doesn't pop()
register("inch2centimeter", 0)
end

View File

@@ -387,6 +387,85 @@ func DefineFunctions() Funcalls {
return NewR(math.Hypot(arg[0], arg[1]), nil) return NewR(math.Hypot(arg[0], arg[1]), nil)
}, },
2), 2),
// converters of all kinds
"cm-to-inch": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/2.54, nil)
},
1),
"inch-to-cm": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*2.54, nil)
},
1),
"gallons-to-liters": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*3.785, nil)
},
1),
"liters-to-gallons": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/3.785, nil)
},
1),
"yards-to-meters": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*91.44, nil)
},
1),
"meters-to-yards": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/91.44, nil)
},
1),
"miles-to-kilometers": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*1.609, nil)
},
1),
"kilometers-to-miles": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]/1.609, nil)
},
1),
"or": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])|int(arg[1])), nil)
},
2),
"and": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])&int(arg[1])), nil)
},
2),
"xor": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])^int(arg[1])), nil)
},
2),
"<": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])<<int(arg[1])), nil)
},
2),
">": NewFuncall(
func(arg Numbers) R {
return NewR(float64(int(arg[0])>>int(arg[1])), nil)
},
2),
} }
// aliases // aliases

View File

@@ -25,7 +25,8 @@ import (
) )
type Interpreter struct { type Interpreter struct {
debug bool debug bool
script string
} }
// LUA interpreter, instanciated in main() // LUA interpreter, instanciated in main()
@@ -42,8 +43,12 @@ type LuaFunction struct {
// 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 {
return &Interpreter{debug: debug, script: script}
}
// initialize the lua environment properly // initialize the lua environment properly
func InitLua(config string, debug bool) *Interpreter { func (i *Interpreter) InitLua() {
// we only load a subset of lua Open modules and don't allow // we only load a subset of lua Open modules and don't allow
// net, system or io stuff // net, system or io stuff
for _, pair := range []struct { for _, pair := range []struct {
@@ -66,7 +71,7 @@ func InitLua(config string, debug bool) *Interpreter {
} }
// 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 := L.DoFile(config); err != nil { if err := L.DoFile(i.script); err != nil {
panic(err) panic(err)
} }
@@ -84,8 +89,6 @@ func InitLua(config string, debug bool) *Interpreter {
}); err != nil { }); err != nil {
panic(err) panic(err)
} }
return &Interpreter{debug: debug}
} }
func (i *Interpreter) Debug(msg string) { func (i *Interpreter) Debug(msg string) {
@@ -113,6 +116,8 @@ func (i *Interpreter) CallLuaFunc(funcname string, items []float64) (float64, er
funcname, LuaFuncs[funcname].numargs)) funcname, LuaFuncs[funcname].numargs))
switch LuaFuncs[funcname].numargs { switch LuaFuncs[funcname].numargs {
case 0:
fallthrough
case 1: case 1:
// 1 arg variant // 1 arg variant
if err := L.CallByParam(lua.P{ if err := L.CallByParam(lua.P{

View File

@@ -30,7 +30,7 @@ import (
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
) )
const VERSION string = "2.0.5" const VERSION string = "2.0.12"
const Usage string = `This is rpn, a reverse polish notation calculator cli. const Usage string = `This is rpn, a reverse polish notation calculator cli.
@@ -98,8 +98,9 @@ func main() {
// 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().
if _, err := os.Stat(configfile); err == nil { if _, err := os.Stat(configfile); err == nil {
I := InitLua(configfile, enabledebug) luarunner := NewInterpreter(configfile, enabledebug)
calc.SetInt(I) luarunner.InitLua()
calc.SetInt(luarunner)
} }
if len(flag.Args()) > 1 { if len(flag.Args()) > 1 {

68
rpn.go
View File

@@ -105,6 +105,9 @@ DESCRIPTION
If the first parameter to rpn is a math operator or function, batch mode If the first parameter to rpn is a math operator or function, batch mode
is enabled automatically, see last example. is enabled automatically, see last example.
You can enter integers, floating point numbers (positive or negative) or
hex numbers (prefixed with 0x).
STACK MANIPULATION STACK MANIPULATION
There are lots of stack manipulation commands provided. The most There are lots of stack manipulation commands provided. The most
important one is undo which goes back to the stack before the last math important one is undo which goes back to the stack before the last math
@@ -129,6 +132,14 @@ DESCRIPTION
x multiply (alias: *) x multiply (alias: *)
^ power ^ power
Bitwise operators:
and bitwise and
or bitwise or
xor bitwise xor
< left shift
> right shift
Percent functions: Percent functions:
% percent % percent
@@ -150,18 +161,44 @@ DESCRIPTION
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0 log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
y1 copysign dim hypot y1 copysign dim hypot
Commands: 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:
[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:
batch toggle batch mode
debug toggle debug output
dump display the stack contents 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 clear clear the whole stack
shift remove the last element of the stack shift remove the last element of the stack
reverse reverse the stack elements reverse reverse the stack elements
swap exchange the last two stack elements swap exchange the last two stack elements
show show the last 5 items of the stack dup duplicate last stack item
history display calculation history undo undo last operation
edit edit the stack interactively using vi or $EDITOR
Other commands:
help|? show this message help|? show this message
manual show manual
quit|exit|c-d|c-c exit program quit|exit|c-d|c-c exit program
Register variables: Register variables:
@@ -171,6 +208,17 @@ DESCRIPTION
Refer to https://pkg.go.dev/math for details about those functions. 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 INTERACTIVE REPL
While you can use rpn in the command-line, the best experience you'll 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" have is the interactive repl (read eval print loop). Just execute "rpn"
@@ -210,7 +258,6 @@ COMMENTS
In this case only 123 will be added to the stack. In this case only 123 will be added to the stack.
VARIABLES VARIABLES
You can register the last item of the stack into a variable. Variable You can register the last item of the stack into a variable. Variable
names must be all caps. Use the ">NAME" command to put a value into names must be all caps. Use the ">NAME" command to put a value into
@@ -245,8 +292,13 @@ EXTENDING RPN USING LUA
* function name * function name
* number of arguments expected (1,2 or -1 allowed), -1 means batch * number of arguments expected (see below)
mode.
Number of expected arguments can be:
- 0: expect 1 argument but do NOT modify the stack
- 1-n: do a singular calculation
- -1: batch mode work with all numbers on the stack
* help text * help text

67
rpn.pod
View File

@@ -109,6 +109,9 @@ Example of batch mode usage:
If the first parameter to rpn is a math operator or function, batch 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)
or hex numbers (prefixed with 0x).
=head2 STACK MANIPULATION =head2 STACK MANIPULATION
There are lots of stack manipulation commands provided. The most There are lots of stack manipulation commands provided. The most
@@ -136,6 +139,14 @@ Basic operators:
x multiply (alias: *) x multiply (alias: *)
^ power ^ power
Bitwise operators:
and bitwise and
or bitwise or
xor bitwise xor
< left shift
> right shift
Percent functions: Percent functions:
% percent % percent
@@ -157,18 +168,44 @@ Math functions:
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0 log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
y1 copysign dim hypot y1 copysign dim hypot
Commands: 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:
[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:
batch toggle batch mode
debug toggle debug output
dump display the stack contents 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 clear clear the whole stack
shift remove the last element of the stack shift remove the last element of the stack
reverse reverse the stack elements reverse reverse the stack elements
swap exchange the last two stack elements swap exchange the last two stack elements
show show the last 5 items of the stack dup duplicate last stack item
history display calculation history undo undo last operation
edit edit the stack interactively using vi or $EDITOR
Other commands:
help|? show this message help|? show this message
manual show manual
quit|exit|c-d|c-c exit program quit|exit|c-d|c-c exit program
@@ -179,6 +216,17 @@ Register variables:
Refer to https://pkg.go.dev/math for details about those functions. 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 =head1 INTERACTIVE REPL
While you can use rpn in the command-line, the best experience you'll While you can use rpn in the command-line, the best experience you'll
@@ -273,8 +321,13 @@ function name
=item * =item *
number of arguments expected (1,2 or -1 allowed), -1 means batch number of arguments expected (see below)
mode.
Number of expected arguments can be:
- 0: expect 1 argument but do NOT modify the stack
- 1-n: do a singular calculation
- -1: batch mode work with all numbers on the stack
=item * =item *

View File

@@ -195,14 +195,24 @@ func (s *Stack) Backup() {
// make a backup, because the elements in list.List{} are pointers // make a backup, because the elements in list.List{} are pointers
// and lead to unexpected results. The methid here works reliably // and lead to unexpected results. The methid here works reliably
// at least. // at least.
s.mutex.Lock()
defer s.mutex.Unlock()
s.Debug(fmt.Sprintf("backing up %d items from rev %d",
s.linklist.Len(), s.rev))
s.backup = list.List{} s.backup = list.List{}
for e := s.linklist.Front(); e != nil; e = e.Next() { for e := s.linklist.Front(); e != nil; e = e.Next() {
s.backup.PushBack(e.Value) s.backup.PushBack(e.Value.(float64))
} }
s.backuprev = s.rev s.backuprev = s.rev
} }
func (s *Stack) Restore() { func (s *Stack) Restore() {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.rev == 0 { if s.rev == 0 {
fmt.Println("error: stack is empty.") fmt.Println("error: stack is empty.")
return return
@@ -211,15 +221,26 @@ func (s *Stack) Restore() {
s.Debug(fmt.Sprintf("restoring stack to revision %d", s.backuprev)) s.Debug(fmt.Sprintf("restoring stack to revision %d", s.backuprev))
s.rev = s.backuprev s.rev = s.backuprev
s.linklist = s.backup
s.linklist = list.List{}
for e := s.backup.Front(); e != nil; e = e.Next() {
s.linklist.PushBack(e.Value.(float64))
}
} }
func (s *Stack) Reverse() { func (s *Stack) Reverse() {
newstack := list.List{} s.mutex.Lock()
defer s.mutex.Unlock()
items := []float64{}
for e := s.linklist.Front(); e != nil; e = e.Next() { for e := s.linklist.Front(); e != nil; e = e.Next() {
newstack.PushFront(e.Value) tail := s.linklist.Back()
items = append(items, tail.Value.(float64))
s.linklist.Remove(tail)
} }
s.linklist = newstack for i := len(items) - 1; i >= 0; i-- {
s.linklist.PushFront(items[i])
}
} }

View File

@@ -1,17 +0,0 @@
function add(a,b)
return a + b
end
function test(a)
return a
end
function parallelresistance(a,b)
return 1.0 / (a * b)
end
function init()
register("add", 2, "addition")
register("test", 1, "test")
register("parallelresistance", 2, "parallel resistance")
end