dentaku 2.0.11 → 3.0.0

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +0 -1
  4. data/CHANGELOG.md +19 -0
  5. data/README.md +3 -2
  6. data/dentaku.gemspec +1 -0
  7. data/lib/dentaku/ast.rb +4 -0
  8. data/lib/dentaku/ast/access.rb +27 -0
  9. data/lib/dentaku/ast/arithmetic.rb +49 -7
  10. data/lib/dentaku/ast/case.rb +17 -3
  11. data/lib/dentaku/ast/combinators.rb +8 -2
  12. data/lib/dentaku/ast/function.rb +16 -0
  13. data/lib/dentaku/ast/function_registry.rb +15 -2
  14. data/lib/dentaku/ast/functions/and.rb +25 -0
  15. data/lib/dentaku/ast/functions/max.rb +1 -1
  16. data/lib/dentaku/ast/functions/min.rb +1 -1
  17. data/lib/dentaku/ast/functions/or.rb +25 -0
  18. data/lib/dentaku/ast/functions/round.rb +2 -2
  19. data/lib/dentaku/ast/functions/rounddown.rb +3 -2
  20. data/lib/dentaku/ast/functions/roundup.rb +3 -2
  21. data/lib/dentaku/ast/functions/ruby_math.rb +3 -3
  22. data/lib/dentaku/ast/functions/switch.rb +8 -0
  23. data/lib/dentaku/ast/identifier.rb +3 -2
  24. data/lib/dentaku/ast/negation.rb +5 -1
  25. data/lib/dentaku/ast/node.rb +3 -0
  26. data/lib/dentaku/bulk_expression_solver.rb +1 -2
  27. data/lib/dentaku/calculator.rb +7 -6
  28. data/lib/dentaku/exceptions.rb +75 -1
  29. data/lib/dentaku/parser.rb +73 -12
  30. data/lib/dentaku/token.rb +4 -0
  31. data/lib/dentaku/token_scanner.rb +20 -3
  32. data/lib/dentaku/tokenizer.rb +31 -4
  33. data/lib/dentaku/version.rb +1 -1
  34. data/spec/ast/addition_spec.rb +6 -6
  35. data/spec/ast/and_function_spec.rb +35 -0
  36. data/spec/ast/and_spec.rb +1 -1
  37. data/spec/ast/arithmetic_spec.rb +56 -0
  38. data/spec/ast/division_spec.rb +1 -1
  39. data/spec/ast/function_spec.rb +43 -6
  40. data/spec/ast/max_spec.rb +15 -0
  41. data/spec/ast/min_spec.rb +15 -0
  42. data/spec/ast/or_spec.rb +35 -0
  43. data/spec/ast/round_spec.rb +25 -0
  44. data/spec/ast/rounddown_spec.rb +25 -0
  45. data/spec/ast/roundup_spec.rb +25 -0
  46. data/spec/ast/switch_spec.rb +30 -0
  47. data/spec/calculator_spec.rb +26 -4
  48. data/spec/exceptions_spec.rb +1 -1
  49. data/spec/parser_spec.rb +22 -3
  50. data/spec/spec_helper.rb +12 -2
  51. data/spec/token_scanner_spec.rb +0 -4
  52. data/spec/tokenizer_spec.rb +40 -2
  53. metadata +39 -3
@@ -4,6 +4,6 @@ require 'dentaku/exceptions'
4
4
  describe Dentaku::UnboundVariableError do
5
5
  it 'includes variable name(s) in message' do
6
6
  exception = described_class.new(['length'])
7
- expect(exception.message).to match /length/
7
+ expect(exception.unbound_variables).to include('length')
8
8
  end
9
9
  end
@@ -14,7 +14,7 @@ describe Dentaku::Parser do
14
14
  plus = Dentaku::Token.new(:operator, :add)
15
15
  four = Dentaku::Token.new(:numeric, 4)
16
16
 
17
- node = described_class.new([five, plus, four]).parse
17
+ node = described_class.new([five, plus, four]).parse
18
18
  expect(node.value).to eq 9
