excel_to_code 0.2.19 → 0.2.20

Sign up to get free protection for your applications and to get access to all the features.
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