mirror of
https://codeberg.org/scip/rpnc.git
synced 2025-12-16 20:11:02 +01:00
code re-organization, added more commands and more flexible lua api
This commit is contained in:
247
go/calc.go
247
go/calc.go
@@ -1,42 +1,73 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
type Calc struct {
|
||||
debug bool
|
||||
batch bool
|
||||
stdin bool
|
||||
stack *Stack
|
||||
history []string
|
||||
completer readline.AutoCompleter
|
||||
L *lua.LState
|
||||
debug bool
|
||||
batch bool
|
||||
stdin bool
|
||||
stack *Stack
|
||||
history []string
|
||||
completer readline.AutoCompleter
|
||||
interpreter *Interpreter
|
||||
}
|
||||
|
||||
// help for lua functions will be added dynamically
|
||||
const Help string = `Available commands:
|
||||
batch enable batch mode
|
||||
debug enable debug output
|
||||
dump display the stack contents
|
||||
clear clear the whole stack
|
||||
shift remove the last element of the stack
|
||||
history display calculation history
|
||||
help show this message
|
||||
batch toggle batch mode
|
||||
debug toggle debug output
|
||||
dump display the stack contents
|
||||
clear clear the whole stack
|
||||
shift remove the last element of the stack
|
||||
history display calculation history
|
||||
help|? show this message
|
||||
quit|exit|c-d|c-c exit program
|
||||
|
||||
Available operators:
|
||||
basic operators: + - * /
|
||||
basic operators: + - x /
|
||||
|
||||
Available math functions:
|
||||
sqrt square root
|
||||
mod remainder of division
|
||||
max batch mode only: max of all values
|
||||
min batch mode only: min of all values
|
||||
mean batch mode only: mean of all values (alias: avg)
|
||||
median batch mode only: median of all values
|
||||
% percent
|
||||
%- substract percent
|
||||
%+ add percent
|
||||
|
||||
Math operators:
|
||||
^ power` // FIXME: add help strings from lua functions
|
||||
^ power`
|
||||
|
||||
// That way I can add custom functions to completion
|
||||
// That way we can add custom functions to completion
|
||||
func GetCompleteCustomFunctions() func(string) []string {
|
||||
return func(line string) []string {
|
||||
funcs := []string{}
|
||||
@@ -58,12 +89,15 @@ func NewCalc() *Calc {
|
||||
readline.PcItem("dump"),
|
||||
readline.PcItem("reverse"),
|
||||
readline.PcItem("debug"),
|
||||
readline.PcItem("undebug"),
|
||||
readline.PcItem("clear"),
|
||||
readline.PcItem("batch"),
|
||||
readline.PcItem("shift"),
|
||||
readline.PcItem("undo"),
|
||||
readline.PcItem("help"),
|
||||
readline.PcItem("history"),
|
||||
readline.PcItem("exit"),
|
||||
readline.PcItem("quit"),
|
||||
|
||||
// ops
|
||||
readline.PcItem("+"),
|
||||
@@ -91,41 +125,69 @@ func NewCalc() *Calc {
|
||||
readline.PcItem("sqrt"),
|
||||
readline.PcItem("remainder"),
|
||||
readline.PcItem("avg"),
|
||||
readline.PcItem("mean"), // alias for avg
|
||||
readline.PcItem("min"),
|
||||
readline.PcItem("max"),
|
||||
readline.PcItem("median"),
|
||||
)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
// setup the interpreter, called from main()
|
||||
func (c *Calc) SetInt(I *Interpreter) {
|
||||
c.interpreter = I
|
||||
}
|
||||
|
||||
func (c *Calc) ToggleDebug() {
|
||||
c.debug = !c.debug
|
||||
c.stack.ToggleDebug()
|
||||
fmt.Printf("debugging set to %t\n", c.debug)
|
||||
}
|
||||
|
||||
func (c *Calc) ToggleBatch() {
|
||||
c.batch = !c.batch
|
||||
fmt.Printf("batchmode set to %t\n", c.batch)
|
||||
}
|
||||
|
||||
func (c *Calc) ToggleStdin() {
|
||||
c.stdin = !c.stdin
|
||||
}
|
||||
|
||||
func (c *Calc) Prompt() string {
|
||||
p := "\033[31m»\033[0m "
|
||||
b := ""
|
||||
if c.batch {
|
||||
b = "->batch"
|
||||
}
|
||||
d := ""
|
||||
v := ""
|
||||
if c.debug {
|
||||
d = "->debug"
|
||||
v = fmt.Sprintf("/rev%d", c.stack.rev)
|
||||
}
|
||||
|
||||
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) {
|
||||
line = strings.TrimSpace(line)
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
simple := regexp.MustCompile(`^[\+\-\*\/]$`)
|
||||
constants := []string{"E", "Pi", "Phi", "Sqrt2", "SqrtE", "SqrtPi",
|
||||
"SqrtPhi", "Ln2", "Log2E", "Ln10", "Log10E"}
|
||||
functions := []string{"sqrt", "remainder", "%", "%-", "%+"}
|
||||
batch := []string{"median", "avg"}
|
||||
luafuncs := []string{}
|
||||
|
||||
if line == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for luafunc := range LuaFuncs {
|
||||
luafuncs = append(luafuncs, luafunc)
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
simple := regexp.MustCompile(`^[\+\-\*\/x\^]$`)
|
||||
constants := []string{"E", "Pi", "Phi", "Sqrt2", "SqrtE", "SqrtPi",
|
||||
"SqrtPhi", "Ln2", "Log2E", "Ln10", "Log10E"}
|
||||
functions := []string{"sqrt", "remainder", "mod", "%", "%-", "%+"}
|
||||
batch := []string{"median", "avg", "mean", "max", "min"}
|
||||
|
||||
luafuncs := []string{}
|
||||
for name := range LuaFuncs {
|
||||
luafuncs = append(luafuncs, name)
|
||||
}
|
||||
|
||||
for _, item := range space.Split(line, -1) {
|
||||
@@ -135,39 +197,53 @@ func (c *Calc) Eval(line string) {
|
||||
c.stack.Backup()
|
||||
c.stack.Push(num)
|
||||
} else {
|
||||
if simple.MatchString(line) {
|
||||
if simple.MatchString(item) {
|
||||
// simple ops like + or x
|
||||
c.simple(item[0])
|
||||
continue
|
||||
}
|
||||
|
||||
if contains(constants, item) {
|
||||
// put the constant onto the stack
|
||||
c.stack.Backup()
|
||||
c.stack.Push(const2num(item))
|
||||
continue
|
||||
}
|
||||
|
||||
if contains(functions, item) {
|
||||
// go builtin math function, if implemented
|
||||
c.mathfunc(item)
|
||||
continue
|
||||
}
|
||||
|
||||
if contains(batch, item) {
|
||||
// math functions only supported in batch mode like max or mean
|
||||
c.batchfunc(item)
|
||||
continue
|
||||
}
|
||||
|
||||
if contains(luafuncs, item) {
|
||||
// user provided custom lua functions
|
||||
c.luafunc(item)
|
||||
continue
|
||||
}
|
||||
|
||||
// management commands
|
||||
switch item {
|
||||
case "?":
|
||||
fallthrough
|
||||
case "help":
|
||||
fmt.Println(Help)
|
||||
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":
|
||||
@@ -185,8 +261,10 @@ func (c *Calc) Eval(line string) {
|
||||
for _, entry := range c.history {
|
||||
fmt.Println(entry)
|
||||
}
|
||||
case "^":
|
||||
c.exp()
|
||||
case "exit":
|
||||
fallthrough
|
||||
case "quit":
|
||||
os.Exit(0)
|
||||
default:
|
||||
fmt.Println("unknown command or operator!")
|
||||
}
|
||||
@@ -194,10 +272,13 @@ func (c *Calc) Eval(line string) {
|
||||
}
|
||||
}
|
||||
|
||||
// just a textual representation of math operations, viewable with the
|
||||
// history command
|
||||
func (c *Calc) History(format string, args ...any) {
|
||||
c.history = append(c.history, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// print the result
|
||||
func (c *Calc) Result() float64 {
|
||||
if !c.stdin {
|
||||
fmt.Print("= ")
|
||||
@@ -208,6 +289,13 @@ func (c *Calc) Result() float64 {
|
||||
return c.stack.Last()
|
||||
}
|
||||
|
||||
func (c *Calc) Debug(msg string) {
|
||||
if c.debug {
|
||||
fmt.Printf("DEBUG(calc): %s\n", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// do simple calculations
|
||||
func (c *Calc) simple(op byte) {
|
||||
c.stack.Backup()
|
||||
|
||||
@@ -216,15 +304,15 @@ func (c *Calc) simple(op byte) {
|
||||
a := c.stack.Pop()
|
||||
var x float64
|
||||
|
||||
if c.debug {
|
||||
fmt.Printf("DEBUG: evaluating: %.2f %c %.2f\n", a, op, b)
|
||||
}
|
||||
c.Debug(fmt.Sprintf("evaluating: %.2f %c %.2f", a, op, b))
|
||||
|
||||
switch op {
|
||||
case '+':
|
||||
x = a + b
|
||||
case '-':
|
||||
x = a - b
|
||||
case 'x':
|
||||
fallthrough // alias for *
|
||||
case '*':
|
||||
x = a * b
|
||||
case '/':
|
||||
@@ -233,6 +321,8 @@ func (c *Calc) simple(op byte) {
|
||||
return
|
||||
}
|
||||
x = a / b
|
||||
case '^':
|
||||
x = math.Pow(a, b)
|
||||
default:
|
||||
panic("invalid operator!")
|
||||
}
|
||||
@@ -246,32 +336,11 @@ func (c *Calc) simple(op byte) {
|
||||
}
|
||||
}
|
||||
|
||||
_ = c.Result()
|
||||
}
|
||||
|
||||
func (c *Calc) exp() {
|
||||
c.stack.Backup()
|
||||
|
||||
for c.stack.Len() > 1 {
|
||||
b := c.stack.Pop()
|
||||
a := c.stack.Pop()
|
||||
x := math.Pow(a, b)
|
||||
|
||||
c.stack.Push(x)
|
||||
|
||||
c.History("%f ^ %f = %f", a, b, x)
|
||||
|
||||
if !c.batch {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_ = c.Result()
|
||||
c.Result()
|
||||
}
|
||||
|
||||
// calc using go math lib functions
|
||||
func (c *Calc) mathfunc(funcname string) {
|
||||
// FIXME: split into 2 funcs, one working with 1 the other with 2
|
||||
// args, saving Pop calls
|
||||
c.stack.Backup()
|
||||
|
||||
for c.stack.Len() > 0 {
|
||||
@@ -283,6 +352,8 @@ func (c *Calc) mathfunc(funcname string) {
|
||||
x = math.Sqrt(a)
|
||||
c.History("sqrt(%f) = %f", a, x)
|
||||
|
||||
case "mod":
|
||||
fallthrough // alias
|
||||
case "remainder":
|
||||
b := c.stack.Pop()
|
||||
a := c.stack.Pop()
|
||||
@@ -319,9 +390,10 @@ func (c *Calc) mathfunc(funcname string) {
|
||||
}
|
||||
}
|
||||
|
||||
_ = c.Result()
|
||||
c.Result()
|
||||
}
|
||||
|
||||
// execute pure batch functions, operating on the whole stack
|
||||
func (c *Calc) batchfunc(funcname string) {
|
||||
if !c.batch {
|
||||
fmt.Println("error: only available in batch mode")
|
||||
@@ -344,6 +416,8 @@ func (c *Calc) batchfunc(funcname string) {
|
||||
x = all[middle]
|
||||
c.History("median(all)")
|
||||
|
||||
case "mean":
|
||||
fallthrough // alias
|
||||
case "avg":
|
||||
var sum float64
|
||||
|
||||
@@ -353,6 +427,28 @@ func (c *Calc) batchfunc(funcname string) {
|
||||
|
||||
x = sum / float64(count)
|
||||
c.History("avg(all)")
|
||||
case "min":
|
||||
x = c.stack.Pop() // initialize with the last one
|
||||
|
||||
for c.stack.Len() > 0 {
|
||||
val := c.stack.Pop()
|
||||
if val < x {
|
||||
x = val
|
||||
}
|
||||
}
|
||||
|
||||
c.History("min(all)")
|
||||
case "max":
|
||||
x = c.stack.Pop() // initialize with the last one
|
||||
|
||||
for c.stack.Len() > 0 {
|
||||
val := c.stack.Pop()
|
||||
if val > x {
|
||||
x = val
|
||||
}
|
||||
}
|
||||
|
||||
c.History("max(all)")
|
||||
}
|
||||
|
||||
c.stack.Push(x)
|
||||
@@ -360,20 +456,41 @@ func (c *Calc) batchfunc(funcname string) {
|
||||
}
|
||||
|
||||
func (c *Calc) luafunc(funcname string) {
|
||||
// we may need to put them onto the stack afterwards!
|
||||
c.stack.Backup()
|
||||
b := c.stack.Pop()
|
||||
a := c.stack.Pop()
|
||||
// called from calc loop
|
||||
var x float64
|
||||
var err error
|
||||
|
||||
switch c.interpreter.FuncNumArgs(funcname) {
|
||||
case 1:
|
||||
x, err = c.interpreter.CallLuaFunc(funcname, []float64{c.stack.Last()})
|
||||
case 2:
|
||||
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.LastTwo())
|
||||
case -1:
|
||||
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
|
||||
default:
|
||||
x, err = 0, errors.New("invalid number of argument requested")
|
||||
}
|
||||
|
||||
x, err := CallLuaFunc(c.L, funcname, a, b)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
c.stack.Push(a)
|
||||
c.stack.Push(b)
|
||||
return
|
||||
}
|
||||
|
||||
c.History("%s(%f,%f) = %f", funcname, a, b, x)
|
||||
c.stack.Backup()
|
||||
|
||||
switch c.interpreter.FuncNumArgs(funcname) {
|
||||
case 1:
|
||||
a := c.stack.Pop()
|
||||
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, x)
|
||||
case -1:
|
||||
c.stack.Clear()
|
||||
c.History("%s(*) = %f", funcname, x)
|
||||
}
|
||||
|
||||
c.stack.Push(x)
|
||||
|
||||
c.Result()
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
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 (
|
||||
@@ -7,31 +24,59 @@ import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
type Interpreter struct {
|
||||
debug bool
|
||||
}
|
||||
|
||||
// LUA interpreter, instanciated in main()
|
||||
var L *lua.LState
|
||||
|
||||
var LuaFuncs map[string]int
|
||||
|
||||
// FIXME: add 2nd var with help string
|
||||
// called from lua to register a 1 arg math function
|
||||
func RegisterFuncOneArg(L *lua.LState) int {
|
||||
function := L.ToString(1)
|
||||
LuaFuncs[function] = 1
|
||||
return 1
|
||||
// holds a user provided lua function
|
||||
type LuaFunction struct {
|
||||
name string
|
||||
help string
|
||||
numargs int
|
||||
}
|
||||
|
||||
// called from lua to register a 1 arg math function
|
||||
func RegisterFuncTwoArg(L *lua.LState) int {
|
||||
function := L.ToString(1)
|
||||
LuaFuncs[function] = 2
|
||||
return 1
|
||||
}
|
||||
// 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 InitLua(L *lua.LState) {
|
||||
LuaFuncs = map[string]int{}
|
||||
L.SetGlobal("RegisterFuncOneArg", L.NewFunction(RegisterFuncOneArg))
|
||||
L.SetGlobal("RegisterFuncTwoArg", L.NewFunction(RegisterFuncTwoArg))
|
||||
// initialize the lua environment properly
|
||||
func InitLua(config string, debug bool) *Interpreter {
|
||||
// we only load a subset of lua Open modules and don't allow
|
||||
// net, system or io stuff
|
||||
for _, pair := range []struct {
|
||||
n string
|
||||
f lua.LGFunction
|
||||
}{
|
||||
{lua.LoadLibName, lua.OpenPackage},
|
||||
{lua.BaseLibName, lua.OpenBase},
|
||||
{lua.TabLibName, lua.OpenTable},
|
||||
{lua.DebugLibName, lua.OpenDebug},
|
||||
{lua.MathLibName, lua.OpenMath},
|
||||
} {
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.NewFunction(pair.f),
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, lua.LString(pair.n)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// load the lua config (which we expect to contain init() and math functions)
|
||||
if err := L.DoFile(config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// instanciate
|
||||
LuaFuncs = map[string]LuaFunction{}
|
||||
|
||||
// that way the user can call register(...) from lua inside init()
|
||||
L.SetGlobal("register", L.NewFunction(register))
|
||||
|
||||
// actually call init()
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.GetGlobal("init"),
|
||||
NRet: 0,
|
||||
@@ -39,26 +84,68 @@ func InitLua(L *lua.LState) {
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Interpreter{debug: debug}
|
||||
}
|
||||
|
||||
func CallLuaFunc(L *lua.LState, funcname string, a float64, b float64) (float64, error) {
|
||||
if LuaFuncs[funcname] == 1 {
|
||||
func (i *Interpreter) Debug(msg string) {
|
||||
if i.debug {
|
||||
fmt.Printf("DEBUG(lua): %s\n", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Interpreter) FuncNumArgs(name string) int {
|
||||
return LuaFuncs[name].numargs
|
||||
}
|
||||
|
||||
// Call a user provided math function registered with register().
|
||||
//
|
||||
// Each function has to tell us how many args it expects, the actual
|
||||
// function call from here is different depending on the number of
|
||||
// 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 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 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))
|
||||
|
||||
switch LuaFuncs[funcname].numargs {
|
||||
case 1:
|
||||
// 1 arg variant
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.GetGlobal(funcname),
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, lua.LNumber(a)); err != nil {
|
||||
}, lua.LNumber(items[0])); err != nil {
|
||||
fmt.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
case 2:
|
||||
// 2 arg variant
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.GetGlobal(funcname),
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, lua.LNumber(a), lua.LNumber(b)); err != nil {
|
||||
}, lua.LNumber(items[0]), lua.LNumber(items[1])); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case -1:
|
||||
// batch variant, use lua table as array
|
||||
tb := L.NewTable()
|
||||
|
||||
// put the whole stack into it
|
||||
for _, item := range items {
|
||||
tb.Append(lua.LNumber(item))
|
||||
}
|
||||
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.GetGlobal(funcname),
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, tb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
@@ -69,5 +156,22 @@ func CallLuaFunc(L *lua.LState, funcname string, a float64, b float64) (float64,
|
||||
return float64(res), nil
|
||||
}
|
||||
|
||||
return 0, errors.New("function did not return a float64!")
|
||||
return 0, errors.New("function did not return a float64")
|
||||
}
|
||||
|
||||
// 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(L *lua.LState) int {
|
||||
function := L.ToString(1)
|
||||
numargs := L.ToInt(2)
|
||||
help := L.ToString(3)
|
||||
|
||||
LuaFuncs[function] = LuaFunction{
|
||||
name: function,
|
||||
numargs: numargs,
|
||||
help: help,
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
80
go/main.go
80
go/main.go
@@ -1,8 +1,26 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
flag "github.com/spf13/pflag"
|
||||
@@ -38,7 +56,8 @@ func main() {
|
||||
flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode")
|
||||
flag.BoolVarP(&showversion, "version", "v", false, "show version")
|
||||
flag.BoolVarP(&showhelp, "help", "h", false, "show usage")
|
||||
flag.StringVarP(&configfile, "config", "c", os.Getenv("HOME")+"/.rpn.lua", "config file (lua format)")
|
||||
flag.StringVarP(&configfile, "config", "c",
|
||||
os.Getenv("HOME")+"/.rpn.lua", "config file (lua format)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -56,43 +75,28 @@ func main() {
|
||||
calc.ToggleDebug()
|
||||
}
|
||||
|
||||
// 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().
|
||||
if _, err := os.Stat(configfile); err == nil {
|
||||
// FIXME: put into interpreter.go, probably with its own obj
|
||||
// then just Interpreter.Init(configfile) should suffice
|
||||
L = lua.NewState(lua.Options{SkipOpenLibs: true})
|
||||
defer L.Close()
|
||||
|
||||
// we only load a subset of lua Open modules and don't allow
|
||||
// net, system or io stuff
|
||||
for _, pair := range []struct {
|
||||
n string
|
||||
f lua.LGFunction
|
||||
}{
|
||||
{lua.LoadLibName, lua.OpenPackage},
|
||||
{lua.BaseLibName, lua.OpenBase},
|
||||
{lua.TabLibName, lua.OpenTable},
|
||||
{lua.DebugLibName, lua.OpenDebug},
|
||||
{lua.MathLibName, lua.OpenMath},
|
||||
} {
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: L.NewFunction(pair.f),
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, lua.LString(pair.n)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := L.DoFile(configfile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
InitLua(L)
|
||||
calc.L = L
|
||||
I := InitLua(configfile, enabledebug)
|
||||
calc.SetInt(I)
|
||||
}
|
||||
|
||||
if len(flag.Args()) > 1 {
|
||||
// commandline calc operation, no readline etc needed
|
||||
// called like rpn 2 2 +
|
||||
calc.stdin = true
|
||||
calc.Eval(strings.Join(flag.Args(), " "))
|
||||
return
|
||||
}
|
||||
|
||||
// interactive mode, need readline
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "\033[31m»\033[0m ",
|
||||
Prompt: calc.Prompt(),
|
||||
HistoryFile: os.Getenv("HOME") + "/.rpn-history",
|
||||
HistoryLimit: 500,
|
||||
AutoComplete: calc.completer,
|
||||
@@ -108,19 +112,27 @@ func main() {
|
||||
rl.CaptureExitSignal()
|
||||
|
||||
if inputIsStdin() {
|
||||
// commands are coming on stdin, however we will still enter
|
||||
// the same loop since readline just reads fine from stdin
|
||||
calc.ToggleStdin()
|
||||
}
|
||||
|
||||
for {
|
||||
// primary program repl
|
||||
line, err := rl.Readline()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
calc.Eval(line)
|
||||
rl.SetPrompt(calc.Prompt())
|
||||
}
|
||||
|
||||
if len(flag.Args()) > 0 {
|
||||
// called like this:
|
||||
// echo 1 2 3 4 | rpn +
|
||||
// batch mode enabled automatically
|
||||
calc.batch = true
|
||||
calc.Eval(flag.Args()[0])
|
||||
}
|
||||
}
|
||||
|
||||
106
go/stack.go
106
go/stack.go
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
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 (
|
||||
@@ -6,8 +23,14 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// The stack uses a linked list provided by container/list as storage
|
||||
// and works after the LIFO principle (last in first out). Most of the
|
||||
// work is being done in the linked list, but we add a couple of
|
||||
// cenvenient functions, so that the user doesn't have to cope with
|
||||
// list directly.
|
||||
|
||||
type Stack struct {
|
||||
dll list.List
|
||||
linklist list.List
|
||||
backup list.List
|
||||
debug bool
|
||||
rev int
|
||||
@@ -15,8 +38,15 @@ type Stack struct {
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// FIXME: maybe use a separate stack object for backup so that it has
|
||||
// its own revision etc
|
||||
func NewStack() *Stack {
|
||||
return &Stack{dll: list.List{}, backup: list.List{}, rev: 0, backuprev: 0}
|
||||
return &Stack{
|
||||
linklist: list.List{},
|
||||
backup: list.List{},
|
||||
rev: 0,
|
||||
backuprev: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stack) Debug(msg string) {
|
||||
@@ -33,6 +63,7 @@ func (s *Stack) Bump() {
|
||||
s.rev++
|
||||
}
|
||||
|
||||
// append an item to the stack
|
||||
func (s *Stack) Push(x float64) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
@@ -40,52 +71,87 @@ func (s *Stack) Push(x float64) {
|
||||
s.Debug(fmt.Sprintf(" push to stack: %.2f", x))
|
||||
|
||||
s.Bump()
|
||||
s.dll.PushBack(x)
|
||||
s.linklist.PushBack(x)
|
||||
}
|
||||
|
||||
// remove and return an item from the stack
|
||||
func (s *Stack) Pop() float64 {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.dll.Len() == 0 {
|
||||
if s.linklist.Len() == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
tail := s.dll.Back()
|
||||
tail := s.linklist.Back()
|
||||
val := tail.Value
|
||||
s.dll.Remove(tail)
|
||||
s.linklist.Remove(tail)
|
||||
|
||||
s.Debug(fmt.Sprintf("remove from stack: %.2f", val))
|
||||
s.Debug(fmt.Sprintf(" remove from stack: %.2f", val))
|
||||
|
||||
s.Bump()
|
||||
return val.(float64)
|
||||
}
|
||||
|
||||
// just remove the last item, do not return it
|
||||
func (s *Stack) Shift() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.dll.Len() == 0 {
|
||||
if s.linklist.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
tail := s.dll.Back()
|
||||
s.dll.Remove(tail)
|
||||
tail := s.linklist.Back()
|
||||
s.linklist.Remove(tail)
|
||||
|
||||
s.Debug(fmt.Sprintf("remove from stack: %.2f", tail.Value))
|
||||
}
|
||||
|
||||
// just return the last item, do not remove it
|
||||
func (s *Stack) Last() float64 {
|
||||
if s.dll.Back() == nil {
|
||||
if s.linklist.Back() == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return s.dll.Back().Value.(float64)
|
||||
return s.linklist.Back().Value.(float64)
|
||||
}
|
||||
|
||||
// Return the last 2 elements of the stack without modifying it.
|
||||
//
|
||||
// We need to return the last 2 elements of the stack, however
|
||||
// container/list only supports access to 1 last element. So, we
|
||||
// pop the last, retrieve the second last and push the popped one
|
||||
// back.
|
||||
func (s *Stack) LastTwo() []float64 {
|
||||
items := []float64{}
|
||||
if s.linklist.Back() == nil {
|
||||
return items
|
||||
}
|
||||
|
||||
last := s.Pop()
|
||||
items = append(items, last)
|
||||
items = append(items, s.linklist.Back().Value.(float64))
|
||||
|
||||
s.Push(last)
|
||||
return items
|
||||
}
|
||||
|
||||
// Return all elements of the stack without modifying it.
|
||||
func (s *Stack) All() []float64 {
|
||||
items := []float64{}
|
||||
|
||||
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
||||
items = append(items, e.Value.(float64))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
// 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.dll)
|
||||
for e := s.dll.Front(); e != nil; e = e.Next() {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -100,11 +166,11 @@ func (s *Stack) Dump() {
|
||||
func (s *Stack) Clear() {
|
||||
s.Debug("DEBUG: clearing stack")
|
||||
|
||||
s.dll = list.List{}
|
||||
s.linklist = list.List{}
|
||||
}
|
||||
|
||||
func (s *Stack) Len() int {
|
||||
return s.dll.Len()
|
||||
return s.linklist.Len()
|
||||
}
|
||||
|
||||
func (s *Stack) Backup() {
|
||||
@@ -113,7 +179,7 @@ func (s *Stack) Backup() {
|
||||
// and lead to unexpected results. The methid here works reliably
|
||||
// at least.
|
||||
s.backup = list.List{}
|
||||
for e := s.dll.Front(); e != nil; e = e.Next() {
|
||||
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
||||
s.backup.PushBack(e.Value)
|
||||
}
|
||||
s.backuprev = s.rev
|
||||
@@ -128,15 +194,15 @@ func (s *Stack) Restore() {
|
||||
s.Debug(fmt.Sprintf("restoring stack to revision %d", s.backuprev))
|
||||
|
||||
s.rev = s.backuprev
|
||||
s.dll = s.backup
|
||||
s.linklist = s.backup
|
||||
}
|
||||
|
||||
func (s *Stack) Reverse() {
|
||||
newstack := list.List{}
|
||||
|
||||
for e := s.dll.Front(); e != nil; e = e.Next() {
|
||||
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
||||
newstack.PushFront(e.Value)
|
||||
}
|
||||
|
||||
s.dll = newstack
|
||||
s.linklist = newstack
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ 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()
|
||||
RegisterFuncTwoArg("add")
|
||||
RegisterFuncTwoArg("parallelresistance")
|
||||
register("add", 2, "addition")
|
||||
register("test", 1, "test")
|
||||
register("parallelresistance", 2, "parallel resistance")
|
||||
end
|
||||
|
||||
18
go/util.go
18
go/util.go
@@ -1,7 +1,25 @@
|
||||
/*
|
||||
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 "math"
|
||||
|
||||
// find an item in a list
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
|
||||
Reference in New Issue
Block a user