summaryrefslogtreecommitdiff
path: root/deployment_scripts/puppet/files/embedded/lib/ruby/gems/2.3.0/gems/dentaku-2.0.9/lib/dentaku/bulk_expression_solver.rb
blob: aa8c7e4f3d5a9c1c7d6946410fc42683cdfc4258 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
require 'dentaku/calculator'
require 'dentaku/dependency_resolver'
require 'dentaku/exceptions'
require 'dentaku/parser'
require 'dentaku/tokenizer'

module Dentaku
  class BulkExpressionSolver
    def initialize(expression_hash, calculator)
      self.expression_hash = expression_hash
      self.calculator = calculator
    end

    def solve!
      solve(&raise_exception_handler)
    end

    def solve(&block)
      error_handler = block || return_undefined_handler
      results = load_results(&error_handler)

      expression_hash.each_with_object({}) do |(k, _), r|
        r[k] = results[k.to_s]
      end
    end

    private

    def self.dependency_cache
      @dep_cache ||= {}
    end

    attr_accessor :expression_hash, :calculator

    def return_undefined_handler
      ->(*) { :undefined }
    end

    def raise_exception_handler
      ->(ex) { raise ex }
    end

    def load_results(&block)
      variables_in_resolve_order.each_with_object({}) do |var_name, r|
        begin
          value_from_memory = calculator.memory[var_name]

          if value_from_memory.nil? &&
              expressions[var_name].nil? &&
              !calculator.memory.has_key?(var_name)
            next
          end

          value = value_from_memory ||
            evaluate!(expressions[var_name], expressions.merge(r))

          r[var_name] = value
        rescue Dentaku::UnboundVariableError, ZeroDivisionError => ex
          ex.recipient_variable = var_name
          r[var_name] = block.call(ex)
        end
      end
    end

    def expressions
      @expressions ||= Hash[expression_hash.map { |k,v| [k.to_s, v] }]
    end

    def expression_dependencies
      Hash[expressions.map { |var, expr| [var, calculator.dependencies(expr)] }].tap do |d|
        d.values.each do |deps|
          unresolved = deps.reject { |ud| d.has_key?(ud) }
          unresolved.each { |u| add_dependencies(d, u) }
        end
      end
    end

    def add_dependencies(current_dependencies, variable)
      node = calculator.memory[variable]
      if node.respond_to?(:dependencies)
        current_dependencies[variable] = node.dependencies
        node.dependencies.each { |d| add_dependencies(current_dependencies, d) }
      end
    end

    def variables_in_resolve_order
      cache_key = expressions.keys.map(&:to_s).sort.join("|")
      @ordered_deps ||= self.class.dependency_cache.fetch(cache_key) {
        DependencyResolver.find_resolve_order(expression_dependencies).tap do |d|
          self.class.dependency_cache[cache_key] = d if Dentaku.cache_dependency_order?
        end
      }
    end

    def evaluate!(expression, results)
      calculator.evaluate!(expression, results)
    end
  end
end