8 Commits

Author SHA1 Message Date
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
10 changed files with 241 additions and 55 deletions

View File

@@ -225,10 +225,15 @@ the `register()` function to register your functions to the
calculator. This function takes these parameters:
- function name
- number of arguments expected (1,2 or -1 allowed), -1 means batch
mode
- number of arguments expected (see below)
- 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
reference](https://www.lua.org/manual/5.4/) for more details about
LUA.

49
calc.go
View File

@@ -53,9 +53,9 @@ type Calc struct {
// help for lua functions will be added dynamically
const Help string = `Available commands:
batch toggle batch mode
debug toggle debug output
show show the last 5 items of the stack
batch toggle batch mode (nobatch turns it off)
debug toggle debug output (nodebug turns it off)
showstack toggle show last 5 items of the stack (noshowtack turns it off)
dump display the stack contents
clear clear the whole stack
shift remove the last element of the stack
@@ -94,7 +94,7 @@ Register variables:
// commands, constants and operators, defined here to feed completion
// and our mode switch in Eval() dynamically
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`
)
@@ -151,16 +151,16 @@ func NewCalc() *Calc {
// pre-calculate mode switching arrays
c.Constants = strings.Split(Constants, " ")
for name := range LuaFuncs {
c.LuaFunctions = append(c.LuaFunctions, name)
}
return &c
}
// setup the interpreter, called from main()
// setup the interpreter, called from main(), import lua functions
func (c *Calc) SetInt(I *Interpreter) {
c.interpreter = I
for name := range LuaFuncs {
c.LuaFunctions = append(c.LuaFunctions, name)
}
}
func (c *Calc) ToggleDebug() {
@@ -259,7 +259,7 @@ func (c *Calc) Eval(line string) {
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
c.luafunc(item)
c.EvalLuaFunction(item)
continue
}
@@ -290,10 +290,15 @@ func (c *Calc) Eval(line string) {
c.stack.Dump()
case "debug":
c.ToggleDebug()
case "nodebug":
fallthrough
case "undebug":
c.debug = false
c.stack.debug = false
case "batch":
c.ToggleBatch()
case "nobatch":
c.batch = false
case "clear":
c.stack.Backup()
c.stack.Clear()
@@ -316,8 +321,12 @@ func (c *Calc) Eval(line string) {
for _, entry := range c.history {
fmt.Println(entry)
}
case "showstack":
fallthrough
case "show":
c.ToggleShow()
case "noshowstack":
c.showstack = false
case "exit":
fallthrough
case "quit":
@@ -393,6 +402,10 @@ func (c *Calc) DoFuncall(funcname string) error {
return R.Err
}
// don't forget to backup!
c.stack.Backup()
// "pop"
if batch {
// get rid of stack
c.stack.Clear()
@@ -442,12 +455,14 @@ func (c *Calc) Debug(msg string) {
}
}
func (c *Calc) luafunc(funcname string) {
func (c *Calc) EvalLuaFunction(funcname string) {
// called from calc loop
var x float64
var err error
switch c.interpreter.FuncNumArgs(funcname) {
case 0:
fallthrough
case 1:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
case 2:
@@ -465,7 +480,15 @@ func (c *Calc) luafunc(funcname string) {
c.stack.Backup()
dopush := true
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:
a := c.stack.Pop()
c.History("%s(%f) = %f", funcname, a, x)
@@ -478,7 +501,9 @@ func (c *Calc) luafunc(funcname string) {
c.History("%s(*) = %f", funcname, x)
}
c.stack.Push(x)
if dopush {
c.stack.Push(x)
}
c.Result()
}

View File

@@ -20,6 +20,8 @@ package main
import (
"fmt"
"testing"
lua "github.com/yuin/gopher-lua"
)
func TestCommentsAndWhitespace(t *testing.T) {
@@ -104,6 +106,7 @@ func TestCalc(t *testing.T) {
exp float64
batch bool
}{
// ops
{
name: "plus",
cmd: `15 15 +`,
@@ -144,6 +147,8 @@ func TestCalc(t *testing.T) {
cmd: `400 20 %+`,
exp: 480,
},
// math tests
{
name: "mod",
cmd: `9 2 mod`,
@@ -164,6 +169,20 @@ func TestCalc(t *testing.T) {
cmd: `6 4 dim`,
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",
cmd: `2 2 2 2 sum`,
@@ -194,6 +213,34 @@ func TestCalc(t *testing.T) {
exp: 5,
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,
},
}
for _, tt := range tests {
@@ -212,3 +259,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)
}
})
}
}

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

@@ -25,7 +25,8 @@ import (
)
type Interpreter struct {
debug bool
debug bool
script string
}
// LUA interpreter, instanciated in main()
@@ -42,8 +43,12 @@ type LuaFunction struct {
// have access to the interpreter instance
var LuaFuncs map[string]LuaFunction
func NewInterpreter(script string, debug bool) *Interpreter {
return &Interpreter{debug: debug, script: script}
}
// 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
// net, system or io stuff
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)
if err := L.DoFile(config); err != nil {
if err := L.DoFile(i.script); err != nil {
panic(err)
}
@@ -84,8 +89,6 @@ func InitLua(config string, debug bool) *Interpreter {
}); err != nil {
panic(err)
}
return &Interpreter{debug: debug}
}
func (i *Interpreter) Debug(msg string) {
@@ -113,6 +116,8 @@ func (i *Interpreter) CallLuaFunc(funcname string, items []float64) (float64, er
funcname, LuaFuncs[funcname].numargs))
switch LuaFuncs[funcname].numargs {
case 0:
fallthrough
case 1:
// 1 arg variant
if err := L.CallByParam(lua.P{

View File

@@ -30,7 +30,7 @@ import (
lua "github.com/yuin/gopher-lua"
)
const VERSION string = "2.0.5"
const VERSION string = "2.0.8"
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
// be defined, init() will be called by InitLua().
if _, err := os.Stat(configfile); err == nil {
I := InitLua(configfile, enabledebug)
calc.SetInt(I)
luarunner := NewInterpreter(configfile, enabledebug)
luarunner.InitLua()
calc.SetInt(luarunner)
}
if len(flag.Args()) > 1 {

16
rpn.go
View File

@@ -152,14 +152,14 @@ DESCRIPTION
Commands:
batch toggle batch mode
debug toggle debug output
[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)
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 stack elements
show show the last 5 items of the stack
history display calculation history
help|? show this message
quit|exit|c-d|c-c exit program
@@ -210,7 +210,6 @@ COMMENTS
In this case only 123 will be added to the stack.
VARIABLES
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
@@ -245,8 +244,13 @@ EXTENDING RPN USING LUA
* function name
* number of arguments expected (1,2 or -1 allowed), -1 means batch
mode.
* number of arguments expected (see below)
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

15
rpn.pod
View File

@@ -159,14 +159,14 @@ Math functions:
Commands:
batch toggle batch mode
debug toggle debug output
[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)
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 stack elements
show show the last 5 items of the stack
history display calculation history
help|? show this message
quit|exit|c-d|c-c exit program
@@ -273,8 +273,13 @@ function name
=item *
number of arguments expected (1,2 or -1 allowed), -1 means batch
mode.
number of arguments expected (see below)
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 *

View File

@@ -195,14 +195,24 @@ func (s *Stack) Backup() {
// make a backup, because the elements in list.List{} are pointers
// and lead to unexpected results. The methid here works reliably
// 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{}
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
}
func (s *Stack) Restore() {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.rev == 0 {
fmt.Println("error: stack is empty.")
return
@@ -211,15 +221,26 @@ func (s *Stack) Restore() {
s.Debug(fmt.Sprintf("restoring stack to revision %d", 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() {
newstack := list.List{}
s.mutex.Lock()
defer s.mutex.Unlock()
items := []float64{}
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