dentaku 2.0.5 → 2.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 91e1c068c1ad4f4399eece3c6ac66ab9c17591cf
4
- data.tar.gz: 2df26e17bb06fa076d30e56e880b5d5797df1dc3
3
+ metadata.gz: 813c0e0ea4c6a021ca324515d26386d2f2f28962
4
+ data.tar.gz: 943958582e98916af345553a343b261b978c97c5
5
5
  SHA512:
6
- metadata.gz: 83222ea1e9505e2cdfa692f37142776fdb7be70cf39098d536b6a0c85958272421800f04e7876f9d7ba39bfe4d3eb19a49f2efa5b631b0ecddea4ed858c3e4ca
7
- data.tar.gz: 32dc26ce15c748d7091c775356735fabf5abdf3f391db3407b663769e56c361247478edae5f3b3dc725aa97cb44b3a635e5212b293dc97af9940dedab83852a8
6
+ metadata.gz: d281ac5672cb9badc8ba74033d5624bd2e23d12f37785a6fc47595be0de51c8da9a2419eb0701de273e6ed710212988f47a16a72fef9fcbc119a8c39b38ebad4
7
+ data.tar.gz: 8f3ca27e26d5a603c99d2473a3115fb87c00407127b7d1613f0c0634057f37318583de7a7f8192d47db0ea8236648e916ea6dbc6084add0b591b4939e95c48f1
data/.travis.yml CHANGED
@@ -1,4 +1,7 @@
1
1
  language: ruby
2
+ before_install:
3
+ - gem update --system
4
+ - gem update bundler
2
5
  rvm:
3
6
  - 1.9.3
4
7
  - 2.0.0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [v2.0.6] 2016-01-26
6
+ - support array parameters for external functions
7
+ - support case statements
8
+ - support precision for `ROUNDUP` and `ROUNDDOWN` functions
9
+ - prevent errors from corrupting calculator memory
10
+
3
11
  ## [v2.0.5] 2015-09-03
4
12
  - fix bug with detecting unbound nodes
5
13
  - silence warnings
@@ -90,6 +98,8 @@
90
98
  ## [v0.1.0] 2012-01-20
91
99
  - initial release
92
100
 
101
+ [Unreleased]: https://github.com/rubysolo/dentaku/compare/v2.0.6...HEAD
102
+ [v2.0.6]: https://github.com/rubysolo/dentaku/compare/v2.0.5...v2.0.6
93
103
  [v2.0.5]: https://github.com/rubysolo/dentaku/compare/v2.0.4...v2.0.5
94
104
  [v2.0.4]: https://github.com/rubysolo/dentaku/compare/v2.0.3...v2.0.4
95
105
  [v2.0.3]: https://github.com/rubysolo/dentaku/compare/v2.0.1...v2.0.3
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in dentaku.gemspec
4
4
  gemspec
5
+
6
+ if RUBY_VERSION.to_f >= 2.0 && RUBY_ENGINE == 'ruby'
7
+ gem 'pry-byebug'
8
+ gem 'pry-stack_explorer'
9
+ end
data/README.md CHANGED
@@ -260,21 +260,7 @@ THANKS
260
260
 
