hayadentaku 3.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rspec.yml +26 -0
  3. data/.github/workflows/rubocop.yml +14 -0
  4. data/.gitignore +14 -0
  5. data/.pryrc +2 -0
  6. data/.rubocop.yml +114 -0
  7. data/.travis.yml +10 -0
  8. data/CHANGELOG.md +328 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE +21 -0
  11. data/README.md +352 -0
  12. data/Rakefile +31 -0
  13. data/hayadentaku.gemspec +35 -0
  14. data/lib/dentaku/ast/access.rb +44 -0
  15. data/lib/dentaku/ast/arithmetic.rb +292 -0
  16. data/lib/dentaku/ast/array.rb +38 -0
  17. data/lib/dentaku/ast/bitwise.rb +42 -0
  18. data/lib/dentaku/ast/case/case_conditional.rb +38 -0
  19. data/lib/dentaku/ast/case/case_else.rb +35 -0
  20. data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
  21. data/lib/dentaku/ast/case/case_then.rb +35 -0
  22. data/lib/dentaku/ast/case/case_when.rb +39 -0
  23. data/lib/dentaku/ast/case.rb +93 -0
  24. data/lib/dentaku/ast/combinators.rb +50 -0
  25. data/lib/dentaku/ast/comparators.rb +88 -0
  26. data/lib/dentaku/ast/datetime.rb +8 -0
  27. data/lib/dentaku/ast/function.rb +56 -0
  28. data/lib/dentaku/ast/function_registry.rb +107 -0
  29. data/lib/dentaku/ast/functions/abs.rb +5 -0
  30. data/lib/dentaku/ast/functions/all.rb +19 -0
  31. data/lib/dentaku/ast/functions/and.rb +25 -0
  32. data/lib/dentaku/ast/functions/any.rb +19 -0
  33. data/lib/dentaku/ast/functions/avg.rb +13 -0
  34. data/lib/dentaku/ast/functions/count.rb +26 -0
  35. data/lib/dentaku/ast/functions/duration.rb +51 -0
  36. data/lib/dentaku/ast/functions/enum.rb +54 -0
  37. data/lib/dentaku/ast/functions/filter.rb +21 -0
  38. data/lib/dentaku/ast/functions/if.rb +47 -0
  39. data/lib/dentaku/ast/functions/intercept.rb +33 -0
  40. data/lib/dentaku/ast/functions/map.rb +19 -0
  41. data/lib/dentaku/ast/functions/max.rb +5 -0
  42. data/lib/dentaku/ast/functions/min.rb +5 -0
  43. data/lib/dentaku/ast/functions/mul.rb +12 -0
  44. data/lib/dentaku/ast/functions/not.rb +5 -0
  45. data/lib/dentaku/ast/functions/or.rb +25 -0
  46. data/lib/dentaku/ast/functions/pluck.rb +34 -0
  47. data/lib/dentaku/ast/functions/reduce.rb +60 -0
  48. data/lib/dentaku/ast/functions/round.rb +5 -0
  49. data/lib/dentaku/ast/functions/rounddown.rb +8 -0
  50. data/lib/dentaku/ast/functions/roundup.rb +8 -0
  51. data/lib/dentaku/ast/functions/ruby_math.rb +57 -0
  52. data/lib/dentaku/ast/functions/string_functions.rb +212 -0
  53. data/lib/dentaku/ast/functions/sum.rb +12 -0
  54. data/lib/dentaku/ast/functions/switch.rb +8 -0
  55. data/lib/dentaku/ast/functions/xor.rb +44 -0
  56. data/lib/dentaku/ast/grouping.rb +23 -0
  57. data/lib/dentaku/ast/identifier.rb +52 -0
  58. data/lib/dentaku/ast/literal.rb +30 -0
  59. data/lib/dentaku/ast/logical.rb +8 -0
  60. data/lib/dentaku/ast/negation.rb +54 -0
  61. data/lib/dentaku/ast/nil.rb +13 -0
  62. data/lib/dentaku/ast/node.rb +29 -0
  63. data/lib/dentaku/ast/numeric.rb +8 -0
  64. data/lib/dentaku/ast/operation.rb +44 -0
  65. data/lib/dentaku/ast/string.rb +15 -0
  66. data/lib/dentaku/ast.rb +42 -0
  67. data/lib/dentaku/bulk_expression_solver.rb +158 -0
  68. data/lib/dentaku/calculator.rb +192 -0
  69. data/lib/dentaku/date_arithmetic.rb +60 -0
  70. data/lib/dentaku/dependency_resolver.rb +29 -0
  71. data/lib/dentaku/exceptions.rb +116 -0
  72. data/lib/dentaku/flat_hash.rb +161 -0
  73. data/lib/dentaku/parser.rb +318 -0
  74. data/lib/dentaku/print_visitor.rb +112 -0
  75. data/lib/dentaku/string_casing.rb +7 -0
  76. data/lib/dentaku/token.rb +48 -0
  77. data/lib/dentaku/token_matcher.rb +138 -0
  78. data/lib/dentaku/token_matchers.rb +29 -0
  79. data/lib/dentaku/token_scanner.rb +240 -0
  80. data/lib/dentaku/tokenizer.rb +127 -0
  81. data/lib/dentaku/version.rb +3 -0
  82. data/lib/dentaku/visitor/infix.rb +86 -0
  83. data/lib/dentaku.rb +69 -0
  84. data/spec/ast/abs_spec.rb +26 -0
  85. data/spec/ast/addition_spec.rb +67 -0
  86. data/spec/ast/all_spec.rb +38 -0
  87. data/spec/ast/and_function_spec.rb +35 -0
  88. data/spec/ast/and_spec.rb +32 -0
  89. data/spec/ast/any_spec.rb +36 -0
  90. data/spec/ast/arithmetic_spec.rb +147 -0
  91. data/spec/ast/avg_spec.rb +42 -0
  92. data/spec/ast/case_spec.rb +84 -0
  93. data/spec/ast/comparator_spec.rb +87 -0
  94. data/spec/ast/count_spec.rb +40 -0
  95. data/spec/ast/division_spec.rb +64 -0
  96. data/spec/ast/filter_spec.rb +25 -0
  97. data/spec/ast/function_spec.rb +69 -0
  98. data/spec/ast/intercept_spec.rb +30 -0
  99. data/spec/ast/map_spec.rb +40 -0
  100. data/spec/ast/max_spec.rb +33 -0
  101. data/spec/ast/min_spec.rb +33 -0
  102. data/spec/ast/mul_spec.rb +43 -0
  103. data/spec/ast/negation_spec.rb +48 -0
  104. data/spec/ast/node_spec.rb +43 -0
  105. data/spec/ast/numeric_spec.rb +16 -0
  106. data/spec/ast/or_spec.rb +35 -0
  107. data/spec/ast/pluck_spec.rb +49 -0
  108. data/spec/ast/reduce_spec.rb +22 -0
  109. data/spec/ast/round_spec.rb +35 -0
  110. data/spec/ast/rounddown_spec.rb +35 -0
  111. data/spec/ast/roundup_spec.rb +35 -0
  112. data/spec/ast/string_functions_spec.rb +217 -0
  113. data/spec/ast/sum_spec.rb +43 -0
  114. data/spec/ast/switch_spec.rb +30 -0
  115. data/spec/ast/xor_spec.rb +35 -0
  116. data/spec/benchmark.rb +70 -0
  117. data/spec/bulk_expression_solver_spec.rb +241 -0
  118. data/spec/calculator_spec.rb +1003 -0
  119. data/spec/dentaku_spec.rb +52 -0
  120. data/spec/dependency_resolver_spec.rb +18 -0
  121. data/spec/exceptions_spec.rb +9 -0
  122. data/spec/external_function_spec.rb +177 -0
  123. data/spec/parser_spec.rb +183 -0
  124. data/spec/print_visitor_spec.rb +77 -0
  125. data/spec/spec_helper.rb +69 -0
  126. data/spec/token_matcher_spec.rb +134 -0
  127. data/spec/token_scanner_spec.rb +49 -0
  128. data/spec/token_spec.rb +16 -0
  129. data/spec/tokenizer_spec.rb +375 -0
  130. data/spec/visitor/infix_spec.rb +52 -0
  131. data/spec/visitor_spec.rb +139 -0
  132. metadata +353 -0
