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