Class: Dentaku::Calculator
- Inherits:
-
Object
- Object
- Dentaku::Calculator
- Includes:
- StringCasing
- Defined in:
- lib/dentaku/calculator.rb
Instance Attribute Summary collapse
-
#ast_cache ⇒ Object
readonly
Returns the value of attribute ast_cache.
-
#case_sensitive ⇒ Object
readonly
Returns the value of attribute case_sensitive.
-
#memory ⇒ Object
readonly
Returns the value of attribute memory.
-
#nested_data_support ⇒ Object
readonly
Returns the value of attribute nested_data_support.
-
#raw_date_literals ⇒ Object
readonly
Returns the value of attribute raw_date_literals.
-
#result ⇒ Object
readonly
Returns the value of attribute result.
-
#tokenizer ⇒ Object
readonly
Returns the value of attribute tokenizer.
Class Method Summary collapse
- .add_function(name, type, body, callback = nil, volatile: false) ⇒ Object
- .add_functions(functions) ⇒ Object
Instance Method Summary collapse
- #add_function(name, type, body, callback = nil, volatile: false) ⇒ Object
- #add_functions(functions) ⇒ Object
-
#aliases ⇒ Object
explicitly configured aliases win; otherwise the module-level default is resolved lazily so it can be set after this calculator was created.
- #ast(expression) ⇒ Object
- #cache_ast? ⇒ Boolean
- #cache_dependency_order? ⇒ Boolean
- #clear ⇒ Object
- #clear_cache(pattern = :all) ⇒ Object
- #dependencies(expression, context = {}) ⇒ Object
- #disable_cache ⇒ Object
- #empty? ⇒ Boolean
- #evaluate(expression, data = {}, &block) ⇒ Object
- #evaluate!(expression, data = {}, &block) ⇒ Object
- #evaluation_context(data, evaluation_mode) ⇒ Object
-
#identifiers(expression) ⇒ Object
every identifier the expression could reference, regardless of branching: purely syntactic, ignores stored memory, and never evaluates guards or functions.
-
#initialize(case_sensitive: false, aliases: nil, nested_data_support: true, raw_date_literals: true, ast_cache: {}, cache_ast: nil, cache_dependency_order: nil) ⇒ Calculator
constructor
A new instance of Calculator.
- #load_cache(ast_cache) ⇒ Object
- #solve(expression_hash, &block) ⇒ Object
- #solve!(expression_hash) ⇒ Object
- #store(key_or_hash, value = nil) ⇒ Object (also: #bind)
- #store_formula(key, formula) ⇒ Object
Methods included from StringCasing
Constructor Details
#initialize(case_sensitive: false, aliases: nil, nested_data_support: true, raw_date_literals: true, ast_cache: {}, cache_ast: nil, cache_dependency_order: nil) ⇒ Calculator
Returns a new instance of Calculator.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/dentaku/calculator.rb', line 15 def initialize(case_sensitive: false, aliases: nil, nested_data_support: true, raw_date_literals: true, ast_cache: {}, cache_ast: nil, cache_dependency_order: nil) clear @tokenizer = Tokenizer.new @case_sensitive = case_sensitive @aliases = aliases @nested_data_support = nested_data_support @raw_date_literals = raw_date_literals @ast_cache = ast_cache @cache_ast = cache_ast @cache_dependency_order = cache_dependency_order @disable_ast_cache = false @function_registry = Dentaku::AST::FunctionRegistry.new end |
Instance Attribute Details
#ast_cache ⇒ Object (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_sensitive ⇒ Object (readonly)
Returns the value of attribute case_sensitive.
12 13 14 |
# File 'lib/dentaku/calculator.rb', line 12 def case_sensitive @case_sensitive end |
#memory ⇒ Object (readonly)
Returns the value of attribute memory.
12 13 14 |
# File 'lib/dentaku/calculator.rb', line 12 def memory @memory end |
#nested_data_support ⇒ Object (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_literals ⇒ Object (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 |
#result ⇒ Object (readonly)
Returns the value of attribute result.
12 13 14 |
# File 'lib/dentaku/calculator.rb', line 12 def result @result end |
#tokenizer ⇒ Object (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, volatile: false) ⇒ Object
37 38 39 |
# File 'lib/dentaku/calculator.rb', line 37 def self.add_function(name, type, body, callback = nil, volatile: false) Dentaku::AST::FunctionRegistry.default.register(name, type, body, callback, volatile: volatile) end |
.add_functions(functions) ⇒ Object
41 42 43 |
# File 'lib/dentaku/calculator.rb', line 41 def self.add_functions(functions) functions.each { |(name, type, body, callback, volatile)| add_function(name, type, body, callback, volatile: !!volatile) } end |
Instance Method Details
#add_function(name, type, body, callback = nil, volatile: false) ⇒ Object
45 46 47 48 |
# File 'lib/dentaku/calculator.rb', line 45 def add_function(name, type, body, callback = nil, volatile: false) @function_registry.register(name, type, body, callback, volatile: volatile) self end |
#add_functions(functions) ⇒ Object
50 51 52 53 |
# File 'lib/dentaku/calculator.rb', line 50 def add_functions(functions) functions.each { |(name, type, body, callback, volatile)| add_function(name, type, body, callback, volatile: !!volatile) } self end |
#aliases ⇒ Object
explicitly configured aliases win; otherwise the module-level default is resolved lazily so it can be set after this calculator was created
33 34 35 |
# File 'lib/dentaku/calculator.rb', line 33 def aliases @aliases || Dentaku.aliases end |
#ast(expression) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/dentaku/calculator.rb', line 131 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) { = { aliases: aliases, case_sensitive: case_sensitive, function_registry: @function_registry, raw_date_literals: raw_date_literals } tokens = tokenizer.tokenize(expression, ) Parser.new(tokens, ).parse.tap do |node| @ast_cache[expression] = node if cache_ast? end } end |
#cache_ast? ⇒ Boolean
210 211 212 213 214 |
# File 'lib/dentaku/calculator.rb', line 210 def cache_ast? return false if @disable_ast_cache @cache_ast.nil? ? Dentaku.cache_ast? : @cache_ast end |
#cache_dependency_order? ⇒ Boolean
216 217 218 |
# File 'lib/dentaku/calculator.rb', line 216 def cache_dependency_order? @cache_dependency_order.nil? ? Dentaku.cache_dependency_order? : @cache_dependency_order end |
#clear ⇒ Object
202 203 204 |
# File 'lib/dentaku/calculator.rb', line 202 def clear @memory = {} end |
#clear_cache(pattern = :all) ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/dentaku/calculator.rb', line 154 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
104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/dentaku/calculator.rb', line 104 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_cache ⇒ Object
55 56 57 58 59 60 |
# File 'lib/dentaku/calculator.rb', line 55 def disable_cache @disable_ast_cache = true yield(self) if block_given? ensure @disable_ast_cache = false end |
#empty? ⇒ Boolean
206 207 208 |
# File 'lib/dentaku/calculator.rb', line 206 def empty? memory.empty? end |
#evaluate(expression, data = {}, &block) ⇒ Object
62 63 64 65 66 67 68 69 |
# File 'lib/dentaku/calculator.rb', line 62 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 => ex block.call(expression, ex) if block_given? end |
#evaluate!(expression, data = {}, &block) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/dentaku/calculator.rb', line 75 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
167 168 169 |
# File 'lib/dentaku/calculator.rb', line 167 def evaluation_context(data, evaluation_mode) data.key?(:__evaluation_mode) ? data : data.merge(__evaluation_mode: evaluation_mode) end |
#identifiers(expression) ⇒ Object
every identifier the expression could reference, regardless of branching: purely syntactic, ignores stored memory, and never evaluates guards or functions
120 121 122 123 124 125 126 127 128 129 |
# File 'lib/dentaku/calculator.rb', line 120 def identifiers(expression) case expression when Dentaku::AST::Node expression.dependencies(AST::Node::STATIC_CONTEXT).uniq when Array expression.flat_map { |e| identifiers(e) }.uniq else ast(expression).dependencies(AST::Node::STATIC_CONTEXT).uniq end end |
#load_cache(ast_cache) ⇒ Object
150 151 152 |
# File 'lib/dentaku/calculator.rb', line 150 def load_cache(ast_cache) @ast_cache = ast_cache end |
#solve(expression_hash, &block) ⇒ Object
100 101 102 |
# File 'lib/dentaku/calculator.rb', line 100 def solve(expression_hash, &block) BulkExpressionSolver.new(expression_hash, self).solve(&block) end |
#solve!(expression_hash) ⇒ Object
96 97 98 |
# File 'lib/dentaku/calculator.rb', line 96 def solve!(expression_hash) BulkExpressionSolver.new(expression_hash, self).solve! end |
#store(key_or_hash, value = nil) ⇒ Object Also known as: bind
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/dentaku/calculator.rb', line 171 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
198 199 200 |
# File 'lib/dentaku/calculator.rb', line 198 def store_formula(key, formula) store(key, ast(formula)) end |