excel_to_code 0.2.19 → 0.2.20

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 74698377e3e89c2dae52564440694e4e2e97f2f1
4
- data.tar.gz: 73e3a3af8271ac02a3f68727f43e5874153d2d2f
3
+ metadata.gz: 8683fda68a9053d3873f3ae6612dc61af4ad4393
4
+ data.tar.gz: 5ce67a7a836a8931a2b41f7f790ca07b9c565960
5
5
  SHA512:
6
- metadata.gz: f97a19688abe21b1cdbc7755a77158379746f012bda38154b35c7114add213ef4d1cee34f6121ce61d09d3bced7ae5de98f413e297ce5c0beecc9bd586a7177d
7
- data.tar.gz: b911c226eed1bd735828b5a80927b8198ea1b226b312032c937c9bb96a98f10ccb3bff9fc13e391c2aef0c3163307e3b1e541e371c74459ae628ac714aa34027
6
+ metadata.gz: abef9a443559d67c2dd453d6c386febc7516c2fa88b13c2a0a8e43e932809d90d51e400e637bf4845d85bd6296136805c42e762b1d7dc563192ba0b32841f93d
7
+ data.tar.gz: 6d74e0cfe7dcdad64037700c137066f21afddc10f2acafb2df63c8348e26ea0618e1b321697af7b1024853949bbc71ef1bfd87edb0839e7c75e039ae5d7d36d3
data/src/commands.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require_relative "commands/excel_to_x"
2
2
  require_relative "commands/excel_to_ruby"
