From 2abb05d8955854066738b8da524b6d9bd3010fa8 Mon Sep 17 00:00:00 2001 From: Oleg Yamnikov Date: Sat, 4 Feb 2017 00:13:08 +0500 Subject: [PATCH] Implement calculator --- calc_entry.go | 226 ++++++++++++++++++++++++++++++++++++++++++++++++ config.go | 2 + launch_entry.go | 14 +++ ui.go | 11 ++- 4 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 calc_entry.go diff --git a/calc_entry.go b/calc_entry.go new file mode 100644 index 0000000..ae0c1b5 --- /dev/null +++ b/calc_entry.go @@ -0,0 +1,226 @@ +package main + +import ( + "go/ast" + "go/parser" + "math" + "strconv" + "strings" +) + +func PerformCalc(query string) LaunchEntriesList { + expr, err := parser.ParseExpr(query) + if err != nil { + return nil + } + + value, ok := EvaluateGoExpr(expr) + if !ok { + return nil + } + + return LaunchEntriesList{NewCalcLaunchEntry(value)} +} + +func EvaluateGoExpr(expr ast.Expr) (val float64, ok bool) { + switch expr := expr.(type) { + case *ast.ParenExpr: + return EvaluateGoExpr(expr.X) + + case *ast.BinaryExpr: + return EvaluateGoBinaryExpr(expr.X, expr.Op.String(), expr.Y) + + case *ast.BasicLit: + val, err := strconv.ParseFloat(expr.Value, 64) + if err != nil { + return 0, false + } + return val, true + + case *ast.CallExpr: + return EvaluateGoCallExpr(expr.Fun, expr.Args) + + case *ast.Ident: + val, ok := SupportedConstants[strings.ToLower(expr.String())] + return val, ok + + case *ast.UnaryExpr: + // Only negation operator i supported + if expr.Op.String() != "-" { + return 0, false + } + val, ok := EvaluateGoExpr(expr.X) + if !ok { + return 0, false + } + return -val, true + + default: + return 0, false + } +} + +func EvaluateGoBinaryExpr(x ast.Expr, op string, y ast.Expr) (float64, bool) { + valX, ok := EvaluateGoExpr(x) + if !ok { + return 0, false + } + + valY, ok := EvaluateGoExpr(y) + if !ok { + return 0, false + } + + switch op { + case "+": + return valX + valY, true + case "-": + return valX - valY, true + case "*": + return valX * valY, true + case "/": + return valX / valY, true + case "^": + return math.Pow(valX, valY), true + default: + return 0, false + } +} + +type CalcFunc struct { + Names []string + ArgCount int + Eval func(...float64) (float64, bool) +} + +func (c *CalcFunc) Matches(name string, argCount int) bool { + if c.ArgCount != argCount { + return false + } + + for _, n := range c.Names { + if n == name { + return true + } + } + return false +} + +func Factorial(x int64) int64 { + if x <= 1 { + return 1 + } + return x * Factorial(x-1) +} + +func EvaluateGoCallExpr(fun ast.Expr, args []ast.Expr) (float64, bool) { + funIndent, ok := fun.(*ast.Ident) + if !ok { + return 0, false + } + + funName := strings.ToLower(funIndent.String()) + + var calcFunc *CalcFunc + for _, cf := range SupportedCalcFuncs { + if cf.Matches(funName, len(args)) { + calcFunc = &cf + break + } + } + + if calcFunc == nil { + return 0, false + } + + argVals := []float64{} + for _, arg := range args { + val, ok := EvaluateGoExpr(arg) + if !ok { + return 0, false + } + argVals = append(argVals, val) + } + + return calcFunc.Eval(argVals...) +} + +var SupportedCalcFuncs []CalcFunc = []CalcFunc{ + { + []string{"sqrt"}, 1, + func(args ...float64) (float64, bool) { + return math.Sqrt(args[0]), true + }, + }, + { + []string{"power", "pow"}, 2, + func(args ...float64) (float64, bool) { + return math.Pow(args[0], args[1]), true + }, + }, + { + []string{"root", "rt"}, 2, + func(args ...float64) (float64, bool) { + return math.Pow(args[0], 1/args[1]), true + }, + }, + { + []string{"sin"}, 1, + func(args ...float64) (float64, bool) { + return math.Sin(args[0]), true + }, + }, + { + []string{"cos"}, 1, + func(args ...float64) (float64, bool) { + return math.Cos(args[0]), true + }, + }, + { + []string{"tan", "tg"}, 1, + func(args ...float64) (float64, bool) { + return math.Tan(args[0]), true + }, + }, + { + []string{"cot", "ctg"}, 1, + func(args ...float64) (float64, bool) { + return 1 / math.Tan(args[0]), true + }, + }, + { + []string{"fact"}, 1, + func(args ...float64) (float64, bool) { + return float64(Factorial(int64(args[0]))), true + }, + }, + { + []string{"log", "ln"}, 1, + func(args ...float64) (float64, bool) { + return math.Log(args[0]), true + }, + }, + { + []string{"log2"}, 1, + func(args ...float64) (float64, bool) { + return math.Log2(args[0]), true + }, + }, + { + []string{"log10"}, 1, + func(args ...float64) (float64, bool) { + return math.Log10(args[0]), true + }, + }, + { + []string{"log"}, 2, + func(args ...float64) (float64, bool) { + return math.Log(args[1])/math.Log(args[0]), true + }, + }, +} + +var SupportedConstants = map[string]float64{ + "pi": math.Pi, + "e": math.E, +} diff --git a/config.go b/config.go index ea365d2..b67ba43 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ import ( type ProjektorConfig struct { EnabledCategories struct { + Calc bool History bool Apps bool URL bool @@ -28,6 +29,7 @@ var ( func DefaultConfig() *ProjektorConfig { c := &ProjektorConfig{} + c.EnabledCategories.Calc = true c.EnabledCategories.History = true c.EnabledCategories.Apps = true c.EnabledCategories.URL = true diff --git a/launch_entry.go b/launch_entry.go index eeab620..65de475 100644 --- a/launch_entry.go +++ b/launch_entry.go @@ -19,6 +19,7 @@ const ( FileEntry UrlEntry HistEntry + CalcEntry ) type LaunchEntry struct { @@ -116,6 +117,19 @@ func NewUrlLaunchEntry(url string) *LaunchEntry { } } +func NewCalcLaunchEntry(val float64) *LaunchEntry { + valStr := fmt.Sprint(val) + + return &LaunchEntry{ + Type: CalcEntry, + Icon: "gnome-calculator", + Name: valStr, + MarkupName: fmt.Sprintf("= %v", val), + TabName: valStr, + Cmdline: "", + } +} + func (le *LaunchEntry) UpdateMarkupName(index, length int) { index2 := index + length le.MarkupName = EscapeAmpersand( diff --git a/ui.go b/ui.go index fa865f6..a741359 100644 --- a/ui.go +++ b/ui.go @@ -122,7 +122,13 @@ func (iter UiTreeIter) Execute() { } var val glib.GValue Ui.ListStore.GetValue(iter.TreeIter, 2, &val) - cmd := SplitCommandline(val.GetString()) + + cmdline := val.GetString() + if cmdline == "" { + return + } + + cmd := SplitCommandline(cmdline) exec.Command(cmd[0], cmd[1:]...).Start() MakeHistRecord(HistRecord{ Name: iter.Name(), @@ -256,6 +262,9 @@ type Category struct { func EnabledCategories() []Category { cats := []Category{} + if Config.EnabledCategories.Calc { + cats = append(cats, Category{"Calc", PerformCalc}) + } if Config.EnabledCategories.History { cats = append(cats, Category{"History", SearchHistEntries}) }