@@ -0,0 +1,88 @@
1
+ require_relative './operation'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Comparator < Operation
6
+ def self.precedence
7
+ 5
8
+ end
9
+
10
+ def type
11
+ :logical
12
+ end
13
+
14
+ def operator
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def value(context = {})
19
+ l = validate_value(cast(left.value(context)))
20
+ r = validate_value(cast(right.value(context)))
21
+
22
+ l.public_send(operator, r)
23
+ rescue ::ArgumentError, ::TypeError => e
24
+ raise Dentaku::ArgumentError.for(:incompatible_type, value: r, for: l.class), e.message
25
+ end
26
+
27
+ private
28
+
29
+ def cast(val)
30
+ return val unless val.is_a?(::String)
31
+ return val unless val.match?(Arithmetic::DECIMAL) || val.match?(Arithmetic::INTEGER)
32
+
33
+ v = BigDecimal(val, Float::DIG + 1)
34
+ v = v.to_i if v.frac.zero?
35
+ v
36
+ end
37
+
38
+ def validate_value(value)
39
+ unless value.respond_to?(operator)
40
+ raise Dentaku::ArgumentError.for(:invalid_operator, operation: self.class, operator: operator),
41
+ "#{ self.class } requires operands that respond to #{operator}"
42
+ end
43
+
44
+ value
45
+ end
46
+ end
47
+
48
+ class LessThan < Comparator
49
+ def operator
50
+ :<
51
+ end
52
+ end
53
+
54
+ class LessThanOrEqual < Comparator
55
+ def operator
56
+ :<=
57
+ end
58
+ end
59
+
60
+ class GreaterThan < Comparator
61
+ def operator
62
+ :>
63
+ end
64
+ end
65
+
66
+ class GreaterThanOrEqual < Comparator
67
+ def operator
68
+ :>=
69
+ end
70
+ end
71
+
72
+ class NotEqual < Comparator
73
+ def operator
74
+ :!=
75
+ end
76
+ end
77
+
78
+ class Equal < Comparator
79
+ def operator
80
+ :==
81
+ end
82
+
83
+ def display_operator
84
+ "="
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "./literal"
2
+
3
+ module Dentaku
4
+ module AST
5
+ class DateTime < Literal
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,56 @@
1
+ require_relative 'node'
2
+ require_relative 'function_registry'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Function < Node
7
+ attr_reader :args
8
+
9
+ # @return [Integer] with the number of significant decimal digits to use.
10
+ DIG = Float::DIG + 1
11
+
12
+ def initialize(*args)
13
+ @args = args
14
+ end
15
+
16
+ def accept(visitor)
17
+ visitor.visit_function(self)
18
+ end
19
+
20
+ def dependencies(context = {})
21
+ @args.each_with_index
22
+ .flat_map { |a, _| a.dependencies(context) }
23
+ end
24
+
25
+ def self.get(name)
26
+ registry.get(name)
27
+ end
28
+
29
+ def self.register(name, type, implementation)
30
+ registry.register(name, type, implementation)
31
+ end
32
+
33
+ def self.register_class(name, function_class)
34
+ registry.register_class(name, function_class)
35
+ end
36
+
37
+ def self.registry
38
+ @registry ||= FunctionRegistry.new
39
+ end
40
+
41
+ # @return [Numeric] where possible it returns an Integer otherwise a BigDecimal.
42
+ # An Exception will be raised if a value is passed that cannot be cast to a Number.
43
+ def self.numeric(value)
44
+ return value if value.is_a?(::Numeric)
45
+
46
+ if value.is_a?(::String)
47
+ number = value[/\A-?\d*\.?\d+\z/]
48
+ return number.include?('.') ? BigDecimal(number, DIG) : number.to_i if number
49
+ end
50
+
51
+ raise Dentaku::ArgumentError.for(:incompatible_type, value: value, for: Numeric),
52
+ "'#{value || value.class}' is not coercible to numeric"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,107 @@
1
+ module Dentaku
2
+ module AST
3
+ class FunctionRegistry < Hash
4
+ def get(name)
5
+ name = function_name(name)
6
+ return self[name] if has_key?(name)
7
+ return default[name] if default.has_key?(name)
8
+ nil
9
+ end
10
+
11
+ def register(name, type, implementation, callback = nil)
12
+ function = Class.new(Function) do
13
+ def self.name=(name)
14
+ @name = name
15
+ end
16
+
17
+ def self.name
18
+ @name
19
+ end
20
+
21
+ def self.implementation=(impl)
22
+ @implementation = impl
23
+ end
24
+
25
+ def self.implementation
26
+ @implementation
27
+ end
28
+
29
+ def self.type=(type)
30
+ @type = type
31
+ end
32
+
33
+ def self.type
34
+ @type
35
+ end
36
+
37
+ def self.callback=(callback)
38
+ @callback = callback
39
+ end
40
+
41
+ def self.callback
42
+ @callback
43
+ end
44
+
45
+ def self.arity
46
+ @implementation.arity < 0 ? nil : @implementation.arity
47
+ end
48
+
49
+ def self.min_param_count
50
+ @implementation.parameters.select { |type, _name| type == :req }.count
51
+ end
52
+
53
+ def self.max_param_count
54
+ @implementation.parameters.select { |type, _name| type == :rest }.any? ? Float::INFINITY : @implementation.parameters.count
55
+ end
56
+
57
+ def value(context = {})
58
+ args = @args.map { |a| a.value(context) }
59
+ self.class.implementation.call(*args)
60
+ end
61
+
62
+ def type
63
+ self.class.type
64
+ end
65
+ end
66
+
67
+ define_class(name, function)
68
+
69
+ function.name = name
70
+ function.type = type
71
+ function.implementation = implementation
72
+ function.callback = callback
73
+
74
+ self[function_name(name)] = function
75
+ end
76
+
77
+ def register_class(name, function_class)
78
+ self[function_name(name)] = function_class
79
+ end
80
+
81
+ def default
82
+ self.class.default
83
+ end
84
+
85
+ def self.default
86
+ Dentaku::AST::Function.registry
87
+ end
88
+
89
+ private
90
+
91
+ def function_name(name)
92
+ name.to_s.downcase
93
+ end
94
+
95
+ def normalize_name(function_name)
96
+ function_name.to_s.capitalize.gsub(/\W/, '_')
97
+ end
98
+
99
+ def define_class(function_name, function)
100
+ class_name = normalize_name(function_name)
101
+ return if Dentaku::AST::Function.const_defined?(class_name)
102
+
103
+ Dentaku::AST::Function.const_set(class_name, function)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:abs, :numeric, lambda { |numeric|
4
+ Dentaku::AST::Function.numeric(numeric).abs
5
+ })
@@ -0,0 +1,19 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class All < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.all? do |item_value|
12
+ mapped_value(expression, context, item_identifier => item_value)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Dentaku::AST::Function.register_class(:all, Dentaku::AST::All)
@@ -0,0 +1,25 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ Dentaku::AST::Function.register(:and, :logical, lambda { |*args|
5
+ if args.empty?
6
+ raise Dentaku::ArgumentError.for(
7
+ :too_few_arguments,
8
+ function_name: 'AND()', at_least: 1, given: 0
9
+ ), 'AND() requires at least one argument'
10
+ end
11
+
12
+ args.all? do |arg|
13
+ case arg
14
+ when TrueClass
15
+ true
16
+ when FalseClass, nil
17
+ false
18
+ else
19
+ raise Dentaku::ArgumentError.for(
20
+ :incompatible_type,
21
+ function_name: 'AND()', expect: :logical, actual: arg.class
22
+ ), 'AND() requires arguments to be logical expressions'
23
+ end
24
+ end
25
+ })
@@ -0,0 +1,19 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Any < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.any? do |item_value|
12
+ mapped_value(expression, context, item_identifier => item_value)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Dentaku::AST::Function.register_class(:any, Dentaku::AST::Any)
@@ -0,0 +1,13 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:avg, :numeric, ->(*args) {
4
+ flatten_args = args.flatten
5
+ if flatten_args.empty?
6
+ raise Dentaku::ArgumentError.for(
7
+ :too_few_arguments,
8
+ function_name: 'AVG()', at_least: 1, given: 0
9
+ ), 'AVG() requires at least one argument'
10
+ end
11
+
12
+ flatten_args.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(0, :+) / BigDecimal(flatten_args.length)
13
+ })
@@ -0,0 +1,26 @@
1
+ require_relative '../function'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Count < Function
6
+ def self.min_param_count
7
+ 0
8
+ end
9
+
10
+ def self.max_param_count
11
+ Float::INFINITY
12
+ end
13
+
14
+ def value(context = {})
15
+ if @args.length == 1
16
+ first_arg = @args[0].value(context)
17
+ return first_arg.length if first_arg.respond_to?(:length)
18
+ end
19
+
20
+ @args.length
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Dentaku::AST::Function.register_class(:count, Dentaku::AST::Count)
@@ -0,0 +1,51 @@
1
+ require_relative '../function'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Duration < Function
6
+ def self.min_param_count
7
+ 2
8
+ end
9
+
10
+ def self.max_param_count
11
+ 2
12
+ end
13
+
14
+ class Value
15
+ attr_reader :value, :unit
16
+
17
+ def initialize(value, unit)
18
+ @value = value
19
+ @unit = validate_unit(unit)
20
+ end
21
+
22
+ def validate_unit(unit)
23
+ case unit.downcase
24
+ when /years?/ then :year
25
+ when /months?/ then :month
26
+ when /days?/ then :day
27
+ else
28
+ raise Dentaku::ArgumentError.for(:incompatible_type, value: unit, for: Duration),
29
+ "'#{unit || unit.class}' is not a valid duration unit"
30
+ end
31
+ end
32
+ end
33
+
34
+ def type
35
+ :duration
36
+ end
37
+
38
+ def value(context = {})
39
+ value_node, unit_node = *@args
40
+ Value.new(value_node.value(context), unit_node.identifier)
41
+ end
42
+
43
+ def dependencies(context = {})
44
+ value_node = @args.first
45
+ value_node.dependencies(context)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ Dentaku::AST::Function.register_class(:duration, Dentaku::AST::Duration)
@@ -0,0 +1,54 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ module Dentaku
5
+ module AST
6
+ class Enum < Function
7
+ def self.min_param_count
8
+ 3
9
+ end
10
+
11
+ def self.max_param_count
12
+ 3
13
+ end
14
+
15
+ def initialize(*args)
16
+ super
17
+ validate_identifier(@args[1])
18
+ end
19
+
20
+ def dependencies(context = {})
21
+ collection = @args[0]
22
+ item_identifier = @args[1].identifier
23
+ expression = @args[2]
24
+
25
+ collection_deps = collection.dependencies(context)
26
+ expression_deps = (expression&.dependencies(context) || []).reject do |i|
27
+ i == item_identifier || i.start_with?("#{item_identifier}.")
28
+ end
29
+
30
+ collection_deps + expression_deps
31
+ end
32
+
33
+ def validate_identifier(arg, message = "#{name}() requires second argument to be an identifier")
34
+ raise ParseError.for(:node_invalid), message unless arg.is_a?(Identifier)
35
+ end
36
+
37
+ private
38
+
39
+ def mapped_value(expression, context, item_context)
40
+ # Avoid the `context.merge(FlatHash.from_hash_with_intermediates(...))`
41
+ # double allocation per iteration: dup the context once and write the
42
+ # flattened keys directly into it.
43
+ scratch = context.dup
44
+ item_context.each do |k, v|
45
+ FlatHash.write_pair_with_intermediates!(k, v, scratch)
46
+ end
47
+ expression.value(scratch)
48
+ rescue => e
49
+ raise e if context["__evaluation_mode"] == :strict
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Filter < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.select do |item_value|
12
+ scratch = context.dup
13
+ FlatHash.write_pair_with_intermediates!(item_identifier, item_value, scratch)
14
+ expression.value(scratch)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Dentaku::AST::Function.register_class(:filter, Dentaku::AST::Filter)
@@ -0,0 +1,47 @@
1
+ require_relative '../function'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class If < Function
6
+ attr_reader :predicate, :left, :right
7
+
8
+ def self.min_param_count
9
+ 3
10
+ end
11
+
12
+ def self.max_param_count
13
+ 3
14
+ end
15
+
16
+ def initialize(predicate, left, right)
17
+ @predicate = predicate
18
+ @left = left
19
+ @right = right
20
+ end
21
+
22
+ def args
23
+ [predicate, left, right]
24
+ end
25
+
26
+ def value(context = {})
27
+ predicate.value(context) ? left.value(context) : right.value(context)
28
+ end
29
+
30
+ def node_type
31
+ :condition
32
+ end
33
+
34
+ def type
35
+ left.type
36
+ end
37
+
38
+ def dependencies(context = {})
39
+ predicate.value(context) ? left.dependencies(context) : right.dependencies(context)
40
+ rescue Dentaku::Error, Dentaku::ArgumentError, Dentaku::ZeroDivisionError
41
+ args.flat_map { |arg| arg.dependencies(context) }.uniq
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ Dentaku::AST::Function.register_class(:if, Dentaku::AST::If)
@@ -0,0 +1,33 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:intercept, :list, ->(*args) {
4
+ if args.length != 2
5
+ raise Dentaku::ArgumentError.for(
6
+ :wrong_number_of_arguments,
7
+ function_name: 'INTERCEPT()', exact: 2, given: args.length
8
+ ), 'INTERCEPT() requires exactly two arrays of numbers'
9
+ end
10
+
11
+ x_values, y_values = args
12
+ if !x_values.is_a?(Array) || !y_values.is_a?(Array) || x_values.length != y_values.length
13
+ raise Dentaku::ArgumentError.for(
14
+ :invalid_value,
15
+ function_name: 'INTERCEPT()'
16
+ ), 'INTERCEPT() requires arrays of equal length'
17
+ end
18
+
19
+ n = x_values.length.to_f
20
+ x_values = x_values.map { |arg| Dentaku::AST::Function.numeric(arg) }
21
+ y_values = y_values.map { |arg| Dentaku::AST::Function.numeric(arg) }
22
+
23
+ x_avg = x_values.sum / n
24
+ y_avg = y_values.sum / n
25
+
26
+ xy_sum = x_values.zip(y_values).map { |x, y| (x_avg - x) * (y_avg - y) }.sum
27
+ x_square_sum = x_values.map { |x| (x_avg - x)**2 }.sum
28
+
29
+ slope = xy_sum / x_square_sum
30
+ intercept = x_values.zip(y_values).map { |x, y| y - slope * x }.sum / n
31
+
32
+ BigDecimal(intercept, Float::DIG + 1)
33
+ })
@@ -0,0 +1,19 @@
1
+ require_relative './enum'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Map < Enum
6
+ def value(context = {})
7
+ collection = Array(@args[0].value(context))
8
+ item_identifier = @args[1].identifier
9
+ expression = @args[2]
10
+
11
+ collection.map do |item_value|
12
+ mapped_value(expression, context, item_identifier => item_value)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Dentaku::AST::Function.register_class(:map, Dentaku::AST::Map)
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:max, :numeric, ->(*args) {
4
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.max
5
+ })
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:min, :numeric, ->(*args) {
4
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.min
5
+ })
@@ -0,0 +1,12 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:mul, :numeric, ->(*args) {
4
+ if args.empty?
5
+ raise Dentaku::ArgumentError.for(
6
+ :too_few_arguments,
7
+ function_name: 'MUL()', at_least: 1, given: 0
8
+ ), 'MUL() requires at least one argument'
9
+ end
10
+
11
+ args.flatten.map { |arg| Dentaku::AST::Function.numeric(arg) }.reduce(1, :*)
12
+ })
@@ -0,0 +1,5 @@
1
+ require_relative '../function'
2
+
3
+ Dentaku::AST::Function.register(:not, :logical, ->(logical) {
4
+ ! logical
5
+ })
@@ -0,0 +1,25 @@
1
+ require_relative '../function'
2
+ require_relative '../../exceptions'
3
+
4
+ Dentaku::AST::Function.register(:or, :logical, lambda { |*args|
5
+ if args.empty?
6
+ raise Dentaku::ArgumentError.for(
7
+ :too_few_arguments,
8
+ function_name: 'OR()', at_least: 1, given: 0
9
+ ), 'OR() requires at least one argument'
10
+ end
11
+
12
+ args.any? do |arg|
13
+ case arg
14
+ when TrueClass
15
+ true
16
+ when FalseClass, nil
17
+ false
18
+ else
19
+ raise Dentaku::ArgumentError.for(
20
+ :incompatible_type,
21
+ function_name: 'OR()', expect: :logical, actual: arg.class
22
+ ), 'OR() requires arguments to be logical expressions'
23
+ end
24
+ end
25
+ })