3
- require_relative "commands/excel_to_c"
3
+ require_relative "commands/excel_to_c"
4
+ require_relative "commands/excel_to_test"
@@ -0,0 +1,137 @@
1
+ # coding: utf-8
2
+ require_relative 'excel_to_x'
3
+ require 'ffi'
4
+
5
+ class ExcelToTest < ExcelToX
6
+
7
+ attr_accessor :test_name
8
+
9
+ def language
10
+ "C"
11
+ end
12
+
13
+ def go!
14
+ # This sorts out the settings
15
+ set_defaults
16
+
17
+ log.info "Excel to Code version #{ExcelToCode.version}\n\n"
18
+
19
+ # These turn the excel into xml on disk
20
+ sort_out_output_directories
21
+ unzip_excel
22
+
23
+ # These gets the named references, worksheet names and shared strings out of the excel
24
+ extract_data_from_workbook
25
+
26
+ # This checks that the user inputs of which cells to keep are in the right
27
+ # format and refer to sheets and references that actually exist
28
+ clean_cells_that_can_be_set_at_runtime
29
+ clean_cells_to_keep
30
+ clean_named_references_to_keep
31
+ clean_named_references_that_can_be_set_at_runtime
32
+
33
+ # This turns named references that are specified as getters and setters
34
+ # into a series of required cell references
35
+ transfer_named_references_to_keep_into_cells_to_keep
36
+ transfer_named_references_that_can_be_set_at_runtime_into_cells_that_can_be_set_at_runtime
37
+
38
+ # This makes sure we only extract values from the worksheets we actuall care about
39
+ extractor = ExtractDataFromWorksheet.new
40
+ extractor.only_extract_values = true
41
+
42
+ cells_to_keep.each do |name, refs|
43
+ xml_filename = @worksheet_xmls[name]
44
+
45
+ log.info "Extracting data from #{name}"
46
+ xml(xml_filename) do |input|
47
+ extractor.extract(name, input)
48
+ end
49
+ end
50
+ @values = extractor.values
51
+
52
+
53
+ # These perform some translations to tsimplify the excel
54
+ # Including:
55
+ # * Turning row and column references (e.g., A:A) to areas, based on the size of the worksheet
56
+ # * Turning range references (e.g., A1:B2) into array litterals (e.g., {A1,B1;A2,B2})
57
+ # * Turning shared formulae into a series of conventional formulae
58
+ # * Turning array formulae into a series of conventional formulae
59
+ # * Mergining all the different types of formulae and values into a single hash
60
+ rewrite_values_to_remove_shared_strings
61
+
62
+ # FIXME: Bodge for the moment
63
+ @formulae = @values
64
+ create_sorted_references_to_test
65
+
66
+ # This actually creates the code (implemented in subclasses)
67
+ write_code
68
+
69
+ # These compile and run the code version of the excel (implemented in subclasses)
70
+ run_tests
71
+
72
+ log.info "The generated code is available in #{File.join(output_directory)}"
73
+ end
74
+
75
+ # These actually create the code version of the excel
76
+ def write_code
77
+ write_tests
78
+ end
79
+
80
+ def write_code_to_set_values
81
+ log.info "Writing code to set values to match those in the worksheet"
82
+ end
83
+
84
+ # FIXME: Should make a Rakefile, especially in order to make sure the dynamic library name
85
+
86
+ def write_tests
87
+ log.info "Writing tests"
88
+
89
+ name = output_name.downcase
90
+ @test_name = "test_#{name}_#{Time.now.to_i}.rb"
91
+ o = output(@test_name)
92
+ o.puts "# coding: utf-8"
93
+ o.puts "# Test for #{name}"
94
+ o.puts "require 'minitest/autorun'"
95
+ o.puts "require_relative '#{output_name.downcase}'"
96
+ o.puts
97
+ o.puts "class Test#{ruby_module_name} < Minitest::Unit::TestCase"
98
+ o.puts " def self.runnable_methods"
99
+ o.puts " puts 'Overriding minitest to run tests in a defined order'"
100
+ o.puts " methods = methods_matching(/^test_/)"
101
+ o.puts " end"
102
+ o.puts " def worksheet; @worksheet ||= init_spreadsheet; end"
103
+
104
+ # We need to set up the worksheet with the correct starting values
105
+ o.puts " def init_spreadsheet"
106
+ o.puts " s = #{ruby_module_name}Shim.new"
107
+
108
+ mapper = MapValuesToRuby.new
109
+
110
+ cells_that_can_be_set_at_runtime.each do |sheet, cells|
111
+ worksheet_c_name = c_name_for_worksheet_name(sheet)
112
+ cells.each do |cell|
113
+ ast = @values[[sheet.to_sym, cell.to_sym]]
114
+ next unless ast
115
+ value = mapper.map(ast)
116
+
117
+ full_reference = worksheet_c_name.length > 0 ? "#{worksheet_c_name}_#{cell.downcase}" : "#{cell.downcase}"
118
+ o.puts " s.#{full_reference} = #{value}"
119
+ end
120
+ end
121
+
122
+ o.puts " return s"
123
+ o.puts " end"
124
+
125
+ CompileToCUnitTest.rewrite(Hash[@references_to_test_array], sloppy_tests, @worksheet_c_names, @constants, o)
126
+ o.puts "end"
127
+ close(o)
128
+ puts "New test is in #{File.join(output_directory, @test_name)}"
129
+ end
130
+
131
+ def run_tests
132
+ return unless actually_run_tests
133
+ puts "Running the resulting tests"
134
+ puts `cd #{File.join(output_directory)}; ruby "#{@test_name}"`
135
+ end
136
+
137
+ end
data/src/compile/c/a.out CHANGED
Binary file
@@ -1066,113 +1066,174 @@ static ExcelValue iferror(ExcelValue value, ExcelValue value_if_error) {
1066
1066
  return value;
1067
1067
  }
1068
1068
 
