package calculator import ( "context" "fmt" "math" "strconv" "strings" "unicode" "git.yeij.top/AskaEth/Cyrene-Plugins/sdk" ) type CalculatorPlugin struct { sdk.BasePlugin } func (p *CalculatorPlugin) Metadata() sdk.PluginMetadata { return sdk.PluginMetadata{ Name: "calculator", DisplayName: "Calculator", Version: "1.0.0", Description: "Safe mathematical expression evaluation with custom parser", Category: "utility", Author: sdk.PluginAuthor{Name: "Cyrene Team"}, } } func (p *CalculatorPlugin) Tools() []sdk.Tool { return []sdk.Tool{&CalculatorTool{}} } type CalculatorTool struct { sdk.BaseTool } func (t *CalculatorTool) Definition() sdk.ToolDefinition { return sdk.ToolDefinition{ ID: "calculator", Name: "calculator", DisplayName: "Calculator", Description: "Execute mathematical calculations. Supports arithmetic, trig, logs, powers.", Category: "utility", Complexity: sdk.ComplexitySimple, Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{"expression": map[string]interface{}{"type": "string"}}, "required": []string{"expression"}, }, } } func (t *CalculatorTool) Validate(args map[string]interface{}) error { if _, ok := args["expression"]; !ok { return fmt.Errorf("missing required parameter: expression") } return nil } func (t *CalculatorTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) { expr, _ := args["expression"].(string) result, err := evalExpression(expr) if err != nil { return &sdk.ToolResult{ToolName: "calculator", Success: false, Error: err.Error()}, nil } return &sdk.ToolResult{ToolName: "calculator", Success: true, Output: fmt.Sprintf("%v", result)}, nil } // Expression parser supporting +, -, *, /, %, ^, functions, constants. type exprParser struct { s string pos int } func evalExpression(s string) (float64, error) { p := &exprParser{s: strings.TrimSpace(s)} result, err := p.parseAddSub() if err != nil { return 0, err } if p.pos < len(p.s) { return 0, fmt.Errorf("unexpected character at position %d: %c", p.pos, p.s[p.pos]) } return result, nil } func (p *exprParser) peek() byte { if p.pos < len(p.s) { return p.s[p.pos] } return 0 } func (p *exprParser) skipSpaces() { for p.pos < len(p.s) && p.s[p.pos] == ' ' { p.pos++ } } func (p *exprParser) parseAddSub() (float64, error) { left, err := p.parseMulDiv() if err != nil { return 0, err } for { p.skipSpaces() op := p.peek() if op != '+' && op != '-' { break } p.pos++ right, err := p.parseMulDiv() if err != nil { return 0, err } if op == '+' { left += right } else { left -= right } } return left, nil } func (p *exprParser) parseMulDiv() (float64, error) { left, err := p.parsePower() if err != nil { return 0, err } for { p.skipSpaces() op := p.peek() if op != '*' && op != '/' && op != '%' { break } p.pos++ right, err := p.parsePower() if err != nil { return 0, err } switch op { case '*': left *= right case '/': if right == 0 { return 0, fmt.Errorf("division by zero") } left /= right case '%': left = math.Mod(left, right) } } return left, nil } func (p *exprParser) parsePower() (float64, error) { left, err := p.parseUnary() if err != nil { return 0, err } p.skipSpaces() if p.peek() == '^' { p.pos++ right, err := p.parseUnary() if err != nil { return 0, err } return math.Pow(left, right), nil } return left, nil } func (p *exprParser) parseUnary() (float64, error) { p.skipSpaces() if p.peek() == '-' { p.pos++ val, err := p.parseAtom() if err != nil { return 0, err } return -val, nil } if p.peek() == '+' { p.pos++ } return p.parseAtom() } func (p *exprParser) parseAtom() (float64, error) { p.skipSpaces() if p.peek() == '(' { p.pos++ result, err := p.parseAddSub() if err != nil { return 0, err } p.skipSpaces() if p.peek() != ')' { return 0, fmt.Errorf("missing closing parenthesis") } p.pos++ return result, nil } if p.peek() == 0 { return 0, fmt.Errorf("unexpected end of expression") } if unicode.IsDigit(rune(p.peek())) || p.peek() == '.' { return p.parseNumber() } return p.parseFuncOrConst() } func (p *exprParser) parseNumber() (float64, error) { start := p.pos for p.pos < len(p.s) && (unicode.IsDigit(rune(p.s[p.pos])) || p.s[p.pos] == '.') { p.pos++ } return strconv.ParseFloat(p.s[start:p.pos], 64) } func (p *exprParser) parseFuncOrConst() (float64, error) { start := p.pos for p.pos < len(p.s) && (unicode.IsLetter(rune(p.s[p.pos])) || p.s[p.pos] == '_') { p.pos++ } name := p.s[start:p.pos] p.skipSpaces() switch name { case "pi": return math.Pi, nil case "e": return math.E, nil case "sqrt", "sin", "cos", "tan", "abs", "floor", "ceil", "round", "log", "ln": if p.peek() != '(' { return 0, fmt.Errorf("expected '(' after function %s", name) } p.pos++ arg, err := p.parseAddSub() if err != nil { return 0, err } if p.peek() != ')' { return 0, fmt.Errorf("missing ')' after function argument") } p.pos++ return applyFunc(name, arg) default: return 0, fmt.Errorf("unknown function or constant: %s", name) } } func applyFunc(name string, x float64) (float64, error) { switch name { case "sqrt": if x < 0 { return 0, fmt.Errorf("square root of negative number") } return math.Sqrt(x), nil case "sin": return math.Sin(x), nil case "cos": return math.Cos(x), nil case "tan": return math.Tan(x), nil case "abs": return math.Abs(x), nil case "floor": return math.Floor(x), nil case "ceil": return math.Ceil(x), nil case "round": return math.Round(x), nil case "log": if x <= 0 { return 0, fmt.Errorf("log of non-positive number") } return math.Log10(x), nil case "ln": if x <= 0 { return 0, fmt.Errorf("ln of non-positive number") } return math.Log(x), nil } return 0, fmt.Errorf("unknown function: %s", name) }