Class: Dentaku::Calculator

Inherits:
Object
  • Object
show all
Includes:
StringCasing
Defined in:
lib/dentaku/calculator.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from StringCasing

#standardize_case

Constructor Details

#initialize(options = {}) ⇒ Calculator

Returns a new instance of Calculator.



15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/dentaku/calculator.rb', line 15

def initialize(options = {})
  clear
  @tokenizer = Tokenizer.new
  @case_sensitive = options.delete(:case_sensitive)
  @aliases = options.delete(:aliases) || Dentaku.aliases
  @nested_data_support = options.fetch(:nested_data_support, true)
  options.delete(:nested_data_support)
  @raw_date_literals = options.fetch(:raw_date_literals, true)
  options.delete(:raw_date_literals)
  @ast_cache = options
  @disable_ast_cache = false
  @function_registry = Dentaku::AST::FunctionRegistry.new
end

Instance Attribute Details

#aliasesObject (readonly)

Returns the value of attribute aliases.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def aliases
  @aliases
end

#ast_cacheObject (readonly)

Returns the value of attribute ast_cache.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def ast_cache
  @ast_cache
end

#case_sensitiveObject (readonly)

Returns the value of attribute case_sensitive.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def case_sensitive
  @case_sensitive
end

#memoryObject (readonly)

Returns the value of attribute memory.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def memory
  @memory
end

#nested_data_supportObject (readonly)

Returns the value of attribute nested_data_support.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def nested_data_support
  @nested_data_support
end

#raw_date_literalsObject (readonly)

Returns the value of attribute raw_date_literals.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def raw_date_literals
  @raw_date_literals
end

#resultObject (readonly)

Returns the value of attribute result.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def result
  @result
end

#tokenizerObject (readonly)

Returns the value of attribute tokenizer.



12
13
14
# File 'lib/dentaku/calculator.rb', line 12

def tokenizer
  @tokenizer
end

Class Method Details

.add_function(name, type, body, callback = nil) ⇒ Object



29
30
31
# File 'lib/dentaku/calculator.rb', line 29

def self.add_function(name, type, body, callback = nil)
  Dentaku::AST::FunctionRegistry.default.register(name, type, body, callback)
end

.add_functions(functions) ⇒ Object



33
34
35
# File 'lib/dentaku/calculator.rb', line 33

def self.add_functions(functions)
  functions.each { |(name, type, body, callback)| add_function(name, type, body, callback) }
end

Instance Method Details

#add_function(name, type, body, callback = nil) ⇒ Object



37
38
39
40
# File 'lib/dentaku/calculator.rb', line 37

def add_function(name, type, body, callback = nil)
  @function_registry.register(name, type, body, callback)
  self
end

#add_functions(functions) ⇒ Object



42
43
44
45
# File 'lib/dentaku/calculator.rb', line 42

def add_functions(functions)
  functions.each { |(name, type, body, callback)| add_function(name, type, body, callback) }
  self
end

#ast(expression) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/dentaku/calculator.rb', line 109

def ast(expression)
  return expression if expression.is_a?(AST::Node)
  return expression.map { |e| ast(e) } if expression.is_a? Array

  @ast_cache.fetch(expression) {
    options = {
      aliases: aliases,
      case_sensitive: case_sensitive,
      function_registry: @function_registry,
      raw_date_literals: raw_date_literals
    }

    tokens = tokenizer.tokenize(expression, options)
    Parser.new(tokens, options).parse.tap do |node|
      @ast_cache[expression] = node if cache_ast?
    end
  }
end

#cache_ast?Boolean

Returns:

  • (Boolean)


188
189
190
# File 'lib/dentaku/calculator.rb', line 188

def cache_ast?
  Dentaku.cache_ast? && !@disable_ast_cache
end

#clearObject



180
181
182
# File 'lib/dentaku/calculator.rb', line 180

def clear
  @memory = {}
end

#clear_cache(pattern = :all) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/dentaku/calculator.rb', line 132

