Class: Odin::Validation::InvariantEvaluator

Inherits:
Object
  • Object
show all
Defined in:
lib/odin/validation/invariant_evaluator.rb

Overview

Recursive-descent evaluator over the invariant grammar:

expression     = logic_or
logic_or       = logic_and , { "||" , logic_and }
logic_and      = equality , { "&&" , equality }
equality       = comparison , { ( "==" | "!=" | "=" ) , comparison }
comparison     = additive , { ( ">" | "<" | ">=" | "<=" ) , additive }
additive       = multiplicative , { ( "+" | "-" ) , multiplicative }
multiplicative = unary , { ( "*" | "/" | "%" ) , unary }
unary          = [ "!" ] , primary
primary        = path | number | string | "(" , expression , ")"

An expression is parsed to an AST once and cached by its source string; each document validation evaluates the cached AST against that document’s values.

Defined Under Namespace

Classes: Additive, Bool, Compare, Equality, Field, Logic, Multiplicative, Not, Num, Parser, Result, Str, Token

Constant Summary collapse

EPSILON =
1e-9
MULTI_CHAR_OPS =
%w[== != >= <= && ||].freeze
SINGLE_CHAR_OPS =
Set.new(%w[+ - * / % > < = !]).freeze
BOOLEAN_LITERALS =
{ "true" => true, "false" => false }.freeze
ParseError =
Class.new(StandardError)
AST_CACHE =

Compiled, document-independent invariant ASTs keyed by source string.

{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(resolver) ⇒ InvariantEvaluator

resolver: a callable taking a field name, returning an OdinValue or nil.



51
52
53
# File 'lib/odin/validation/invariant_evaluator.rb', line 51

def initialize(resolver)
  @resolve = resolver
end

Class Method Details

.parse(expr) ⇒ Object

Return the cached AST for an expression, parsing on first use.



75
76
77
78
79
# File 'lib/odin/validation/invariant_evaluator.rb', line 75

def self.parse(expr)
  AST_CACHE.fetch(expr) do
    AST_CACHE[expr] = Parser.new(expr).parse
  end
end

Instance Method Details

#evaluate(expr) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/odin/validation/invariant_evaluator.rb', line 55

def evaluate(expr)
  ast = self.class.parse(expr)
  @absent_operand = false
  @null_operand = false

  final = eval_node(ast)

  result =
    if @null_operand
      false
    elsif @absent_operand
      nil
    else
      to_bool(final)
    end

  Result.new(result, @null_operand)
end