excel_to_code 0.2.14 → 0.2.15
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.
- checksums.yaml +4 -4
- data/src/commands/excel_to_x.rb +33 -15
- data/src/compile/c/map_formulae_to_c.rb +39 -0
- data/src/excel_to_code.rb +1 -1
- data/src/simplify/replace_arrays_with_single_cells.rb +86 -20
- data/src/simplify/replace_offsets_with_references.rb +5 -0
- data/src/simplify/replace_ranges_with_array_literals.rb +69 -19
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f02167c81fefd0532b50c1fc9c9014a4398306f5
|
4
|
+
data.tar.gz: ec0ce7e9de3c7ed214052a9a45bad36c02e1c763
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52fd6998dddf65a826c6bb3a014d5b915342845ab5a1801dfc3c4fc7572f446aedd2f8662c92aa186be8c0b5dd9719ffcc2f90f1b71842df6cb5c97e886cbc8f
|
7
|
+
data.tar.gz: 7725a362a6049b3533e1db2c1be5c256581fb915519d274b89d3c9790f83e654d9470deb6c9148549fe905cf51e3ff6ee35794f6d4026feb357d23e2c07a1d74
|
data/src/commands/excel_to_x.rb
CHANGED
@@ -98,6 +98,11 @@ class ExcelToX
|
|
98
98
|
# * false - empty cells and zeros are treated as being different in tests. Numbers must match to full accuracy.
|
99
99
|
attr_accessor :sloppy_tests
|
100
100
|
|
101
|
+
# Optional attribute, Boolean. Default true
|
102
|
+
# * true - the compiler attempts to extract bits of calculation that appear in more than one formula into separate methods. This should increase performance
|
103
|
+
# * false - the compiler leaves calculations fully expanded. This may make debugging easier
|
104
|
+
attr_accessor :extract_repeated_parts_of_formulae
|
105
|
+
|
101
106
|
# Deprecated
|
102
107
|
def run_in_memory=(boolean)
|
103
108
|
$stderr.puts "The run_in_memory switch is deprecated (it is now always true). Please remove calls to it"
|
@@ -179,7 +184,7 @@ class ExcelToX
|
|
179
184
|
replace_formulae_with_their_results
|
180
185
|
inline_formulae_that_are_only_used_once
|
181
186
|
remove_any_cells_not_needed_for_outputs
|
182
|
-
separate_formulae_elements
|
187
|
+
separate_formulae_elements if extract_repeated_parts_of_formulae
|
183
188
|
replace_values_with_constants
|
184
189
|
create_sorted_references_to_test
|
185
190
|
|
@@ -213,6 +218,9 @@ class ExcelToX
|
|
213
218
|
|
214
219
|
# By default, tests allow empty cells and zeros to be treated as equivalent, and numbers only have to match to a 0.001 epsilon (if expected>1) or 0.001 delta (if expected<1)
|
215
220
|
self.sloppy_tests ||= true
|
221
|
+
|
222
|
+
# Setting this to false may make it easier to figure out errors
|
223
|
+
self.extract_repeated_parts_of_formulae = true if @extract_repeated_parts_of_formulae == nil
|
216
224
|
end
|
217
225
|
|
218
226
|
|
@@ -811,21 +819,31 @@ class ExcelToX
|
|
811
819
|
@sheetless_cell_reference_replacer ||= RewriteCellReferencesToIncludeSheetAst.new
|
812
820
|
|
813
821
|
cells.each do |ref, ast|
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
822
|
+
begin
|
823
|
+
@sheetless_cell_reference_replacer.worksheet = ref.first
|
824
|
+
@sheetless_cell_reference_replacer.map(ast)
|
825
|
+
@shared_string_replacer.map(ast)
|
826
|
+
@named_reference_replacer.default_sheet_name = ref.first
|
827
|
+
@named_reference_replacer.map(ast)
|
828
|
+
@table_reference_replacer.worksheet = ref.first
|
829
|
+
@table_reference_replacer.referring_cell = ref.last
|
830
|
+
@table_reference_replacer.map(ast)
|
831
|
+
@replace_ranges_with_array_literals_replacer.map(ast)
|
832
|
+
#@replace_arithmetic_on_ranges_replacer.map(ast)
|
833
|
+
|
834
|
+
@replace_arrays_with_single_cells_replacer.ref = ref
|
835
|
+
a = @replace_arrays_with_single_cells_replacer.map(ast)
|
836
|
+
if @replace_arrays_with_single_cells_replacer.need_to_replace
|
837
|
+
cells[ref] = a
|
838
|
+
end
|
828
839
|
|
840
|
+
#@replace_string_joins_on_ranges_replacer.map(ast)
|
841
|
+
@wrap_formulae_that_return_arrays_replacer.map(ast)
|
842
|
+
rescue Exception => e
|
843
|
+
log.fatal "Exception when simplifying #{ref}: #{ast}"
|
844
|
+
raise
|
845
|
+
end
|
846
|
+
end
|
829
847
|
end
|
830
848
|
|
831
849
|
# These types of cells don't conatain formulae and can therefore be skipped
|
@@ -148,6 +148,45 @@ class MapFormulaeToC < MapValuesToC
|
|
148
148
|
"#{FUNCTIONS[:AVERAGEIFS]}(#{map(average_range)}, #{map_arguments_to_array(criteria)})"
|
149
149
|
end
|
150
150
|
|
151
|
+
def function_if(condition, true_case, false_case = [:boolean_false])
|
152
|
+
true_code = map(true_case)
|
153
|
+
false_code = map(false_case)
|
154
|
+
|
155
|
+
condition_name = "condition#{@counter}"
|
156
|
+
result_name = "ifresult#{@counter}"
|
157
|
+
@counter += 1
|
158
|
+
|
159
|
+
initializers << "ExcelValue #{condition_name} = #{map(condition)};"
|
160
|
+
initializers << "ExcelValue #{result_name};"
|
161
|
+
initializers << "switch(#{condition_name}.type) {"
|
162
|
+
initializers << "case ExcelBoolean:"
|
163
|
+
initializers << " if(#{condition_name}.number == true) {"
|
164
|
+
initializers << " #{result_name} = #{true_code};"
|
165
|
+
initializers << " } else {"
|
166
|
+
initializers << " #{result_name} = #{false_code};"
|
167
|
+
initializers << " }"
|
168
|
+
initializers << " break;"
|
169
|
+
initializers << "case ExcelNumber:"
|
170
|
+
initializers << " if(#{condition_name}.number == 0) {"
|
171
|
+
initializers << " #{result_name} = #{false_code};"
|
172
|
+
initializers << " } else {"
|
173
|
+
initializers << " #{result_name} = #{true_code};"
|
174
|
+
initializers << " }"
|
175
|
+
initializers << " break;"
|
176
|
+
initializers << "case ExcelEmpty: "
|
177
|
+
initializers << " #{result_name} = #{false_code};"
|
178
|
+
initializers << " break;"
|
179
|
+
initializers << "case ExcelString:"
|
180
|
+
initializers << "case ExcelRange:"
|
181
|
+
initializers << " #{result_name} = VALUE;"
|
182
|
+
initializers << " break;"
|
183
|
+
initializers << "case ExcelError:"
|
184
|
+
initializers << " #{result_name} = #{condition_name};"
|
185
|
+
initializers << " break;"
|
186
|
+
initializers << "}"
|
187
|
+
|
188
|
+
return result_name
|
189
|
+
end
|
151
190
|
|
152
191
|
def any_number_of_argument_function(function_name,arguments)
|
153
192
|
"#{FUNCTIONS[function_name.to_sym]}(#{map_arguments_to_array(arguments)})"
|
data/src/excel_to_code.rb
CHANGED
@@ -2,32 +2,98 @@ require_relative '../excel'
|
|
2
2
|
|
3
3
|
class ReplaceArraysWithSingleCellsAst
|
4
4
|
|
5
|
+
attr_accessor :ref
|
6
|
+
attr_accessor :need_to_replace
|
7
|
+
|
8
|
+
ERROR = [:error, :"#VALUE!"]
|
9
|
+
|
5
10
|
def map(ast)
|
6
|
-
|
7
|
-
ast
|
11
|
+
@need_to_replace = false
|
12
|
+
return unless ast.is_a?(Array)
|
13
|
+
if ast.first == :array
|
14
|
+
@need_to_replace = true
|
15
|
+
return try_and_convert_array(ast)
|
16
|
+
else
|
17
|
+
do_map(ast)
|
18
|
+
ast
|
19
|
+
end
|
8
20
|
end
|
9
|
-
end
|
10
21
|
|
22
|
+
def do_map(ast)
|
23
|
+
return ast unless ast.is_a?(Array)
|
24
|
+
case ast.first
|
25
|
+
when :arithmetic
|
26
|
+
left, op, right = ast[1], ast[2], ast[3]
|
27
|
+
if left.first == :array || right.first == :array
|
28
|
+
left = try_and_convert_array(left)
|
29
|
+
right = try_and_convert_array(right)
|
30
|
+
ast.replace([:arithmetic, left, op, right])
|
31
|
+
else
|
32
|
+
ast[1..-1].each { |a| do_map(a) }
|
33
|
+
end
|
34
|
+
when :string_join
|
35
|
+
strings = ast[1..-1]
|
36
|
+
if strings.any? { |s| s.first == :array }
|
37
|
+
strings = strings.map { |s| try_and_convert_array(s) }
|
38
|
+
ast.replace([:string_join, *strings])
|
39
|
+
else
|
40
|
+
ast[1..-1].each { |a| do_map(a) }
|
41
|
+
end
|
42
|
+
else
|
43
|
+
ast[1..-1].each { |a| do_map(a) }
|
44
|
+
end
|
45
|
+
end
|
11
46
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
47
|
+
def try_and_convert_array(ast)
|
48
|
+
return ast unless ast.first == :array
|
49
|
+
return ast unless all_references?(ast)
|
50
|
+
#return ast unless ast[1..-1].all? { |c| c.first == :sheet_reference }
|
51
|
+
if ast.length == 2
|
52
|
+
single_row(ast)
|
53
|
+
elsif ast[1].length == 2
|
54
|
+
single_column(ast)
|
55
|
+
else
|
56
|
+
ERROR
|
57
|
+
end
|
16
58
|
end
|
17
|
-
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# Looks to match shared string lines
|
23
|
-
if line =~ /\[:array/
|
24
|
-
content = line.split("\t")
|
25
|
-
ast = eval(content.pop)
|
26
|
-
output.puts "#{content.join("\t")}\t#{replacer.map(ast).inspect}"
|
27
|
-
else
|
28
|
-
output.puts line
|
59
|
+
|
60
|
+
def all_references?(ast)
|
61
|
+
ast[1..-1].all? do |row|
|
62
|
+
row[1..-1].all? do |cell|
|
63
|
+
cell.first == :sheet_reference
|
29
64
|
end
|
30
65
|
end
|
31
66
|
end
|
32
|
-
|
67
|
+
|
68
|
+
def single_row(ast)
|
69
|
+
r = Reference.for(ref.last)
|
70
|
+
r.calculate_excel_variables
|
71
|
+
column = r.excel_column
|
72
|
+
sheet = ref.first
|
73
|
+
|
74
|
+
cells = ast[1][1..-1]
|
75
|
+
match = cells.find do |cell|
|
76
|
+
s = cell[1]
|
77
|
+
c = cell[2][1][/([A-Za-z]{1,3})/,1]
|
78
|
+
sheet == s && column == c
|
79
|
+
end
|
80
|
+
|
81
|
+
match || ERROR
|
82
|
+
end
|
83
|
+
|
84
|
+
def single_column(ast)
|
85
|
+
r = Reference.for(ref.last)
|
86
|
+
r.calculate_excel_variables
|
87
|
+
row = r.excel_row
|
88
|
+
sheet = ref.first
|
89
|
+
|
90
|
+
cells = ast[1..-1].map { |row| row.last }
|
91
|
+
match = cells.find do |cell|
|
92
|
+
s = cell[1]
|
93
|
+
r = cell[2][1][/([A-Za-z]{1,3})(\d+)/,2]
|
94
|
+
sheet == s && row == r
|
95
|
+
end
|
96
|
+
|
97
|
+
match || ERROR
|
98
|
+
end
|
33
99
|
end
|
@@ -29,6 +29,11 @@ class ReplaceOffsetsWithReferencesAst
|
|
29
29
|
column_offset = ast[4]
|
30
30
|
height = ast[5] || [:number, 1]
|
31
31
|
width = ast[6] || [:number, 1]
|
32
|
+
[row_offset, column_offset, height, width].each do |arg|
|
33
|
+
next unless arg.first == :error
|
34
|
+
ast.replace(arg)
|
35
|
+
return
|
36
|
+
end
|
32
37
|
return unless [row_offset, column_offset, height, width].all? { |a| a.first == :number }
|
33
38
|
if reference.first == :cell
|
34
39
|
ast.replace(offset_cell(reference, row_offset, column_offset, height, width))
|
@@ -16,19 +16,69 @@ class ReplaceRangesWithArrayLiteralsAst
|
|
16
16
|
case ast[0]
|
17
17
|
when :sheet_reference; return sheet_reference(ast)
|
18
18
|
when :area; return area(ast)
|
19
|
+
when :function;
|
20
|
+
if ast[1] == :SUMIF
|
21
|
+
return sumif(ast)
|
22
|
+
else
|
23
|
+
map_args(ast)
|
24
|
+
end
|
19
25
|
else
|
20
|
-
ast
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
map_args(ast)
|
27
|
+
end
|
28
|
+
ast
|
29
|
+
end
|
30
|
+
|
31
|
+
def map_args(ast)
|
32
|
+
ast.each.with_index do |a,i|
|
33
|
+
next unless a.is_a?(Array)
|
34
|
+
case a[0]
|
35
|
+
when :sheet_reference; ast[i] = sheet_reference(a)
|
36
|
+
when :area; ast[i] = area(a)
|
37
|
+
when :function
|
38
|
+
if ast[1] == :sumif
|
39
|
+
ast[i] = sumif(a)
|
40
|
+
else
|
41
|
+
do_map(a)
|
26
42
|
end
|
43
|
+
else
|
44
|
+
do_map(a)
|
27
45
|
end
|
28
46
|
end
|
29
|
-
ast
|
30
47
|
end
|
31
|
-
|
48
|
+
|
49
|
+
# ARGH: SUMIF(A1:A10, 10, B5:B6) is interpreted by Excel as SUMIF(A1:A10, 10, B5:B15)
|
50
|
+
def sumif(ast)
|
51
|
+
# If only two arguments to SUMIF, won't be a problem
|
52
|
+
return map_args(ast) unless ast.length == 5
|
53
|
+
check_range, criteria, sum_range = ast[2], ast[3], ast[4]
|
54
|
+
return map_args(ast) unless [:area, :sheet_reference, :cell].include?(check_range.first)
|
55
|
+
return map_args(ast) unless [:area, :sheet_reference, :cell].include?(sum_range.first)
|
56
|
+
check_area = area_for(check_range)
|
57
|
+
sum_area = area_for(sum_range)
|
58
|
+
|
59
|
+
check_area.calculate_excel_variables
|
60
|
+
sum_area.calculate_excel_variables
|
61
|
+
|
62
|
+
return map_args(ast) if check_area.height == sum_area.height && check_area.width == sum_area.width
|
63
|
+
|
64
|
+
new_sum_area = [:area, sum_area.excel_start.to_sym, sum_area.excel_start.offset(check_area.height, check_area.width).to_sym]
|
65
|
+
|
66
|
+
if sum_range.first == :sheet_reference
|
67
|
+
ast[4][2] = new_sum_area
|
68
|
+
else
|
69
|
+
ast[4] = new_sum_area
|
70
|
+
end
|
71
|
+
map_args(ast)
|
72
|
+
end
|
73
|
+
|
74
|
+
def area_for(ast)
|
75
|
+
case ast.first
|
76
|
+
when :cell then Area.for(ast[1])
|
77
|
+
when :area then Area.for("#{ast[1]}:#{ast[2]}")
|
78
|
+
when :sheet_reference then area_for(ast[2])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
32
82
|
# Of the form [:sheet_reference, sheet, reference]
|
33
83
|
def sheet_reference(ast)
|
34
84
|
@cache[ast] || calculate_expansion_for(ast)
|
@@ -40,16 +90,16 @@ class ReplaceRangesWithArrayLiteralsAst
|
|
40
90
|
return ast unless reference.first == :area
|
41
91
|
area = Area.for("#{reference[1]}:#{reference[2]}")
|
42
92
|
a = area.to_array_literal(sheet)
|
43
|
-
|
93
|
+
|
44
94
|
# Don't convert single cell ranges
|
45
95
|
result = if a.size == 2 && a[1].size == 2
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
96
|
+
a[1][1]
|
97
|
+
else
|
98
|
+
a
|
99
|
+
end
|
50
100
|
@cache[ast.dup] = result
|
51
101
|
end
|
52
|
-
|
102
|
+
|
53
103
|
# Of the form [:area, start, finish]
|
54
104
|
def area(ast)
|
55
105
|
start = ast[1]
|
@@ -64,18 +114,18 @@ class ReplaceRangesWithArrayLiteralsAst
|
|
64
114
|
a
|
65
115
|
end
|
66
116
|
end
|
67
|
-
|
117
|
+
|
68
118
|
end
|
69
119
|
|
70
120
|
class ReplaceRangesWithArrayLiterals
|
71
|
-
|
121
|
+
|
72
122
|
def self.replace(*args)
|
73
123
|
self.new.replace(*args)
|
74
124
|
end
|
75
|
-
|
125
|
+
|
76
126
|
def replace(input,output)
|
77
127
|
rewriter = ReplaceRangesWithArrayLiteralsAst.new
|
78
|
-
|
128
|
+
|
79
129
|
input.each_line do |line|
|
80
130
|
# Looks to match shared string lines
|
81
131
|
if line =~ /\[:area/
|
@@ -87,5 +137,5 @@ class ReplaceRangesWithArrayLiterals
|
|
87
137
|
end
|
88
138
|
end
|
89
139
|
end
|
90
|
-
|
140
|
+
|
91
141
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: excel_to_code
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Counsell, Green on Black Ltd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubypeg
|