Interrotron
A simple non-turing complete lisp meant to be embedded in apps as a rules engine. It is intentionally designed to limit the harm evaluated code can do (in contrast to a straight ruby 'eval') and is constrained to:
- Be totally sandboxed by default
- Always finish executing (no infinite loops)
- Let you easily add variables and functions (simply pass in a hash defining them)
- Be a small, single file
Installation
Either add the interrotron
gem, or just copy and paste interrotron.rb
Usage
# Injecting a variable and evaluating a function is easy!
Interrotron.run('(> 51 custom_var)', :custom_var => 10)
# => true
#You can inject functions just as easily
Interrotron.run("(doubler (+ 2 2))", :doubler => proc {|a| a*2 })
# => 8
# You can even pre-compile scripts for speed / re-use!
tron = Interrotron.new(:is_valid => proc {|a| a.reverse == 'oof'})
compiled = tron.compile("(is_valid my_param)")
compiled.call(:my_param => 'foo')
# => true
compiled.call(:my_param => 'bar')
#=> false
# Since interrotron is meant for business rules, it handles dates as a
# native type as instances of ruby's DateTime class. You can use literals
# for that like so:
Interrotron.run('(> #t{2010-09-04} start_date)', start_date: DateTime.parse('2012-12-12').to_time)
# => true
Interrotron.run('(> (now) (ago (hours 12)))')
# => true
# You can, of course, create arbitarily complex exprs
Interrotron.run("(if false
(+ 4 -3)
(- 10 (+ 2 (+ 1 1))))")
# => 6
# Additionally, it is possible to constrain execution to a maximum number of
# operations by passing in a third argument
Interrotron.run("(str (+ 1 2) (+ 3 4) (+ 5 7))", {}, 3)
# => raises Interrotron::OpsThresholdError since 4 operations were executed
The following functions and variables are built in to Interrotron (and more are on the way!):
(if pred then else) ; it's an if / else statement
(cond pred1 clause1 pred2 clause2 true fallbackclause) ; like a case statement
(and e1, e2, ...) ; logical and, returns last arg if true
(or e1, e2, ...) ; logical or, returns first true arg
(not expr) ; negates
(! expr) ; negates
(identity expr) ; returns its argument
(str s1, s2, ...) ; converts its args to strings, also concatenates them
(floor expr) ; equiv to num.floor
(ceil expr) ; equiv to num.ceil
(round expr) ; equiv to num.round
(int expr) ; int conversion
(float expr) ; float conversion
(rand) ; returns a random float between 0 and 1
(upcase str) ; uppercases a string
(downcase) ; lowercases a string
(array e1, e2, ...) ; creates an array
(max arr) ; returns the largest element of an array
(min arr) ; returns the smallest element of an array
(length arr) ; get the length of an array
(first arr) ; get arr head
(last arr) ; get arr tail
(nth pos arr) ; get array at index
(member? val arr) ; check if the array has a member with value 'val'
(now) ; returns the current DateTime
(seconds n); n, for completeness
(minutes n); n * secs_in_a_minute
(hours n); n * secs_in_a_hour
(months n) ; n * secs_in_a_month
(ago n) ; yields a time in seconds from now
(from-now n) ; yields a time in seconds from now
(time 'str') ; parses a string to a Time
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request