261
261
  Big thanks to [ElkStone Basements](http://www.elkstonebasements.com/) for
262
262
  allowing me to extract and open source this code. Thanks also to all the
263
- contributors:
264
-
265
- * [0xCCD](https://github.com/0xCCD)
266
- * [AlexeyMK](https://github.com/AlexeyMK)
267
- * [antonversal](https://github.com/antonversal)
268
- * [arnaudl](https://github.com/arnaudl)
269
- * [bernardofire](https://github.com/bernardofire)
270
- * [brixen](https://github.com/brixen)
271
- * [CraigCottingham](https://github.com/CraigCottingham)
272
- * [glanotte](https://github.com/glanotte)
273
- * [jasonhutchens](https://github.com/jasonhutchens)
274
- * [jmangs](https://github.com/jmangs)
275
- * [mvbrocato](https://github.com/mvbrocato)
276
- * [schneidmaster](https://github.com/schneidmaster)
277
- * [thbar](https://github.com/thbar) / [BoxCar](https://www.boxcar.io)
263
+ [contributors](https://github.com/rubysolo/dentaku/graphs/contributors)!
278
264
 
279
265
 
280
266
  LICENSE
data/lib/dentaku/ast.rb CHANGED
@@ -9,6 +9,7 @@ require_relative './ast/negation'
9
9
  require_relative './ast/comparators'
10
10
  require_relative './ast/combinators'
11
11
  require_relative './ast/grouping'
12
+ require_relative './ast/case'
12
13
  require_relative './ast/functions/if'
13
14
  require_relative './ast/functions/max'
14
15
  require_relative './ast/functions/min'
@@ -0,0 +1,52 @@
1
+ require_relative './case/case_conditional'
2
+ require_relative './case/case_when'
3
+ require_relative './case/case_then'
4
+ require_relative './case/case_switch_variable'
5
+ require_relative './case/case_else'
6
+
7
+ module Dentaku
8
+ module AST
9
+ class Case < Node
10
+ def initialize(*nodes)
11
+ @switch = nodes.shift
12
+
13
+ unless @switch.is_a?(AST::CaseSwitchVariable)
14
+ raise 'Case missing switch variable'
15
+ end
16
+
17
+ @conditions = nodes
18
+
19
+ @else = @conditions.pop if @conditions.last.is_a?(AST::CaseElse)
20
+
21
+ @conditions.each do |condition|
22
+ unless condition.is_a?(AST::CaseConditional)
23
+ raise "#{condition} is not a CaseConditional"
24
+ end
25
+ end
26
+ end
27
+
28
+ def value(context={})
29
+ switch_value = @switch.value(context)
30
+ @conditions.each do |condition|
31
+ if condition.when.value(context) == switch_value
32
+ return condition.then.value(context)
33
+ end
34
+ end
35
+
36
+ if @else
37
+ return @else.value(context)
38
+ else
39
+ raise "No block matched the switch value '#{switch_value}'"
40
+ end
41
+ end
42
+
43
+ def dependencies(context={})
44
+ # TODO: should short-circuit
45
+ @switch.dependencies(context) +
46
+ @conditions.flat_map do |condition|
47
+ condition.dependencies(context)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseConditional < Node
4
+ attr_reader :when,
5
+ :then
6
+
7
+ def initialize(when_statement, then_statement)
8
+ @when = when_statement
9
+ unless @when.is_a?(AST::CaseWhen)
10
+ raise 'Expected first argument to be a CaseWhen'
11
+ end
12
+ @then = then_statement
13
+ unless @then.is_a?(AST::CaseThen)
14
+ raise 'Expected second argument to be a CaseThen'
15
+ end
16
+ end
17
+
18
+ def dependencies(context={})
19
+ @when.dependencies(context) + @then.dependencies(context)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseElse < Node
4
+ def self.arity
5
+ 1
6
+ end
7
+
8
+ def initialize(node)
9
+ @node = node
10
+ end
11
+
12
+ def value(context={})
13
+ @node.value(context)
14
+ end
15
+
16
+ def dependencies(context={})
17
+ @node.dependencies(context)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseSwitchVariable < Node
4
+ def initialize(node)
5
+ @node = node
6
+ end
7
+
8
+ def value(context={})
9
+ @node.value(context)
10
+ end
11
+
12
+ def dependencies(context={})
13
+ @node.dependencies(context)
14
+ end
15
+
16
+ def self.arity
17
+ 1
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseThen < Node
4
+ def self.arity
5
+ 1
6
+ end
7
+
8
+ def initialize(node)
9
+ @node = node
10
+ end
11
+
12
+ def value(context={})
13
+ @node.value(context)
14
+ end
15
+
16
+ def dependencies(context={})
17
+ @node.dependencies(context)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Dentaku
2
+ module AST
3
+ class CaseWhen < Operation
4
+ def self.arity
5
+ 1
6
+ end
7
+
8
+ def initialize(node)
9
+ @node = node
10
+ end
11
+
12
+ def value(context={})
13
+ @node.value(context)
14
+ end
15
+
16
+ def dependencies(context={})
17
+ @node.dependencies(context)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -34,7 +34,7 @@ module Dentaku
34
34
  end
35
35
 
36
36
  def value(context={})
37
- args = @args.flat_map { |a| a.value(context) }
37
+ args = @args.map { |a| a.value(context) }
38
38
  self.class.implementation.call(*args)
39
39
  end
40
40
 
@@ -1,5 +1,7 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:rounddown, :numeric, ->(numeric) {
4
- numeric.floor
3
+ Dentaku::AST::Function.register(:rounddown, :numeric, ->(numeric, precision=0) {
4
+ tens = 10.0**precision
5
+ result = (numeric * tens).floor / tens
6
+ precision <= 0 ? result.to_i : result
5
7
  })
@@ -1,5 +1,7 @@
1
1
  require_relative '../function'
2
2
 
3
- Dentaku::AST::Function.register(:roundup, :numeric, ->(numeric) {
4
- numeric.ceil
3
+ Dentaku::AST::Function.register(:roundup, :numeric, ->(numeric, precision=0) {
4
+ tens = 10.0**precision
5
+ result = (numeric * tens).ceil / tens
6
+ precision <= 0 ? result.to_i : result
5
7
  })
@@ -71,9 +71,14 @@ module Dentaku
71
71
  end
72
72
 
73
73
  if block_given?
74
- result = yield
75
- @memory = restore
76
- return result
74
+ begin
75
+ result = yield
76
+ @memory = restore
77
+ return result
78
+ rescue => e
79
+ @memory = restore
80
+ raise e
81
+ end
77
82
  end
78
83
 
79
84
  self
@@ -4,11 +4,11 @@ module Dentaku
4
4
  class Parser
5
5
  attr_reader :input, :output, :operations, :arities
6
6
 
7
- def initialize(tokens)
7
+ def initialize(tokens, options={})
8
8
  @input = tokens.dup
9
9
  @output = []
10
- @operations = []
11
- @arities = []
10
+ @operations = options.fetch(:operations, [])
11
+ @arities = options.fetch(:arities, [])
12
12
  end
13
13
 
14
14
  def get_args(count)
@@ -58,6 +58,90 @@ module Dentaku
58
58
  arities.push 0
59
59
  operations.push function(token)
60
60
 
61
+ when :case
62
+ case token.value
63
+ when :open
64
+ # special handling for case nesting: strip out inner case
65
+ # statements and parse their AST segments recursively
66
+ if operations.include?(AST::Case)
67
+ last_case_close_index = nil
68
+ first_nested_case_close_index = nil
69
+ input.each_with_index do |token, index|
70
+ first_nested_case_close_index = last_case_close_index
71
+ if token.category == :case && token.value == :close
72
+ last_case_close_index = index
73
+ end
74
+ end
75
+ inner_case_inputs = input.slice!(0..first_nested_case_close_index)
76
+ subparser = Parser.new(
77
+ inner_case_inputs,
78
+ operations: [AST::Case],
79
+ arities: [0]
80
+ )
81
+ subparser.parse
82
+ output.concat(subparser.output)
83
+ else
84
+ operations.push AST::Case
85
+ arities.push(0)
86
+ end
87
+ when :close
88
+ if operations[1] == AST::CaseThen
89
+ while operations.last != AST::Case
90
+ consume
91
+ end
92
+
93
+ operations.push(AST::CaseConditional)
94
+ consume(2)
95
+ arities[-1] += 1
96
+ elsif operations[1] == AST::CaseElse
97
+ while operations.last != AST::Case
98
+ consume
99
+ end
100
+
101
+ arities[-1] += 1
102
+ end
103
+
104
+ unless operations.count == 1 && operations.last == AST::Case
105
+ fail "Unprocessed token #{ token.value }"
106
+ end
107
+ consume(arities.pop.succ)
108
+ when :when
109
+ if operations[1] == AST::CaseThen
110
+ while ![AST::CaseWhen, AST::Case].include?(operations.last)
111
+ consume
112
+ end
113
+ operations.push(AST::CaseConditional)
114
+ consume(2)
115
+ arities[-1] += 1
116
+ elsif operations.last == AST::Case
117
+ operations.push(AST::CaseSwitchVariable)
118
+ consume
119
+ end
120
+
121
+ operations.push(AST::CaseWhen)
122
+ when :then
123
+ if operations[1] == AST::CaseWhen
124
+ while ![AST::CaseThen, AST::Case].include?(operations.last)
125
+ consume
126
+ end
127
+ end
128
+ operations.push(AST::CaseThen)
129
+ when :else
130
+ if operations[1] == AST::CaseThen
131
+ while operations.last != AST::Case
132
+ consume
133
+ end
134
+
135
+ operations.push(AST::CaseConditional)
136
+ consume(2)
137
+ arities[-1] += 1
138
+ end
139
+
140
+ operations.push(AST::CaseElse)
141
+ else
142
+ fail "Unknown case token #{ token.value }"
143
+ end
144
+
61
145
  when :grouping
62
146
  case token.value
63
147
  when :open
@@ -33,6 +33,7 @@ module Dentaku
33
33
  :negate,
34
34
  :operator,
35
35
  :grouping,
36
+ :case_statement,
36
37
  :comparator,
37
38
  :combinator,
38
39
  :boolean,
@@ -100,6 +101,11 @@ module Dentaku
100
101
  new(:grouping, '\(|\)|,', lambda { |raw| names[raw] })
101
102
  end
102
103
 
104
+ def case_statement
105
+ names = { open: 'case', close: 'end', then: 'then', when: 'when', else: 'else' }.invert
106
+ new(:case, '(case|end|then|when|else)\b', lambda { |raw| names[raw.downcase] })
107
+ end
108
+
103
109
  def comparator
104
110
  names = { le: '<=', ge: '>=', ne: '!=', lt: '<', gt: '>', eq: '=' }.invert
105
111
  alternate = { ne: '<>', eq: '==' }.invert
@@ -1,3 +1,3 @@
1
1
  module Dentaku
2
- VERSION = "2.0.5"
2
+ VERSION = "2.0.6"
3
3
  end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'dentaku/ast/operation'
3
+ require 'dentaku/ast/logical'
4
+ require 'dentaku/ast/identifier'
5
+ require 'dentaku/ast/arithmetic'
6
+ require 'dentaku/ast/case'
7
+
8
+ require 'dentaku/token'
9
+
10
+ describe Dentaku::AST::Case do
11
+ let!(:one) { Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 1) }
12
+ let!(:two) { Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 2) }
13
+ let!(:apple) do
14
+ Dentaku::AST::Logical.new Dentaku::Token.new(:string, 'apple')
15
+ end
16
+ let!(:banana) do
17
+ Dentaku::AST::Logical.new Dentaku::Token.new(:string, 'banana')
18
+ end
19
+ let!(:identifier) do
20
+ Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, :fruit))
21
+ end
22
+ let!(:switch) { Dentaku::AST::CaseSwitchVariable.new(identifier) }
23
+
24
+ let!(:when1) { Dentaku::AST::CaseWhen.new(apple) }
25
+ let!(:then1) { Dentaku::AST::CaseThen.new(one) }
26
+ let!(:conditional1) { Dentaku::AST::CaseConditional.new(when1, then1) }
27
+
28
+ let!(:when2) { Dentaku::AST::CaseWhen.new(banana) }
29
+ let!(:then2) { Dentaku::AST::CaseThen.new(two) }
30
+ let!(:conditional2) { Dentaku::AST::CaseConditional.new(when2, then2) }
31
+
32
+ describe '#value' do
33
+ it 'raises an exception if there is no switch variable' do
34
+ expect { described_class.new(conditional1, conditional2) }
35
+ .to raise_error('Case missing switch variable')
36
+ end
37
+
38
+ it 'raises an exception if a non-conditional is passed' do
39
+ expect { described_class.new(switch, conditional1, when2) }
40
+ .to raise_error(/is not a CaseConditional/)
41
+ end
42
+
43
+ it 'tests each conditional against the switch variable' do
44
+ node = described_class.new(switch, conditional1, conditional2)
45
+ expect(node.value(fruit: 'banana')).to eq(2)
46
+ end
47
+
48
+ it 'raises an exception if the conditional is not matched' do
49
+ node = described_class.new(switch, conditional1, conditional2)
50
+ expect { node.value(fruit: 'orange') }
51
+ .to raise_error("No block matched the switch value 'orange'")
52
+ end
53
+
54
+ it 'uses the else value if provided and conditional is not matched' do
55
+ three = Dentaku::AST::Logical.new Dentaku::Token.new(:numeric, 3)
56
+ else_statement = Dentaku::AST::CaseElse.new(three)
57
+ node = described_class.new(
58
+ switch,
59
+ conditional1,
60
+ conditional2,
61
+ else_statement)
62
+ expect(node.value(fruit: 'orange')).to eq(3)
63
+ end
64
+ end
65
+
66
+ describe '#dependencies' do
67
+ let!(:tax) do
68
+ Dentaku::AST::Identifier.new(Dentaku::Token.new(:identifier, :tax))
69
+ end
70
+ let!(:addition) { Dentaku::AST::Addition.new(two, tax) }
71
+ let!(:when2) { Dentaku::AST::CaseWhen.new(banana) }
72
+ let!(:then2) { Dentaku::AST::CaseThen.new(addition) }
73
+ let!(:conditional2) { Dentaku::AST::CaseConditional.new(when2, then2) }
74
+
75
+ it 'gathers dependencies from switch and conditionals' do
76
+ node = described_class.new(switch, conditional1, conditional2)
77
+ expect(node.dependencies).to eq([:fruit, :tax])
78
+ end
79
+ end
80
+ end
@@ -130,6 +130,17 @@ describe Dentaku::Calculator do
130
130
  expect(calculator.solve(expressions) { :foo })
131
131
  .to eq(more_apples: :foo)
132
132
  end
133
+
134
+ it "solves remainder of expressions with unbound variable" do
135
+ calculator.store(peaches: 1, oranges: 1)
136
+ expressions = { more_apples: "apples + 1", more_peaches: "peaches + 1" }
137
+ result = calculator.solve(expressions)
138
+ expect(calculator.memory).to eq("peaches" => 1, "oranges" => 1)
139
+ expect(result).to eq(
140
+ more_apples: :undefined,
141
+ more_peaches: 2
142
+ )
143
+ end
133
144
  end
134
145
 
135
146
  it 'evaluates a statement with no variables' do
@@ -227,6 +238,104 @@ describe Dentaku::Calculator do
227
238
  result = calculator.evaluate('number_of_sheets / if(multi_color, sheets_per_minute_color, sheets_per_minute_black)')
228
239
  expect(result).to eq(5)
229
240
  end
241
+
242
+ describe 'roundup' do
243
+ it 'should work with one argument' do
244
+ expect(calculator.evaluate('roundup(1.234)')).to eq(2)
245
+ end
246
+
247
+ it 'should accept second precision argument like in Office formula' do
248
+ expect(calculator.evaluate('roundup(1.234, 2)')).to eq(1.24)
249
+ end
250
+ end
251
+
252
+ describe 'rounddown' do
253
+ it 'should work with one argument' do
254
+ expect(calculator.evaluate('rounddown(1.234)')).to eq(1)
255
+ end
256
+
257
+ it 'should accept second precision argument like in Office formula' do
258
+ expect(calculator.evaluate('rounddown(1.234, 2)')).to eq(1.23)
259
+ end
260
+ end
261
+ end
262
+
263
+ describe 'case statements' do
264
+ it 'handles complex then statements' do
265
+ formula = <<-FORMULA
266
+ CASE fruit
267
+ WHEN 'apple'
268
+ THEN (1 * quantity)
269
+ WHEN 'banana'
270
+ THEN (2 * quantity)
271
+ END
272
+ FORMULA
273
+ expect(calculator.evaluate(formula, quantity: 3, fruit: 'apple')).to eq(3)
274
+ expect(calculator.evaluate(formula, quantity: 3, fruit: 'banana')).to eq(6)
275
+ end
276
+
277
+ it 'handles complex when statements' do
278
+ formula = <<-FORMULA
279
+ CASE number
280
+ WHEN (2 * 2)
281
+ THEN 1
282
+ WHEN (2 * 3)
283
+ THEN 2
284
+ END
285
+ FORMULA
286
+ expect(calculator.evaluate(formula, number: 4)).to eq(1)
287
+ expect(calculator.evaluate(formula, number: 6)).to eq(2)
288
+ end
289
+
290
+ it 'throws an exception when no match and there is no default value' do
291
+ formula = <<-FORMULA
292
+ CASE number
293
+ WHEN 42
294
+ THEN 1
295
+ END
296
+ FORMULA
297
+ expect { calculator.evaluate(formula, number: 2) }
298
+ .to raise_error("No block matched the switch value '2'")
299
+ end
300
+
301
+ it 'handles a default else statement' do
302
+ formula = <<-FORMULA
303
+ CASE fruit
304
+ WHEN 'apple'
305
+ THEN 1 * quantity
306
+ WHEN 'banana'
307
+ THEN 2 * quantity
308
+ ELSE
309
+ 3 * quantity
310
+ END
311
+ FORMULA
312
+ expect(calculator.evaluate(formula, quantity: 1, fruit: 'banana')).to eq(2)
313
+ expect(calculator.evaluate(formula, quantity: 1, fruit: 'orange')).to eq(3)
314
+ end
315
+
316
+ it 'handles nested case statements' do
317
+ formula = <<-FORMULA
318
+ CASE fruit
319
+ WHEN 'apple'
320
+ THEN 1 * quantity
321
+ WHEN 'banana'
322
+ THEN
323
+ CASE quantity
324
+ WHEN 1 THEN 2
325
+ WHEN 10 THEN
326
+ CASE type
327
+ WHEN 'organic' THEN 5
328
+ END
329
+ END
330
+ END
331
+ FORMULA
332
+ value = calculator.evaluate(
333
+ formula,
334
+ type: 'organic',
335
+ quantity: 10,
336
+ fruit: 'banana')
337
+ expect(value).to eq(5)
338
+ end
230
339
  end
231
340
 
232
341
  describe 'math functions' do
@@ -38,6 +38,19 @@ describe Dentaku::Calculator do
38
38
  it 'includes SMALLEST' do
39
39
  expect(with_external_funcs.evaluate('SMALLEST(8,6,7,5,3,0,9)')).to eq(0)
40
40
  end
41
+
42
+ it 'supports array parameters' do
43
+ calculator = described_class.new
44
+ calculator.add_function(
45
+ :includes,
46
+ :logical,
47
+ ->(haystack, needle) {
48
+ haystack.include?(needle)
49
+ }
50
+ )
51
+
52
+ expect(calculator.evaluate("INCLUDES(list, 2)", list: [1,2,3])).to eq(true)
53
+ end
41
54
  end
42
55
  end
43
56
  end
data/spec/parser_spec.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'dentaku/token'
2
3
  require 'dentaku/parser'
3
4
 
4
5
  describe Dentaku::Parser do
@@ -94,4 +95,32 @@ describe Dentaku::Parser do
94
95
  node = described_class.new([d_true, d_and, d_false]).parse
95
96
  expect(node.value).to eq false
96
97
  end
98
+
99
+ it 'evaluates a case statement' do
100
+ case_start = Dentaku::Token.new(:case, :open)
101
+ x = Dentaku::Token.new(:identifier, :x)
102
+ case_when1 = Dentaku::Token.new(:case, :when)
103
+ one = Dentaku::Token.new(:numeric, 1)
104
+ case_then1 = Dentaku::Token.new(:case, :then)
105
+ two = Dentaku::Token.new(:numeric, 2)
106
+ case_when2 = Dentaku::Token.new(:case, :when)
107
+ three = Dentaku::Token.new(:numeric, 3)
108
+ case_then2 = Dentaku::Token.new(:case, :then)
109
+ four = Dentaku::Token.new(:numeric, 4)
110
+ case_close = Dentaku::Token.new(:case, :close)
111
+
112
+ node = described_class.new(
113
+ [case_start,
114
+ x,
115
+ case_when1,
116
+ one,
117
+ case_then1,
118
+ two,
119
+ case_when2,
120
+ three,
121
+ case_then2,
122
+ four,
123
+ case_close]).parse
124
+ expect(node.value(x: 3)).to eq(4)
125
+ end
97
126
  end
@@ -28,7 +28,7 @@ describe Dentaku::TokenScanner do
28
28
  end
29
29
 
30
30
  it 'returns a list of all configured scanners' do
31
- expect(described_class.scanners.length).to eq 12
31
+ expect(described_class.scanners.length).to eq 13
32
32
  end
33
33
 
34
34
  it 'allows customizing available scanners' do
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.5
4
+ version: 2.0.6
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-10-19 00:00:00.000000000 Z
11
+ date: 2016-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -71,6 +71,12 @@ files:
71
71
  - lib/dentaku.rb
72
72
  - lib/dentaku/ast.rb
73
73
  - lib/dentaku/ast/arithmetic.rb
74
+ - lib/dentaku/ast/case.rb
75
+ - lib/dentaku/ast/case/case_conditional.rb
76
+ - lib/dentaku/ast/case/case_else.rb
77
+ - lib/dentaku/ast/case/case_switch_variable.rb
78
+ - lib/dentaku/ast/case/case_then.rb
79
+ - lib/dentaku/ast/case/case_when.rb
74
80
  - lib/dentaku/ast/combinators.rb
75
81
  - lib/dentaku/ast/comparators.rb
76
82
  - lib/dentaku/ast/function.rb
@@ -105,6 +111,7 @@ files:
105
111
  - lib/dentaku/version.rb
106
112
  - spec/ast/addition_spec.rb
107
113
  - spec/ast/and_spec.rb
114
+ - spec/ast/case_spec.rb
108
115
  - spec/ast/division_spec.rb
109
116
  - spec/ast/function_spec.rb
110
117
  - spec/ast/node_spec.rb
@@ -148,6 +155,7 @@ summary: A formula language parser and evaluator
148
155
  test_files:
149
156
  - spec/ast/addition_spec.rb
150
157
  - spec/ast/and_spec.rb
158
+ - spec/ast/case_spec.rb
151
159
  - spec/ast/division_spec.rb
152
160
  - spec/ast/function_spec.rb
153
161
  - spec/ast/node_spec.rb
@@ -164,4 +172,3 @@ test_files:
164
172
  - spec/token_scanner_spec.rb
165
173
  - spec/token_spec.rb
166
174
  - spec/tokenizer_spec.rb
167
- has_rdoc: