dentaku 2.0.11 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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