soroban 0.4.0 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Agworld Pty. Ltd.
1
+ Copyright (c) 2013 Agworld Pty. Ltd.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -134,7 +134,7 @@ Soroban implements some Excel functions, but you may find that you need more
134
134
  than those. In that case, it's easy to add more.
135
135
 
136
136
  ```ruby
137
- Soroban::functions # => ["MIN", "VLOOKUP", "AND", "MAX", "OR", "NOT", "IF", "AVERAGE", "SUM"]
137
+ Soroban::functions # => ["AVERAGE", "SUM", "VLOOKUP", "IF", "AND", "OR", "NOT", "MAX", "MIN", "LN", "EXP"]
138
138
 
139
139
  Soroban::define :FOO => lambda { |lo, hi|
140
140
  raise ArgumentError if lo > hi
@@ -160,4 +160,4 @@ Contributing to Soroban
160
160
  Copyright
161
161
  ---------
162
162
 
163
- Copyright (c) 2012 Agworld Pty. Ltd. See LICENSE.txt for further details.
163
+ Copyright (c) 2013 Agworld Pty. Ltd. See LICENSE.txt for further details.
data/Soroban.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "soroban"
8
- s.version = "0.4.0"
8
+ s.version = "0.5.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jason Hutchens"]
12
- s.date = "2013-01-09"
12
+ s.date = "2013-01-18"
13
13
  s.description = "Soroban makes it easy to extract and execute formulas from Excel spreadsheets. It rewrites Excel formulas as Ruby expressions, and allows you to bind named variables to spreadsheet cells to easily manipulate inputs and capture outputs."
14
14
  s.email = "jason.hutchens@agworld.com.au"
15
15
  s.extra_rdoc_files = [
@@ -35,7 +35,9 @@ Gem::Specification.new do |s|
35
35
  "lib/soroban/functions.rb",
36
36
  "lib/soroban/functions/and.rb",
37
37
  "lib/soroban/functions/average.rb",
38
+ "lib/soroban/functions/exp.rb",
38
39
  "lib/soroban/functions/if.rb",
40
+ "lib/soroban/functions/ln.rb",
39
41
  "lib/soroban/functions/max.rb",
40
42
  "lib/soroban/functions/min.rb",
41
43
  "lib/soroban/functions/not.rb",
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.4
data/lib/soroban/cell.rb CHANGED
@@ -10,26 +10,33 @@ module Soroban
10
10
  attr_reader :excel, :ruby, :dependencies
11
11
 
12
12
  # Cells are initialised with a binding to allow formulas to be executed
13
- # within the context of the sheet which ownes the cell.
13
+ # within the context of the sheet which owns the cell.
14
14
  def initialize(context)
15
15
  @dependencies = []
16
16
  @binding = context
17
17
  @touched = false
18
+ @value = nil
18
19
  end
19
20
 
20
21
  # Set the contents of a cell, and store the executable Ruby version.
21
22
  def set(contents)
22
23
  contents = contents.to_s
23
24
  contents = "'#{contents}'" if Soroban::unknown?(contents)
25
+ clear
24
26
  @excel, @ruby = contents, _convert(contents)
25
27
  end
26
28
 
29
+ # Clear the cached value of a cell to force it to be recalculated
30
+ def clear
31
+ @value = nil
32
+ end
33
+
27
34
  # Eval the Ruby version of the string contents within the context of the
28
35
  # owning sheet. Will throw Soroban::RecursionError if recursion is detected.
29
36
  def get
30
37
  raise Soroban::RecursionError, "Loop detected when evaluating '#{@excel}'" if @touched
31
38
  @touched = true
32
- eval(@ruby, @binding)
39
+ @value ||= eval(@ruby, @binding)
33
40
  rescue TypeError, RangeError, ZeroDivisionError
34
41
  nil
35
42
  ensure
@@ -29,3 +29,5 @@ require 'soroban/functions/or'
29
29
  require 'soroban/functions/not'
30
30
  require 'soroban/functions/max'
31
31
  require 'soroban/functions/min'
32
+ require 'soroban/functions/ln'
33
+ require 'soroban/functions/exp'
@@ -0,0 +1,4 @@
1
+ # Return e raised to the power of the argument
2
+ Soroban::define :EXP => lambda { |val|
3
+ Math.exp(val)
4
+ }
@@ -0,0 +1,4 @@
1
+ # Return the natural logarithm of the argument
2
+ Soroban::define :LN => lambda { |val|
3
+ Math.log(val)
4
+ }
@@ -1340,7 +1340,7 @@ module Soroban
1340
1340
  end
1341
1341
  end
1342
1342
  if s0.last
1343
- r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
1343
+ r0 = instantiate_node(FloatValue,input, i0...index, s0)
1344
1344
  r0.extend(Float0)
1345
1345
  else
1346
1346
  @index = i0
@@ -1381,7 +1381,7 @@ module Soroban
1381
1381
  @index = i0
1382
1382
  r0 = nil
1383
1383
  else
1384
- r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
1384
+ r0 = instantiate_node(IntegerValue,input, i0...index, s0)
1385
1385
  end
1386
1386
 
1387
1387
  node_cache[:integer][start_index] = r0
@@ -45,10 +45,10 @@ grammar Soroban
45
45
  ( float / integer / '-' float / '-' integer )
46
46
  end
47
47
  rule float
48
- [0-9]* '.' [0-9]+
48
+ [0-9]* '.' [0-9]+ <FloatValue>
49
49
  end
50
50
  rule integer
51
- [0-9]+
51
+ [0-9]+ <IntegerValue>
52
52
  end
53
53
  rule identifier
54
54
  [a-zA-Z] [a-zA-Z0-9]* <Identifier>
@@ -15,6 +15,18 @@ module Soroban
15
15
  end
16
16
  end
17
17
 
18
+ class IntegerValue < Treetop::Runtime::SyntaxNode
19
+ def rewrite(value)
20
+ "#{value.to_f}"
21
+ end
22
+ end
23
+
24
+ class FloatValue < Treetop::Runtime::SyntaxNode
25
+ def rewrite(value)
26
+ "#{value.to_f}"
27
+ end
28
+ end
29
+
18
30
  class Function < Treetop::Runtime::SyntaxNode
19
31
  def rewrite(value)
20
32
  match = /^([^(]*)(.*)$/.match(value)
data/lib/soroban/sheet.rb CHANGED
@@ -14,6 +14,7 @@ module Soroban
14
14
  def initialize(logger=nil)
15
15
  @logger = logger
16
16
  @cells = {}
17
+ @changes = Hash.new{ |h, k| h[k] = Set.new }
17
18
  @bindings = {}
18
19
  end
19
20
 
@@ -106,7 +107,21 @@ module Soroban
106
107
  @logger.debug "SOROBAN: #{message}"
107
108
  end
108
109
 
110
+ def _link(name, dependencies)
111
+ dependencies.each { |target| @changes[target] << name if name != target }
112
+ end
113
+
114
+ def _unlink(name, dependencies)
115
+ dependencies.each { |target| @changes[target].delete(name) }
116
+ end
117
+
109
118
  def _add(label, contents)
119
+ name = @bindings[label.to_sym] || label
120
+ if cells.has_key?(name)
121
+ cell = eval("@#{name}", binding)
122
+ cell.set(contents)
123
+ return
124
+ end
110
125
  internal = "@#{label}"
111
126
  _expose(internal, label)
112
127
  cell = Cell.new(binding)
@@ -117,8 +132,22 @@ module Soroban
117
132
  def _set(label_or_name, cell, contents)
118
133
  label = label_or_name.to_sym
119
134
  name = @bindings[label] || label
135
+ _unlink(name, cell.dependencies)
120
136
  cell.set(contents)
121
137
  @cells[name] = cell.dependencies
138
+ _link(name, cell.dependencies)
139
+ _clear(name)
140
+ end
141
+
142
+ def _clear(name)
143
+ @changes[name].each do |target|
144
+ next unless @cells.has_key?(target)
145
+ begin
146
+ eval("@#{target.to_s}.clear")
147
+ _clear(target)
148
+ rescue
149
+ end
150
+ end
122
151
  end
123
152
 
124
153
  def _get(label_or_name, cell)
data/spec/soroban_spec.rb CHANGED
@@ -75,6 +75,32 @@ describe "Soroban" do
75
75
  sheet.Z3.should eq('foo')
76
76
  end
77
77
 
78
+ it "can retrieve bound variables in multiple ways" do
79
+ sheet.set(:A1 => 'life')
80
+ sheet.A1.should eq('life')
81
+ sheet.get(:A1).should eq('life')
82
+ sheet.get('A1').should eq('life')
83
+ sheet.A1 = 'death'
84
+ sheet.A1.should eq('death')
85
+ sheet.get(:A1).should eq('death')
86
+ sheet.get('A1').should eq('death')
87
+ sheet.bind(:foo => :A1)
88
+ sheet.set('foo' => 'win')
89
+ sheet.A1.should eq('win')
90
+ sheet.get(:A1).should eq('win')
91
+ sheet.get('A1').should eq('win')
92
+ sheet.foo.should eq('win')
93
+ sheet.get(:foo).should eq('win')
94
+ sheet.get('foo').should eq('win')
95
+ sheet.foo = 'lose'
96
+ sheet.A1.should eq('lose')
97
+ sheet.get(:A1).should eq('lose')
98
+ sheet.get('A1').should eq('lose')
99
+ sheet.foo.should eq('lose')
100
+ sheet.get(:foo).should eq('lose')
101
+ sheet.get('foo').should eq('lose')
102
+ end
103
+
78
104
  it "can define new functions" do
79
105
  Soroban::define :FOO => lambda { |a, b| 2 * a + b / 2 }
80
106
  sheet.A1 = 7
@@ -111,4 +137,28 @@ describe "Soroban" do
111
137
  sheet.A3.should eq(-20)
112
138
  end
113
139
 
140
+ it "can calculate natural logarithms" do
141
+ sheet.set(:A1 => "=LN(#{Math::E})")
142
+ sheet.A1.should be_within(1e-6).of(1.0)
143
+ end
144
+
145
+ it "can calculate exponentials" do
146
+ sheet.set(:A1 => 1)
147
+ sheet.set(:A2 => "=EXP(A1)")
148
+ sheet.A2.should be_within(1e-6).of(Math::E)
149
+ end
150
+
151
+ it "can use special symbols in values" do
152
+ sheet.set(:A1 => "> Threshold")
153
+ sheet.bind(:input => :A1)
154
+ sheet.get(:input).should eq('> Threshold')
155
+ end
156
+
157
+ it "should use floating point numbers" do
158
+ sheet.set(:A1 => "20")
159
+ sheet.set(:A2 => "10")
160
+ sheet.set(:A3 => "=A2/A1")
161
+ sheet.A3.should be_within(1e-6).of(0.5)
162
+ end
163
+
114
164
  end
metadata CHANGED
@@ -1,90 +1,96 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: soroban
3
- version: !ruby/object:Gem::Version
4
- version: 0.4.0
3
+ version: !ruby/object:Gem::Version
4
+ hash: 3
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 4
10
+ version: 0.5.4
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Jason Hutchens
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2013-01-09 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2013-01-18 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: treetop
16
- requirement: !ruby/object:Gem::Requirement
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
17
23
  none: false
18
- requirements:
24
+ requirements:
19
25
  - - ~>
20
- - !ruby/object:Gem::Version
26
+ - !ruby/object:Gem::Version
27
+ hash: 19
28
+ segments:
29
+ - 1
30
+ - 4
31
+ - 10
21
32
  version: 1.4.10
22
33
  type: :runtime
34
+ requirement: *id001
23
35
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ~>
28
- - !ruby/object:Gem::Version
29
- version: 1.4.10
30
- - !ruby/object:Gem::Dependency
36
+ - !ruby/object:Gem::Dependency
31
37
  name: rubyXL
32
- requirement: !ruby/object:Gem::Requirement
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
33
39
  none: false
34
- requirements:
40
+ requirements:
35
41
  - - ~>
36
- - !ruby/object:Gem::Version
42
+ - !ruby/object:Gem::Version
43
+ hash: 17
44
+ segments:
45
+ - 1
46
+ - 2
47
+ - 7
37
48
  version: 1.2.7
38
49
  type: :development
50
+ requirement: *id002
39
51
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: 1.2.7
46
- - !ruby/object:Gem::Dependency
52
+ - !ruby/object:Gem::Dependency
47
53
  name: nokogiri
48
- requirement: !ruby/object:Gem::Requirement
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
49
55
  none: false
50
- requirements:
51
- - - ! '>='
52
- - !ruby/object:Gem::Version
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 15
60
+ segments:
61
+ - 1
62
+ - 4
63
+ - 4
53
64
  version: 1.4.4
54
65
  type: :development
66
+ requirement: *id003
55
67
  prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: 1.4.4
62
- - !ruby/object:Gem::Dependency
68
+ - !ruby/object:Gem::Dependency
63
69
  name: rubyzip
64
- requirement: !ruby/object:Gem::Requirement
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
65
71
  none: false
66
- requirements:
67
- - - ! '>='
68
- - !ruby/object:Gem::Version
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 51
76
+ segments:
77
+ - 0
78
+ - 9
79
+ - 4
69
80
  version: 0.9.4
70
81
  type: :development
82
+ requirement: *id004
71
83
  prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: 0.9.4
78
- description: Soroban makes it easy to extract and execute formulas from Excel spreadsheets.
79
- It rewrites Excel formulas as Ruby expressions, and allows you to bind named variables
80
- to spreadsheet cells to easily manipulate inputs and capture outputs.
84
+ description: Soroban makes it easy to extract and execute formulas from Excel spreadsheets. It rewrites Excel formulas as Ruby expressions, and allows you to bind named variables to spreadsheet cells to easily manipulate inputs and capture outputs.
81
85
  email: jason.hutchens@agworld.com.au
82
86
  executables: []
87
+
83
88
  extensions: []
84
- extra_rdoc_files:
89
+
90
+ extra_rdoc_files:
85
91
  - LICENSE.txt
86
92
  - README.md
87
- files:
93
+ files:
88
94
  - .document
89
95
  - .rspec
90
96
  - .travis.yml
@@ -103,7 +109,9 @@ files:
103
109
  - lib/soroban/functions.rb
104
110
  - lib/soroban/functions/and.rb
105
111
  - lib/soroban/functions/average.rb
112
+ - lib/soroban/functions/exp.rb
106
113
  - lib/soroban/functions/if.rb
114
+ - lib/soroban/functions/ln.rb
107
115
  - lib/soroban/functions/max.rb
108
116
  - lib/soroban/functions/min.rb
109
117
  - lib/soroban/functions/not.rb
@@ -127,31 +135,37 @@ files:
127
135
  - spec/soroban_spec.rb
128
136
  - spec/spec_helper.rb
129
137
  homepage: https://github.com/agworld/soroban
130
- licenses:
138
+ licenses:
131
139
  - MIT
132
140
  post_install_message:
133
141
  rdoc_options: []
134
- require_paths:
142
+
143
+ require_paths:
135
144
  - lib
136
- required_ruby_version: !ruby/object:Gem::Requirement
145
+ required_ruby_version: !ruby/object:Gem::Requirement
137
146
  none: false
138
- requirements:
139
- - - ! '>='
140
- - !ruby/object:Gem::Version
141
- version: '0'
142
- segments:
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ hash: 3
151
+ segments:
143
152
  - 0
144
- hash: 1741913376236821456
145
- required_rubygems_version: !ruby/object:Gem::Requirement
153
+ version: "0"
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
155
  none: false
147
- requirements:
148
- - - ! '>='
149
- - !ruby/object:Gem::Version
150
- version: '0'
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ hash: 3
160
+ segments:
161
+ - 0
162
+ version: "0"
151
163
  requirements: []
164
+
152
165
  rubyforge_project:
153
166
  rubygems_version: 1.8.24
154
167
  signing_key:
155
168
  specification_version: 3
156
169
  summary: Soroban is a calculating engine that understands Excel formulas.
157
170
  test_files: []
171
+