1069
+ // Order is TRUE, FALSE, String, Number; Blank is zero
1069
1070
  static ExcelValue more_than(ExcelValue a_v, ExcelValue b_v) {
1070
- CHECK_FOR_PASSED_ERROR(a_v)
1071
- CHECK_FOR_PASSED_ERROR(b_v)
1071
+ CHECK_FOR_PASSED_ERROR(a_v)
1072
+ CHECK_FOR_PASSED_ERROR(b_v)
1072
1073
 
1073
- switch (a_v.type) {
1074
- case ExcelNumber:
1075
- case ExcelBoolean:
1076
- case ExcelEmpty:
1077
- if((b_v.type == ExcelNumber) || (b_v.type == ExcelBoolean) || (b_v.type == ExcelEmpty)) {
1078
- if(a_v.number <= b_v.number) return FALSE;
1079
- return TRUE;
1080
- }
1081
- return FALSE;
1082
- case ExcelString:
1083
- if(b_v.type == ExcelString) {
1084
- if(strcasecmp(a_v.string,b_v.string) <= 0 ) return FALSE;
1085
- return TRUE;
1086
- }
1087
- return FALSE;
1088
- case ExcelError:
1089
- return a_v;
1090
- case ExcelRange:
1091
- return NA;
1092
- }
1093
- return FALSE;
1074
+ if(a_v.type == ExcelEmpty) { a_v = ZERO; }
1075
+ if(b_v.type == ExcelEmpty) { b_v = ZERO; }
1076
+
1077
+ switch (a_v.type) {
1078
+ case ExcelString:
1079
+ switch (b_v.type) {
1080
+ case ExcelString:
1081
+ if(strcasecmp(a_v.string,b_v.string) <= 0 ) {return FALSE;} else {return TRUE;}
1082
+ case ExcelNumber:
1083
+ return TRUE;
1084
+ case ExcelBoolean:
1085
+ return FALSE;
1086
+ // Following shouldn't happen
1087
+ case ExcelEmpty:
1088
+ case ExcelError:
1089
+ case ExcelRange:
1090
+ return NA;
1091
+ }
1092
+ case ExcelBoolean:
1093
+ switch (b_v.type) {
1094
+ case ExcelBoolean:
1095
+ if(a_v.number == true) {
1096
+ if (b_v.number == true) { return FALSE; } else { return TRUE; }
1097
+ } else { // a_v == FALSE
1098
+ return FALSE;
1099
+ }
1100
+ case ExcelString:
1101
+ case ExcelNumber:
1102
+ return TRUE;
1103
+ // Following shouldn't happen
1104
+ case ExcelEmpty:
1105
+ case ExcelError:
1106
+ case ExcelRange:
1107
+ return NA;
1108
+ }
1109
+ case ExcelNumber:
1110
+ switch (b_v.type) {
1111
+ case ExcelNumber:
1112
+ if(a_v.number > b_v.number) { return TRUE; } else { return FALSE; }
1113
+ case ExcelString:
1114
+ case ExcelBoolean:
1115
+ return FALSE;
1116
+ // Following shouldn't happen
1117
+ case ExcelEmpty:
1118
+ case ExcelError:
1119
+ case ExcelRange:
1120
+ return NA;
1121
+ }
1122
+ // Following shouldn't happen
1123
+ case ExcelEmpty:
1124
+ case ExcelError:
1125
+ case ExcelRange:
1126
+ return NA;
1127
+ }
1128
+ // Shouldn't reach here
1129
+ return NA;
1094
1130
  }
1095
1131
 
