Module: RubyLLM::Toolbox::SafeMath

Defined in:
lib/ruby_llm/toolbox/safe_math.rb

Overview

A safe arithmetic evaluator. Parses and evaluates a math expression with a hand-written recursive-descent parser — it never calls eval, so a malicious expression can’t execute Ruby. Supports + - * / % **, unary minus, parentheses, a small set of functions, and the constants pi and e.

Defined Under Namespace

Classes: Error, Parser

Constant Summary collapse

FUNCTIONS =
{
  "sqrt" => ->(x) { Math.sqrt(x) },
  "abs" => ->(x) { x.abs },
  "sin" => ->(x) { Math.sin(x) },
  "cos" => ->(x) { Math.cos(x) },
  "tan" => ->(x) { Math.tan(x) },
  "ln" => ->(x) { Math.log(x) },
  "log10" => ->(x) { Math.log10(x) },
  "exp" => ->(x) { Math.exp(x) },
  "floor" => ->(x) { x.floor },
  "ceil" => ->(x) { x.ceil },
  "round" => ->(x) { x.round }
}.freeze
CONSTANTS =
{ "pi" => Math::PI, "e" => Math::E }.freeze

Class Method Summary collapse

Class Method Details

.evaluate(expression) ⇒ Object

Raises:



30
31
32
33
34
35
36
37
# File 'lib/ruby_llm/toolbox/safe_math.rb', line 30

def evaluate(expression)
  tokens = tokenize(expression.to_s)
  parser = Parser.new(tokens)
  value = parser.expression
  raise Error, "unexpected token: #{parser.current.inspect}" unless parser.done?

  value
end

.tokenize(str) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/ruby_llm/toolbox/safe_math.rb', line 39

def tokenize(str)
  tokens = []
  scanner = str.dup
  until scanner.empty?
    case scanner
    when /\A\s+/ then scanner = scanner[Regexp.last_match(0).length..]
    when /\A(\d+\.\d+|\.\d+|\d+)([eE][+-]?\d+)?/
      tokens << [:num, Regexp.last_match(0).to_f]
      scanner = scanner[Regexp.last_match(0).length..]
    when /\A[A-Za-z_]\w*/
      tokens << [:ident, Regexp.last_match(0)]
      scanner = scanner[Regexp.last_match(0).length..]
    when /\A\*\*/ then tokens << [:op, "**"]; scanner = scanner[2..]
    when %r{\A[-+*/%(),]} then tokens << [:op, Regexp.last_match(0)]; scanner = scanner[1..]
    else raise Error, "unexpected character: #{scanner[0].inspect}"
    end
  end
  tokens
end