class Tml::RulesEngine::Evaluator

Attributes

env[R]
vars[R]

Public Class Methods

new() click to toggle source
# File lib/tml/rules_engine/evaluator.rb, line 39
def initialize
  @vars = {}
  @env = {
      # McCarthy's Elementary S-functions and Predicates
      'label'   => lambda { |l, r|      @vars[l] = r },
      'quote'   => lambda { |expr|      expr },
      'car'     => lambda { |list|      list[1] },
      'cdr'     => lambda { |list|      list.drop(1) },
      'cons'    => lambda { |e, cell|   [e] + cell },
      'eq'      => lambda { |l, r|      l == r },
      'atom'    => lambda { |expr|      [Symbol, String, Fixnum, Float].include?(expr.class) },
      'cond'    => lambda { |c, t, f|   evaluate(c) ? evaluate(t) : evaluate(f) },

      # Tml Extensions
      '='       => lambda { |l, r|      l == r },                                             # ['=', 1, 2]
      '!='      => lambda { |l, r|      l != r },                                             # ['!=', 1, 2]
      '<'       => lambda { |l, r|      l < r },                                              # ['<', 1, 2]
      '>'       => lambda { |l, r|      l > r },                                              # ['>', 1, 2]
      '+'       => lambda { |l, r|      l + r },                                              # ['+', 1, 2]
      '-'       => lambda { |l, r|      l - r },                                              # ['-', 1, 2]
      '*'       => lambda { |l, r|      l * r },                                              # ['*', 1, 2]
      '%'       => lambda { |l, r|      l % r },                                              # ['%', 14, 10]
      'mod'     => lambda { |l, r|      l % r },                                              # ['mod', '@n', 10]
      '/'       => lambda { |l, r|      (l * 1.0) / r },                                      # ['/', 1, 2]
      '!'       => lambda { |expr|      not expr },                                           # ['!', ['true']]
      'not'     => lambda { |val|       not val },                                            # ['not', ['true']]
      '&&'      => lambda { |*expr|     expr.all?{|e| evaluate(e)} },                         # ['&&', [], [], ...]
      'and'     => lambda { |*expr|     expr.all?{|e| evaluate(e)} },                         # ['and', [], [], ...]
      '||'      => lambda { |*expr|     expr.any?{|e| evaluate(e)} },                         # ['||', [], [], ...]
      'or'      => lambda { |*expr|     expr.any?{|e| evaluate(e)} },                         # ['or', [], [], ...]
      'if'      => lambda { |c, t, f|   evaluate(c) ? evaluate(t) : evaluate(f) },            # ['if', 'cond', 'true', 'false']
      'let'     => lambda { |l, r|      @vars[l] = r },                                       # ['let', 'n', 5]
      'true'    => lambda { true },                                                           # ['true']
      'false'   => lambda { false },                                                          # ['false']

      'date'    => lambda { |date|      Date.strptime(date, '%Y-%m-%d') },                    # ['date', '2010-01-01']
      'today'   => lambda { Time.now.to_date },                                               # ['today']
      'time'    => lambda { |expr|      Time.strptime(expr, '%Y-%m-%d %H:%M:%S') },           # ['time', '2010-01-01 10:10:05']
      'now'     => lambda { Time.now },                                                       # ['now']

      'append'  => lambda { |l, r|      r.to_s + l.to_s },                                    # ['append', 'world', 'hello ']
      'prepend' => lambda { |l, r|      l.to_s + r.to_s },                                    # ['prepend', 'hello  ', 'world']
      'match'   => lambda { |search, subject|                                                 # ['match', /a/, 'abc']
        search = regexp_from_string(search)
        not search.match(subject).nil?
      },
      'in'      => lambda { |values, search|                                                  # ['in', '1,2,3,5..10,20..24', '@n']
        search = search.to_s.strip
        values.split(',').each do |e|
          if e.index('..')
            bounds = e.strip.split('..')
            return true if (bounds.first.strip..bounds.last.strip).include?(search)
          end
          return true if e.strip == search
        end
        false
      },
      'within'  => lambda { |values, search|                                                 # ['within', '0..3', '@n']
        bounds = values.split('..').map{|d| Integer(d)}
        (bounds.first..bounds.last).include?(search)
      },
      'replace' => lambda { |search, replace, subject|                                       # ['replace', '/^a/', 'v', 'abc']
                                                                                             # handle regular expression
        if /\/i$/.match(search)
          replace = replace.gsub(/\$(\d+)/, '\\\\\1') # for compatibility with Perl notation
        end
        search = regexp_from_string(search)
        subject.gsub(search, replace)
      },
      'count'   => lambda { |list|                                                          # ['count', '@genders']
        (list.is_a?(String) ? vars[list] : list).count
      },
      'all'     => lambda { |list, value|                                                   # ['all', '@genders', 'male']
        list = (list.is_a?(String) ? vars[list] : list)
        list.is_a?(Array) ? list.all?{|e| e == value} : false
      },
      'any'     => lambda { |list, value|                                                   # ['any', '@genders', 'female']
        list = (list.is_a?(String) ? vars[list] : list)
        list.is_a?(Array) ? list.any?{|e| e == value} : false
      },
  }
end

Public Instance Methods

apply(fn, args) click to toggle source
# File lib/tml/rules_engine/evaluator.rb, line 140
def apply(fn, args)
  raise "undefined symbols #{fn}" unless @env.keys.include?(fn)
  @env[fn].call(*args)
end
evaluate(expr) click to toggle source
# File lib/tml/rules_engine/evaluator.rb, line 145
def evaluate(expr)
  if @env['atom'].call(expr)
    return @vars[expr] if expr.is_a?(String) and @vars[expr]
    return expr
  end

  fn = expr[0]
  args = expr.drop(1)

  unless %w(quote car cdr cond if && || and or true false let count all any).member?(fn)
    args = args.map { |a| self.evaluate(a) }
  end
  apply(fn, args)
end
regexp_from_string(str) click to toggle source
# File lib/tml/rules_engine/evaluator.rb, line 122
def regexp_from_string(str)
  return Regexp.new(/#{str}/) unless /^\//.match(str)

  str = str.gsub(/^\//, '')

  if /\/i$/.match(str)
    str = str.gsub(/\/i$/, '')
    return Regexp.new(/#{str}/i)
  end

  str = str.gsub(/\/$/, '')
  Regexp.new(/#{str}/)
end
reset!() click to toggle source
# File lib/tml/rules_engine/evaluator.rb, line 136
def reset!
  @vars = {}
end