package main import ( "fmt" "math" "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 } 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 Available operators: basic operators: + - * / Math operators: ^ power` // That way I can add custom functions to completion func GetCompleteCustomFunctions() func(string) []string { return func(line string) []string { funcs := []string{} for luafunc := range LuaFuncs { funcs = append(funcs, luafunc) } return funcs } } func NewCalc() *Calc { c := Calc{stack: NewStack(), debug: false} c.completer = readline.NewPrefixCompleter( // custom lua functions readline.PcItemDynamic(GetCompleteCustomFunctions()), // commands readline.PcItem("dump"), readline.PcItem("reverse"), readline.PcItem("debug"), readline.PcItem("clear"), readline.PcItem("batch"), readline.PcItem("shift"), readline.PcItem("undo"), readline.PcItem("help"), readline.PcItem("history"), // ops readline.PcItem("+"), readline.PcItem("-"), readline.PcItem("*"), readline.PcItem("/"), readline.PcItem("^"), readline.PcItem("%"), readline.PcItem("%-"), readline.PcItem("%+"), // constants readline.PcItem("Pi"), readline.PcItem("Phi"), readline.PcItem("Sqrt2"), readline.PcItem("SqrtE"), readline.PcItem("SqrtPi"), readline.PcItem("SqrtPhi"), readline.PcItem("Ln2"), readline.PcItem("Log2E"), readline.PcItem("Ln10"), readline.PcItem("Log10E"), // math functions readline.PcItem("sqrt"), readline.PcItem("remainder"), readline.PcItem("avg"), readline.PcItem("median"), ) return &c } func (c *Calc) ToggleDebug() { c.debug = !c.debug c.stack.ToggleDebug() } func (c *Calc) ToggleBatch() { c.batch = !c.batch } func (c *Calc) ToggleStdin() { c.stdin = !c.stdin } 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) } for _, item := range space.Split(line, -1) { num, err := strconv.ParseFloat(item, 64) if err == nil { c.stack.Backup() c.stack.Push(num) } else { if simple.MatchString(line) { c.simple(item[0]) continue } if contains(constants, item) { c.stack.Backup() c.stack.Push(const2num(item)) continue } if contains(functions, item) { c.mathfunc(item) continue } if contains(batch, item) { c.batchfunc(item) continue } if contains(luafuncs, item) { c.luafunc(item) continue } switch item { case "help": fmt.Println(Help) case "dump": c.stack.Dump() case "debug": c.ToggleDebug() case "batch": c.ToggleBatch() case "clear": c.stack.Backup() c.stack.Clear() case "shift": c.stack.Backup() c.stack.Shift() case "reverse": c.stack.Backup() c.stack.Reverse() case "undo": c.stack.Restore() case "history": for _, entry := range c.history { fmt.Println(entry) } case "^": c.exp() default: fmt.Println("unknown command or operator!") } } } } func (c *Calc) History(format string, args ...any) { c.history = append(c.history, fmt.Sprintf(format, args...)) } func (c *Calc) Result() float64 { if !c.stdin { fmt.Print("= ") } fmt.Println(c.stack.Last()) return c.stack.Last() } func (c *Calc) simple(op byte) { c.stack.Backup() for c.stack.Len() > 1 { b := c.stack.Pop() a := c.stack.Pop() var x float64 if c.debug { fmt.Printf("DEBUG: evaluating: %.2f %c %.2f\n", a, op, b) } switch op { case '+': x = a + b case '-': x = a - b case '*': x = a * b case '/': if b == 0 { fmt.Println("error: division by null!") return } x = a / b default: panic("invalid operator!") } c.stack.Push(x) c.History("%f %c %f = %f", a, op, b, x) if !c.batch { break } } _ = 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() } 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 { var x float64 switch funcname { case "sqrt": a := c.stack.Pop() x = math.Sqrt(a) c.History("sqrt(%f) = %f", a, x) 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() } 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 "avg": var sum float64 for c.stack.Len() > 0 { sum += c.stack.Pop() } x = sum / float64(count) c.History("avg(all)") } c.stack.Push(x) _ = c.Result() } 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() 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.Push(x) c.Result() }