code re-organization, added more commands and more flexible lua api

This commit is contained in:
2023-11-01 13:07:08 +01:00
parent 1a89d0ab85
commit 19b8aa7883
6 changed files with 467 additions and 145 deletions

View File

@@ -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 package main
import ( import (
"errors"
"fmt" "fmt"
"math" "math"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/chzyer/readline" "github.com/chzyer/readline"
lua "github.com/yuin/gopher-lua"
) )
type Calc struct { type Calc struct {
debug bool debug bool
batch bool batch bool
stdin bool stdin bool
stack *Stack stack *Stack
history []string history []string
completer readline.AutoCompleter completer readline.AutoCompleter
L *lua.LState interpreter *Interpreter
} }
// help for lua functions will be added dynamically
const Help string = `Available commands: const Help string = `Available commands:
batch enable batch mode batch toggle batch mode
debug enable debug output debug toggle debug output
dump display the stack contents dump display the stack contents
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
history display calculation history history display calculation history
help show this message help|? show this message
quit|exit|c-d|c-c exit program
Available operators: 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: 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 { func GetCompleteCustomFunctions() func(string) []string {
return func(line string) []string { return func(line string) []string {
funcs := []string{} funcs := []string{}
@@ -58,12 +89,15 @@ func NewCalc() *Calc {
readline.PcItem("dump"), readline.PcItem("dump"),
readline.PcItem("reverse"), readline.PcItem("reverse"),
readline.PcItem("debug"), readline.PcItem("debug"),
readline.PcItem("undebug"),
readline.PcItem("clear"), readline.PcItem("clear"),
readline.PcItem("batch"), readline.PcItem("batch"),
readline.PcItem("shift"), readline.PcItem("shift"),
readline.PcItem("undo"), readline.PcItem("undo"),
readline.PcItem("help"), readline.PcItem("help"),
readline.PcItem("history"), readline.PcItem("history"),
readline.PcItem("exit"),
readline.PcItem("quit"),
// ops // ops
readline.PcItem("+"), readline.PcItem("+"),
@@ -91,41 +125,69 @@ func NewCalc() *Calc {
readline.PcItem("sqrt"), readline.PcItem("sqrt"),
readline.PcItem("remainder"), readline.PcItem("remainder"),
readline.PcItem("avg"), readline.PcItem("avg"),
readline.PcItem("mean"), // alias for avg
readline.PcItem("min"),
readline.PcItem("max"),
readline.PcItem("median"), readline.PcItem("median"),
) )
return &c return &c
} }
// setup the interpreter, called from main()
func (c *Calc) SetInt(I *Interpreter) {
c.interpreter = I
}
func (c *Calc) ToggleDebug() { func (c *Calc) ToggleDebug() {
c.debug = !c.debug c.debug = !c.debug
c.stack.ToggleDebug() c.stack.ToggleDebug()
fmt.Printf("debugging set to %t\n", c.debug)
} }
func (c *Calc) ToggleBatch() { func (c *Calc) ToggleBatch() {
c.batch = !c.batch c.batch = !c.batch
fmt.Printf("batchmode set to %t\n", c.batch)
} }
func (c *Calc) ToggleStdin() { func (c *Calc) ToggleStdin() {
c.stdin = !c.stdin 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) { func (c *Calc) Eval(line string) {
line = strings.TrimSpace(line) 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 == "" { if line == "" {
return return
} }
for luafunc := range LuaFuncs { space := regexp.MustCompile(`\s+`)
luafuncs = append(luafuncs, luafunc) 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) { for _, item := range space.Split(line, -1) {
@@ -135,39 +197,53 @@ func (c *Calc) Eval(line string) {
c.stack.Backup() c.stack.Backup()
c.stack.Push(num) c.stack.Push(num)
} else { } else {
if simple.MatchString(line) { if simple.MatchString(item) {
// simple ops like + or x
c.simple(item[0]) c.simple(item[0])
continue continue
} }
if contains(constants, item) { if contains(constants, item) {
// put the constant onto the stack
c.stack.Backup() c.stack.Backup()
c.stack.Push(const2num(item)) c.stack.Push(const2num(item))
continue continue
} }
if contains(functions, item) { if contains(functions, item) {
// go builtin math function, if implemented
c.mathfunc(item) c.mathfunc(item)
continue continue
} }
if contains(batch, item) { if contains(batch, item) {
// math functions only supported in batch mode like max or mean
c.batchfunc(item) c.batchfunc(item)
continue continue
} }
if contains(luafuncs, item) { if contains(luafuncs, item) {
// user provided custom lua functions
c.luafunc(item) c.luafunc(item)
continue continue
} }
// management commands
switch item { switch item {
case "?":
fallthrough
case "help": case "help":
fmt.Println(Help) fmt.Println(Help)
fmt.Println("Lua functions:")
for name, function := range LuaFuncs {
fmt.Printf("%-20s %s\n", name, function.help)
}
case "dump": case "dump":
c.stack.Dump() c.stack.Dump()
case "debug": case "debug":
c.ToggleDebug() c.ToggleDebug()
case "undebug":
c.debug = false
case "batch": case "batch":
c.ToggleBatch() c.ToggleBatch()
case "clear": case "clear":
@@ -185,8 +261,10 @@ func (c *Calc) Eval(line string) {
for _, entry := range c.history { for _, entry := range c.history {
fmt.Println(entry) fmt.Println(entry)
} }
case "^": case "exit":
c.exp() fallthrough
case "quit":
os.Exit(0)
default: default:
fmt.Println("unknown command or operator!") 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) { func (c *Calc) History(format string, args ...any) {
c.history = append(c.history, fmt.Sprintf(format, args...)) c.history = append(c.history, fmt.Sprintf(format, args...))
} }
// print the result
func (c *Calc) Result() float64 { func (c *Calc) Result() float64 {
if !c.stdin { if !c.stdin {
fmt.Print("= ") fmt.Print("= ")
@@ -208,6 +289,13 @@ func (c *Calc) Result() float64 {
return c.stack.Last() 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) { func (c *Calc) simple(op byte) {
c.stack.Backup() c.stack.Backup()
@@ -216,15 +304,15 @@ func (c *Calc) simple(op byte) {
a := c.stack.Pop() a := c.stack.Pop()
var x float64 var x float64
if c.debug { c.Debug(fmt.Sprintf("evaluating: %.2f %c %.2f", a, op, b))
fmt.Printf("DEBUG: evaluating: %.2f %c %.2f\n", a, op, b)
}
switch op { switch op {
case '+': case '+':
x = a + b x = a + b
case '-': case '-':
x = a - b x = a - b
case 'x':
fallthrough // alias for *
case '*': case '*':
x = a * b x = a * b
case '/': case '/':
@@ -233,6 +321,8 @@ func (c *Calc) simple(op byte) {
return return
} }
x = a / b x = a / b
case '^':
x = math.Pow(a, b)
default: default:
panic("invalid operator!") panic("invalid operator!")
} }
@@ -246,32 +336,11 @@ func (c *Calc) simple(op byte) {
} }
} }
_ = c.Result() 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()
} }
// calc using go math lib functions
func (c *Calc) mathfunc(funcname string) { 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() c.stack.Backup()
for c.stack.Len() > 0 { for c.stack.Len() > 0 {
@@ -283,6 +352,8 @@ func (c *Calc) mathfunc(funcname string) {
x = math.Sqrt(a) x = math.Sqrt(a)
c.History("sqrt(%f) = %f", a, x) c.History("sqrt(%f) = %f", a, x)
case "mod":
fallthrough // alias
case "remainder": case "remainder":
b := c.stack.Pop() b := c.stack.Pop()
a := 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) { func (c *Calc) batchfunc(funcname string) {
if !c.batch { if !c.batch {
fmt.Println("error: only available in batch mode") fmt.Println("error: only available in batch mode")
@@ -344,6 +416,8 @@ func (c *Calc) batchfunc(funcname string) {
x = all[middle] x = all[middle]
c.History("median(all)") c.History("median(all)")
case "mean":
fallthrough // alias
case "avg": case "avg":
var sum float64 var sum float64
@@ -353,6 +427,28 @@ func (c *Calc) batchfunc(funcname string) {
x = sum / float64(count) x = sum / float64(count)
c.History("avg(all)") 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) c.stack.Push(x)
@@ -360,20 +456,41 @@ func (c *Calc) batchfunc(funcname string) {
} }
func (c *Calc) luafunc(funcname string) { func (c *Calc) luafunc(funcname string) {
// we may need to put them onto the stack afterwards! // called from calc loop
c.stack.Backup() var x float64
b := c.stack.Pop() var err error
a := c.stack.Pop()
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 { if err != nil {
fmt.Println(err) fmt.Println(err)
c.stack.Push(a)
c.stack.Push(b)
return 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.stack.Push(x)
c.Result() c.Result()

View File

@@ -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 package main
import ( import (
@@ -7,31 +24,59 @@ import (
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
) )
type Interpreter struct {
debug bool
}
// LUA interpreter, instanciated in main() // LUA interpreter, instanciated in main()
var L *lua.LState var L *lua.LState
var LuaFuncs map[string]int // holds a user provided lua function
type LuaFunction struct {
// FIXME: add 2nd var with help string name string
// called from lua to register a 1 arg math function help string
func RegisterFuncOneArg(L *lua.LState) int { numargs int
function := L.ToString(1)
LuaFuncs[function] = 1
return 1
} }
// called from lua to register a 1 arg math function // must be global since init() is being called from lua which doesn't
func RegisterFuncTwoArg(L *lua.LState) int { // have access to the interpreter instance
function := L.ToString(1) var LuaFuncs map[string]LuaFunction
LuaFuncs[function] = 2
return 1
}
func InitLua(L *lua.LState) { // initialize the lua environment properly
LuaFuncs = map[string]int{} func InitLua(config string, debug bool) *Interpreter {
L.SetGlobal("RegisterFuncOneArg", L.NewFunction(RegisterFuncOneArg)) // we only load a subset of lua Open modules and don't allow
L.SetGlobal("RegisterFuncTwoArg", L.NewFunction(RegisterFuncTwoArg)) // 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{ if err := L.CallByParam(lua.P{
Fn: L.GetGlobal("init"), Fn: L.GetGlobal("init"),
NRet: 0, NRet: 0,
@@ -39,26 +84,68 @@ func InitLua(L *lua.LState) {
}); err != nil { }); err != nil {
panic(err) panic(err)
} }
return &Interpreter{debug: debug}
} }
func CallLuaFunc(L *lua.LState, funcname string, a float64, b float64) (float64, error) { func (i *Interpreter) Debug(msg string) {
if LuaFuncs[funcname] == 1 { 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 // 1 arg variant
if err := L.CallByParam(lua.P{ if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname), Fn: L.GetGlobal(funcname),
NRet: 1, NRet: 1,
Protect: true, Protect: true,
}, lua.LNumber(a)); err != nil { }, lua.LNumber(items[0])); err != nil {
fmt.Println(err) fmt.Println(err)
return 0, err return 0, err
} }
} else { case 2:
// 2 arg variant // 2 arg variant
if err := L.CallByParam(lua.P{ if err := L.CallByParam(lua.P{
Fn: L.GetGlobal(funcname), Fn: L.GetGlobal(funcname),
NRet: 1, NRet: 1,
Protect: true, 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 return 0, err
} }
} }
@@ -69,5 +156,22 @@ func CallLuaFunc(L *lua.LState, funcname string, a float64, b float64) (float64,
return float64(res), nil 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
} }

View File

@@ -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 package main
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"github.com/chzyer/readline" "github.com/chzyer/readline"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
@@ -38,7 +56,8 @@ func main() {
flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode") flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode")
flag.BoolVarP(&showversion, "version", "v", false, "show version") flag.BoolVarP(&showversion, "version", "v", false, "show version")
flag.BoolVarP(&showhelp, "help", "h", false, "show usage") 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() flag.Parse()
@@ -56,43 +75,28 @@ func main() {
calc.ToggleDebug() 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 { if _, err := os.Stat(configfile); err == nil {
// FIXME: put into interpreter.go, probably with its own obj I := InitLua(configfile, enabledebug)
// then just Interpreter.Init(configfile) should suffice calc.SetInt(I)
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
} }
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{ rl, err := readline.NewEx(&readline.Config{
Prompt: "\033[31m»\033[0m ", Prompt: calc.Prompt(),
HistoryFile: os.Getenv("HOME") + "/.rpn-history", HistoryFile: os.Getenv("HOME") + "/.rpn-history",
HistoryLimit: 500, HistoryLimit: 500,
AutoComplete: calc.completer, AutoComplete: calc.completer,
@@ -108,19 +112,27 @@ func main() {
rl.CaptureExitSignal() rl.CaptureExitSignal()
if inputIsStdin() { if inputIsStdin() {
// commands are coming on stdin, however we will still enter
// the same loop since readline just reads fine from stdin
calc.ToggleStdin() calc.ToggleStdin()
} }
for { for {
// primary program repl
line, err := rl.Readline() line, err := rl.Readline()
if err != nil { if err != nil {
break break
} }
calc.Eval(line) calc.Eval(line)
rl.SetPrompt(calc.Prompt())
} }
if len(flag.Args()) > 0 { 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]) calc.Eval(flag.Args()[0])
} }
} }

View File

@@ -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 package main
import ( import (
@@ -6,8 +23,14 @@ import (
"sync" "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 { type Stack struct {
dll list.List linklist list.List
backup list.List backup list.List
debug bool debug bool
rev int rev int
@@ -15,8 +38,15 @@ type Stack struct {
mutex sync.Mutex mutex sync.Mutex
} }
// FIXME: maybe use a separate stack object for backup so that it has
// its own revision etc
func NewStack() *Stack { 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) { func (s *Stack) Debug(msg string) {
@@ -33,6 +63,7 @@ func (s *Stack) Bump() {
s.rev++ s.rev++
} }
// append an item to the stack
func (s *Stack) Push(x float64) { func (s *Stack) Push(x float64) {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
@@ -40,52 +71,87 @@ func (s *Stack) Push(x float64) {
s.Debug(fmt.Sprintf(" push to stack: %.2f", x)) s.Debug(fmt.Sprintf(" push to stack: %.2f", x))
s.Bump() s.Bump()
s.dll.PushBack(x) s.linklist.PushBack(x)
} }
// remove and return an item from the stack
func (s *Stack) Pop() float64 { func (s *Stack) Pop() float64 {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
if s.dll.Len() == 0 { if s.linklist.Len() == 0 {
return 0 return 0
} }
tail := s.dll.Back() tail := s.linklist.Back()
val := tail.Value 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() s.Bump()
return val.(float64) return val.(float64)
} }
// just remove the last item, do not return it
func (s *Stack) Shift() { func (s *Stack) Shift() {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
if s.dll.Len() == 0 { if s.linklist.Len() == 0 {
return return
} }
tail := s.dll.Back() tail := s.linklist.Back()
s.dll.Remove(tail) s.linklist.Remove(tail)
s.Debug(fmt.Sprintf("remove from stack: %.2f", tail.Value)) s.Debug(fmt.Sprintf("remove from stack: %.2f", tail.Value))
} }
// just return the last item, do not remove it
func (s *Stack) Last() float64 { func (s *Stack) Last() float64 {
if s.dll.Back() == nil { if s.linklist.Back() == nil {
return 0 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() { func (s *Stack) Dump() {
fmt.Printf("Stack revision %d (%p):\n", s.rev, &s.dll) fmt.Printf("Stack revision %d (%p):\n", s.rev, &s.linklist)
for e := s.dll.Front(); e != nil; e = e.Next() { for e := s.linklist.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value) fmt.Println(e.Value)
} }
@@ -100,11 +166,11 @@ func (s *Stack) Dump() {
func (s *Stack) Clear() { func (s *Stack) Clear() {
s.Debug("DEBUG: clearing stack") s.Debug("DEBUG: clearing stack")
s.dll = list.List{} s.linklist = list.List{}
} }
func (s *Stack) Len() int { func (s *Stack) Len() int {
return s.dll.Len() return s.linklist.Len()
} }
func (s *Stack) Backup() { func (s *Stack) Backup() {
@@ -113,7 +179,7 @@ func (s *Stack) Backup() {
// 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.backup = list.List{} 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.backup.PushBack(e.Value)
} }
s.backuprev = s.rev s.backuprev = s.rev
@@ -128,15 +194,15 @@ 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.dll = s.backup s.linklist = s.backup
} }
func (s *Stack) Reverse() { func (s *Stack) Reverse() {
newstack := list.List{} 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) newstack.PushFront(e.Value)
} }
s.dll = newstack s.linklist = newstack
} }

View File

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

View File

@@ -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 package main
import "math" import "math"
// find an item in a list
func contains(s []string, e string) bool { func contains(s []string, e string) bool {
for _, a := range s { for _, a := range s {
if a == e { if a == e {