3 Commits

Author SHA1 Message Date
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
T.v.Dein
31a0ddd547 add variable support, implements #10 (#14) 2023-11-08 14:47:15 +01:00
T.v.Dein
fa5f8dcb3b suppress intermediate results unless -i, addresses #11, fix man (#13) 2023-11-08 14:43:34 +01:00
8 changed files with 233 additions and 64 deletions

View File

@@ -22,6 +22,7 @@ Features:
- completion
- history
- comments (comment character is `#`)
- variables
## Demo
@@ -224,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.

97
calc.go
View File

@@ -33,17 +33,22 @@ type Calc struct {
batch bool
stdin bool
showstack bool
intermediate bool
notdone bool // set to true as long as there are items left in the eval loop
stack *Stack
history []string
completer readline.AutoCompleter
interpreter *Interpreter
Space *regexp.Regexp
Comment *regexp.Regexp
Register *regexp.Regexp
Constants []string
LuaFunctions []string
Funcalls Funcalls
BatchFuncalls Funcalls
Vars map[string]float64
}
// help for lua functions will be added dynamically
@@ -56,6 +61,7 @@ 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
@@ -79,12 +85,16 @@ sum sum of all values (alias: +)
max max of all values
min min of all values
mean mean of all values (alias: avg)
median median of all values`
median median of all values
Register variables:
>NAME Put last stack element into variable NAME
<NAME Retrieve variable NAME and put onto stack`
// 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`
Commands string = `dump reverse debug undebug clear batch shift undo help history manual exit quit swap show vars`
Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
)
@@ -126,6 +136,7 @@ func NewCalc() *Calc {
c.Funcalls = DefineFunctions()
c.BatchFuncalls = DefineBatchFunctions()
c.Vars = map[string]float64{}
c.completer = readline.NewPrefixCompleter(
// custom lua functions
@@ -135,20 +146,21 @@ func NewCalc() *Calc {
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
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() {
@@ -198,7 +210,15 @@ func (c *Calc) Eval(line string) {
return
}
for _, item := range c.Space.Split(line, -1) {
items := c.Space.Split(line, -1)
for pos, item := range items {
if pos+1 < len(items) {
c.notdone = true
} else {
c.notdone = false
}
num, err := strconv.ParseFloat(item, 64)
if err == nil {
@@ -243,16 +263,29 @@ func (c *Calc) Eval(line string) {
continue
}
regmatches := c.Register.FindStringSubmatch(item)
if len(regmatches) == 3 {
switch regmatches[1] {
case ">":
c.PutVar(regmatches[2])
case "<":
c.GetVar(regmatches[2])
}
continue
}
// management commands
switch item {
case "?":
fallthrough
case "help":
fmt.Println(Help)
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":
@@ -291,6 +324,16 @@ func (c *Calc) Eval(line string) {
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:
fmt.Println("unknown command or operator!")
}
@@ -379,11 +422,16 @@ func (c *Calc) History(format string, args ...any) {
// print the result
func (c *Calc) Result() float64 {
// we only print the result if it's either a final result or
// (if it is intermediate) if -i has been given
if c.intermediate || !c.notdone {
// only needed in repl
if !c.stdin {
fmt.Print("= ")
}
fmt.Println(c.stack.Last()[0])
}
return c.stack.Last()[0]
}
@@ -400,6 +448,8 @@ func (c *Calc) luafunc(funcname string) {
var err error
switch c.interpreter.FuncNumArgs(funcname) {
case 0:
fallthrough
case 1:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
case 2:
@@ -417,7 +467,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)
@@ -430,7 +488,30 @@ func (c *Calc) luafunc(funcname string) {
c.History("%s(*) = %f", funcname, x)
}
if dopush {
c.stack.Push(x)
}
c.Result()
}
func (c *Calc) PutVar(name string) {
last := c.stack.Last()
if len(last) == 1 {
c.Debug(fmt.Sprintf("register %.2f in %s", last[0], name))
c.Vars[name] = last[0]
} else {
fmt.Println("empty stack")
}
}
func (c *Calc) GetVar(name string) {
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])
} else {
fmt.Println("variable doesn't exist")
}
}

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

@@ -26,6 +26,7 @@ import (
type Interpreter struct {
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{

12
main.go
View File

@@ -30,7 +30,7 @@ import (
lua "github.com/yuin/gopher-lua"
)
const VERSION string = "2.0.4"
const VERSION string = "2.0.6"
const Usage string = `This is rpn, a reverse polish notation calculator cli.
@@ -40,6 +40,7 @@ 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
-v, --version show version
-h, --help show help
@@ -59,7 +60,9 @@ func main() {
configfile := ""
flag.BoolVarP(&calc.batch, "batchmode", "b", false, "batch mode")
flag.BoolVarP(&calc.showstack, "showstack", "s", false, "show stack")
flag.BoolVarP(&calc.showstack, "show-stack", "s", false, "show stack")
flag.BoolVarP(&calc.intermediate, "showin-termediate", "i", false,
"show intermediate results")
flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode")
flag.BoolVarP(&showversion, "version", "v", false, "show version")
flag.BoolVarP(&showhelp, "help", "h", false, "show usage")
@@ -95,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 {

36
rpn.go
View File

@@ -2,7 +2,7 @@ package main
var manpage = `
NAME
rpn - Reverse Polish Notation Calculator for the commandline
rpn - Programmable command-line calculator using reverse polish notation
SYNOPSIS
Usage: rpn [-bdvh] [<operator>]
@@ -10,6 +10,9 @@ SYNOPSIS
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
-v, --version show version
-h, --help show help
@@ -161,6 +164,11 @@ DESCRIPTION
help|? show this message
quit|exit|c-d|c-c exit program
Register variables:
>NAME Put last stack element into variable NAME
<NAME Retrieve variable NAME and put onto stack
Refer to https://pkg.go.dev/math for details about those functions.
INTERACTIVE REPL
@@ -193,6 +201,23 @@ INTERACTIVE REPL
ctrl-r
Search through history.
COMMENTS
Lines starting with "#" are being ignored as comments. You can also
append comments to rpn input, e.g.:
# a comment
123 # another comment
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
variable "NAME". Use "<NAME" to retrieve the value of variable "NAME"
and put it onto the stack.
The command vars can be used to get a list of all variables.
EXTENDING RPN USING LUA
You can use a lua script with lua functions to extend the calculator. By
default the tool looks for "~/.rpn.lua". You can also specify a script
@@ -219,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

28
rpn.pod
View File

@@ -1,6 +1,6 @@
=head1 NAME
rpn - Reverse Polish Notation Calculator for the commandline
rpn - Programmable command-line calculator using reverse polish notation
=head1 SYNOPSIS
@@ -9,6 +9,9 @@ rpn - Reverse Polish Notation Calculator for the commandline
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
-v, --version show version
-h, --help show help
@@ -169,6 +172,11 @@ Commands:
quit|exit|c-d|c-c exit program
Register variables:
>NAME Put last stack element into variable NAME
<NAME Retrieve variable NAME and put onto stack
Refer to https://pkg.go.dev/math for details about those functions.
=head1 INTERACTIVE REPL
@@ -223,6 +231,15 @@ append comments to rpn input, e.g.:
In this case only 123 will be added to the stack.
=head1 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
variable "NAME". Use "<NAME" to retrieve the value of variable "NAME"
and put it onto the stack.
The command B<vars> can be used to get a list of all variables.
=head1 EXTENDING RPN USING LUA
You can use a lua script with lua functions to extend the
@@ -256,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

@@ -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