soroban 0.7.3 → 0.8.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.
- data/.travis.yml +4 -3
- data/README.md +4 -4
- data/Soroban.gemspec +3 -3
- data/VERSION +1 -1
- data/lib/soroban.rb +1 -2
- data/lib/soroban/cell.rb +47 -18
- data/lib/soroban/errors.rb +20 -0
- data/lib/soroban/functions.rb +31 -21
- data/lib/soroban/functions/and.rb +4 -2
- data/lib/soroban/functions/average.rb +2 -2
- data/lib/soroban/functions/exp.rb +1 -3
- data/lib/soroban/functions/if.rb +1 -1
- data/lib/soroban/functions/ln.rb +1 -3
- data/lib/soroban/functions/max.rb +2 -2
- data/lib/soroban/functions/min.rb +2 -2
- data/lib/soroban/functions/not.rb +1 -3
- data/lib/soroban/functions/or.rb +4 -2
- data/lib/soroban/functions/sum.rb +2 -2
- data/lib/soroban/functions/vlookup.rb +2 -2
- data/lib/soroban/helpers.rb +62 -38
- data/lib/soroban/import.rb +4 -1
- data/lib/soroban/import/ruby_xl_importer.rb +21 -15
- data/lib/soroban/import/ruby_xl_patch.rb +2 -0
- data/lib/soroban/label_walker.rb +10 -9
- data/lib/soroban/parser.rb +6 -4
- data/lib/soroban/parser/grammar.rb +1474 -1472
- data/lib/soroban/parser/grammar.treetop +71 -67
- data/lib/soroban/parser/nodes.rb +49 -47
- data/lib/soroban/parser/rewrite.rb +20 -13
- data/lib/soroban/sheet.rb +33 -29
- data/lib/soroban/value_walker.rb +19 -19
- data/spec/documentation_spec.rb +31 -43
- data/spec/import_spec.rb +5 -13
- data/spec/soroban_spec.rb +6 -2
- data/spec/spec_helper.rb +8 -0
- metadata +6 -6
- data/lib/soroban/error.rb +0 -20
data/lib/soroban/value_walker.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
require 'soroban/errors'
|
2
|
+
require 'soroban/label_walker'
|
3
|
+
|
1
4
|
module Soroban
|
2
5
|
|
3
|
-
# An enumerable that allows cells in a range to be visited.
|
6
|
+
# An enumerable that allows values of cells in a range to be visited.
|
4
7
|
class ValueWalker
|
5
8
|
|
6
9
|
include Enumerable
|
@@ -8,40 +11,37 @@ module Soroban
|
|
8
11
|
# Create a new walker from a supplied range and binding. The binding is
|
9
12
|
# required when calculating the value of each visited cell.
|
10
13
|
def initialize(range, context)
|
11
|
-
@
|
12
|
-
@
|
14
|
+
@_range, @_binding = range, context
|
15
|
+
@_labels = Soroban::LabelWalker.new(range).to_a
|
13
16
|
end
|
14
17
|
|
15
18
|
# Yield the value of each cell referenced by the supplied range.
|
16
19
|
def each
|
17
|
-
@
|
20
|
+
@_labels.each { |label| yield eval("get('#{label}')", @_binding) }
|
18
21
|
end
|
19
22
|
|
20
|
-
#
|
23
|
+
# Get the value of a cell within the range by index. Will raise a RangeError
|
24
|
+
# if the index is outside of the range.
|
21
25
|
def [](index)
|
22
|
-
|
23
|
-
|
24
|
-
raise Soroban::RangeError, "Index #{index} falls outside of '#{@range}'"
|
26
|
+
if index < 0 || index >= @_labels.length
|
27
|
+
raise Soroban::RangeError, "Index #{index} falls outside of '#{@_range}'"
|
25
28
|
end
|
26
|
-
eval("get('#{
|
29
|
+
eval("get('#{@_labels[index]}')", @_binding)
|
27
30
|
end
|
28
31
|
|
29
|
-
# Set the value of a cell within the range by index
|
32
|
+
# Set the value of a cell within the range by index. Will raise a RangeError
|
33
|
+
# if the index is outside of the range.
|
30
34
|
def []=(index, value)
|
31
|
-
|
32
|
-
|
33
|
-
if index == count
|
34
|
-
eval("@#{label}.set('#{value}')", @binding)
|
35
|
-
return value
|
36
|
-
end
|
37
|
-
count += 1
|
35
|
+
if index < 0 || index >= @_labels.length
|
36
|
+
raise Soroban::RangeError, "Index #{index} falls outside of '#{@_range}'"
|
38
37
|
end
|
39
|
-
|
38
|
+
eval("@#{@_labels[index]}.set('#{value}')", @_binding)
|
39
|
+
return value
|
40
40
|
end
|
41
41
|
|
42
42
|
# Display the range if the user outputs the binding directly
|
43
43
|
def to_s
|
44
|
-
@
|
44
|
+
@_range
|
45
45
|
end
|
46
46
|
alias inspect to_s
|
47
47
|
|
data/spec/documentation_spec.rb
CHANGED
@@ -2,93 +2,81 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
2
|
|
3
3
|
describe "Documentation" do
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
# Example Usage
|
8
|
-
|
9
|
-
s = Soroban::Sheet.new()
|
5
|
+
let(:s) { Soroban::Sheet.new }
|
10
6
|
|
7
|
+
it "usage works" do
|
11
8
|
s.A1 = 2
|
12
9
|
s.set('B1:B5' => [1,2,3,4,5])
|
13
10
|
s.C1 = "=SUM(A1, B1:B5, 5) + A1 ^ 3"
|
14
11
|
s.C2 = "=IF(C1>30,'Large','Tiny')"
|
15
12
|
|
16
|
-
|
13
|
+
# puts s.C1 # => 30
|
17
14
|
s.C1.should eq(30)
|
18
15
|
|
19
16
|
s.bind(:input => :A1, :output => :C2)
|
20
17
|
|
21
|
-
|
18
|
+
# puts s.output # => "Tiny"
|
22
19
|
s.output.should eq('Tiny')
|
23
20
|
|
24
21
|
s.input = 3
|
25
22
|
|
26
|
-
|
23
|
+
# puts s.output # => "Large"
|
27
24
|
s.output.should eq('Large')
|
28
|
-
|
25
|
+
# puts s.C1 # => 50
|
29
26
|
s.C1.should eq(50)
|
27
|
+
end
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
s = Soroban::Sheet.new()
|
34
|
-
|
29
|
+
it "bindings work" do
|
35
30
|
s.set(:A1 => 'hello', 'B1:B5' => [1,2,3,4,5])
|
36
31
|
|
37
32
|
s.bind(:foo => :A1, :bar => 'B1:B5')
|
38
33
|
|
39
|
-
|
34
|
+
# puts s.foo # => 'hello'
|
40
35
|
s.foo.should eq('hello')
|
41
|
-
|
36
|
+
# puts s.bar[0] # => 1
|
42
37
|
s.bar[0].should eq(1)
|
43
38
|
|
44
39
|
s.bar[0] = 'howdy'
|
45
|
-
|
46
|
-
|
47
|
-
puts s.B1 # => 'howdy'
|
40
|
+
# puts s.B1 # => 'howdy'
|
48
41
|
s.B1.should eq('howdy')
|
42
|
+
end
|
49
43
|
|
50
|
-
|
51
|
-
|
52
|
-
s = Soroban::Sheet.new()
|
53
|
-
|
44
|
+
it "persistence works" do
|
54
45
|
s.F1 = "= E1 + SUM(D1:D5)"
|
55
46
|
|
56
|
-
|
57
|
-
|
58
|
-
s.missing.should =~ expected
|
47
|
+
# s.missing # => [:E1, :D1, :D2, :D3, :D4, :D5]
|
48
|
+
s.missing.should =~ [:E1, :D1, :D2, :D3, :D4, :D5]
|
59
49
|
|
60
50
|
s.E1 = "= D1 ^ D2"
|
61
51
|
s.set("D1:D5" => [1,2,3,4,5])
|
62
52
|
|
63
|
-
|
64
|
-
|
65
|
-
s.missing.should =~ expected
|
66
|
-
|
67
|
-
s.cells # => {"F1"=>"= E1 + SUM(D1:D5)", "E1"=>"= D1 ^ D2", "D1"=>"1", "D2"=>"2", "D3"=>"3", "D4"=>"4", "D5"=>"5"}
|
68
|
-
|
69
|
-
# Importers
|
70
|
-
|
71
|
-
# (TBD)
|
53
|
+
# s.missing # => []
|
54
|
+
s.missing.should =~ []
|
72
55
|
|
73
|
-
|
56
|
+
# s.cells # => {:F1=>"= E1 + SUM(D1:D5)", :E1=>"= D1 ^ D2", :D1=>"1", :D2=>"2", :D3=>"3", :D4=>"4", :D5=>"5"}
|
57
|
+
s.cells.keys.should =~ [:F1, :E1, :D1, :D2, :D3, :D4, :D5]
|
58
|
+
s.cells[:D3].should eq("3")
|
59
|
+
end
|
74
60
|
|
61
|
+
it "iteration works" do
|
75
62
|
s.set('D1:D5' => [1,2,3,4,5])
|
76
|
-
|
63
|
+
# s.walk('D1:D5').reduce(:+) # => 15
|
77
64
|
s.walk('D1:D5').reduce(:+).should eq(15)
|
65
|
+
end
|
78
66
|
|
79
|
-
|
80
|
-
|
81
|
-
Soroban::
|
67
|
+
it "functions work" do
|
68
|
+
# Soroban::functions # => ["AND", "AVERAGE", "EXP", "IF", "LN", "MAX", "MIN", "NOT", "OR", "SUM", "VLOOKUP"]
|
69
|
+
Soroban::Functions.all.should =~ ["AND", "AVERAGE", "EXP", "IF", "LN", "MAX", "MIN", "NOT", "OR", "SUM", "VLOOKUP"]
|
82
70
|
|
83
|
-
Soroban::define :FOO => lambda { |lo, hi|
|
71
|
+
Soroban::Functions.define :FOO => lambda { |lo, hi|
|
84
72
|
raise ArgumentError if lo > hi
|
85
|
-
|
73
|
+
rand(hi-lo) + lo
|
86
74
|
}
|
87
75
|
|
88
76
|
s.g = "=FOO(10, 20)"
|
89
77
|
|
90
|
-
|
91
|
-
|
78
|
+
# puts s.g # => 17
|
79
|
+
s.g.between?(10, 20).should be_true
|
92
80
|
end
|
93
81
|
|
94
82
|
end
|
data/spec/import_spec.rb
CHANGED
@@ -1,33 +1,25 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
|
4
|
-
Gem::Specification::find_by_name("rubyXL")
|
5
|
-
rescue Gem::LoadError
|
6
|
-
false
|
7
|
-
end
|
8
|
-
|
9
|
-
describe "Documentation", :if => has_rubyxl do
|
10
|
-
|
11
|
-
it "can import xlsx files using RubyXL" do
|
3
|
+
describe "Documentation", :if => defined?(RubyXL) do
|
12
4
|
|
5
|
+
it "importers work" do
|
13
6
|
BINDINGS = {
|
14
7
|
:planet => :B1,
|
15
8
|
:mass => :B2,
|
16
9
|
:force => :B3
|
17
10
|
}
|
18
11
|
|
19
|
-
s = Soroban::Import::rubyXL("files/Physics.xlsx", 0, BINDINGS
|
12
|
+
s = Soroban::Import::rubyXL("files/Physics.xlsx", 0, BINDINGS)
|
20
13
|
|
21
14
|
s.planet = 'Earth'
|
22
15
|
s.mass = 80
|
23
|
-
|
16
|
+
# puts s.force # => 783.459251241996
|
24
17
|
s.force.should be_within(0.01).of(783.46)
|
25
18
|
|
26
19
|
s.planet = 'Venus'
|
27
20
|
s.mass = 80
|
28
|
-
|
21
|
+
# puts s.force # => 710.044826106394
|
29
22
|
s.force.should be_within(0.01).of(710.04)
|
30
|
-
|
31
23
|
end
|
32
24
|
|
33
25
|
end
|
data/spec/soroban_spec.rb
CHANGED
@@ -102,15 +102,19 @@ describe "Soroban" do
|
|
102
102
|
end
|
103
103
|
|
104
104
|
it "can define new functions" do
|
105
|
-
Soroban::define :FOO => lambda { |a, b| 2 * a + b / 2 }
|
105
|
+
Soroban::Functions.define :FOO => lambda { |a, b| 2 * a + b / 2 }
|
106
106
|
sheet.A1 = 7
|
107
107
|
sheet.A2 = 8
|
108
108
|
sheet.A3 = "=foo(A1, A2)"
|
109
109
|
sheet.A3.should eq(18)
|
110
|
-
Soroban::
|
110
|
+
Soroban::Functions.all.should include 'FOO'
|
111
111
|
end
|
112
112
|
|
113
113
|
it "can report on missing cells" do
|
114
|
+
sheet.A3 = "=A2"
|
115
|
+
expected = [:A2]
|
116
|
+
sheet.missing.should =~ expected
|
117
|
+
|
114
118
|
sheet.A3 = "=A2+foo(A3:B4)"
|
115
119
|
expected = [:A2, :A4, :B3, :B4 ]
|
116
120
|
sheet.missing.should =~ expected
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
3
4
|
require 'rspec'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'RubyXL'
|
8
|
+
rescue LoadError
|
9
|
+
# it is OK for the user not to have RubyXL
|
10
|
+
end
|
11
|
+
|
4
12
|
require 'soroban'
|
5
13
|
|
6
14
|
# Requires supporting files with custom matchers and macros, etc,
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: soroban
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 63
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 8
|
9
|
+
- 0
|
10
|
+
version: 0.8.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jason Hutchens
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2014-03-
|
18
|
+
date: 2014-03-25 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: treetop
|
@@ -105,7 +105,7 @@ files:
|
|
105
105
|
- files/Physics.xlsx
|
106
106
|
- lib/soroban.rb
|
107
107
|
- lib/soroban/cell.rb
|
108
|
-
- lib/soroban/
|
108
|
+
- lib/soroban/errors.rb
|
109
109
|
- lib/soroban/functions.rb
|
110
110
|
- lib/soroban/functions/and.rb
|
111
111
|
- lib/soroban/functions/average.rb
|
data/lib/soroban/error.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
module Soroban
|
2
|
-
|
3
|
-
# Thrown if an invalid formula is assigned to a cell.
|
4
|
-
class ParseError < StandardError
|
5
|
-
end
|
6
|
-
|
7
|
-
# Thrown if calculation of a cell's formula depends on the value of the same
|
8
|
-
# cell.
|
9
|
-
class RecursionError < StandardError
|
10
|
-
end
|
11
|
-
|
12
|
-
# Thrown if a referenced cell falls outside the limits of a supplied range.
|
13
|
-
class RangeError < StandardError
|
14
|
-
end
|
15
|
-
|
16
|
-
# Thrown is access is attempted to an undefined cell.
|
17
|
-
class UndefinedError < StandardError
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|