soroban 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,68 +1,72 @@
1
- grammar Soroban
2
- rule formula
3
- '=' space? logical <Formula> / string / number / boolean
4
- end
5
- rule logical
6
- and ( space? 'or' space? and )*
7
- end
8
- rule and
9
- truthval ( space? 'and' space? truthval )*
10
- end
11
- rule truthval
12
- comparison / '(' space? logical space? ')' / boolean
13
- end
14
- rule boolean
15
- 'true' / 'false' / 'TRUE' / 'FALSE'
16
- end
17
- rule comparison
18
- expression ( space? comparator space? expression )*
19
- end
20
- rule comparator
21
- '=' <Equal> / '<>' <NotEqual> / '>=' / '<=' / '>' / '<'
22
- end
23
- rule expression
24
- multiplicative ( space? additive_operator space? multiplicative )*
25
- end
26
- rule additive_operator
27
- '+' / '-'
28
- end
29
- rule multiplicative
30
- value ( space? multiplicative_operator space? value )*
31
- end
32
- rule multiplicative_operator
33
- '^' <Pow> / '*' / '/'
34
- end
35
- rule value
36
- ( function / '(' space? expression space? ')' / range / number / boolean / identifier / string / '-' value )
37
- end
38
- rule function
39
- [a-zA-Z]+ '(' space? arguments? space? ')' <Function>
40
- end
41
- rule arguments
42
- logical ( space? ',' space? logical )*
43
- end
44
- rule number
45
- ( float / integer / '-' float / '-' integer )
46
- end
47
- rule float
48
- [0-9]* '.' [0-9]+ <FloatValue>
49
- end
50
- rule integer
51
- [0-9]+ <IntegerValue>
52
- end
53
- rule identifier
54
- [a-zA-Z] [a-zA-Z0-9]* <Identifier>
55
- end
56
- rule label
57
- [A-Za-z]+ [1-9] [0-9]* <Label> / '$' [A-Za-z]+ '$' [1-9] [0-9]* <Label>
58
- end
59
- rule string
60
- '"' ('\"' / !'"' .)* '"' / "'" [^']* "'"
61
- end
62
- rule range
63
- label ':' label <Range>
64
- end
65
- rule space
66
- [\s]+
67
- end
1
+ module Soroban
2
+
3
+ grammar Excel
4
+ rule formula
5
+ '=' space? logical <Formula> / string / number / boolean
6
+ end
7
+ rule logical
8
+ and ( space? 'or' space? and )*
9
+ end
10
+ rule and
11
+ truthval ( space? 'and' space? truthval )*
12
+ end
13
+ rule truthval
14
+ comparison / '(' space? logical space? ')' / boolean
15
+ end
16
+ rule boolean
17
+ 'true' / 'false' / 'TRUE' / 'FALSE'
18
+ end
19
+ rule comparison
20
+ expression ( space? comparator space? expression )*
21
+ end
22
+ rule comparator
23
+ '=' <Equal> / '<>' <NotEqual> / '>=' / '<=' / '>' / '<'
24
+ end
25
+ rule expression
26
+ multiplicative ( space? additive_operator space? multiplicative )*
27
+ end
28
+ rule additive_operator
29
+ '+' / '-'
30
+ end
31
+ rule multiplicative
32
+ value ( space? multiplicative_operator space? value )*
33
+ end
34
+ rule multiplicative_operator
35
+ '^' <Pow> / '*' / '/'
36
+ end
37
+ rule value
38
+ ( function / '(' space? expression space? ')' / range / number / boolean / identifier / string / '-' value )
39
+ end
40
+ rule function
41
+ [a-zA-Z]+ '(' space? arguments? space? ')' <Function>
42
+ end
43
+ rule arguments
44
+ logical ( space? ',' space? logical )*
45
+ end
46
+ rule number
47
+ ( float / integer / '-' float / '-' integer )
48
+ end
49
+ rule float
50
+ [0-9]* '.' [0-9]+ <FloatValue>
51
+ end
52
+ rule integer
53
+ [0-9]+ <IntegerValue>
54
+ end
55
+ rule identifier
56
+ [a-zA-Z] [a-zA-Z0-9]* <Identifier>
57
+ end
58
+ rule label
59
+ [A-Za-z]+ [1-9] [0-9]* <Label> / '$' [A-Za-z]+ '$' [1-9] [0-9]* <Label>
60
+ end
61
+ rule string
62
+ '"' ('\"' / !'"' .)* '"' / "'" [^']* "'"
63
+ end
64
+ rule range
65
+ label ':' label <Range>
66
+ end
67
+ rule space
68
+ [\s]+
69
+ end
70
+ end
71
+
68
72
  end
@@ -1,70 +1,72 @@
1
1
  module Soroban
2
+ module Excel
2
3
 
3
- class Formula < Treetop::Runtime::SyntaxNode
4
- def rewrite_ruby(value)
5
- value.gsub(/^= */, '')
4
+ class Formula < Treetop::Runtime::SyntaxNode
5
+ def rewrite_ruby(value)
6
+ value.gsub(/^= */, '')
7
+ end
6
8
  end
7
- end
8
9
 
9
- class Identifier < Treetop::Runtime::SyntaxNode
10
- def rewrite_ruby(value)
11
- "@#{value}.get"
12
- end
13
- def extract(value)
14
- value.to_sym
10
+ class Identifier < Treetop::Runtime::SyntaxNode
11
+ def rewrite_ruby(value)
12
+ "@#{value}.get"
13
+ end
14
+ def extract_labels(value)
15
+ value.to_sym
16
+ end
15
17
  end
16
- end
17
18
 
18
- class IntegerValue < Treetop::Runtime::SyntaxNode
19
- def rewrite_ruby(value)
20
- "#{value.to_f}"
19
+ class IntegerValue < Treetop::Runtime::SyntaxNode
20
+ def rewrite_ruby(value)
21
+ "#{value.to_f}"
22
+ end
21
23
  end
22
- end
23
24
 
24
- class FloatValue < Treetop::Runtime::SyntaxNode
25
- def rewrite_ruby(value)
26
- "#{value.to_f}"
25
+ class FloatValue < Treetop::Runtime::SyntaxNode
26
+ def rewrite_ruby(value)
27
+ "#{value.to_f}"
28
+ end
27
29
  end
28
- end
29
30
 
30
- class Function < Treetop::Runtime::SyntaxNode
31
- def rewrite_ruby(value)
32
- match = /^([^(]*)(.*)$/.match(value)
33
- "func_#{match[1].downcase}#{match[2]}"
31
+ class Function < Treetop::Runtime::SyntaxNode
32
+ def rewrite_ruby(value)
33
+ match = /^([^(]*)(.*)$/.match(value)
34
+ "func_#{match[1].downcase}#{match[2]}"
35
+ end
34
36
  end
35
- end
36
37
 
37
- class Pow < Treetop::Runtime::SyntaxNode
38
- def rewrite_ruby(value)
39
- "**"
38
+ class Pow < Treetop::Runtime::SyntaxNode
39
+ def rewrite_ruby(value)
40
+ "**"
41
+ end
40
42
  end
41
- end
42
43
 
43
- class Equal < Treetop::Runtime::SyntaxNode
44
- def rewrite_ruby(value)
45
- "=="
44
+ class Equal < Treetop::Runtime::SyntaxNode
45
+ def rewrite_ruby(value)
46
+ "=="
47
+ end
46
48
  end
47
- end
48
49
 
49
- class NotEqual < Treetop::Runtime::SyntaxNode
50
- def rewrite_ruby(value)
51
- "!="
50
+ class NotEqual < Treetop::Runtime::SyntaxNode
51
+ def rewrite_ruby(value)
52
+ "!="
53
+ end
52
54
  end
53
- end
54
55
 
55
- class Label < Treetop::Runtime::SyntaxNode
56
- def rewrite_ruby(value)
57
- value.gsub('$', '')
56
+ class Label < Treetop::Runtime::SyntaxNode
57
+ def rewrite_ruby(value)
58
+ value.gsub('$', '')
59
+ end
58
60
  end
59
- end
60
61
 
61
- class Range < Treetop::Runtime::SyntaxNode
62
- def rewrite_ruby(value)
63
- "'#{value}'"
62
+ class Range < Treetop::Runtime::SyntaxNode
63
+ def rewrite_ruby(value)
64
+ "'#{value}'"
65
+ end
66
+ def extract_labels(value)
67
+ Soroban::LabelWalker.new(value).map { |label| "#{label}".to_sym }
68
+ end
64
69
  end
65
- def extract(value)
66
- LabelWalker.new(value).map { |label| "#{label}".to_sym }
67
- end
68
- end
69
70
 
71
+ end
70
72
  end
@@ -1,36 +1,43 @@
1
1
  module Treetop
2
2
  module Runtime
3
- class SyntaxNode
4
3
 
5
- def to_ruby(dependencies)
4
+ # Each node in the AST produced by the treetop parser implements to_ruby,
5
+ # which allows the Soroban sheet to store the ruby version of the Excel
6
+ # contents of each cell in the sheet (and which also gathers and stores the
7
+ # dependencies that call has on other cells). Each concrete syntax node may
8
+ # override rewrite_ruby and extract_labels.
9
+ class SyntaxNode
10
+ def to_ruby(cell)
6
11
  if nonterminal?
7
12
  value = ""
8
- elements.each { |element| value << element.to_ruby(dependencies) }
9
- _add_dependency(dependencies, extract(value))
13
+ elements.each { |element| value << element.to_ruby(cell) }
14
+ _add_dependency(cell, value)
10
15
  rewrite_ruby(value)
11
16
  else
12
- _add_dependency(dependencies, extract(text_value))
17
+ _add_dependency(cell, text_value)
13
18
  rewrite_ruby(text_value)
14
19
  end
15
20
  end
16
21
 
22
+ # Return the ruby version of the Excel value. By default this does
23
+ # nothing; see nodes.rb for concrete implementations.
17
24
  def rewrite_ruby(value)
18
25
  value
19
26
  end
20
27
 
21
- def extract(value)
28
+ # Return either a single label of the form :A1, or an array of labels of
29
+ # the form [:B1, :B2, ...]. This is used to keep track of the dependencies
30
+ # of this particular cell.
31
+ def extract_labels(value)
32
+ nil
22
33
  end
23
34
 
24
35
  private
25
36
 
26
- def _add_dependency(dependencies, value)
27
- return if value.nil?
28
- dependencies << value
29
- dependencies.flatten!
30
- dependencies.compact!
31
- dependencies.uniq!
37
+ def _add_dependency(cell, value)
38
+ cell.add_dependencies(extract_labels(value))
32
39
  end
33
-
34
40
  end
41
+
35
42
  end
36
43
  end
@@ -1,22 +1,26 @@
1
+ unless defined?(Set)
2
+ require 'set'
3
+ end
4
+
5
+ require 'soroban/errors'
1
6
  require 'soroban/helpers'
2
7
  require 'soroban/functions'
8
+ require 'soroban/cell'
3
9
  require 'soroban/label_walker'
4
10
  require 'soroban/value_walker'
5
- require 'soroban/cell'
6
-
7
- require 'set'
8
11
 
9
12
  module Soroban
10
13
 
11
- # A container for cells.
14
+ # A container for cells. This is what the end user of Soroban will manipulate,
15
+ # either directly or via an importer that returns a Sheet instance.
12
16
  class Sheet
13
17
  attr_reader :bindings
14
18
 
15
19
  # Creates a new sheet.
16
20
  def initialize(logger=nil)
17
- @logger = logger
18
- @cells = {}
19
- @changes = Hash.new{ |h, k| h[k] = Set.new }
21
+ @_logger = logger
22
+ @_cells = {}
23
+ @_changes = Hash.new { |h, k| h[k] = Set.new }
20
24
  @bindings = {}
21
25
  end
22
26
 
@@ -24,7 +28,7 @@ module Soroban
24
28
  # cells (via `label=`).
25
29
  def method_missing(method, *args, &block)
26
30
  if match = /^func_(.*)$/i.match(method.to_s)
27
- return Soroban::call(self, match[1], *args)
31
+ return Soroban::Functions.call(self, match[1], *args)
28
32
  elsif match = /^([a-z][\w]*)=$/i.match(method.to_s)
29
33
  return _add(match[1], args[0])
30
34
  end
@@ -35,11 +39,11 @@ module Soroban
35
39
  def set(options_hash)
36
40
  options_hash.each do |label_or_range, contents|
37
41
  _debug("setting '#{label_or_range}' to '#{contents}'")
38
- unless range = Soroban::getRange(label_or_range)
42
+ unless Soroban::Helpers.range?(label_or_range)
39
43
  _add(label_or_range, contents)
40
44
  next
41
45
  end
42
- fc, fr, tc, tr = range
46
+ fc, fr, tc, tr = Soroban::Helpers.getRange(label_or_range)
43
47
  if fc == tc || fr == tr
44
48
  raise ArgumentError, "Expecting an array when setting #{label_or_range}" unless contents.kind_of? Array
45
49
  cc, cr = fc, fr
@@ -59,7 +63,7 @@ module Soroban
59
63
  def get(label_or_name)
60
64
  label = @bindings[label_or_name.to_sym] || label_or_name
61
65
  _debug("retrieving '#{label_or_name}' from '#{label}'}")
62
- if Soroban::range?(label)
66
+ if Soroban::Helpers.range?(label)
63
67
  walk(label)
64
68
  else
65
69
  _get(label_or_name, eval("@#{label}", binding))
@@ -70,14 +74,14 @@ module Soroban
70
74
  def bind(options_hash)
71
75
  options_hash.each do |name, label_or_range|
72
76
  _debug("binding '#{name}' to '#{label_or_range}'}")
73
- if Soroban::range?(label_or_range)
74
- LabelWalker.new(label_or_range).each do |label|
75
- next if @cells.keys.include?(label.to_sym)
77
+ if Soroban::Helpers.range?(label_or_range)
78
+ Soroban::LabelWalker.new(label_or_range).each do |label|
79
+ next if @_cells.has_key?(label.to_sym)
76
80
  raise Soroban::UndefinedError, "Cannot bind '#{name}' to range '#{label_or_range}'; cell #{label} is not defined"
77
81
  end
78
82
  _bind_range(name, label_or_range)
79
83
  else
80
- unless @cells.keys.include?(label_or_range.to_sym)
84
+ unless @_cells.has_key?(label_or_range.to_sym)
81
85
  raise Soroban::UndefinedError, "Cannot bind '#{name}' to non-existent cell '#{label_or_range}'"
82
86
  end
83
87
  _bind(name, label_or_range)
@@ -87,34 +91,34 @@ module Soroban
87
91
 
88
92
  # Visit each cell in the supplied range, yielding its value.
89
93
  def walk(range)
90
- ValueWalker.new(range, binding)
94
+ Soroban::ValueWalker.new(range, binding)
91
95
  end
92
96
 
93
97
  # Return a hash of `label => contents` for each cell in the sheet.
94
98
  def cells
95
- labels = @cells.keys.map { |label| label.to_sym }
99
+ labels = @_cells.keys.map(&:to_sym)
96
100
  contents = labels.map { |label| eval("@#{label}.excel") }
97
101
  Hash[labels.zip(contents)]
98
102
  end
99
103
 
100
- # Return a list of referenced but undefined cells.
104
+ # Return an array of referenced but undefined cells.
101
105
  def missing
102
- @cells.values.flatten.uniq - @cells.keys
106
+ (@_cells.values.reduce(:|) - @_cells.keys).to_a
103
107
  end
104
108
 
105
109
  private
106
110
 
107
111
  def _debug(message)
108
- return if @logger.nil?
109
- @logger.debug "SOROBAN: #{message}"
112
+ return if @_logger.nil?
113
+ @_logger.debug "SOROBAN: #{message}"
110
114
  end
111
115
 
112
116
  def _link(name, dependencies)
113
- dependencies.each { |target| @changes[target] << name if name != target }
117
+ dependencies.each { |target| @_changes[target] << name if name != target }
114
118
  end
115
119
 
116
120
  def _unlink(name, dependencies)
117
- dependencies.each { |target| @changes[target].delete(name) }
121
+ dependencies.each { |target| @_changes[target].delete(name) }
118
122
  end
119
123
 
120
124
  def _add(label, contents)
@@ -126,7 +130,7 @@ module Soroban
126
130
  end
127
131
  internal = "@#{label}"
128
132
  _expose(internal, label)
129
- cell = Cell.new(binding)
133
+ cell = Soroban::Cell.new(binding)
130
134
  _set(label, cell, contents)
131
135
  instance_variable_set(internal, cell)
132
136
  end
@@ -136,14 +140,14 @@ module Soroban
136
140
  name = @bindings[label] || label
137
141
  _unlink(name, cell.dependencies)
138
142
  cell.set(contents)
139
- @cells[name] = cell.dependencies
143
+ @_cells[name] = cell.dependencies
140
144
  _link(name, cell.dependencies)
141
145
  _clear(name)
142
146
  end
143
147
 
144
148
  def _clear(name)
145
- @changes[name].each do |target|
146
- next unless @cells.has_key?(target)
149
+ @_changes[name].each do |target|
150
+ next unless @_cells.has_key?(target)
147
151
  begin
148
152
  eval("@#{target.to_s}.clear")
149
153
  _clear(target)
@@ -155,8 +159,8 @@ module Soroban
155
159
  def _get(label_or_name, cell)
156
160
  label = label_or_name.to_sym
157
161
  name = @bindings[label] || label
158
- badref = @cells[name] & missing
159
- raise Soroban::UndefinedError, "Unmet dependencies #{badref.join(', ')} for #{label}" if badref.length > 0
162
+ badref = @_cells[name] & missing
163
+ raise Soroban::UndefinedError, "Unmet dependencies #{badref.to_a.join(', ')} for #{label}" if badref.length > 0
160
164
  cell.get
161
165
  end
162
166