philiprehberger-safe_exec

Tests Gem Version Last updated

Sandboxed expression evaluator with whitelisted operations

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-safe_exec"

Or install directly:

gem install philiprehberger-safe_exec

Usage

require "philiprehberger/safe_exec"

Philiprehberger::SafeExec.evaluate('2 + 3 * 4')           # => 14
Philiprehberger::SafeExec.evaluate('(2 + 3) * 4')         # => 20
Philiprehberger::SafeExec.evaluate('price * 1.08', { price: 100 })  # => 108.0

Arithmetic

Philiprehberger::SafeExec.evaluate('10 + 5')   # => 15
Philiprehberger::SafeExec.evaluate('10 - 5')   # => 5
Philiprehberger::SafeExec.evaluate('10 * 5')   # => 50
Philiprehberger::SafeExec.evaluate('10 / 3')   # => 3 (integer division)
Philiprehberger::SafeExec.evaluate('10.0 / 3') # => 3.333...
Philiprehberger::SafeExec.evaluate('10 % 3')   # => 1
Philiprehberger::SafeExec.evaluate('2 ** 10')  # => 1024

Comparisons and Booleans

Philiprehberger::SafeExec.evaluate('5 > 3')              # => true
Philiprehberger::SafeExec.evaluate('5 == 5')              # => true
Philiprehberger::SafeExec.evaluate('true && false')       # => false
Philiprehberger::SafeExec.evaluate('!false || true')      # => true
Philiprehberger::SafeExec.evaluate('age >= 18 && age < 65', { age: 25 })  # => true

String Operations

Philiprehberger::SafeExec.evaluate("'hello' + ' ' + 'world'")  # => "hello world"
Philiprehberger::SafeExec.evaluate("name == 'Alice'", { name: 'Alice' })  # => true

Ternary Operator

Philiprehberger::SafeExec.evaluate('true ? 1 : 2')                       # => 1
Philiprehberger::SafeExec.evaluate('5 > 3 ? 10 : 20')                    # => 10
Philiprehberger::SafeExec.evaluate("age >= 18 ? 'adult' : 'minor'", { age: 25 })  # => "adult"

Built-in Functions

Philiprehberger::SafeExec.evaluate('min(3, 7)')          # => 3
Philiprehberger::SafeExec.evaluate('max(3, 7)')          # => 7
Philiprehberger::SafeExec.evaluate('abs(-5)')             # => 5
Philiprehberger::SafeExec.evaluate("length('hello')")     # => 5
Philiprehberger::SafeExec.evaluate('round(3.14159, 2)')   # => 3.14
Philiprehberger::SafeExec.evaluate('sqrt(16)')            # => 4.0
Philiprehberger::SafeExec.evaluate('ceil(3.2)')           # => 4
Philiprehberger::SafeExec.evaluate('floor(3.9)')          # => 3
Philiprehberger::SafeExec.evaluate('pow(2, 10)')          # => 1024

String Functions

Philiprehberger::SafeExec.evaluate("upcase('hello')")      # => "HELLO"
Philiprehberger::SafeExec.evaluate("downcase('HELLO')")    # => "hello"
Philiprehberger::SafeExec.evaluate("trim('  hello  ')")    # => "hello"

Hash and Array Access

context = { items: [10, 20, 30], user: { 'name' => 'Alice', 'role' => 'admin' } }

Philiprehberger::SafeExec.evaluate('items[0]', context)        # => 10
Philiprehberger::SafeExec.evaluate("user['name']", context)    # => "Alice"
Philiprehberger::SafeExec.evaluate('user.role', context)       # => "admin"

Compiled Expressions

Pre-parse an expression once and evaluate it many times against different contexts — useful for rules engines and calculation pipelines.

formula = Philiprehberger::SafeExec.compile('price * (1 + tax_rate) - discount')

formula.evaluate(price: 100, tax_rate: 0.08, discount: 5)   # => 103.0
formula.evaluate(price: 50,  tax_rate: 0.10, discount: 0)   # => 55.0

formula.source  # => "price * (1 + tax_rate) - discount"

Syntax errors raise immediately at compile time, before any evaluation.

Timeout

Philiprehberger::SafeExec.evaluate('1 + 1', {}, timeout: 2)
# Raises Philiprehberger::SafeExec::TimeoutError if evaluation exceeds 2 seconds

API

Method Description
SafeExec.evaluate(expr, context = {}, timeout: 5) Evaluate a sandboxed expression string with optional context variables and timeout (seconds)
SafeExec.compile(expr) Pre-parse an expression and return a reusable Compiled instance
Compiled#evaluate(context = {}, timeout: 5) Evaluate the compiled expression against a context
Compiled#source The original expression source string
SafeExec::Error Base error class for parse and evaluation failures
SafeExec::TimeoutError Raised when evaluation exceeds the timeout
SafeExec::DEFAULT_TIMEOUT Default timeout in seconds (5)
SafeExec::VERSION Gem version string
Tokenizer.tokenize(input) Tokenize an expression string into an array of Token structs
Tokenizer::Token Struct with type (Symbol) and value (String) fields
Tokenizer::TOKEN_PATTERNS Ordered array of [type, regex] pairs used by the tokenizer
Parser.new(tokens) Create a parser from an array of Token structs
Parser#parse Parse the token stream into an AST hash
Evaluator.new(context = {}) Create an evaluator with a context hash (keys are normalized to strings)
Evaluator#evaluate(node) Evaluate an AST node and return the result

Supported Operations

Category Operations
Arithmetic +, -, *, /, %, **
Comparison ==, !=, >, <, >=, <=
Boolean &&, `\
Ternary condition ? value_if_true : value_if_false
String concatenation via +, comparison
Functions min(a, b), max(a, b), abs(n), length(str_or_arr), round(n, precision), sqrt(n), ceil(n), floor(n), pow(base, exp), upcase(str), downcase(str), trim(str)
Access array[index], hash['key'], hash.key
Literals integers, floats, strings, booleans, nil
Grouping parentheses ()

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT