moved all math functions and operators to funcalls

So now if you want to add a new operator or math function all you have
to do is to add it to func.go. Conpletion will be generated from it.
This commit is contained in:
2023-11-05 12:55:59 +01:00
parent 5189d351c6
commit c4c60651d1
4 changed files with 243 additions and 235 deletions

282
calc.go
View File

@@ -37,14 +37,12 @@ type Calc struct {
history []string
completer readline.AutoCompleter
interpreter *Interpreter
Operators *regexp.Regexp
Space *regexp.Regexp
Constants []string
MathFunctions []string
BatchFunctions []string
LuaFunctions []string
Functions Funcalls
Funcalls Funcalls
BatchFuncalls Funcalls
}
// help for lua functions will be added dynamically
@@ -79,10 +77,7 @@ Math operators:
// and our mode switch in Eval() dynamically
const (
Commands string = `dump reverse debug undebug clear batch shift undo help history manual exit quit`
Operators string = `+ - * x / ^ % %- %+`
MathFunctions string = `sqrt remainder`
Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
BatchFunctions string = `median avg mean max min`
)
// That way we can add custom functions to completion
@@ -95,41 +90,45 @@ func GetCompleteCustomFunctions() func(string) []string {
}
completions = append(completions, strings.Split(Commands, " ")...)
completions = append(completions, strings.Split(Operators, " ")...)
completions = append(completions, strings.Split(MathFunctions, " ")...)
completions = append(completions, strings.Split(Constants, " ")...)
return completions
}
}
func (c *Calc) GetCompleteCustomFuncalls() func(string) []string {
return func(line string) []string {
completions := []string{}
for function := range c.Funcalls {
completions = append(completions, function)
}
for function := range c.BatchFuncalls {
completions = append(completions, function)
}
return completions
}
}
func NewCalc() *Calc {
c := Calc{stack: NewStack(), debug: false}
c.Funcalls = DefineFunctions()
c.BatchFuncalls = DefineBatchFunctions()
c.completer = readline.NewPrefixCompleter(
// custom lua functions
readline.PcItemDynamic(GetCompleteCustomFunctions()),
readline.PcItemDynamic(c.GetCompleteCustomFuncalls()),
)
// pre-calculate mode switching regexes
reg := `^[`
for _, op := range strings.Split(Operators, " ") {
switch op {
case "x":
reg += op
default:
reg += `\` + op
}
}
reg += `]$`
c.Operators = regexp.MustCompile(reg)
c.Space = regexp.MustCompile(`\s+`)
// pre-calculate mode switching arrays
c.Constants = strings.Split(Constants, " ")
c.MathFunctions = strings.Split(MathFunctions, " ")
c.BatchFunctions = strings.Split(BatchFunctions, " ")
for name := range LuaFuncs {
c.LuaFunctions = append(c.LuaFunctions, name)
@@ -189,19 +188,7 @@ func (c *Calc) Eval(line string) {
c.stack.Backup()
c.stack.Push(num)
} else {
if c.Operators.MatchString(item) {
// simple ops like + or x
c.simple(item[0])
continue
}
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
continue
}
/*
if contains(c.MathFunctions, item) {
// go builtin math function, if implemented
c.mathfunc(item)
@@ -213,6 +200,39 @@ func (c *Calc) Eval(line string) {
c.batchfunc(item)
continue
}
*/
if contains(c.Constants, item) {
// put the constant onto the stack
c.stack.Backup()
c.stack.Push(const2num(item))
continue
}
if _, ok := c.Funcalls[item]; ok {
if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
}
if c.batch {
if _, ok := c.BatchFuncalls[item]; ok {
if err := c.DoFuncall(item); err != nil {
fmt.Println(err)
} else {
c.Result()
}
continue
}
} else {
if _, ok := c.BatchFuncalls[item]; ok {
fmt.Println("only supported in batch mode")
continue
}
}
if contains(c.LuaFunctions, item) {
// user provided custom lua functions
@@ -266,6 +286,70 @@ func (c *Calc) Eval(line string) {
}
}
// Execute a math function, check if it is defined just in case
//
// FIXME: add a loop over DoFuncall() for non-batch-only functions
// like + or *
//
// FIXME: use R{} as well? or even everywhere, while we're at it?
func (c *Calc) DoFuncall(funcname string) error {
var function *Funcall
if c.batch {
function = c.BatchFuncalls[funcname]
} else {
function = c.Funcalls[funcname]
}
var args Numbers
batch := false
if function.Expectargs == -1 {
// batch mode, but always < stack len, so check first
args = c.stack.All()
batch = true
} else {
// this is way better behavior than just using 0 in place of
// non-existing stack items
if c.stack.Len() < function.Expectargs {
return errors.New("stack doesn't provide enough arguments")
}
args = c.stack.Last(function.Expectargs)
}
c.Debug(fmt.Sprintf("args: %v", args))
// the actual lambda call, so to say. We provide a slice of
// the requested size, fetched from the stack (but not popped
// yet!)
R := function.Func(args)
if R.Err != nil {
// leave the stack untouched in case of any error
return R.Err
}
if batch {
// get rid of stack
c.stack.Clear()
} else {
// remove operands
c.stack.Shift(function.Expectargs)
}
// save result
c.stack.Push(R.Res)
// thanks a lot
c.SetHistory(funcname, args, R.Res)
return nil
}
// we need to add a history entry for each operation
func (c *Calc) SetHistory(op string, args Numbers, res float64) {
c.History("%s %s -> %f", list2str(args), op, res)
}
// just a textual representation of math operations, viewable with the
// history command
func (c *Calc) History(format string, args ...any) {
@@ -278,9 +362,9 @@ func (c *Calc) Result() float64 {
fmt.Print("= ")
}
fmt.Println(c.stack.Last())
fmt.Println(c.stack.Last()[0])
return c.stack.Last()
return c.stack.Last()[0]
}
func (c *Calc) Debug(msg string) {
@@ -333,122 +417,6 @@ func (c *Calc) simple(op byte) {
c.Result()
}
// calc using go math lib functions
func (c *Calc) mathfunc(funcname string) {
c.stack.Backup()
for c.stack.Len() > 0 {
var x float64
switch funcname {
case "sqrt":
a := c.stack.Pop()
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()
x = math.Remainder(a, b)
c.History("remainderf(%f / %f) = %f", a, b, x)
case "%":
b := c.stack.Pop()
a := c.stack.Pop()
x = (a / 100) * b
c.History("%f percent of %f = %f", b, a, x)
case "%-":
b := c.stack.Pop()
a := c.stack.Pop()
x = a - ((a / 100) * b)
c.History("%f minus %f percent of %f = %f", a, b, a, x)
case "%+":
b := c.stack.Pop()
a := c.stack.Pop()
x = a + ((a / 100) * b)
c.History("%f plus %f percent of %f = %f", a, b, a, x)
}
c.stack.Push(x)
if !c.batch {
break
}
}
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")
}
c.stack.Backup()
var x float64
count := c.stack.Len()
switch funcname {
case "median":
all := []float64{}
for c.stack.Len() > 0 {
all = append(all, c.stack.Pop())
}
middle := count / 2
x = all[middle]
c.History("median(all)")
case "mean":
fallthrough // alias
case "avg":
var sum float64
for c.stack.Len() > 0 {
sum += c.stack.Pop()
}
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)
_ = c.Result()
}
func (c *Calc) luafunc(funcname string) {
// called from calc loop
var x float64
@@ -456,9 +424,9 @@ func (c *Calc) luafunc(funcname string) {
switch c.interpreter.FuncNumArgs(funcname) {
case 1:
x, err = c.interpreter.CallLuaFunc(funcname, []float64{c.stack.Last()})
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
case 2:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.LastTwo())
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2))
case -1:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
default:

156
funcs.go
View File

@@ -19,9 +19,7 @@ package main
import (
"errors"
"fmt"
"math"
"strings"
)
type R struct {
@@ -79,16 +77,19 @@ func DefineFunctions() Funcalls {
return NewR(arg[0]+arg[1], nil)
},
),
"-": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]-arg[1], nil)
},
),
"x": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]*arg[1], nil)
},
),
"/": NewFuncall(
func(arg Numbers) R {
if arg[1] == 0 {
@@ -98,79 +99,110 @@ func DefineFunctions() Funcalls {
return NewR(arg[0]/arg[1], nil)
},
),
"^": NewFuncall(
func(arg Numbers) R {
return NewR(math.Pow(arg[0], arg[1]), nil)
},
),
"%": NewFuncall(
func(arg Numbers) R {
return NewR((arg[0]/100)*arg[1], nil)
},
),
"%-": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]-((arg[0]/100)*arg[1]), nil)
},
),
"%+": NewFuncall(
func(arg Numbers) R {
return NewR(arg[0]+((arg[0]/100)*arg[1]), nil)
},
),
"mod": NewFuncall(
func(arg Numbers) R {
return NewR(math.Remainder(arg[0], arg[1]), nil)
},
),
"sqrt": NewFuncall(
func(arg Numbers) R {
return NewR(math.Sqrt(arg[0]), nil)
},
1),
}
// aliases
f["*"] = f["x"]
f["mod"] = f["remainder"]
return f
}
func list2str(list Numbers) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]")
func DefineBatchFunctions() Funcalls {
f := map[string]*Funcall{
"median": NewFuncall(
func(args Numbers) R {
middle := len(args) / 2
return NewR(args[middle], nil)
},
-1),
"mean": NewFuncall(
func(args Numbers) R {
var sum float64
for _, item := range args {
sum += item
}
return NewR(sum/float64(len(args)), nil)
},
-1),
"min": NewFuncall(
func(args Numbers) R {
var min float64
min, args = args[0], args[1:]
for _, item := range args {
if item < min {
min = item
}
}
return NewR(min, nil)
},
-1),
"max": NewFuncall(
func(args Numbers) R {
var max float64
max, args = args[0], args[1:]
for _, item := range args {
if item > max {
max = item
}
}
return NewR(max, nil)
},
-1),
"sum": NewFuncall(
func(args Numbers) R {
var sum float64
for _, item := range args {
sum += item
}
return NewR(sum, nil)
},
-1),
}
// we need to add a history entry for each operation
func (c *Calc) SetHistory(op string, args Numbers) {
c.History("%s %s", list2str(args))
}
// Execute a math function, check if it is defined just in case
//
// FIXME: add a loop over DoFuncall() for non-batch-only functions
// like + or *
//
// FIXME: use R{} as well? or even everywhere, while we're at it?
func (c *Calc) DoFuncall(funcname string) (float64, error) {
if function, ok := c.Functions[funcname]; ok {
args := Numbers{}
batch := false
if function.Expectargs == -1 {
// batch mode, but always < stack len, so check first
args = c.stack.All()
batch = true
} else {
// this is way better behavior than just using 0 in place of
// non-existing stack items
if c.stack.Len() < function.Expectargs {
return -1, errors.New("stack doesn't provide enough arguments")
}
args = c.stack.Last(function.Expectargs)
}
// the actual lambda call, so to say. We provide a slice of
// the requested size, fetched from the stack (but not popped
// yet!)
R := function.Func(args)
if R.Err != nil {
// leave the stack untouched in case of any error
return R.Res, R.Err
}
if batch {
// get rid of stack
c.stack.Clear()
} else {
// remove operands
c.stack.Shift(function.Expectargs)
}
// save result
c.stack.Push(R.Res)
// thanks a lot
c.SetHistory(funcname, args)
return R.Res, nil
}
// should not happen, if it does: programmer fault!
return -1, errors.New("heck, no such function")
// aliases
f["+"] = f["sum"]
f["avg"] = f["mean"]
return f
}

View File

@@ -115,20 +115,20 @@ func (s *Stack) Shift(num ...int) {
}
}
// just return the last item, do not remove it
// Return the last num items from the stack w/o modifying it.
func (s *Stack) Last(num ...int) []float64 {
items := []float64{}
i := s.Len()
count := 1
var items []float64
if len(num) > 0 {
count = num[0]
}
if s.linklist.Back() == nil {
return nil
for e := s.linklist.Front(); e != nil; e = e.Next() {
if i <= count {
items = append(items, e.Value.(float64))
}
for i := 0; i < count; i++ {
items = append(items, s.linklist.Back().Value.(float64))
i--
}
return items

10
util.go
View File

@@ -17,7 +17,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import "math"
import (
"fmt"
"math"
"strings"
)
// find an item in a list
func contains(s []string, e string) bool {
@@ -55,3 +59,7 @@ func const2num(name string) float64 {
return 0
}
}
func list2str(list Numbers) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]")
}