1096
1132
  static ExcelValue more_than_or_equal(ExcelValue a_v, ExcelValue b_v) {
1097
- CHECK_FOR_PASSED_ERROR(a_v)
1098
- CHECK_FOR_PASSED_ERROR(b_v)
1099
-
1100
- switch (a_v.type) {
1101
- case ExcelNumber:
1102
- case ExcelBoolean:
1103
- case ExcelEmpty:
1104
- if((b_v.type == ExcelNumber) || (b_v.type == ExcelBoolean) || (b_v.type == ExcelEmpty)) {
1105
- if(a_v.number < b_v.number) return FALSE;
1106
- return TRUE;
1107
- }
1108
- return FALSE;
1109
- case ExcelString:
1110
- if(b_v.type == ExcelString) {
1111
- if(strcasecmp(a_v.string,b_v.string) < 0 ) return FALSE;
1112
- return TRUE;
1113
- }
1114
- return FALSE;
1115
- case ExcelError:
1116
- return a_v;
1117
- case ExcelRange:
1118
- return NA;
1133
+ ExcelValue opposite = less_than(a_v, b_v);
1134
+ switch (opposite.type) {
1135
+ case ExcelBoolean:
1136
+ if(opposite.number == true) { return FALSE; } else { return TRUE; }
1137
+ case ExcelError:
1138
+ return opposite;
1139
+ // Shouldn't reach below
1140
+ case ExcelNumber:
1141
+ case ExcelString:
1142
+ case ExcelEmpty:
1143
+ case ExcelRange:
1144
+ return NA;
1119
1145
  }
1120
- return FALSE;
1121
1146
  }
1122
1147
 
1123
-
1148
+ // Order is TRUE, FALSE, String, Number; Blank is zero
1124
1149
  static ExcelValue less_than(ExcelValue a_v, ExcelValue b_v) {
1125
1150
  CHECK_FOR_PASSED_ERROR(a_v)
1126
1151
  CHECK_FOR_PASSED_ERROR(b_v)
1127
1152
 
1153
+ if(a_v.type == ExcelEmpty) { a_v = ZERO; }
1154
+ if(b_v.type == ExcelEmpty) { b_v = ZERO; }
1155
+
1128
1156
  switch (a_v.type) {
1129
- case ExcelNumber:
1130
- case ExcelBoolean:
1131
- case ExcelEmpty:
1132
- if((b_v.type == ExcelNumber) || (b_v.type == ExcelBoolean) || (b_v.type == ExcelEmpty)) {
1133
- if(a_v.number >= b_v.number) return FALSE;
1134
- return TRUE;
1135
- }
1136
- return FALSE;
1137
- case ExcelString:
1138
- if(b_v.type == ExcelString) {
1139
- if(strcasecmp(a_v.string,b_v.string) >= 0 ) return FALSE;
1140
- return TRUE;
1141
- }
1142
- return FALSE;
1143
- case ExcelError:
1144
- return a_v;
1145
- case ExcelRange:
1146
- return NA;
1157
+ case ExcelString:
1158
+ switch (b_v.type) {
1159
+ case ExcelString:
1160
+ if(strcasecmp(a_v.string, b_v.string) >= 0 ) {
1161
+ return FALSE;
1162
+ } else {
1163
+ return TRUE;
1164
+ }
1165
+ case ExcelNumber:
1166
+ return FALSE;
1167
+ case ExcelBoolean:
1168
+ return TRUE;
1169
+ // The following shouldn't happen
1170
+ // FIXME: Should abort if it does
1171
+ case ExcelError:
1172
+ case ExcelRange:
1173
+ case ExcelEmpty:
1174
+ return NA;
1175
+ }
1176
+ case ExcelNumber:
1177
+ switch(b_v.type) {
1178
+ case ExcelNumber:
1179
+ if(a_v.number < b_v.number) {
1180
+ return TRUE;
1181
+ } else {
1182
+ return FALSE;
1183
+ }
1184
+ case ExcelBoolean:
1185
+ case ExcelString:
1186
+ return TRUE;
1187
+ // The following shouldn't happen
1188
+ // FIXME: Should abort if it does
1189
+ case ExcelError:
1190
+ case ExcelRange:
1191
+ case ExcelEmpty:
1192
+ return NA;
1193
+ }
1194
+ case ExcelBoolean:
1195
+ switch(b_v.type) {
1196
+ case ExcelBoolean:
1197
+ if(a_v.number == true) {
1198
+ return FALSE;
1199
+ } else { // a_v.number == false
1200
+ if(b_v.number == true) {return TRUE;} else {return FALSE;}
1201
+ }
1202
+ case ExcelString:
1203
+ case ExcelNumber:
1204
+ return FALSE;
1205
+ // The following shouldn't happen
1206
+ // FIXME: Should abort if it does
1207
+ case ExcelError:
1208
+ case ExcelRange:
1209
+ case ExcelEmpty:
1210
+ return NA;
1211
+ }
1212
+ // The following shouldn't happen
1213
+ // FIXME: Should abort if it does
1214
+ case ExcelError:
1215
+ case ExcelRange:
1216
+ case ExcelEmpty:
1217
+ return VALUE;
1147
1218
  }
1148
- return FALSE;
1219
+ // Shouldn't reach here
1220
+ return NA;
1149
1221
  }
1150
1222
 
1151
1223
  static ExcelValue less_than_or_equal(ExcelValue a_v, ExcelValue b_v) {
1152
- CHECK_FOR_PASSED_ERROR(a_v)
1153
- CHECK_FOR_PASSED_ERROR(b_v)
1154
-
1155
- switch (a_v.type) {
1156
- case ExcelNumber:
1157
- case ExcelBoolean:
1158
- case ExcelEmpty:
1159
- if((b_v.type == ExcelNumber) || (b_v.type == ExcelBoolean) || (b_v.type == ExcelEmpty)) {
1160
- if(a_v.number > b_v.number) return FALSE;
1161
- return TRUE;
1162
- }
1163
- return FALSE;
1164
- case ExcelString:
1165
- if(b_v.type == ExcelString) {
1166
- if(strcasecmp(a_v.string,b_v.string) > 0 ) return FALSE;
1167
- return TRUE;
1168
- }
1169
- return FALSE;
1170
- case ExcelError:
1171
- return a_v;
1172
- case ExcelRange:
1173
- return NA;
1224
+ ExcelValue opposite = more_than(a_v, b_v);
1225
+ switch (opposite.type) {
1226
+ case ExcelBoolean:
1227
+ if(opposite.number == true) { return FALSE; } else { return TRUE; }
1228
+ case ExcelError:
1229
+ return opposite;
1230
+ // Shouldn't reach below
1231
+ case ExcelNumber:
1232
+ case ExcelString:
1233
+ case ExcelEmpty:
1234
+ case ExcelRange:
1235
+ return VALUE;
1174
1236
  }
1175
- return FALSE;
1176
1237
  }
1177
1238
 
1178
1239
  static ExcelValue subtract(ExcelValue a_v, ExcelValue b_v) {
@@ -154,6 +154,13 @@ int test_functions() {
154
154
  assert(more_than(BLANK,new_excel_number(-1)).number == true);
155
155
  assert(more_than(ONE,BLANK).number == true);
156
156
  assert(more_than(new_excel_number(-1),BLANK).number == false);
157
+ // .. of different types
158
+ assert(more_than(TRUE,new_excel_string("Hello")).number == true);
159
+ assert(more_than(FALSE,new_excel_string("Hello")).number == true);
160
+ assert(more_than(new_excel_string("Hello"), ONE).number == true);
161
+ assert(more_than(ONE,new_excel_string("Hello")).number == false);
162
+ assert(more_than(new_excel_string("Hello"), TRUE).number == false);
163
+ assert(more_than(new_excel_string("Hello"), FALSE).number == false);
157
164
 
158
165
  // Test less than on
159
166
  // .. numbers
@@ -174,6 +181,13 @@ int test_functions() {
174
181
  assert(less_than(BLANK,new_excel_number(-1)).number == false);
175
182
  assert(less_than(ONE,BLANK).number == false);
176
183
  assert(less_than(new_excel_number(-1),BLANK).number == true);
184
+ // .. of different types
185
+ assert(less_than(TRUE,new_excel_string("Hello")).number == false);
186
+ assert(less_than(FALSE,new_excel_string("Hello")).number == false);
187
+ assert(less_than(new_excel_string("Hello"), ONE).number == false);
188
+ assert(less_than(ONE,new_excel_string("Hello")).number == true);
189
+ assert(less_than(new_excel_string("Hello"), TRUE).number == true);
190
+ assert(less_than(new_excel_string("Hello"), FALSE).number == true);
177
191
 
178
192
  // Test FIND function
179
193
  // ... should find the first occurrence of one string in another, returning :value if the string doesn't match
@@ -13,13 +13,27 @@ module ExcelFunctions
13
13
 
14
14
  case a
15
15
  when String
16
- a.downcase < b.downcase
17
- when TrueClass, FalseClass
18
- a = a ? 1 : 0
19
- b = b ? 1 : 0 if (b.is_a?(TrueClass) || b.is_a?(FalseClass))
20
- a < b
21
- else
22
- a < b
16
+ case b
17
+ when String
18
+ a.downcase < b.downcase
19
+ when Numeric
20
+ false
21
+ when TrueClass, FalseClass
22
+ true
23
+ end
24
+ when TrueClass
25
+ false
26
+ when FalseClass
27
+ b.is_a?(TrueClass)
28
+ when Numeric
29
+ case b
30
+ when Numeric
31
+ a < b
32
+ when String
33
+ true
34
+ when TrueClass, FalseClass
35
+ true
36
+ end
23
37
  end
24
38
  end
25
39
 
@@ -3,24 +3,9 @@ require_relative 'apply_to_range'
3
3
  module ExcelFunctions
4
4
 
5
5
  def less_than_or_equal?(a,b)
6
- # return apply_to_range(a,b) { |a,b| less_than_or_equal?(a,b) } if a.is_a?(Array) || b.is_a?(Array)
7
-
8
- return a if a.is_a?(Symbol)
9
- return b if b.is_a?(Symbol)
10
-
11
- a = 0 if a == nil
12
- b = 0 if b == nil
13
-
14
- case a
15
- when String
16
- a.downcase <= b.downcase
17
- when TrueClass, FalseClass
18
- a = a ? 1 : 0
19
- b = b ? 1 : 0 if (b.is_a?(TrueClass) || b.is_a?(FalseClass))
20
- a <= b
21
- else
22
- a <= b
23
- end
6
+ opposite = more_than?(a,b)
7
+ return opposite if opposite.is_a?(Symbol)
8
+ !opposite
24
9
  end
25
10
 
26
11
  end
@@ -10,16 +10,33 @@ module ExcelFunctions
10
10
 
11
11
  a = 0 if a == nil
12
12
  b = 0 if b == nil
13
-
13
+
14
14
  case a
15
15
  when String
16
- a.downcase > b.downcase
17
- when TrueClass, FalseClass
18
- a = a ? 1 : 0
19
- b = b ? 1 : 0 if (b.is_a?(TrueClass) || b.is_a?(FalseClass))
20
- a > b
21
- else
22
- a > b
16
+ case b
17
+ when String
18
+ a.downcase > b.downcase
19
+ when Numeric
20
+ true
21
+ when TrueClass, FalseClass
22
+ false
23
+ end
24
+ when TrueClass
25
+ !b.is_a?(TrueClass)
26
+ when FalseClass
27
+ case b
28
+ when TrueClass, FalseClass
29
+ false
30
+ else
31
+ true
32
+ end
33
+ when Numeric
34
+ case b
35
+ when Numeric
36
+ a > b
37
+ else
38
+ false
39
+ end
23
40
  end
24
41
  end
25
42
 
@@ -3,24 +3,9 @@ require_relative 'apply_to_range'
3
3
  module ExcelFunctions
4
4
 
5
5
  def more_than_or_equal?(a,b)
6
- # return apply_to_range(a,b) { |a,b| more_than_or_equal?(a,b) } if a.is_a?(Array) || b.is_a?(Array)
7
-
8
- return a if a.is_a?(Symbol)
9
- return b if b.is_a?(Symbol)
10
-
11
- a = 0 if a == nil
12
- b = 0 if b == nil
13
-
14
- case a
15
- when String
16
- a.downcase >= b.downcase
17
- when TrueClass, FalseClass
18
- a = a ? 1 : 0
19
- b = b ? 1 : 0 if (b.is_a?(TrueClass) || b.is_a?(FalseClass))
20
- a >= b
21
- else
22
- a >= b
23
- end
6
+ opposite = less_than?(a,b)
7
+ return opposite if opposite.is_a?(Symbol)
8
+ !opposite
24
9
  end
25
10
 
26
11
  end
@@ -5,6 +5,12 @@ module ExcelFunctions
5
5
  range = [range] unless range.is_a?(Array)
6
6
  range = range.flatten
7
7
 
8
+ indexes = _filtered_range_indexes(range, *criteria)
9
+ return indexes if indexes.is_a?(Symbol)
10
+ range.values_at(*indexes)
11
+ end
12
+
13
+ def _filtered_range_indexes(range, *criteria)
8
14
  # Sort out the criteria
9
15
  0.step(criteria.length-1,2).each do |i|
10
16
  if criteria[i].is_a?(Array)
@@ -59,7 +65,7 @@ module ExcelFunctions
59
65
  break unless pass
60
66
  end # criteria loop
61
67
 
62
- filtered << potential if pass
68
+ filtered << index if pass
63
69
  end
64
70
 
65
71
  return filtered
data/src/excel_to_code.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class ExcelToCode
2
- def self.version() "0.2.19" end
2
+ def self.version() "0.2.20" end
3
3
  end
4
4
 
5
5
  require_relative 'commands'
data/src/simplify.rb CHANGED
@@ -3,7 +3,7 @@ require_relative "simplify/replace_named_references"
3
3
  require_relative "simplify/replace_table_references"
4
4
  require_relative "simplify/replace_ranges_with_array_literals"
5
5
  require_relative "simplify/inline_formulae"
6
- require_relative "simplify/replace_formulae_with_calculated_values"
6
+ require_relative "simplify/map_formulae_to_values"
7
7
  require_relative "simplify/replace_indirects_with_references"
8
8
  require_relative "simplify/replace_offsets_with_references"
9
9
  require_relative "simplify/replace_column_with_column_number"
@@ -181,12 +181,15 @@ class MapFormulaeToValues
181
181
  normal_function(ast,nil)
182
182
  end
183
183
 
184
+ OK_CHECK_RANGE_TYPES = [:sheet_reference, :cell, :area, :array, :number, :string, :boolean_true, :boolean_false]
185
+
184
186
  def map_sumifs(ast)
185
187
  values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
186
- return if values.any? { |a| a == :not_a_value }
187
- return unless [:sheet_reference, :cell, :area, :array, :number, :string, :boolean_true, :boolean_false].include?(ast[2].first)
188
+ return if values.all? { |a| a == :not_a_value } # Nothing to be done
189
+ return attempt_to_reduce_sumifs(ast) if values.any? { |a| a == :not_a_value } # Maybe a reduction to be done
190
+ return unless OK_CHECK_RANGE_TYPES.include?(ast[2].first)
188
191
  sum_value = value(ast[2])
189
- if sum_value == :not_a_value
192
+ if sum_value == :not_a_value # i.e., a sheet_reference, :cell or :area
190
193
  partially_map_sumifs(ast)
191
194
  else
192
195
  ast.replace(formula_value( ast[1], sum_value, *values))
@@ -195,27 +198,58 @@ class MapFormulaeToValues
195
198
 
196
199
  def partially_map_sumifs(ast)
197
200
  values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
198
- sum_range = []
199
- if ast[2].first == :array
200
- ast[2].each do |row|
201
- next if row.is_a?(Symbol)
202
- row.each do |cell|
203
- next if cell.is_a?(Symbol)
204
- sum_range << cell
205
- end
206
- end
201
+ sum_range = array_as_values(ast[2]).flatten(1)
202
+ indexes = @calculator._filtered_range_indexes(sum_range, *values)
203
+ if indexes.is_a?(Symbol)
204
+ new_ast = value(filtered_range)
207
205
  else
208
- sum_range = [ast[2]]
206
+ new_ast = [:function, :SUM, *sum_range.values_at(*indexes)]
207
+ end
208
+ if new_ast != ast
209
+ @replacements_made_in_the_last_pass += 1
210
+ ast.replace(new_ast)
211
+ end
212
+ end
213
+
214
+ # FIXME: Ends up making everything single column. Is that ok?!
215
+ def attempt_to_reduce_sumifs(ast)
216
+ return unless OK_CHECK_RANGE_TYPES.include?(ast[2].first)
217
+ # First combine into a series of checks
218
+ criteria_that_can_be_resolved = []
219
+ criteria_that_cant_be_resolved = []
220
+ ast[3..-1].each_slice(2) do |check|
221
+ # Give up unless we have something that can actually be used
222
+ return unless OK_CHECK_RANGE_TYPES.include?(check[0].first)
223
+ check_range_value = value(check[0])
224
+ check_criteria_value = value(check[1])
225
+ if check_range_value == :not_a_value || check_criteria_value == :not_a_value
226
+ criteria_that_cant_be_resolved << check
227
+ else
228
+ criteria_that_can_be_resolved << [check_range_value, check_criteria_value]
229
+ end
209
230
  end
210
- sum_range_indexes = 0.upto(sum_range.length-1).to_a
211
- filtered_range = @calculator._filtered_range(sum_range_indexes, *values)
212
- if filtered_range.is_a?(Symbol)
213
- ast.replace(value(filtered_range))
231
+ return if criteria_that_can_be_resolved.empty?
232
+ sum_range = array_as_values(ast[2]).flatten(1)
233
+ indexes = @calculator._filtered_range_indexes(sum_range, *criteria_that_can_be_resolved.flatten(1))
234
+ if indexes.is_a?(Symbol)
235
+ return
236
+ elsif indexes.empty?
237
+ new_ast = [:number, 0]
214
238
  else
215
- ast.replace([:function, :SUM, *sum_range.values_at(*filtered_range)])
239
+ new_ast = [:function, :SUMIFS]
240
+ new_ast << ast_for_array(sum_range.values_at(*indexes))
241
+ criteria_that_cant_be_resolved.each do |check|
242
+ new_ast << ast_for_array(array_as_values(check.first).flatten(1).values_at(*indexes))
243
+ new_ast << check.last
244
+ end
245
+ end
246
+ if new_ast != ast
247
+ @replacements_made_in_the_last_pass += 1
248
+ ast.replace(new_ast)
216
249
  end
217
250
  end
218
251
 
252
+
219
253
  # [:function, "COUNT", range]
220
254
  def map_count(ast)
221
255
  values = ast[2..-1].map { |a| value(a, nil) }
@@ -274,17 +308,32 @@ class MapFormulaeToValues
274
308
  not_number_array.concat(result.last)
275
309
  end
276
310
  if number_total == 0 && not_number_array.empty?
277
- ast.replace([:number, number_total])
311
+ new_ast = [:number, number_total]
312
+ if new_ast != ast
313
+ @replacements_made_in_the_last_pass += 1
314
+ ast.replace(new_ast)
315
+ end
278
316
  # FIXME: Will I be haunted by this? What if doing a sum of something that isn't a number
279
317
  # and so what is expected is a VALUE error?. YES. This doesn't work well.
280
318
  elsif ast.length == 3 && [:cell, :sheet_reference].include?(ast[2].first)
281
- ast.replace(n(ast[2]))
319
+ new_ast = n(ast[2])
320
+ if new_ast != ast
321
+ @replacements_made_in_the_last_pass += 1
322
+ ast.replace(new_ast)
323
+ end
282
324
  elsif ast.length == 3 && ast[2][0] == :function && ast[2][1] == :ENSURE_IS_NUMBER
283
- ast.replace(ast[2])
325
+ new_ast = ast[2]
326
+ if new_ast != ast
327
+ @replacements_made_in_the_last_pass += 1
328
+ ast.replace(new_ast)
329
+ end
284
330
  else
285
331
  new_ast = [:function, :SUM].concat(not_number_array)
286
332
  new_ast.push([:number, number_total]) unless number_total == 0
287
- ast.replace(new_ast)
333
+ if new_ast != ast
334
+ @replacements_made_in_the_last_pass += 1
335
+ ast.replace(new_ast)
336
+ end
288
337
  end
289
338
  ast
290
339
  end
@@ -346,6 +395,11 @@ class MapFormulaeToValues
346
395
  end
347
396
  end
348
397
 
398
+ # FIXME: Assumes single column. Not wise?
399
+ def ast_for_array(array)
400
+ [:array,*array.map { |row| [:row, row ]}]
401
+ end
402
+
349
403
  ERRORS = {
350
404
  :"#NAME?" => :name,
351
405
  :"#VALUE!" => :value,
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.19
4
+ version: 0.2.20
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-03-18 00:00:00.000000000 Z
11
+ date: 2014-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubypeg
@@ -113,6 +113,7 @@ files:
113
113
  - src/commands.rb
114
114
  - src/commands/excel_to_c.rb
115
115
  - src/commands/excel_to_ruby.rb
116
+ - src/commands/excel_to_test.rb
116
117
  - src/commands/excel_to_x.rb
117
118
  - src/compile.rb
118
119
  - src/compile/c.rb
@@ -240,7 +241,6 @@ files:
240
241
  - src/simplify/replace_arrays_with_single_cells.rb
241
242
  - src/simplify/replace_column_with_column_number.rb
242
243
  - src/simplify/replace_common_elements_in_formulae.rb
243
- - src/simplify/replace_formulae_with_calculated_values.rb
244
244
  - src/simplify/replace_indirects_with_references.rb
245
245
  - src/simplify/replace_named_references.rb
246
246
  - src/simplify/replace_offsets_with_references.rb
@@ -1,28 +0,0 @@
1
- require_relative 'map_formulae_to_values'
2
-
3
- class ReplaceFormulaeWithCalculatedValues
4
-
5
- attr_accessor :excel_file
6
-
7
- def self.replace(*args)
8
- self.new.replace(*args)
9
- end
10
-
11
- attr_accessor :replacements_made_in_the_last_pass
12
-
13
- def replace(input,output)
14
- rewriter = MapFormulaeToValues.new
15
- rewriter.original_excel_filename = excel_file
16
- input.each_line do |line|
17
- begin
18
- ref, ast = line.split("\t")
19
- output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
20
- rewriter.reset
21
- rescue Exception => e
22
- puts "Exception at line #{line}"
23
- raise
24
- end
25
- end
26
- @replacements_made_in_the_last_pass = rewriter.replacements_made_in_the_last_pass
27
- end
28
- end