dentaku 2.0.0 → 2.0.1

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -0
  3. data/README.md +5 -4
  4. data/lib/dentaku/ast.rb +2 -5
  5. data/lib/dentaku/ast/arithmetic.rb +72 -0
  6. data/lib/dentaku/ast/combinators.rb +17 -2
  7. data/lib/dentaku/ast/comparators.rb +4 -0
  8. data/lib/dentaku/ast/function.rb +18 -1
  9. data/lib/dentaku/ast/functions/if.rb +4 -0
  10. data/lib/dentaku/ast/functions/max.rb +1 -1
  11. data/lib/dentaku/ast/functions/min.rb +1 -1
  12. data/lib/dentaku/ast/functions/not.rb +1 -1
  13. data/lib/dentaku/ast/functions/round.rb +1 -1
  14. data/lib/dentaku/ast/functions/rounddown.rb +1 -1
  15. data/lib/dentaku/ast/functions/roundup.rb +1 -1
  16. data/lib/dentaku/ast/functions/ruby_math.rb +1 -1
  17. data/lib/dentaku/ast/grouping.rb +4 -0
  18. data/lib/dentaku/ast/literal.rb +20 -0
  19. data/lib/dentaku/ast/logical.rb +8 -0
  20. data/lib/dentaku/ast/negation.rb +11 -0
  21. data/lib/dentaku/ast/numeric.rb +3 -12
  22. data/lib/dentaku/ast/operation.rb +2 -0
  23. data/lib/dentaku/ast/string.rb +3 -12
  24. data/lib/dentaku/calculator.rb +3 -3
  25. data/lib/dentaku/parser.rb +3 -0
  26. data/lib/dentaku/token_scanner.rb +5 -0
  27. data/lib/dentaku/version.rb +1 -1
  28. data/spec/ast/addition_spec.rb +29 -0
  29. data/spec/ast/and_spec.rb +27 -0
  30. data/spec/ast/function_spec.rb +1 -1
  31. data/spec/ast/numeric_spec.rb +16 -0
  32. data/spec/external_function_spec.rb +4 -4
  33. data/spec/parser_spec.rb +9 -0
  34. data/spec/token_scanner_spec.rb +2 -2
  35. data/spec/tokenizer_spec.rb +12 -0
  36. metadata +12 -7
  37. data/lib/dentaku/ast/addition.rb +0 -15
  38. data/lib/dentaku/ast/division.rb +0 -15
  39. data/lib/dentaku/ast/exponentiation.rb +0 -15
  40. data/lib/dentaku/ast/multiplication.rb +0 -15
  41. data/lib/dentaku/ast/subtraction.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 85e18c02f88a1fc906c69667776e00ccb1f969f5
4
- data.tar.gz: 6fba624e3b93a4e64e2ce55fb6fe13f42d9138be
3
+ metadata.gz: fa37d9abba8c7bf0ea6d99f1f0ac472058772436
4
+ data.tar.gz: c07c67b22659bb6c26d7a70edd119bee53ada064
5
5
  SHA512:
6
- metadata.gz: befe922779a83e64b95ed17907846a29901c59badb2810d2c28f2555aabc5359f7a21a1aeac6cca12b63c39bb59dad225ce233bfccc59b91af9eea027f3ab3cf
7
- data.tar.gz: 2bac8daaa567d69860f8c5d00ed52375f989bde4893bb7b405ca513c257edf987a742ea7dc97d5817d8fa6023a82a77040bd69e17ca487c4f2fc45c8c6fba4e1
6
+ metadata.gz: 4b489326fecca7a6a114c64d01efd17149992338167e42cec9fd71f1e12b96bafb72a91ccf2b8b4f6ab895b85d72a74ceb12ad3c3a2b574bff7f3a43471bbb2f
7
+ data.tar.gz: 58bf32696c4c604269f117fa062dc5ea155dd6c9d2eab7007611dd8bc75b129b332d63dfa15aff3deb4f5c6bd1153aed9546fa3ffbca0b59a8c9fa398dfc0868
data/CHANGELOG.md ADDED
@@ -0,0 +1,97 @@
1
+ # Change Log
2
+
3
+ ## [v2.0.1] 2015-08-15
4
+ - add support for boolean literals
5
+ - implement basic parse-time type checking
6
+
7
+ ## [v2.0.0] 2015-08-07
8
+ - shunting-yard parser for performance enhancement and AST generation
9
+ - AST caching for performance enhancement
10
+ - support comments in formulas
11
+ - support all functions from the Ruby Math module
12
+
13
+ ## [v1.2.6] 2015-05-30
14
+ - support custom error handlers for systems of formulas
15
+
16
+ ## [v1.2.5] 2015-05-23
17
+ - fix memory leak
18
+
19
+ ## [v1.2.2] 2014-12-19
20
+ - performance enhancements
21
+ - unary minus bug fixes
22
+ - preserve provided hash keys for systems of formulas
23
+
24
+ ## [v1.2.0] 2014-10-21
25
+ - add dependency resolution to automatically solve systems of formulas
26
+
27
+ ## [v1.1.0] 2014-07-30
28
+ - add strict evaluation mode to raise `UnboundVariableError` if not all variable values are provided
29
+ - return division results as `BigDecimal` values
30
+
31
+ ## [v1.0.0] 2014-03-06
32
+ - cleanup and 1.0 release
33
+
34
+ ## [v0.2.14] 2014-01-24
35
+ - add modulo operator
36
+ - add unary percentage operator
37
+ - support registration of custom functions at runtime
38
+
39
+ ## [v0.2.10] 2012-12-10
40
+ - return integer result for exact division, decimal otherwise
41
+
42
+ ## [v0.2.9] 2012-10-17
43
+ - add `ROUNDUP` / `ROUNDDOWN` functions
44
+
45
+ ## [v0.2.8] 2012-09-30
46
+ - make function name matching case-insensitive
47
+
48
+ ## [v0.2.7] 2012-09-26
49
+ - support passing arbitrary expressions as function arguments
50
+
51
+ ## [v0.2.6] 2012-09-19
52
+ - add `NOT` function
53
+
54
+ ## [v0.2.5] 2012-06-20
55
+ - add exponent operator
56
+ - add support for digits in variable identifiers
57
+
58
+ ## [v0.2.4] 2012-02-29
59
+ - add support for `min < x < max` syntax for inequality ranges
60
+
61
+ ## [v0.2.2] 2012-02-22
62
+ - support `ROUND` to arbitrary decimal place on older Rubies
63
+ - ensure case is preserved for string values
64
+
65
+ ## [v0.2.1] 2012-02-12
66
+ - add `ROUND` function
67
+
68
+ ## [v0.1.3] 2012-01-31
69
+ - add support for string datatype
70
+
71
+ ## [v0.1.1] 2012-01-24
72
+ - change from square bracket to parentheses for top-level evaluation
73
+ - add `IF` function
74
+
75
+ ## [v0.1.0] 2012-01-20
76
+ - initial release
77
+
78
+ [v2.0.0]: https://github.com/rubysolo/dentaku/compare/v1.2.6...v2.0.0
79
+ [v1.2.6]: https://github.com/rubysolo/dentaku/compare/v1.2.5...v1.2.6
80
+ [v1.2.5]: https://github.com/rubysolo/dentaku/compare/v1.2.2...v1.2.5
81
+ [v1.2.2]: https://github.com/rubysolo/dentaku/compare/v1.2.0...v1.2.2
82
+ [v1.2.0]: https://github.com/rubysolo/dentaku/compare/v1.1.0...v1.2.0
83
+ [v1.1.0]: https://github.com/rubysolo/dentaku/compare/v1.0.0...v1.1.0
84
+ [v1.0.0]: https://github.com/rubysolo/dentaku/compare/v0.2.14...v1.0.0
85
+ [v0.2.14]: https://github.com/rubysolo/dentaku/compare/v0.2.10...v0.2.14
86
+ [v0.2.10]: https://github.com/rubysolo/dentaku/compare/v0.2.9...v0.2.10
87
+ [v0.2.9]: https://github.com/rubysolo/dentaku/compare/v0.2.8...v0.2.9
88
+ [v0.2.8]: https://github.com/rubysolo/dentaku/compare/v0.2.7...v0.2.8
89
+ [v0.2.7]: https://github.com/rubysolo/dentaku/compare/v0.2.6...v0.2.7
90
+ [v0.2.6]: https://github.com/rubysolo/dentaku/compare/v0.2.5...v0.2.6
91
+ [v0.2.5]: https://github.com/rubysolo/dentaku/compare/v0.2.4...v0.2.5
92
+ [v0.2.4]: https://github.com/rubysolo/dentaku/compare/v0.2.2...v0.2.4
93
+ [v0.2.2]: https://github.com/rubysolo/dentaku/compare/v0.2.1...v0.2.2
94
+ [v0.2.1]: https://github.com/rubysolo/dentaku/compare/v0.1.3...v0.2.1
95
+ [v0.1.3]: https://github.com/rubysolo/dentaku/compare/v0.1.1...v0.1.3
96
+ [v0.1.1]: https://github.com/rubysolo/dentaku/compare/v0.1.0...v0.1.1
97
+ [v0.1.0]: https://github.com/rubysolo/dentaku/commit/68724fd9c8fa637baf7b9d5515df0caa31e226bd
data/README.md CHANGED
@@ -5,6 +5,7 @@ Dentaku
5
5
  [![Gem Version](https://badge.fury.io/rb/dentaku.png)](http://badge.fury.io/rb/dentaku)
6
6
  [![Build Status](https://travis-ci.org/rubysolo/dentaku.png?branch=master)](https://travis-ci.org/rubysolo/dentaku)
7
7
  [![Code Climate](https://codeclimate.com/github/rubysolo/dentaku.png)](https://codeclimate.com/github/rubysolo/dentaku)
8
+ [![Hakiri](https://hakiri.io/github/rubysolo/dentaku/master.svg)](https://hakiri.io/github/rubysolo/dentaku)
8
9
 
9
10
  DESCRIPTION
10
11
  -----------
@@ -204,15 +205,15 @@ that's not feasible because:
204
205
  1. The formula is the secret sauce for your startup
205
206
 
206
207
  Whatever your reasons, Dentaku supports adding functions at runtime. To add a
207
- function, you'll need to specify a name and a lambda that accepts all function
208
- arguments and returns the result value.
208
+ function, you'll need to specify a name, a return type, and a lambda that
209
+ accepts all function arguments and returns the result value.
209
210
 
210
211
  Here's an example of adding a function named `POW` that implements
211
212
  exponentiation.
212
213
 
213
214
  ```ruby
214
215
  > c = Dentaku::Calculator.new
215
- > c.add_function(:pow, ->(mantissa, exponent) { mantissa ** exponent })
216
+ > c.add_function(:pow, :numeric, ->(mantissa, exponent) { mantissa ** exponent })
216
217
  > c.evaluate('POW(3,2)')
217
218
  #=> 9
218
219
  > c.evaluate('POW(2,3)')
@@ -223,7 +224,7 @@ Here's an example of adding a variadic function:
223
224
 
224
225
  ```ruby
225
226
  > c = Dentaku::Calculator.new
226
- > c.add_function(:max, ->(*args) { args.max })
227
+ > c.add_function(:max, :numeric, ->(*args) { args.max })
227
228
  > c.evaluate 'MAX(8,6,7,5,3,0,9)'
228
229
  #=> 9
229
230
  ```
data/lib/dentaku/ast.rb CHANGED
@@ -1,13 +1,10 @@
1
1
  require_relative './ast/node'
2
2
  require_relative './ast/nil'
3
3
  require_relative './ast/numeric'
4
+ require_relative './ast/logical'
4
5
  require_relative './ast/string'
5
6
  require_relative './ast/identifier'
6
- require_relative './ast/addition'
7
- require_relative './ast/subtraction'
8
- require_relative './ast/multiplication'
9
- require_relative './ast/division'
10
- require_relative './ast/exponentiation'
7
+ require_relative './ast/arithmetic'
11
8
  require_relative './ast/negation'
12
9
  require_relative './ast/comparators'
13
10
  require_relative './ast/combinators'
@@ -0,0 +1,72 @@
1
+ require_relative './operation'
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Arithmetic < Operation
6
+ def initialize(*)
7
+ super
8
+ fail "#{ self.class } requires numeric operands" unless valid_node?(left) && valid_node?(right)
9
+ end
10
+
11
+ def type
12
+ :numeric
13
+ end
14
+
15
+ private
16
+
17
+ def valid_node?(node)
18
+ node.is_a?(Identifier) || node.type == :numeric
19
+ end
20
+ end
21
+
22
+ class Addition < Arithmetic
23
+ def value(context={})
24
+ left.value(context) + right.value(context)
25
+ end
26
+
27
+ def self.precedence
28
+ 10
29
+ end
30
+ end
31
+
32
+ class Subtraction < Arithmetic
33
+ def value(context={})
34
+ left.value(context) - right.value(context)
35
+ end
36
+
37
+ def self.precedence
38
+ 10
39
+ end
40
+ end
41
+
42
+ class Multiplication < Arithmetic
43
+ def value(context={})
44
+ left.value(context) * right.value(context)
45
+ end
46
+
47
+ def self.precedence
48
+ 20
49
+ end
50
+ end
51
+
52
+ class Division < Arithmetic
53
+ def value(context={})
54
+ left.value(context) / right.value(context)
55
+ end
56
+
57
+ def self.precedence
58
+ 20
59
+ end
60
+ end
61
+
62
+ class Exponentiation < Arithmetic
63
+ def value(context={})
64
+ left.value(context) ** right.value(context)
65
+ end
66
+
67
+ def self.precedence
68
+ 30
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,12 +1,27 @@
1
+ require_relative './operation'
2
+
1
3
  module Dentaku
2
4
  module AST
3
- class And < Operation
5
+ class Combinator < Operation
6
+ def initialize(*)
7
+ super
8
+ fail "#{ self.class } requires logical operands" unless valid_node?(left) && valid_node?(right)
9
+ end
10
+
11
+ private
12
+
13
+ def valid_node?(node)
14
+ node.is_a?(Identifier) || node.type == :logical
15
+ end
16
+ end
17
+
18
+ class And < Combinator
4
19
  def value(context={})
5
20
  left.value(context) && right.value(context)
6
21
  end
7
22
  end
8
23
 
9
- class Or < Operation
24
+ class Or < Combinator
10
25
  def value(context={})
11
26
  left.value(context) || right.value(context)
12
27
  end
@@ -6,6 +6,10 @@ module Dentaku
6
6
  def self.precedence
7
7
  5
8
8
  end
9
+
10
+ def type
11
+ :logical
12
+ end
9
13
  end
10
14
 
11
15
  class LessThan < Comparator
@@ -15,7 +15,7 @@ module Dentaku
15
15
  registry.fetch(function_name(name)) { fail "Undefined function #{ name } "}
16
16
  end
17
17
 
18
- def self.register(name, implementation)
18
+ def self.register(name, type, implementation)
19
19
  function = Class.new(self) do
20
20
  def self.implementation=(impl)
21
21
  @implementation = impl
@@ -25,13 +25,30 @@ module Dentaku
25
25
  @implementation
26
26
  end
27
27
 
28
+ def self.type=(type)
29
+ @type = type
30
+ end
31
+
32
+ def self.type
33
+ @type
34
+ end
35
+
28
36
  def value(context={})
29
37
  args = @args.flat_map { |a| a.value(context) }
30
38
  self.class.implementation.call(*args)
31
39
  end
40
+
41
+ def type
42
+ self.class.type
43
+ end
32
44
  end
33
45
 
46
+ function_class = name.to_s.capitalize
47
+ Dentaku::AST.send(:remove_const, function_class) if Dentaku::AST.const_defined?(function_class)
48
+ Dentaku::AST.const_set(function_class, function)
49
+
34
50
  function.implementation = implementation
51
+ function.type = type
35
52
 
36
53
  registry[function_name(name)] = function
37
54
  end
@@ -15,6 +15,10 @@ module Dentaku
15
15
  predicate.value(context) ? left.value(context) : right.value(context)
16
16
  end
17
17
 
18
+ def type
19
+ left.type
20
+ end
21
+
18
22
  def dependencies(context={})
19
23
  # TODO : short-circuit?
20
24
  (predicate.dependencies(context) + left.dependencies(context) + right.dependencies(context)).uniq
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:max, ->(*args) {
3
+ Dentaku::AST::Function.register(:max, :numeric, ->(*args) {
4
4
  args.max
5
5
  })
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:min, ->(*args) {
3
+ Dentaku::AST::Function.register(:min, :numeric, ->(*args) {
4
4
  args.min
5
5
  })
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:not, ->(logical) {
3
+ Dentaku::AST::Function.register(:not, :logical, ->(logical) {
4
4
  ! logical
5
5
  })
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:round, ->(numeric, places=nil) {
3
+ Dentaku::AST::Function.register(:round, :numeric, ->(numeric, places=nil) {
4
4
  numeric.round(places || 0)
5
5
  })
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:rounddown, ->(numeric) {
3
+ Dentaku::AST::Function.register(:rounddown, :numeric, ->(numeric) {
4
4
  numeric.floor
5
5
  })
@@ -1,5 +1,5 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:roundup, ->(numeric) {
3
+ Dentaku::AST::Function.register(:roundup, :numeric, ->(numeric) {
4
4
  numeric.ceil
5
5
  })
@@ -2,7 +2,7 @@
2
2
  require_relative "../function"
3
3
 
4
4
  Math.methods(false).each do |method|
5
- Dentaku::AST::Function.register(method, ->(*args) {
5
+ Dentaku::AST::Function.register(method, :numeric, ->(*args) {
6
6
  Math.send(method, *args)
7
7
  })
8
8
  end
@@ -8,6 +8,10 @@ module Dentaku
8
8
  def value(context={})
9
9
  @node.value(context)
10
10
  end
11
+
12
+ def type
13
+ @node.type
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -0,0 +1,20 @@
1
+ module Dentaku
2
+ module AST
3
+ class Literal < Node
4
+ attr_reader :type
5
+
6
+ def initialize(token)
7
+ @value = token.value
8
+ @type = token.category
9
+ end
10
+
11
+ def value(*)
12
+ @value
13
+ end
14
+
15
+ def dependencies(*)
16
+ []
17
+ end
18
+ end
19
+ end
20
+ 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
@@ -3,12 +3,17 @@ module Dentaku
3
3
  class Negation < Operation
4
4
  def initialize(node)
5
5
  @node = node
6
+ fail "Negation requires numeric operand" unless valid_node?(node)
6
7
  end
7
8
 
8
9
  def value(context={})
9
10
  @node.value(context) * -1
10
11
  end
11
12
 
13
+ def type
14
+ :numeric
15
+ end
16
+
12
17
  def self.arity
13
18
  1
14
19
  end
@@ -20,6 +25,12 @@ module Dentaku
20
25
  def self.precedence
21
26
  40
22
27
  end
28
+
29
+ private
30
+
31
+ def valid_node?(node)
32
+ node.is_a?(Identifier) || node.type == :numeric
33
+ end
23
34
  end
24
35
  end
25
36
  end
@@ -1,17 +1,8 @@
1
+ require_relative "./literal"
2
+
1
3
  module Dentaku
2
4
  module AST
3
- class Numeric < Node
4
- def initialize(token)
5
- @value = token.value
6
- end
7
-
8
- def value(*)
9
- @value
10
- end
11
-
12
- def dependencies(*)
13
- []
14
- end
5
+ class Numeric < Literal
15
6
  end
16
7
  end
17
8
  end
@@ -1,3 +1,5 @@
1
+ require_relative './node'
2
+
1
3
  module Dentaku
2
4
  module AST
3
5
  class Operation < Node
@@ -1,17 +1,8 @@
1
+ require_relative "./literal"
2
+
1
3
  module Dentaku
2
4
  module AST
3
- class String < Node
4
- def initialize(token)
5
- @value = token.value
6
- end
7
-
8
- def value(*)
9
- @value
10
- end
11
-
12
- def dependencies(*)
13
- []
14
- end
5
+ class String < Literal
15
6
  end
16
7
  end
17
8
  end
@@ -14,13 +14,13 @@ module Dentaku
14
14
  @ast_cache = {}
15
15
  end
16
16
 
17
- def add_function(name, body)
18
- Dentaku::AST::Function.register(name, body)
17
+ def add_function(name, type, body)
18
+ Dentaku::AST::Function.register(name, type, body)
19
19
  self
20
20
  end
21
21
 
22
22
  def add_functions(fns)
23
- fns.each { |(name, body)| add_function(name, body) }
23
+ fns.each { |(name, type, body)| add_function(name, type, body) }
24
24
  self
25
25
  end
26
26
 
@@ -28,6 +28,9 @@ module Dentaku
28
28
  when :numeric
29
29
  output.push AST::Numeric.new(token)
30
30
 
31
+ when :logical
32
+ output.push AST::Logical.new(token)
33
+
31
34
  when :string
32
35
  output.push AST::String.new(token)
33
36
 
@@ -35,6 +35,7 @@ module Dentaku
35
35
  grouping,
36
36
  comparator,
37
37
  combinator,
38
+ boolean,
38
39
  function,
39
40
  identifier
40
41
  ]
@@ -88,6 +89,10 @@ module Dentaku
88
89
  new(:combinator, '(and|or)\b', lambda { |raw| raw.strip.downcase.to_sym })
89
90
  end
90
91
 
92
+ def boolean
93
+ new(:logical, '(true|false)\b', lambda { |raw| raw.strip.downcase == 'true' })
94
+ end
95
+
91
96
  def function
92
97
  new(:function, '\w+\s*\(', lambda do |raw|
93
98
  function_name = raw.gsub('(', '')
@@ -1,3 +1,3 @@
1
1
  module Dentaku
2
- VERSION = "2.0.0"
2
+ VERSION = "2.0.1"
3
3
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/arithmetic'
3
+
4
+ require 'dentaku/token'
5
+
6
+ describe Dentaku::AST::Addition do
7
+ let(:five) { Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 5) }
8
+ let(:six) { Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 6) }
9
+
10
+ let(:t) { Dentaku::AST::Numeric.new Dentaku::Token.new(:logical, true) }
11
+
12
+ it 'performs addition' do
13
+ node = described_class.new(five, six)
14
+ expect(node.value).to eq 11
15
+ end
16
+
17
+ it 'requires numeric operands' do
18
+ expect {
19
+ described_class.new(five, t)
20
+ }.to raise_error
21
+
22
+ expression = Dentaku::AST::Multiplication.new(five, five)
23
+ group = Dentaku::AST::Grouping.new(expression)
24
+
25
+ expect {
26
+ described_class.new(group, five)
27
+ }.not_to raise_error
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/combinators'
3
+
4
+ require 'dentaku/token'
5
+
6
+ describe Dentaku::AST::And do
7
+ let(:t) { Dentaku::AST::Logical.new Dentaku::Token.new(:logical, true) }
8
+ let(:f) { Dentaku::AST::Logical.new Dentaku::Token.new(:logical, false) }
9
+
10
+ let(:five) { Dentaku::AST::Numeric.new Dentaku::Token.new(:numeric, 5) }
11
+
12
+ it 'performs logical AND' do
13
+ node = described_class.new(t, f)
14
+ expect(node.value).to eq false
15
+ end
16
+
17
+ it 'requires logical operands' do
18
+ expect {
19
+ described_class.new(t, five)
20
+ }.to raise_error
21
+
22
+ expression = Dentaku::AST::LessThanOrEqual.new(five, five)
23
+ expect {
24
+ described_class.new(t, expression)
25
+ }.not_to raise_error
26
+ end
27
+ end
@@ -11,7 +11,7 @@ describe Dentaku::AST::Function do
11
11
  end
12
12
 
13
13
  it 'registers a custom function' do
14
- described_class.register("flarble", -> { "flarble" })
14
+ described_class.register("flarble", :string, -> { "flarble" })
15
15
  expect { described_class.get("flarble") }.not_to raise_error
16
16
  function = described_class.get("flarble").new
17
17
  expect(function.value).to eq "flarble"
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/numeric'
3
+
4
+ require 'dentaku/token'
5
+
6
+ describe Dentaku::AST::Numeric do
7
+ subject { described_class.new(Dentaku::Token.new(:numeric, 5)) }
8
+
9
+ it 'has numeric type' do
10
+ expect(subject.type).to eq :numeric
11
+ end
12
+
13
+ it 'has no dependencies' do
14
+ expect(subject.dependencies).to be_empty
15
+ end
16
+ end
@@ -8,12 +8,12 @@ describe Dentaku::Calculator do
8
8
  let(:with_external_funcs) do
9
9
  c = described_class.new
10
10
 
11
- c.add_function(:now, -> { Time.now.to_s })
11
+ c.add_function(:now, :string, -> { Time.now.to_s })
12
12
 
13
13
  fns = [
14
- [:pow, ->(mantissa, exponent) { mantissa ** exponent }],
15
- [:biggest, ->(*args) { args.max }],
16
- [:smallest, ->(*args) { args.min }],
14
+ [:pow, :numeric, ->(mantissa, exponent) { mantissa ** exponent }],
15
+ [:biggest, :numeric, ->(*args) { args.max }],
16
+ [:smallest, :numeric, ->(*args) { args.min }],
17
17
  ]
18
18
 
19
19
  c.add_functions(fns)
data/spec/parser_spec.rb CHANGED
@@ -85,4 +85,13 @@ describe Dentaku::Parser do
85
85
  expect { node.value }.to raise_error
86
86
  expect(node.value(x: 3)).to eq 15
87
87
  end
88
+
89
+ it 'evaluates boolean expressions' do
90
+ d_true = Dentaku::Token.new(:logical, true)
91
+ d_and = Dentaku::Token.new(:combinator, :and)
92
+ d_false = Dentaku::Token.new(:logical, false)
93
+
94
+ node = described_class.new([d_true, d_and, d_false]).parse
95
+ expect(node.value).to eq false
96
+ end
88
97
  end
@@ -3,7 +3,7 @@ require 'dentaku/token_scanner'
3
3
  describe Dentaku::TokenScanner do
4
4
  let(:whitespace) { described_class.new(:whitespace, '\s') }
5
5
  let(:numeric) { described_class.new(:numeric, '(\d+(\.\d+)?|\.\d+)',
6
- lambda{|raw| raw =~ /\./ ? BigDecimal.new(raw) : raw.to_i })
6
+ lambda { |raw| raw =~ /\./ ? BigDecimal.new(raw) : raw.to_i })
7
7
  }
8
8
 
9
9
  it 'returns a token for a matching string' do
@@ -23,6 +23,6 @@ describe Dentaku::TokenScanner do
23
23
  end
24
24
 
25
25
  it 'returns a list of all configured scanners' do
26
- expect(described_class.scanners.length).to eq 11
26
+ expect(described_class.scanners.length).to eq 12
27
27
  end
28
28
  end
@@ -153,6 +153,18 @@ describe Dentaku::Tokenizer do
153
153
  expect(tokens.map(&:value)).to eq(['andover', :lt, 10])
154
154
  end
155
155
 
156
+ it 'tokenizes TRUE and FALSE literals' do
157
+ tokens = tokenizer.tokenize('true and false')
158
+ expect(tokens.length).to eq(3)
159
+ expect(tokens.map(&:category)).to eq([:logical, :combinator, :logical])
160
+ expect(tokens.map(&:value)).to eq([true, :and, false])
161
+
162
+ tokens = tokenizer.tokenize('true_lies and falsehoods')
163
+ expect(tokens.length).to eq(3)
164
+ expect(tokens.map(&:category)).to eq([:identifier, :combinator, :identifier])
165
+ expect(tokens.map(&:value)).to eq(['true_lies', :and, 'falsehoods'])
166
+ end
167
+
156
168
  describe 'functions' do
157
169
  it 'include IF' do
158
170
  tokens = tokenizer.tokenize('if(x < 10, y, z)')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dentaku
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Solomon White
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-07 00:00:00.000000000 Z
11
+ date: 2015-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -63,17 +63,16 @@ files:
63
63
  - ".gitignore"
64
64
  - ".pryrc"
65
65
  - ".travis.yml"
66
+ - CHANGELOG.md
66
67
  - Gemfile
67
68
  - README.md
68
69
  - Rakefile
69
70
  - dentaku.gemspec
70
71
  - lib/dentaku.rb
71
72
  - lib/dentaku/ast.rb
72
- - lib/dentaku/ast/addition.rb
73
+ - lib/dentaku/ast/arithmetic.rb
73
74
  - lib/dentaku/ast/combinators.rb
74
75
  - lib/dentaku/ast/comparators.rb
75
- - lib/dentaku/ast/division.rb
76
- - lib/dentaku/ast/exponentiation.rb
77
76
  - lib/dentaku/ast/function.rb
78
77
  - lib/dentaku/ast/functions/if.rb
79
78
  - lib/dentaku/ast/functions/max.rb
@@ -85,14 +84,14 @@ files:
85
84
  - lib/dentaku/ast/functions/ruby_math.rb
86
85
  - lib/dentaku/ast/grouping.rb
87
86
  - lib/dentaku/ast/identifier.rb
88
- - lib/dentaku/ast/multiplication.rb
87
+ - lib/dentaku/ast/literal.rb
88
+ - lib/dentaku/ast/logical.rb
89
89
  - lib/dentaku/ast/negation.rb
90
90
  - lib/dentaku/ast/nil.rb
91
91
  - lib/dentaku/ast/node.rb
92
92
  - lib/dentaku/ast/numeric.rb
93
93
  - lib/dentaku/ast/operation.rb
94
94
  - lib/dentaku/ast/string.rb
95
- - lib/dentaku/ast/subtraction.rb
96
95
  - lib/dentaku/bulk_expression_solver.rb
97
96
  - lib/dentaku/calculator.rb
98
97
  - lib/dentaku/dependency_resolver.rb
@@ -104,8 +103,11 @@ files:
104
103
  - lib/dentaku/token_scanner.rb
105
104
  - lib/dentaku/tokenizer.rb
106
105
  - lib/dentaku/version.rb
106
+ - spec/ast/addition_spec.rb
107
+ - spec/ast/and_spec.rb
107
108
  - spec/ast/function_spec.rb
108
109
  - spec/ast/node_spec.rb
110
+ - spec/ast/numeric_spec.rb
109
111
  - spec/benchmark.rb
110
112
  - spec/bulk_expression_solver_spec.rb
111
113
  - spec/calculator_spec.rb
@@ -142,8 +144,11 @@ signing_key:
142
144
  specification_version: 4
143
145
  summary: A formula language parser and evaluator
144
146
  test_files:
147
+ - spec/ast/addition_spec.rb
148
+ - spec/ast/and_spec.rb
145
149
  - spec/ast/function_spec.rb
146
150
  - spec/ast/node_spec.rb
151
+ - spec/ast/numeric_spec.rb
147
152
  - spec/benchmark.rb
148
153
  - spec/bulk_expression_solver_spec.rb
149
154
  - spec/calculator_spec.rb
@@ -1,15 +0,0 @@
1
- require_relative './operation'
2
-
3
- module Dentaku
4
- module AST
5
- class Addition < Operation
6
- def value(context={})
7
- left.value(context) + right.value(context)
8
- end
9
-
10
- def self.precedence
11
- 10
12
- end
13
- end
14
- end
15
- end
@@ -1,15 +0,0 @@
1
- require_relative './operation'
2
-
3
- module Dentaku
4
- module AST
5
- class Division < Operation
6
- def value(context={})
7
- left.value(context) / right.value(context)
8
- end
9
-
10
- def self.precedence
11
- 20
12
- end
13
- end
14
- end
15
- end
@@ -1,15 +0,0 @@
1
- require_relative './operation'
2
-
3
- module Dentaku
4
- module AST
5
- class Exponentiation < Operation
6
- def value(context={})
7
- left.value(context) ** right.value(context)
8
- end
9
-
10
- def self.precedence
11
- 30
12
- end
13
- end
14
- end
15
- end
@@ -1,15 +0,0 @@
1
- require_relative './operation'
2
-
3
- module Dentaku
4
- module AST
5
- class Multiplication < Operation
6
- def value(context={})
7
- left.value(context) * right.value(context)
8
- end
9
-
10
- def self.precedence
11
- 20
12
- end
13
- end
14
- end
15
- end
@@ -1,15 +0,0 @@
1
- require_relative './operation'
2
-
3
- module Dentaku
4
- module AST
5
- class Subtraction < Operation
6
- def value(context={})
7
- left.value(context) - right.value(context)
8
- end
9
-
10
- def self.precedence
11
- 10
12
- end
13
- end
14
- end
15
- end