19
19
  end
20
20
 
@@ -23,7 +23,7 @@ describe Dentaku::Parser do
23
23
  lt = Dentaku::Token.new(:comparator, :lt)
24
24
  four = Dentaku::Token.new(:numeric, 4)
25
25
 
26
- node = described_class.new([five, lt, four]).parse
26
+ node = described_class.new([five, lt, four]).parse
27
27
  expect(node.value).to eq false
28
28
  end
29
29
 
@@ -31,7 +31,7 @@ describe Dentaku::Parser do
31
31
  five = Dentaku::Token.new(:numeric, 5)
32
32
  mod = Dentaku::Token.new(:operator, :mod)
33
33
 
34
- node = described_class.new([five, mod]).parse
34
+ node = described_class.new([five, mod]).parse
35
35
  expect(node.value).to eq 0.05
36
36
  end
37
37
 
@@ -104,6 +104,17 @@ describe Dentaku::Parser do
104
104
  expect(node.value(x: 3)).to eq 15
105
105
  end
106
106
 
107
+ it 'evaluates access into data structures' do
108
+ a = token(:a)
109
+ lbracket = token(:lbracket)
110
+ one = token(1)
111
+ rbracket = token(:rbracket)
112
+
113
+ node = described_class.new([a, lbracket, one, rbracket]).parse
114
+ expect { node.value }.to raise_error(Dentaku::UnboundVariableError)
115
+ expect(node.value(a: [1, 2, 3])).to eq 2
116
+ end
117
+
107
118
  it 'evaluates boolean expressions' do
108
119
  d_true = Dentaku::Token.new(:logical, true)
109
120
  d_and = Dentaku::Token.new(:combinator, :and)
@@ -160,6 +171,14 @@ describe Dentaku::Parser do
160
171
  described_class.new([this, also]).parse
161
172
  }.to raise_error(Dentaku::ParseError)
162
173
  end
174
+
175
+ it 'raises an exception when trying to access an undefined function' do
176
+ fn = Dentaku::Token.new(:function, 'non_exists_func')
177
+
178
+ expect {
179
+ described_class.new([fn]).parse
180
+ }.to raise_error(Dentaku::ParseError)
181
+ end
163
182
  end
164
183
 
165
184
  it "evaluates explicit 'NULL' as a Nil" do
@@ -1,4 +1,8 @@
1
1
  require 'pry'
2
+ require 'coveralls'
3
+
4
+ # Check the amount of testcoverage
5
+ Coveralls.wear!
2
6
 
3
7
  # automatically create a token stream from bare values
4
8
  def token_stream(*args)
@@ -21,6 +25,8 @@ def type_for(value)
21
25
  :operator
22
26
  when :open, :close, :comma
23
27
  :grouping
28
+ when :lbracket, :rbracket
29
+ :access
24
30
  when :le, :ge, :ne, :ne, :lt, :gt, :eq
25
31
  :comparator
26
32
  when :and, :or
@@ -33,9 +39,13 @@ def type_for(value)
33
39
  end
34
40
 
35
41
  def identifier(name)
36
- Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, name))
42
+ Dentaku::AST::Identifier.new(token(name))
37
43
  end
38
44
 
39
45
  def literal(value)
40
- Dentaku::AST::Literal.new(Dentaku::Token.new(type_for(value), value))
46
+ Dentaku::AST::Literal.new(token(value))
47
+ end
48
+
49
+ def token(value)
50
+ Dentaku::Token.new(type_for(value), value)
41
51
  end
@@ -27,10 +27,6 @@ describe Dentaku::TokenScanner do
27
27
  expect(token.value).to eq(5)
28
28
  end
29
29
 
30
- it 'returns a list of all configured scanners' do
31
- expect(described_class.scanners.length).to eq 15
32
- end
33
-
34
30
  it 'allows customizing available scanners' do
35
31
  described_class.scanners = [:whitespace, :numeric]
36
32
  expect(described_class.scanners.length).to eq 2
