dentaku_zevo 3.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.pryrc +2 -0
  4. data/.rubocop.yml +114 -0
  5. data/.travis.yml +10 -0
  6. data/CHANGELOG.md +281 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +21 -0
  9. data/README.md +342 -0
  10. data/Rakefile +31 -0
  11. data/dentaku.gemspec +32 -0
  12. data/lib/dentaku/ast/access.rb +47 -0
  13. data/lib/dentaku/ast/arithmetic.rb +241 -0
  14. data/lib/dentaku/ast/array.rb +41 -0
  15. data/lib/dentaku/ast/bitwise.rb +42 -0
  16. data/lib/dentaku/ast/case/case_conditional.rb +38 -0
  17. data/lib/dentaku/ast/case/case_else.rb +35 -0
  18. data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
  19. data/lib/dentaku/ast/case/case_then.rb +35 -0
  20. data/lib/dentaku/ast/case/case_when.rb +39 -0
  21. data/lib/dentaku/ast/case.rb +81 -0
  22. data/lib/dentaku/ast/combinators.rb +50 -0
  23. data/lib/dentaku/ast/comparators.rb +89 -0
  24. data/lib/dentaku/ast/datetime.rb +8 -0
  25. data/lib/dentaku/ast/function.rb +56 -0
  26. data/lib/dentaku/ast/function_registry.rb +98 -0
  27. data/lib/dentaku/ast/functions/all.rb +23 -0
  28. data/lib/dentaku/ast/functions/and.rb +25 -0
  29. data/lib/dentaku/ast/functions/any.rb +23 -0
  30. data/lib/dentaku/ast/functions/avg.rb +13 -0
  31. data/lib/dentaku/ast/functions/count.rb +26 -0
  32. data/lib/dentaku/ast/functions/duration.rb +51 -0
  33. data/lib/dentaku/ast/functions/enum.rb +37 -0
  34. data/lib/dentaku/ast/functions/filter.rb +23 -0
  35. data/lib/dentaku/ast/functions/if.rb +51 -0
  36. data/lib/dentaku/ast/functions/map.rb +23 -0
  37. data/lib/dentaku/ast/functions/max.rb +5 -0
  38. data/lib/dentaku/ast/functions/min.rb +5 -0
  39. data/lib/dentaku/ast/functions/mul.rb +12 -0
  40. data/lib/dentaku/ast/functions/not.rb +5 -0
  41. data/lib/dentaku/ast/functions/or.rb +25 -0
  42. data/lib/dentaku/ast/functions/pluck.rb +30 -0
  43. data/lib/dentaku/ast/functions/round.rb +5 -0
  44. data/lib/dentaku/ast/functions/rounddown.rb +8 -0
  45. data/lib/dentaku/ast/functions/roundup.rb +8 -0
  46. data/lib/dentaku/ast/functions/ruby_math.rb +55 -0
  47. data/lib/dentaku/ast/functions/string_functions.rb +212 -0
  48. data/lib/dentaku/ast/functions/sum.rb +12 -0
  49. data/lib/dentaku/ast/functions/switch.rb +8 -0
  50. data/lib/dentaku/ast/functions/xor.rb +44 -0
  51. data/lib/dentaku/ast/grouping.rb +23 -0
  52. data/lib/dentaku/ast/identifier.rb +52 -0
  53. data/lib/dentaku/ast/literal.rb +30 -0
  54. data/lib/dentaku/ast/logical.rb +8 -0
  55. data/lib/dentaku/ast/negation.rb +54 -0
  56. data/lib/dentaku/ast/nil.rb +13 -0
  57. data/lib/dentaku/ast/node.rb +28 -0
  58. data/lib/dentaku/ast/numeric.rb +8 -0
  59. data/lib/dentaku/ast/operation.rb +39 -0
  60. data/lib/dentaku/ast/string.rb +15 -0
  61. data/lib/dentaku/ast.rb +39 -0
  62. data/lib/dentaku/bulk_expression_solver.rb +128 -0
  63. data/lib/dentaku/calculator.rb +169 -0
  64. data/lib/dentaku/date_arithmetic.rb +45 -0
  65. data/lib/dentaku/dependency_resolver.rb +24 -0
  66. data/lib/dentaku/exceptions.rb +102 -0
  67. data/lib/dentaku/flat_hash.rb +38 -0
  68. data/lib/dentaku/parser.rb +349 -0
  69. data/lib/dentaku/print_visitor.rb +101 -0
  70. data/lib/dentaku/string_casing.rb +7 -0
  71. data/lib/dentaku/token.rb +36 -0
  72. data/lib/dentaku/token_matcher.rb +138 -0
  73. data/lib/dentaku/token_matchers.rb +29 -0
  74. data/lib/dentaku/token_scanner.rb +183 -0
  75. data/lib/dentaku/tokenizer.rb +110 -0
  76. data/lib/dentaku/version.rb +3 -0
  77. data/lib/dentaku/visitor/infix.rb +82 -0
  78. data/lib/dentaku.rb +69 -0
  79. data/spec/ast/addition_spec.rb +62 -0
  80. data/spec/ast/all_spec.rb +25 -0
  81. data/spec/ast/and_function_spec.rb +35 -0
  82. data/spec/ast/and_spec.rb +32 -0
  83. data/spec/ast/any_spec.rb +23 -0
  84. data/spec/ast/arithmetic_spec.rb +91 -0
  85. data/spec/ast/avg_spec.rb +37 -0
  86. data/spec/ast/case_spec.rb +84 -0
  87. data/spec/ast/comparator_spec.rb +87 -0
  88. data/spec/ast/count_spec.rb +40 -0
  89. data/spec/ast/division_spec.rb +35 -0
  90. data/spec/ast/filter_spec.rb +25 -0
  91. data/spec/ast/function_spec.rb +69 -0
  92. data/spec/ast/map_spec.rb +27 -0
  93. data/spec/ast/max_spec.rb +33 -0
  94. data/spec/ast/min_spec.rb +33 -0
  95. data/spec/ast/mul_spec.rb +43 -0
  96. data/spec/ast/negation_spec.rb +48 -0
  97. data/spec/ast/node_spec.rb +43 -0
  98. data/spec/ast/numeric_spec.rb +16 -0
  99. data/spec/ast/or_spec.rb +35 -0
  100. data/spec/ast/pluck_spec.rb +32 -0
  101. data/spec/ast/round_spec.rb +35 -0
  102. data/spec/ast/rounddown_spec.rb +35 -0
  103. data/spec/ast/roundup_spec.rb +35 -0
  104. data/spec/ast/string_functions_spec.rb +217 -0
  105. data/spec/ast/sum_spec.rb +43 -0
  106. data/spec/ast/switch_spec.rb +30 -0
  107. data/spec/ast/xor_spec.rb +35 -0
  108. data/spec/benchmark.rb +70 -0
  109. data/spec/bulk_expression_solver_spec.rb +201 -0
  110. data/spec/calculator_spec.rb +898 -0
  111. data/spec/dentaku_spec.rb +52 -0
  112. data/spec/exceptions_spec.rb +9 -0
  113. data/spec/external_function_spec.rb +106 -0
  114. data/spec/parser_spec.rb +166 -0
  115. data/spec/print_visitor_spec.rb +66 -0
  116. data/spec/spec_helper.rb +71 -0
  117. data/spec/token_matcher_spec.rb +134 -0
  118. data/spec/token_scanner_spec.rb +49 -0
  119. data/spec/token_spec.rb +16 -0
  120. data/spec/tokenizer_spec.rb +359 -0
  121. data/spec/visitor/infix_spec.rb +31 -0
  122. data/spec/visitor_spec.rb +138 -0
  123. metadata +335 -0
