diff --git a/funcs.go b/funcs.go new file mode 100644 index 0000000..c5fb6c5 --- /dev/null +++ b/funcs.go @@ -0,0 +1,161 @@ +/* +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 . +*/ + +package main + +import ( + "errors" + "fmt" + "math" + "strings" +) + +// every function we are able to call must be of type Funcall, which +// needs tp specify how many numbers it expects and the actual go +// function to be executed. +// +// The function has to take a float slice as argument and return a +// float and an error object. The float slice is guaranteed to have +// the expected number of arguments. +// +// However, Lua functions are handled differently, see interpreter.go. +type Function func([]float64) (float64, error) +type Funcall struct { + Expectargs int // -1 means batch only mode, you'll get the whole stack as arg + Func Function +} + +// will hold all hard coded functions and operators +type Funcalls map[string]*Funcall + +// convenience function, create a new Funcall object, if expectargs +// was not specified, 2 is assumed. +func NewFuncall(function Function, expectargs ...int) *Funcall { + expect := 2 + + if len(expectargs) > 0 { + expect = expectargs[0] + } + + return &Funcall{ + Expectargs: expect, + Func: function, + } +} + +// the actual functions, called once during initialization. +func DefineFunctions() Funcalls { + f := map[string]*Funcall{ + // simple operators, they all expect 2 args + "+": NewFuncall( + func(arg []float64) (float64, error) { + return arg[0] + arg[1], nil + }, + ), + "-": NewFuncall( + func(arg []float64) (float64, error) { + return arg[0] - arg[1], nil + }, + ), + "x": NewFuncall( + func(arg []float64) (float64, error) { + return arg[0] * arg[1], nil + }, + ), + "/": NewFuncall( + func(arg []float64) (float64, error) { + if arg[1] == 0 { + return 0, errors.New("division by null") + } + + return arg[0] / arg[1], nil + }, + ), + "^": NewFuncall( + func(arg []float64) (float64, error) { + return math.Pow(arg[0], arg[1]), nil + }, + ), + } + + // aliases + f["*"] = f["x"] + + return f +} + +func list2str(list []float64) string { + return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]") +} + +// we need to add a history entry for each operation +func (c *Calc) SetHistory(op string, args []float64) { + 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 * +func (c *Calc) DoFuncall(funcname string) (float64, error) { + if function, ok := c.Functions[funcname]; ok { + args := []float64{} + 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!) + res, err := function.Func(args) + + if err != nil { + // leave the stack untouched in case of any error + return res, err + } + + if batch { + // get rid of stack + c.stack.Clear() + } else { + // remove operands + c.stack.Shift(function.Expectargs) + } + + // save result + c.stack.Push(res) + + // thanks a lot + c.SetHistory(funcname, args) + return res, nil + } + + // should not happen, if it does: programmer fault! + return -1, errors.New("heck, no such function") +}