@@ -7,6 +7,26 @@ describe Dentaku::Tokenizer do
7
7
  expect(tokenizer.tokenize('')).to be_empty
8
8
  end
9
9
 
10
+ it 'tokenizes numeric literal in decimal' do
11
+ token = tokenizer.tokenize('80').first
12
+ expect(token.category).to eq(:numeric)
13
+ expect(token.value).to eq(80)
14
+ end
15
+
16
+ it 'tokenizes numeric literal in hexadecimal' do
17
+ token = tokenizer.tokenize('0x80').first
18
+ expect(token.category).to eq(:numeric)
19
+ expect(token.value).to eq(128)
20
+ end
21
+
22
+ it 'tokenizes numeric literal in scientific notation' do
23
+ %w( 6.02e23 .602E+24 ).each do |s|
24
+ tokens = tokenizer.tokenize(s)
25
+ expect(tokens.map(&:category)).to eq([:numeric])
26
+ expect(tokens.map(&:value)).to eq([6.02e23])
27
+ end
28
+ end
29
+
10
30
  it 'tokenizes addition' do
11
31
  tokens = tokenizer.tokenize('1+1')
12
32
  expect(tokens.map(&:category)).to eq([:numeric, :operator, :numeric])
@@ -135,18 +155,36 @@ describe Dentaku::Tokenizer do
135
155
  expect(tokens.map(&:value)).to eq(['perimeter', :le, 7500])
136
156
  end
137
157
 
138
- it 'matches "and" for logical expressions' do
158
+ it 'tokenizes "and" for logical expressions' do
139
159
  tokens = tokenizer.tokenize('octopi <= 7500 AND sharks > 1500')
140
160
  expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
141
161
  expect(tokens.map(&:value)).to eq(['octopi', :le, 7500, :and, 'sharks', :gt, 1500])
142
162
  end
143
163
 
144
- it 'matches "or" for logical expressions' do
164
+ it 'tokenizes "or" for logical expressions' do
145
165
  tokens = tokenizer.tokenize('size < 3 or admin = 1')
146
166
  expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
147
167
  expect(tokens.map(&:value)).to eq(['size', :lt, 3, :or, 'admin', :eq, 1])
148
168
  end
149
169
 
170
+ it 'tokenizes "&&" for logical expressions' do
171
+ tokens = tokenizer.tokenize('octopi <= 7500 && sharks > 1500')
172
+ expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
173
+ expect(tokens.map(&:value)).to eq(['octopi', :le, 7500, :and, 'sharks', :gt, 1500])
174
+ end
175
+
176
+ it 'tokenizes "||" for logical expressions' do
177
+ tokens = tokenizer.tokenize('size < 3 || admin = 1')
178
+ expect(tokens.map(&:category)).to eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
179
+ expect(tokens.map(&:value)).to eq(['size', :lt, 3, :or, 'admin', :eq, 1])
180
+ end
181
+
182
+ it 'tokenizes square brackets for data structure access' do
183
+ tokens = tokenizer.tokenize('a[1]')
184
+ expect(tokens.map(&:category)).to eq(%i(identifier access numeric access))
185
+ expect(tokens.map(&:value)).to eq(['a', :lbracket, 1, :rbracket])
186
+ end
187
+
150
188
  it 'detects unbalanced parentheses' do
151
189
  expect { tokenizer.tokenize('(5+3') }.to raise_error(Dentaku::TokenizerError, /too many opening parentheses/)
152
190
  expect { tokenizer.tokenize(')') }.to raise_error(Dentaku::TokenizerError, /too many closing parentheses/)
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.11
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Solomon White
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-08 00:00:00.000000000 Z
11
+ date: 2017-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: " Dentaku is a parser and evaluator for mathematical formulas\n"
56
70
  email:
57
71
  - rubysolo@gmail.com
@@ -69,6 +83,7 @@ files:
69
83
  - dentaku.gemspec
70
84
  - lib/dentaku.rb
71
85
  - lib/dentaku/ast.rb