@@ -0,0 +1,44 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Xor < Function
7
+ def self.min_param_count
8
+ 1
9
+ end
10
+
11
+ def self.max_param_count
12
+ Float::INFINITY
13
+ end
14
+
15
+ def value(context = {})
16
+ if @args.empty?
17
+ raise Dentaku::ArgumentError.for(
18
+ :too_few_arguments,
19
+ function_name: 'XOR()', at_least: 1, given: 0
20
+ ), 'XOR() requires at least one argument'
21
+ end
22
+
23
+ true_arg_count = 0
24
+ @args.each do |arg|
25
+ case arg.value(context)
26
+ when TrueClass
27
+ true_arg_count += 1
28
+ break if true_arg_count > 1
29
+ when FalseClass, nil
30
+ next
31
+ else
32
+ raise Dentaku::ArgumentError.for(
33
+ :incompatible_type,
34
+ function_name: 'XOR()', expect: :logical, actual: arg.class
35
+ ), 'XOR() requires arguments to be logical expressions'
36
+ end
37
+ end
38
+ true_arg_count == 1
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ Dentaku::AST::Function.register_class(:xor, Dentaku::AST::Xor)
@@ -0,0 +1,23 @@
1
+ require_relative "./node"
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Grouping < Node
6
+ def initialize(node)
7
+ @node = node
8
+ end
9
+
10
+ def value(context = {})
11
+ @node.value(context)
12
+ end
13
+
14
+ def type
15
+ @node.type
16
+ end
17
+
18
+ def dependencies(context = {})
19
+ @node.dependencies(context)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ require_relative '../exceptions'
2
+ require 'dentaku/string_casing'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Identifier < Node
7
+ include StringCasing
8
+ attr_reader :identifier, :case_sensitive
9
+
10
+ def initialize(token, options = {})
11
+ @case_sensitive = options.fetch(:case_sensitive, false)
12
+ @identifier = standardize_case(token.value)
13
+ end
14
+
15
+ def value(context = {})
16
+ v = context.fetch(identifier) do
17
+ raise UnboundVariableError.new([identifier]),
18
+ "no value provided for variables: #{identifier}"
19
+ end
20
+
21
+ case v
22
+ when Node
23
+ value = v.value(context)
24
+ context[identifier] = value if Dentaku.cache_identifier?
25
+ value
26
+ when Proc
27
+ v.call
28
+ else
29
+ v
30
+ end
31
+ end
32
+
33
+ def dependencies(context = {})
34
+ context.key?(identifier) ? dependencies_of(context[identifier], context) : [identifier]
35
+ end
36
+
37
+ def accept(visitor)
38
+ visitor.visit_identifier(self)
39
+ end
40
+
41
+ def to_s
42
+ identifier.to_s
43
+ end
44
+
45
+ private
46
+
47
+ def dependencies_of(node, context)
48
+ node.respond_to?(:dependencies) ? node.dependencies(context) : []
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,30 @@
1
+ module Dentaku
2
+ module AST
3
+ class Literal < Node
4
+ attr_reader :type
5
+
6
+ def initialize(token)
7
+ @token = token
8
+ @value = token.value
9
+ @type = token.category
10
+ end
11
+
12
+ def value(*)
13
+ @value
14
+ end
15
+
16
+ def dependencies(*)
17
+ []
18
+ end
19
+
20
+ def accept(visitor)
21
+ visitor.visit_literal(self)
22
+ end
23
+
24
+ def quoted
25
+ @token.raw_value || value.to_s
26
+ end
27
+ alias_method :to_s, :quoted
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "./literal"
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Logical < Literal
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,54 @@
1
+ module Dentaku
2
+ module AST
3
+ class Negation < Arithmetic
4
+ attr_reader :node
5
+
6
+ def initialize(node)
7
+ @node = node
8
+
9
+ unless valid_node?(node)
10
+ raise NodeError.new(:numeric, node.type, :node),
11
+ "#{self.class} requires numeric operands"
12
+ end
13
+ end
14
+
15
+ def operator
16
+ :*
17
+ end
18
+
19
+ def value(context = {})
20
+ cast(@node.value(context)) * -1
21
+ end
22
+
23
+ def type
24
+ :numeric
25
+ end
26
+
27
+ def self.arity
28
+ 1
29
+ end
30
+
31
+ def self.right_associative?
32
+ true
33
+ end
34
+
35
+ def self.precedence
36
+ 40
37
+ end
38
+
39
+ def dependencies(context = {})
40
+ @node.dependencies(context)
41
+ end
42
+
43
+ def accept(visitor)
44
+ visitor.visit_negation(self)
45
+ end
46
+
47
+ private
48
+
49
+ def valid_node?(node)
50
+ node && (node.dependencies.any? || node.type == :numeric)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ module Dentaku
2
+ module AST
3
+ class Nil < Node
4
+ def value(*)
5
+ nil
6
+ end
7
+
8
+ def accept(visitor)
9
+ visitor.visit_nil(self)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module Dentaku
2
+ module AST
3
+ class Node
4
+ def self.precedence
5
+ 0
6
+ end
7
+
8
+ def self.arity
9
+ nil
10
+ end
11
+
12
+ def self.peek(*)
13
+ end
14
+
15
+ def dependencies(context = {})
16
+ []
17
+ end
18
+
19
+ def type
20
+ nil
21
+ end
22
+
23
+ def name
24
+ self.class.name.to_s.split("::").last.upcase
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "./literal"
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Numeric < Literal
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,39 @@
1
+ require_relative './node'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Operation < Node
6
+ attr_reader :left, :right
7
+
8
+ def self.min_param_count
9
+ arity
10
+ end
11
+
12
+ def self.max_param_count
13
+ arity
14
+ end
15
+
16
+ def initialize(left, right)
17
+ @left = left
18
+ @right = right
19
+ end
20
+
21
+ def dependencies(context = {})
22
+ (left.dependencies(context) + right.dependencies(context)).uniq
23
+ end
24
+
25
+ def self.right_associative?
26
+ false
27
+ end
28
+
29
+ def accept(visitor)
30
+ visitor.visit_operation(self)
31
+ end
32
+
33
+ def display_operator
34
+ operator.to_s
35
+ end
36
+ alias_method :to_s, :display_operator
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "./literal"
2
+
3
+ module Dentaku
4
+ module AST
5
+ class String < Literal
6
+ def quoted
7
+ %Q{"#{ escaped }"}
8
+ end
9
+
10
+ def escaped
11
+ @value.gsub('"', '\"')
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ require_relative './ast/node'
2
+ require_relative './ast/nil'
3
+ require_relative './ast/datetime'
4
+ require_relative './ast/numeric'
5
+ require_relative './ast/logical'
6
+ require_relative './ast/string'
7
+ require_relative './ast/identifier'
8
+ require_relative './ast/arithmetic'
9
+ require_relative './ast/bitwise'
10
+ require_relative './ast/negation'
11
+ require_relative './ast/comparators'
12
+ require_relative './ast/combinators'
13
+ require_relative './ast/access'
14
+ require_relative './ast/array'
15
+ require_relative './ast/grouping'
16
+ require_relative './ast/case'
17
+ require_relative './ast/function_registry'
18
+ require_relative './ast/functions/all'
19
+ require_relative './ast/functions/and'
20
+ require_relative './ast/functions/any'
21
+ require_relative './ast/functions/avg'
22
+ require_relative './ast/functions/count'
23
+ require_relative './ast/functions/duration'
24
+ require_relative './ast/functions/filter'
25
+ require_relative './ast/functions/if'
26
+ require_relative './ast/functions/map'
27
+ require_relative './ast/functions/max'
28
+ require_relative './ast/functions/min'
29
+ require_relative './ast/functions/not'
30
+ require_relative './ast/functions/or'
31
+ require_relative './ast/functions/pluck'
32
+ require_relative './ast/functions/round'
33
+ require_relative './ast/functions/rounddown'
34
+ require_relative './ast/functions/roundup'
35
+ require_relative './ast/functions/ruby_math'
36
+ require_relative './ast/functions/string_functions'
37
+ require_relative './ast/functions/sum'
38
+ require_relative './ast/functions/switch'
39
+ require_relative './ast/functions/xor'
@@ -0,0 +1,128 @@
1
+ require 'dentaku/dependency_resolver'
2
+ require 'dentaku/exceptions'
3
+ require 'dentaku/flat_hash'
4
+ require 'dentaku/parser'
5
+ require 'dentaku/tokenizer'
6
+
7
+ module Dentaku
8
+ class BulkExpressionSolver
9
+ def initialize(expressions, calculator)
10
+ @expression_hash = FlatHash.from_hash(expressions)
11
+ @calculator = calculator
12
+ end
13
+
14
+ def solve!
15
+ solve(&raise_exception_handler)
16
+ end
17
+
18
+ def solve(&block)
19
+ error_handler = block || return_undefined_handler
20
+ results = load_results(&error_handler)
21
+
22
+ FlatHash.expand(
23
+ expression_hash.each_with_object({}) do |(k, v), r|
24
+ default = v.nil? ? v : :undefined
25
+ r[k] = results.fetch(k.to_s, default)
26
+ end
27
+ )
28
+ end
29
+
30
+ def dependencies
31
+ Hash[expression_deps].tap do |d|
32
+ d.values.each do |deps|
33
+ unresolved = deps.reject { |ud| d.has_key?(ud) }
34
+ unresolved.each { |u| add_dependencies(d, u) }
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def self.dependency_cache
42
+ @dep_cache ||= {}
43
+ end
44
+
45
+ attr_reader :expression_hash, :calculator
46
+
47
+ def return_undefined_handler
48
+ ->(*) { :undefined }
49
+ end
50
+
51
+ def raise_exception_handler
52
+ ->(ex) { raise ex }
53
+ end
54
+
55
+ def expression_with_exception_handler(&block)
56
+ ->(_expr, ex) { block.call(ex) }
57
+ end
58
+
59
+ def load_results(&block)
60
+ facts, _formulas = expressions.transform_keys(&:downcase)
61
+ .transform_values { |v| calculator.ast(v) }
62
+ .partition { |_, v| calculator.dependencies(v, nil).empty? }
63
+
64
+ evaluated_facts = facts.to_h.each_with_object({}) do |(var_name, ast), h|
65
+ with_rescues(var_name, h, block) do
66
+ h[var_name] = ast.is_a?(Array) ? ast.map(&:value) : ast.value
67
+ end
68
+ end
69
+
70
+ context = calculator.memory.merge(evaluated_facts)
71
+
72
+ variables_in_resolve_order.each_with_object({}) do |var_name, results|
73
+ next if expressions[var_name].nil?
74
+
75
+ with_rescues(var_name, results, block) do
76
+ results[var_name] = evaluated_facts[var_name] || calculator.evaluate!(
77
+ expressions[var_name],
78
+ context.merge(results),
79
+ &expression_with_exception_handler(&block)
80
+ )
81
+ end
82
+ end
83
+
84
+ rescue TSort::Cyclic => ex
85
+ block.call(ex)
86
+ {}
87
+ end
88
+
89
+ def with_rescues(var_name, results, block)
90
+ yield
91
+
92
+ rescue Dentaku::UnboundVariableError, Dentaku::ZeroDivisionError, Dentaku::ArgumentError => ex
93
+ ex.recipient_variable = var_name
94
+ results[var_name] = block.call(ex)
95
+ ensure
96
+ if results[var_name] == :undefined && calculator.memory.has_key?(var_name.downcase)
97
+ results[var_name] = calculator.memory[var_name.downcase]
98
+ end
99
+ end
100
+
101
+ def expressions
102
+ @expressions ||= Hash[expression_hash.map { |k, v| [k.to_s, v] }]
103
+ end
104
+
105
+ def expression_deps
106
+ expressions.map do |var, expr|
107
+ [var, calculator.dependencies(expr)]
108
+ end
109
+ end
110
+
111
+ def add_dependencies(current_dependencies, variable)
112
+ node = calculator.memory[variable]
113
+ if node.respond_to?(:dependencies)
114
+ current_dependencies[variable] = node.dependencies
115
+ node.dependencies.each { |d| add_dependencies(current_dependencies, d) }
116
+ end
117
+ end
118
+
119
+ def variables_in_resolve_order
120
+ cache_key = expressions.keys.map(&:to_s).sort.join("|")
121
+ @ordered_deps ||= self.class.dependency_cache.fetch(cache_key) {
122
+ DependencyResolver.find_resolve_order(dependencies).tap do |d|
123
+ self.class.dependency_cache[cache_key] = d if Dentaku.cache_dependency_order?
124
+ end
125
+ }
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,169 @@
1
+ require 'dentaku/bulk_expression_solver'
2
+ require 'dentaku/dependency_resolver'
3
+ require 'dentaku/exceptions'
4
+ require 'dentaku/flat_hash'
5
+ require 'dentaku/parser'
6
+ require 'dentaku/string_casing'
7
+ require 'dentaku/token'
8
+
9
+ module Dentaku
10
+ class Calculator
11
+ include StringCasing
12
+ attr_reader :result, :memory, :tokenizer, :case_sensitive, :aliases,
13
+ :nested_data_support, :ast_cache
14
+
15
+ def initialize(options = {})
16
+ clear
17
+ @tokenizer = Tokenizer.new
18
+ @case_sensitive = options.delete(:case_sensitive)
19
+ @aliases = options.delete(:aliases) || Dentaku.aliases
20
+ @nested_data_support = options.fetch(:nested_data_support, true)
21
+ options.delete(:nested_data_support)
22
+ @ast_cache = options
23
+ @disable_ast_cache = false
24
+ @function_registry = Dentaku::AST::FunctionRegistry.new
25
+ end
26
+
27
+ def self.add_function(name, type, body)
28
+ Dentaku::AST::FunctionRegistry.default.register(name, type, body)
29
+ end
30
+
31
+ def add_function(name, type, body)
32
+ @function_registry.register(name, type, body)
33
+ self
34
+ end
35
+
36
+ def add_functions(fns)
37
+ fns.each { |(name, type, body)| add_function(name, type, body) }
38
+ self
39
+ end
40
+
41
+ def disable_cache
42
+ @disable_ast_cache = true
43
+ yield(self) if block_given?
44
+ ensure
45
+ @disable_ast_cache = false
46
+ end
47
+
48
+ def evaluate(expression, data = {}, &block)
49
+ evaluate!(expression, data)
50
+ rescue Dentaku::Error, Dentaku::ArgumentError, Dentaku::ZeroDivisionError => ex
51
+ block.call(expression, ex) if block_given?
52
+ end
53
+
54
+ def evaluate!(expression, data = {}, &block)
55
+ return expression.map { |e|
56
+ evaluate(e, data, &block)
57
+ } if expression.is_a? Array
58
+
59
+ store(data) do
60
+ node = expression
61
+ node = ast(node) unless node.is_a?(AST::Node)
62
+ unbound = node.dependencies(memory)
63
+ unless unbound.empty?
64
+ raise UnboundVariableError.new(unbound),
65
+ "no value provided for variables: #{unbound.uniq.join(', ')}"
66
+ end
67
+ node.value(memory)
68
+ end
69
+ end
70
+
71
+ def solve!(expression_hash)
72
+ BulkExpressionSolver.new(expression_hash, self).solve!
73
+ end
74
+
75
+ def solve(expression_hash, &block)
76
+ BulkExpressionSolver.new(expression_hash, self).solve(&block)
77
+ end
78
+
79
+ def dependencies(expression, context = {})
80
+ test_context = context.nil? ? {} : store(context) { memory }
81
+
82
+ case expression
83
+ when Dentaku::AST::Node
84
+ expression.dependencies(test_context)
85
+ when Array
86
+ expression.flat_map { |e| dependencies(e, context) }
87
+ else
88
+ ast(expression).dependencies(test_context)
89
+ end
90
+ end
91
+
92
+ def ast(expression)
93
+ return expression.map { |e| ast(e) } if expression.is_a? Array
94
+
95
+ @ast_cache.fetch(expression) {
96
+ options = {
97
+ case_sensitive: case_sensitive,
98
+ function_registry: @function_registry,
99
+ aliases: aliases
100
+ }
101
+
102
+ tokens = tokenizer.tokenize(expression, options)
103
+ Parser.new(tokens, options).parse.tap do |node|
104
+ @ast_cache[expression] = node if cache_ast?
105
+ end
106
+ }
107
+ end
108
+
109
+ def load_cache(ast_cache)
110
+ @ast_cache = ast_cache
111
+ end
112
+
113
+ def clear_cache(pattern = :all)
114
+ case pattern
115
+ when :all
116
+ @ast_cache = {}
117
+ when String
118
+ @ast_cache.delete(pattern)
119
+ when Regexp
120
+ @ast_cache.delete_if { |k, _| k =~ pattern }
121
+ else
122
+ raise ::ArgumentError
123
+ end
124
+ end
125
+
126
+ def store(key_or_hash, value = nil)
127
+ restore = Hash[memory]
128
+
129
+ if value.nil?
130
+ key_or_hash = FlatHash.from_hash_with_intermediates(key_or_hash) if nested_data_support
131
+ key_or_hash.each do |key, val|
132
+ memory[standardize_case(key.to_s)] = val
133
+ end
134
+ else
135
+ memory[standardize_case(key_or_hash.to_s)] = value
136
+ end
137
+
138
+ if block_given?
139
+ begin
140
+ result = yield
141
+ @memory = restore
142
+ return result
143
+ rescue => e
144
+ @memory = restore
145
+ raise e
146
+ end
147
+ end
148
+
149
+ self
150
+ end
151
+ alias_method :bind, :store
152
+
153
+ def store_formula(key, formula)
154
+ store(key, ast(formula))
155
+ end
156
+
157
+ def clear
158
+ @memory = {}
159
+ end
160
+
161
+ def empty?
162
+ memory.empty?
163
+ end
164
+
165
+ def cache_ast?
166
+ Dentaku.cache_ast? && !@disable_ast_cache
167
+ end
168
+ end
169
+ end