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.
@@ -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