def clear_cache(pattern = :all)
  case pattern
  when :all
    @ast_cache = {}
  when String
    @ast_cache.delete(pattern)
  when Regexp
    @ast_cache.delete_if { |k, _| k =~ pattern }
  else
    raise ::ArgumentError
  end
end

#dependencies(expression, context = {}) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/dentaku/calculator.rb', line 96

def dependencies(expression, context = {})
  test_context = context.nil? ? {} : store(context) { memory }

  case expression
  when Dentaku::AST::Node
    expression.dependencies(test_context)
  when Array
    expression.flat_map { |e| dependencies(e, context) }
  else
    ast(expression).dependencies(test_context)
  end
end

#disable_cacheObject



47
48
49
50
51
52
# File 'lib/dentaku/calculator.rb', line 47

def disable_cache
  @disable_ast_cache = true
  yield(self) if block_given?
ensure
  @disable_ast_cache = false
end

#empty?Boolean

Returns:

  • (Boolean)


184
185
186
# File 'lib/dentaku/calculator.rb', line 184

def empty?
  memory.empty?
end

#evaluate(expression, data = {}, &block) ⇒ Object



54
55
56
57
58
59
60
61
# File 'lib/dentaku/calculator.rb', line 54

def evaluate(expression, data = {}, &block)
  context = evaluation_context(data, :permissive)
  return evaluate_array(expression, context, &block) if expression.is_a?(Array)

  evaluate!(expression, context)
rescue Dentaku::Error, Dentaku::ArgumentError, Dentaku::ZeroDivisionError => ex
  block.call(expression, ex) if block_given?
end

#evaluate!(expression, data = {}, &block) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/dentaku/calculator.rb', line 67

def evaluate!(expression, data = {}, &block)
  context = evaluation_context(data, :strict)
  return evaluate_array!(expression, context, &block) if expression.is_a? Array

  store(context) do
    node = ast(expression)
    unbound = node.dependencies(memory)

    unless unbound.empty?
      raise UnboundVariableError.new(unbound),
            "no value provided for variables: #{unbound.uniq.join(', ')}"
    end

    node.value(memory)
  end
end

#evaluation_context(data, evaluation_mode) ⇒ Object



145
146
147
# File 'lib/dentaku/calculator.rb', line 145

def evaluation_context(data, evaluation_mode)
  data.key?(:__evaluation_mode) ? data : data.merge(__evaluation_mode: evaluation_mode)
end

#load_cache(ast_cache) ⇒ Object



128
129
130
# File 'lib/dentaku/calculator.rb', line 128

def load_cache(ast_cache)
  @ast_cache = ast_cache
end

#solve(expression_hash, &block) ⇒ Object



92
93
94
# File 'lib/dentaku/calculator.rb', line 92

def solve(expression_hash, &block)
  BulkExpressionSolver.new(expression_hash, self).solve(&block)
end

#solve!(expression_hash) ⇒ Object



88
89
90
# File 'lib/dentaku/calculator.rb', line 88

def solve!(expression_hash)
  BulkExpressionSolver.new(expression_hash, self).solve!
end

#store(key_or_hash, value = nil) ⇒ Object Also known as: bind



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/dentaku/calculator.rb', line 149

def store(key_or_hash, value = nil)
  restore = Hash[memory]

  if value.nil?
    key_or_hash = FlatHash.from_hash_with_intermediates(key_or_hash) if nested_data_support
    key_or_hash.each do |key, val|
      memory[standardize_case(key.to_s)] = val
    end
  else
    memory[standardize_case(key_or_hash.to_s)] = value
  end

  if block_given?
    begin
      result = yield
      @memory = restore
      return result
    rescue => e
      @memory = restore
      raise e
    end
  end

  self
end

#store_formula(key, formula) ⇒ Object



176
177
178
# File 'lib/dentaku/calculator.rb', line 176

def store_formula(key, formula)
  store(key, ast(formula))
end