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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +97 -0
- data/README.md +5 -4
- data/lib/dentaku/ast.rb +2 -5
- data/lib/dentaku/ast/arithmetic.rb +72 -0
- data/lib/dentaku/ast/combinators.rb +17 -2
- data/lib/dentaku/ast/comparators.rb +4 -0
- data/lib/dentaku/ast/function.rb +18 -1
- data/lib/dentaku/ast/functions/if.rb +4 -0
- data/lib/dentaku/ast/functions/max.rb +1 -1
- data/lib/dentaku/ast/functions/min.rb +1 -1
- data/lib/dentaku/ast/functions/not.rb +1 -1
- data/lib/dentaku/ast/functions/round.rb +1 -1
- data/lib/dentaku/ast/functions/rounddown.rb +1 -1
- data/lib/dentaku/ast/functions/roundup.rb +1 -1
- data/lib/dentaku/ast/functions/ruby_math.rb +1 -1
- data/lib/dentaku/ast/grouping.rb +4 -0
- data/lib/dentaku/ast/literal.rb +20 -0
- data/lib/dentaku/ast/logical.rb +8 -0
- data/lib/dentaku/ast/negation.rb +11 -0
- data/lib/dentaku/ast/numeric.rb +3 -12
- data/lib/dentaku/ast/operation.rb +2 -0
- data/lib/dentaku/ast/string.rb +3 -12
- data/lib/dentaku/calculator.rb +3 -3
- data/lib/dentaku/parser.rb +3 -0
- data/lib/dentaku/token_scanner.rb +5 -0
- data/lib/dentaku/version.rb +1 -1
- data/spec/ast/addition_spec.rb +29 -0
- data/spec/ast/and_spec.rb +27 -0
- data/spec/ast/function_spec.rb +1 -1
- data/spec/ast/numeric_spec.rb +16 -0
- data/spec/external_function_spec.rb +4 -4
- data/spec/parser_spec.rb +9 -0
- data/spec/token_scanner_spec.rb +2 -2
- data/spec/tokenizer_spec.rb +12 -0
- metadata +12 -7
- data/lib/dentaku/ast/addition.rb +0 -15
- data/lib/dentaku/ast/division.rb +0 -15
- data/lib/dentaku/ast/exponentiation.rb +0 -15
- data/lib/dentaku/ast/multiplication.rb +0 -15
- data/lib/dentaku/ast/subtraction.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa37d9abba8c7bf0ea6d99f1f0ac472058772436
|
4
|
+
data.tar.gz: c07c67b22659bb6c26d7a70edd119bee53ada064
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](http://badge.fury.io/rb/dentaku)
|
6
6
|
[](https://travis-ci.org/rubysolo/dentaku)
|
7
7
|
[](https://codeclimate.com/github/rubysolo/dentaku)
|
8
|
+
[](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
|
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/
|
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
|
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 <
|
24
|
+
class Or < Combinator
|
10
25
|
def value(context={})
|
11
26
|
left.value(context) || right.value(context)
|
12
27
|
end
|
data/lib/dentaku/ast/function.rb
CHANGED
@@ -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
|
data/lib/dentaku/ast/grouping.rb
CHANGED
@@ -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
|
data/lib/dentaku/ast/negation.rb
CHANGED
@@ -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
|
data/lib/dentaku/ast/numeric.rb
CHANGED
@@ -1,17 +1,8 @@
|
|
1
|
+
require_relative "./literal"
|
2
|
+
|
1
3
|
module Dentaku
|
2
4
|
module AST
|
3
|
-
class Numeric <
|
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
|
data/lib/dentaku/ast/string.rb
CHANGED
@@ -1,17 +1,8 @@
|
|
1
|
+
require_relative "./literal"
|
2
|
+
|
1
3
|
module Dentaku
|
2
4
|
module AST
|
3
|
-
class String <
|
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
|
data/lib/dentaku/calculator.rb
CHANGED
@@ -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
|
|
data/lib/dentaku/parser.rb
CHANGED
@@ -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('(', '')
|
data/lib/dentaku/version.rb
CHANGED
@@ -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
|
data/spec/ast/function_spec.rb
CHANGED
@@ -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
|
data/spec/token_scanner_spec.rb
CHANGED
@@ -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
|
26
|
+
expect(described_class.scanners.length).to eq 12
|
27
27
|
end
|
28
28
|
end
|
data/spec/tokenizer_spec.rb
CHANGED
@@ -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.
|
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-
|
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/
|
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/
|
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
|
data/lib/dentaku/ast/addition.rb
DELETED
data/lib/dentaku/ast/division.rb
DELETED