require'dentaku/bulk_expression_solver'require'dentaku/dependency_resolver'require'dentaku/exceptions'require'dentaku/flat_hash'require'dentaku/parser'require'dentaku/string_casing'require'dentaku/token'moduleDentakuclassCalculatorincludeStringCasingattr_reader:result,:memory,:tokenizer,:case_sensitive,:aliases,:nested_data_support,:ast_cachedefinitialize(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)@ast_cache=options@disable_ast_cache=false@function_registry=Dentaku::AST::FunctionRegistry.newenddefself.add_function(name,type,body)Dentaku::AST::FunctionRegistry.default.register(name,type,body)enddefadd_function(name,type,body)@function_registry.register(name,type,body)selfenddefadd_functions(fns)fns.each{|(name,type,body)|add_function(name,type,body)}selfenddefdisable_cache@disable_ast_cache=trueyield(self)ifblock_given?ensure@disable_ast_cache=falseenddefevaluate(expression,data={},&block)evaluate!(expression,data)rescueDentaku::Error,Dentaku::ArgumentError,Dentaku::ZeroDivisionError=>exblock.call(expression,ex)ifblock_given?enddefevaluate!(expression,data={},&block)returnexpression.map{|e|evaluate(e,data,&block)}ifexpression.is_a?Arraystore(data)donode=expressionnode=ast(node)unlessnode.is_a?(AST::Node)unbound=node.dependencies-memory.keysunlessunbound.empty?raiseUnboundVariableError.new(unbound),"no value provided for variables: #{unbound.uniq.join(', ')}"endnode.value(memory)endenddefsolve!(expression_hash)BulkExpressionSolver.new(expression_hash,self).solve!enddefsolve(expression_hash,&block)BulkExpressionSolver.new(expression_hash,self).solve(&block)enddefdependencies(expression,context={})test_context=context.nil??{}:store(context){memory}caseexpressionwhenDentaku::AST::Nodeexpression.dependencies(test_context)whenArrayexpression.flat_map{|e|dependencies(e,context)}elseast(expression).dependencies(test_context)endenddefast(expression)returnexpression.map{|e|ast(e)}ifexpression.is_a?Array@ast_cache.fetch(expression){options={case_sensitive: case_sensitive,function_registry: @function_registry,aliases: aliases}tokens=tokenizer.tokenize(expression,options)Parser.new(tokens,options).parse.tapdo|node|@ast_cache[expression]=nodeifcache_ast?end}enddefload_cache(ast_cache)@ast_cache=ast_cacheenddefclear_cache(pattern=:all)casepatternwhen:all@ast_cache={}whenString@ast_cache.delete(pattern)whenRegexp@ast_cache.delete_if{|k,_|k=~pattern}elseraise::ArgumentErrorendenddefstore(key_or_hash,value=nil)restore=Hash[memory]ifvalue.nil?key_or_hash=FlatHash.from_hash_with_intermediates(key_or_hash)ifnested_data_supportkey_or_hash.eachdo|key,val|memory[standardize_case(key.to_s)]=valendelsememory[standardize_case(key_or_hash.to_s)]=valueendifblock_given?beginresult=yield@memory=restorereturnresultrescue=>e@memory=restoreraiseeendendselfendalias_method:bind,:storedefstore_formula(key,formula)store(key,ast(formula))enddefclear@memory={}enddefempty?memory.empty?enddefcache_ast?Dentaku.cache_ast?&&!@disable_ast_cacheendendend