dentaku 2.0.5 → 2.0.6

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