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

304
calc.go
View File

@@ -30,21 +30,19 @@ import (
) )
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
interpreter *Interpreter interpreter *Interpreter
Operators *regexp.Regexp Space *regexp.Regexp
Space *regexp.Regexp Constants []string
Constants []string LuaFunctions []string
MathFunctions []string
BatchFunctions []string
LuaFunctions []string
Functions Funcalls Funcalls Funcalls
BatchFuncalls Funcalls
} }
// help for lua functions will be added dynamically // help for lua functions will be added dynamically
@@ -78,11 +76,8 @@ Math operators:
// commands, constants and operators, defined here to feed completion // commands, constants and operators, defined here to feed completion
// and our mode switch in Eval() dynamically // and our mode switch in Eval() dynamically
const ( const (
Commands string = `dump reverse debug undebug clear batch shift undo help history manual exit quit` Commands string = `dump reverse debug undebug clear batch shift undo help history manual exit quit`
Operators string = `+ - * x / ^ % %- %+` Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
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 // 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(Commands, " ")...)
completions = append(completions, strings.Split(Operators, " ")...)
completions = append(completions, strings.Split(MathFunctions, " ")...)
completions = append(completions, strings.Split(Constants, " ")...) completions = append(completions, strings.Split(Constants, " ")...)
return completions 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 { func NewCalc() *Calc {
c := Calc{stack: NewStack(), debug: false} c := Calc{stack: NewStack(), debug: false}
c.Funcalls = DefineFunctions()
c.BatchFuncalls = DefineBatchFunctions()
c.completer = readline.NewPrefixCompleter( c.completer = readline.NewPrefixCompleter(
// custom lua functions // custom lua functions
readline.PcItemDynamic(GetCompleteCustomFunctions()), 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+`) c.Space = regexp.MustCompile(`\s+`)
// pre-calculate mode switching arrays // pre-calculate mode switching arrays
c.Constants = strings.Split(Constants, " ") c.Constants = strings.Split(Constants, " ")
c.MathFunctions = strings.Split(MathFunctions, " ")
c.BatchFunctions = strings.Split(BatchFunctions, " ")
for name := range LuaFuncs { for name := range LuaFuncs {
c.LuaFunctions = append(c.LuaFunctions, name) c.LuaFunctions = append(c.LuaFunctions, name)
@@ -189,11 +188,19 @@ func (c *Calc) Eval(line string) {
c.stack.Backup() c.stack.Backup()
c.stack.Push(num) c.stack.Push(num)
} else { } else {
if c.Operators.MatchString(item) { /*
// simple ops like + or x if contains(c.MathFunctions, item) {
c.simple(item[0]) // go builtin math function, if implemented
continue c.mathfunc(item)
} continue
}
if contains(c.BatchFunctions, item) {
// math functions only supported in batch mode like max or mean
c.batchfunc(item)
continue
}
*/
if contains(c.Constants, item) { if contains(c.Constants, item) {
// put the constant onto the stack // put the constant onto the stack
@@ -202,16 +209,29 @@ func (c *Calc) Eval(line string) {
continue continue
} }
if contains(c.MathFunctions, item) { if _, ok := c.Funcalls[item]; ok {
// go builtin math function, if implemented if err := c.DoFuncall(item); err != nil {
c.mathfunc(item) fmt.Println(err)
} else {
c.Result()
}
continue continue
} }
if contains(c.BatchFunctions, item) { if c.batch {
// math functions only supported in batch mode like max or mean if _, ok := c.BatchFuncalls[item]; ok {
c.batchfunc(item) if err := c.DoFuncall(item); err != nil {
continue 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) { if contains(c.LuaFunctions, item) {
@@ -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 // just a textual representation of math operations, viewable with the
// history command // history command
func (c *Calc) History(format string, args ...any) { func (c *Calc) History(format string, args ...any) {
@@ -278,9 +362,9 @@ func (c *Calc) Result() float64 {
fmt.Print("= ") 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) { func (c *Calc) Debug(msg string) {
@@ -333,122 +417,6 @@ func (c *Calc) simple(op byte) {
c.Result() 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) { func (c *Calc) luafunc(funcname string) {
// called from calc loop // called from calc loop
var x float64 var x float64
@@ -456,9 +424,9 @@ func (c *Calc) luafunc(funcname string) {
switch c.interpreter.FuncNumArgs(funcname) { switch c.interpreter.FuncNumArgs(funcname) {
case 1: case 1:
x, err = c.interpreter.CallLuaFunc(funcname, []float64{c.stack.Last()}) x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
case 2: case 2:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.LastTwo()) x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2))
case -1: case -1:
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.All()) x, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
default: default:

148
funcs.go
View File

@@ -19,9 +19,7 @@ package main
import ( import (
"errors" "errors"
"fmt"
"math" "math"
"strings"
) )
type R struct { type R struct {
@@ -79,16 +77,19 @@ func DefineFunctions() Funcalls {
return NewR(arg[0]+arg[1], nil) return NewR(arg[0]+arg[1], nil)
}, },
), ),
"-": NewFuncall( "-": NewFuncall(
func(arg Numbers) R { func(arg Numbers) R {
return NewR(arg[0]-arg[1], nil) return NewR(arg[0]-arg[1], nil)
}, },
), ),
"x": NewFuncall( "x": NewFuncall(
func(arg Numbers) R { func(arg Numbers) R {
return NewR(arg[0]*arg[1], nil) return NewR(arg[0]*arg[1], nil)
}, },
), ),
"/": NewFuncall( "/": NewFuncall(
func(arg Numbers) R { func(arg Numbers) R {
if arg[1] == 0 { if arg[1] == 0 {
@@ -98,79 +99,110 @@ func DefineFunctions() Funcalls {
return NewR(arg[0]/arg[1], nil) return NewR(arg[0]/arg[1], nil)
}, },
), ),
"^": NewFuncall( "^": NewFuncall(
func(arg Numbers) R { func(arg Numbers) R {
return NewR(math.Pow(arg[0], arg[1]), nil) 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 // aliases
f["*"] = f["x"] f["*"] = f["x"]
f["mod"] = f["remainder"]
return f return f
} }
func list2str(list Numbers) string { func DefineBatchFunctions() Funcalls {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]") f := map[string]*Funcall{
} "median": NewFuncall(
func(args Numbers) R {
middle := len(args) / 2
return NewR(args[middle], nil)
},
-1),
// we need to add a history entry for each operation "mean": NewFuncall(
func (c *Calc) SetHistory(op string, args Numbers) { func(args Numbers) R {
c.History("%s %s", list2str(args)) var sum float64
} for _, item := range args {
sum += item
}
return NewR(sum/float64(len(args)), nil)
},
-1),
// Execute a math function, check if it is defined just in case "min": NewFuncall(
// func(args Numbers) R {
// FIXME: add a loop over DoFuncall() for non-batch-only functions var min float64
// like + or * min, args = args[0], args[1:]
// for _, item := range args {
// FIXME: use R{} as well? or even everywhere, while we're at it? if item < min {
func (c *Calc) DoFuncall(funcname string) (float64, error) { min = item
if function, ok := c.Functions[funcname]; ok { }
args := Numbers{} }
batch := false return NewR(min, nil)
},
-1),
if function.Expectargs == -1 { "max": NewFuncall(
// batch mode, but always < stack len, so check first func(args Numbers) R {
args = c.stack.All() var max float64
batch = true max, args = args[0], args[1:]
} else { for _, item := range args {
// this is way better behavior than just using 0 in place of if item > max {
// non-existing stack items max = item
if c.stack.Len() < function.Expectargs { }
return -1, errors.New("stack doesn't provide enough arguments") }
} return NewR(max, nil)
},
-1),
args = c.stack.Last(function.Expectargs) "sum": NewFuncall(
} func(args Numbers) R {
var sum float64
// the actual lambda call, so to say. We provide a slice of for _, item := range args {
// the requested size, fetched from the stack (but not popped sum += item
// yet!) }
R := function.Func(args) return NewR(sum, nil)
},
if R.Err != nil { -1),
// 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! // aliases
return -1, errors.New("heck, no such function") 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 { func (s *Stack) Last(num ...int) []float64 {
items := []float64{}
i := s.Len()
count := 1 count := 1
var items []float64
if len(num) > 0 { if len(num) > 0 {
count = num[0] count = num[0]
} }
if s.linklist.Back() == nil { for e := s.linklist.Front(); e != nil; e = e.Next() {
return nil if i <= count {
} items = append(items, e.Value.(float64))
}
for i := 0; i < count; i++ { i--
items = append(items, s.linklist.Back().Value.(float64))
} }
return items 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 package main
import "math" import (
"fmt"
"math"
"strings"
)
// find an item in a list // find an item in a list
func contains(s []string, e string) bool { func contains(s []string, e string) bool {
@@ -55,3 +59,7 @@ func const2num(name string) float64 {
return 0 return 0
} }
} }
func list2str(list Numbers) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]")
}