86
+ - lib/dentaku/ast/access.rb
72
87
  - lib/dentaku/ast/arithmetic.rb
73
88
  - lib/dentaku/ast/bitwise.rb
74
89
  - lib/dentaku/ast/case.rb
@@ -82,15 +97,18 @@ files:
82
97
  - lib/dentaku/ast/datetime.rb
83
98
  - lib/dentaku/ast/function.rb
84
99
  - lib/dentaku/ast/function_registry.rb
100
+ - lib/dentaku/ast/functions/and.rb
85
101
  - lib/dentaku/ast/functions/if.rb
86
102
  - lib/dentaku/ast/functions/max.rb
87
103
  - lib/dentaku/ast/functions/min.rb
88
104
  - lib/dentaku/ast/functions/not.rb
105
+ - lib/dentaku/ast/functions/or.rb
89
106
  - lib/dentaku/ast/functions/round.rb
90
107
  - lib/dentaku/ast/functions/rounddown.rb
91
108
  - lib/dentaku/ast/functions/roundup.rb
92
109
  - lib/dentaku/ast/functions/ruby_math.rb
93
110
  - lib/dentaku/ast/functions/string_functions.rb
111
+ - lib/dentaku/ast/functions/switch.rb
94
112
  - lib/dentaku/ast/grouping.rb
95
113
  - lib/dentaku/ast/identifier.rb
96
114
  - lib/dentaku/ast/literal.rb
@@ -113,13 +131,22 @@ files:
113
131
  - lib/dentaku/tokenizer.rb
114
132
  - lib/dentaku/version.rb
115
133
  - spec/ast/addition_spec.rb
134
+ - spec/ast/and_function_spec.rb
116
135
  - spec/ast/and_spec.rb
136
+ - spec/ast/arithmetic_spec.rb
117
137
  - spec/ast/case_spec.rb
118
138
  - spec/ast/division_spec.rb
119
139
  - spec/ast/function_spec.rb
140
+ - spec/ast/max_spec.rb
141
+ - spec/ast/min_spec.rb
120
142
  - spec/ast/node_spec.rb
121
143
  - spec/ast/numeric_spec.rb
144
+ - spec/ast/or_spec.rb
145
+ - spec/ast/round_spec.rb
146
+ - spec/ast/rounddown_spec.rb
147
+ - spec/ast/roundup_spec.rb
122
148
  - spec/ast/string_functions_spec.rb
149
+ - spec/ast/switch_spec.rb
123
150
  - spec/benchmark.rb
124
151
  - spec/bulk_expression_solver_spec.rb
125
152
  - spec/calculator_spec.rb
@@ -152,19 +179,28 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
179
  version: '0'
153
180
  requirements: []
154
181
  rubyforge_project: dentaku
155
- rubygems_version: 2.5.1
182
+ rubygems_version: 2.6.13
156
183
  signing_key:
157
184
  specification_version: 4
158
185
  summary: A formula language parser and evaluator
159
186
  test_files:
160
187
  - spec/ast/addition_spec.rb
188
+ - spec/ast/and_function_spec.rb
161
189
  - spec/ast/and_spec.rb
190
+ - spec/ast/arithmetic_spec.rb
162
191
  - spec/ast/case_spec.rb
163
192
  - spec/ast/division_spec.rb
164
193
  - spec/ast/function_spec.rb
194
+ - spec/ast/max_spec.rb
195
+ - spec/ast/min_spec.rb
165
196
  - spec/ast/node_spec.rb
166
197
  - spec/ast/numeric_spec.rb
198
+ - spec/ast/or_spec.rb
199
+ - spec/ast/round_spec.rb
200
+ - spec/ast/rounddown_spec.rb
201
+ - spec/ast/roundup_spec.rb
167
202
  - spec/ast/string_functions_spec.rb
203
+ - spec/ast/switch_spec.rb
168
204
  - spec/benchmark.rb
169
205
  - spec/bulk_expression_solver_spec.rb
170
206
  - spec/calculator_spec.rb