dentaku 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
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