excel_to_code 0.0.1 → 0.0.2
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.
- data/bin/excel_to_c +11 -7
- data/src/commands.rb +1 -0
- data/src/commands/excel_to_c.rb +7 -615
- data/src/commands/excel_to_ruby.rb +14 -514
- data/src/commands/excel_to_x.rb +660 -0
- metadata +7 -6
data/bin/excel_to_c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'optparse'
|
3
|
-
require_relative '../src/
|
3
|
+
require_relative '../src/excel_to_code'
|
4
4
|
|
5
5
|
command = ExcelToC.new
|
6
6
|
|
@@ -8,9 +8,10 @@ opts = OptionParser.new do |opts|
|
|
8
8
|
opts.banner = <<-END
|
9
9
|
|
10
10
|
A command to approximately translate excel files into c code.
|
11
|
+
|
11
12
|
Usage: excel_to_c [options] input_excel_file <output_directory>
|
12
|
-
input_excel_file
|
13
|
-
output_directory
|
13
|
+
input_excel_file the name of a .xlsx excel file (i.e., produced by Excel 2007+, not a .xls file)
|
14
|
+
output_directory the name of a folder in which to place the generated code. If not specified will have the same name as the input_excel_file, without the .xlsx
|
14
15
|
|
15
16
|
Support: http://github.com/tamc/excel2code
|
16
17
|
END
|
@@ -30,15 +31,18 @@ END
|
|
30
31
|
command.cells_to_keep = { sheet => :all }
|
31
32
|
end
|
32
33
|
|
33
|
-
opts.on('-c','--compile',"Compile the generated
|
34
|
-
command.
|
34
|
+
opts.on('-c','--compile',"Compile the generated code (where relevant)") do
|
35
|
+
command.actually_compile_code = true
|
35
36
|
end
|
36
37
|
|
37
|
-
opts.on('-r','--run-tests',"Compile the generated
|
38
|
-
command.actually_compile_c_code = true
|
38
|
+
opts.on('-r','--run-tests',"Compile the generated code and then run the tests") do
|
39
39
|
command.actually_run_tests = true
|
40
40
|
end
|
41
41
|
|
42
|
+
opts.on('-m','--run-in-memory',"Instead of writing intermediate files to disk, uses memory. Requires a lot of memory") do
|
43
|
+
command.run_in_memory = true
|
44
|
+
end
|
45
|
+
|
42
46
|
opts.on("-h", "--help", "Show this message") do
|
43
47
|
puts opts
|
44
48
|
exit
|
data/src/commands.rb
CHANGED
data/src/commands/excel_to_c.rb
CHANGED
@@ -1,521 +1,16 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
require_relative 'excel_to_x'
|
2
3
|
|
3
|
-
|
4
|
-
require_relative '../util'
|
5
|
-
require_relative '../excel'
|
6
|
-
require_relative '../extract'
|
7
|
-
require_relative '../rewrite'
|
8
|
-
require_relative '../simplify'
|
9
|
-
require_relative '../compile'
|
10
|
-
|
11
|
-
# Used to throw normally fatal errors
|
12
|
-
class ExcelToCException < Exception; end
|
13
|
-
|
14
|
-
class ExcelToC
|
15
|
-
|
16
|
-
# Required attribute. The source excel file. This must be .xlsx not .xls
|
17
|
-
attr_accessor :excel_file
|
18
|
-
|
19
|
-
# Optional attribute. The output directory.
|
20
|
-
# If not specified, will be '#{excel_file_name}/c'
|
21
|
-
attr_accessor :output_directory
|
22
|
-
|
23
|
-
# Optional attribute. The name of the resulting c file (and associated ruby ffi module). Defaults to excelspreadsheet
|
24
|
-
attr_accessor :output_name
|
25
|
-
|
26
|
-
# Optional attribute. The excel file will be translated to xml and stored here.
|
27
|
-
# If not specified, will be '#{excel_file_name}/xml'
|
28
|
-
attr_accessor :xml_directory
|
29
|
-
|
30
|
-
# Optional attribute. The intermediate workings will be stored here.
|
31
|
-
# If not specified, will be '#{excel_file_name}/intermediate'
|
32
|
-
attr_accessor :intermediate_directory
|
33
|
-
|
34
|
-
# Optional attribute. Specifies which cells have setters created in the c code so their values can be altered at runtime.
|
35
|
-
# It is a hash. The keys are the sheet names. The values are either the symbol :all to specify that all cells on that sheet
|
36
|
-
# should be setable, or an array of cell names on that sheet that should be settable (e.g., A1)
|
37
|
-
attr_accessor :cells_that_can_be_set_at_runtime
|
38
|
-
|
39
|
-
# Optional attribute. Specifies which cells must appear in the final generated code.
|
40
|
-
# The default is that all cells in the original spreadsheet appear in the final code.
|
41
|
-
#
|
42
|
-
# If specified, then any cells that are not:
|
43
|
-
# * specified
|
44
|
-
# * required to calculate one of the cells that is specified
|
45
|
-
# * specified as a cell that can be set at runtime
|
46
|
-
# may be excluded from the final generated code.
|
47
|
-
#
|
48
|
-
# It is a hash. The keys are the sheet names. The values are either the symbol :all to specify that all cells on that sheet
|
49
|
-
# should be lept, or an array of cell names on that sheet that should be kept (e.g., A1)
|
50
|
-
attr_accessor :cells_to_keep
|
51
|
-
|
52
|
-
# Optional attribute. Boolean.
|
53
|
-
# * true - the generated c code is compiled
|
54
|
-
# * false - the generated c code is not compiled (default, unless actuall_run_tests is specified as true)
|
55
|
-
attr_accessor :actually_compile_c_code
|
56
|
-
|
57
|
-
# Optional attribute. Boolean.
|
58
|
-
# * true - the generated tests are run
|
59
|
-
# * false - the generated tests are not run
|
60
|
-
attr_accessor :actually_run_tests
|
61
|
-
|
62
|
-
def set_defaults
|
63
|
-
raise ExcelToCException.new("No excel file has been specified in ExcelToC.excel_file") unless excel_file
|
64
|
-
|
65
|
-
self.output_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'c')
|
66
|
-
self.xml_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'xml')
|
67
|
-
self.intermediate_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'intermediate')
|
68
|
-
|
69
|
-
self.output_name ||= "Excelspreadsheet"
|
70
|
-
|
71
|
-
self.cells_that_can_be_set_at_runtime ||= {}
|
72
|
-
|
73
|
-
# Make sure that all the cell names are downcase and don't have any $ in them
|
74
|
-
cells_that_can_be_set_at_runtime.keys.each do |sheet|
|
75
|
-
next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
|
76
|
-
cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').downcase }
|
77
|
-
end
|
78
|
-
|
79
|
-
if cells_to_keep
|
80
|
-
cells_to_keep.keys.each do |sheet|
|
81
|
-
next unless cells_to_keep[sheet].is_a?(Array)
|
82
|
-
cells_to_keep[sheet] = cells_to_keep[sheet].map { |reference| reference.gsub('$','').downcase }
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
self.excel_file = File.expand_path(excel_file)
|
87
|
-
self.output_directory = File.expand_path(output_directory)
|
88
|
-
|
89
|
-
end
|
4
|
+
class ExcelToC < ExcelToX
|
90
5
|
|
91
|
-
|
92
|
-
|
93
|
-
set_defaults
|
94
|
-
|
95
|
-
# These turn the excel into a more accesible format
|
96
|
-
sort_out_output_directories
|
97
|
-
unzip_excel
|
98
|
-
|
99
|
-
# These get all the information out of the excel and put
|
100
|
-
# into a useful format
|
101
|
-
extract_data_from_workbook
|
102
|
-
extract_data_from_worksheets
|
103
|
-
merge_table_files
|
104
|
-
rewrite_worksheets
|
105
|
-
|
106
|
-
# These perform a series of transformations to the information
|
107
|
-
# with the intent of removing any redundant calculations
|
108
|
-
# that are in the excel
|
109
|
-
simplify_worksheets
|
110
|
-
optimise_and_replace_indirect_loop
|
111
|
-
replace_blanks
|
112
|
-
remove_any_cells_not_needed_for_outputs
|
113
|
-
inline_formulae_that_are_only_used_once
|
114
|
-
separate_formulae_elements
|
115
|
-
|
116
|
-
# These actually create the code version of the excel
|
6
|
+
# These actually create the code version of the excel
|
7
|
+
def write_code
|
117
8
|
write_out_excel_as_code
|
118
9
|
write_build_script
|
119
10
|
write_fuby_ffi_interface
|
120
11
|
write_tests
|
121
|
-
|
122
|
-
# These compile and run the code version of the excel
|
123
|
-
compile_c_code
|
124
|
-
run_tests
|
125
|
-
|
126
|
-
puts
|
127
|
-
puts "The generated code is available in #{File.join(output_directory)}"
|
128
|
-
end
|
129
|
-
|
130
|
-
def sort_out_output_directories
|
131
|
-
FileUtils.mkdir_p(output_directory)
|
132
|
-
FileUtils.mkdir_p(xml_directory)
|
133
|
-
FileUtils.mkdir_p(intermediate_directory)
|
134
|
-
end
|
135
|
-
|
136
|
-
def unzip_excel
|
137
|
-
puts `rm -fr '#{xml_directory}'`
|
138
|
-
puts `unzip -uo '#{excel_file}' -d '#{xml_directory}'`
|
139
|
-
end
|
140
|
-
|
141
|
-
def extract_data_from_workbook
|
142
|
-
extract_shared_strings
|
143
|
-
|
144
|
-
extract ExtractNamedReferences, 'workbook.xml', 'named_references'
|
145
|
-
rewrite RewriteFormulaeToAst, 'named_references', 'named_references.ast'
|
146
|
-
|
147
|
-
extract ExtractWorksheetNames, 'workbook.xml', 'worksheet_names_without_filenames'
|
148
|
-
extract ExtractRelationships, File.join('_rels','workbook.xml.rels'), 'workbook_relationships'
|
149
|
-
rewrite RewriteWorksheetNames, 'worksheet_names_without_filenames', 'workbook_relationships', 'worksheet_names'
|
150
|
-
rewrite MapSheetNamesToCNames, 'worksheet_names', 'worksheet_c_names'
|
151
|
-
|
152
|
-
extract_dimensions_from_worksheets
|
153
|
-
end
|
154
|
-
|
155
|
-
def extract_shared_strings
|
156
|
-
if File.exists?(File.join(xml_directory,'xl','sharedStrings.xml'))
|
157
|
-
extract ExtractSharedStrings, 'sharedStrings.xml', 'shared_strings'
|
158
|
-
else
|
159
|
-
FileUtils.touch(File.join(intermediate_directory,'shared_strings'))
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def extract_dimensions_from_worksheets
|
164
|
-
dimension_file = intermediate('dimensions')
|
165
|
-
worksheets("Extracting dimensions") do |name,xml_filename|
|
166
|
-
dimension_file.write name
|
167
|
-
dimension_file.write "\t"
|
168
|
-
extract ExtractWorksheetDimensions, File.open(xml_filename,'r'), dimension_file
|
169
|
-
end
|
170
|
-
dimension_file.close
|
171
12
|
end
|
172
|
-
|
173
|
-
def extract_data_from_worksheets
|
174
|
-
worksheets("Initial data extract") do |name,xml_filename|
|
175
|
-
worksheet_directory = File.join(intermediate_directory,name)
|
176
|
-
FileUtils.mkdir_p(worksheet_directory)
|
177
|
-
worksheet_xml = File.open(xml_filename,'r')
|
178
|
-
{ ExtractValues => 'values',
|
179
|
-
ExtractSimpleFormulae => 'simple_formulae',
|
180
|
-
ExtractSharedFormulae => 'shared_formulae',
|
181
|
-
ExtractArrayFormulae => 'array_formulae'
|
182
|
-
}.each do |_klass,output_filename|
|
183
|
-
worksheet_xml.rewind
|
184
|
-
extract _klass, worksheet_xml, File.join(name,output_filename)
|
185
|
-
if _klass == ExtractValues
|
186
|
-
rewrite RewriteValuesToAst, File.join(name,output_filename), File.join(name,"#{output_filename}.ast")
|
187
|
-
else
|
188
|
-
rewrite RewriteFormulaeToAst, File.join(name,output_filename), File.join(name,"#{output_filename}.ast")
|
189
|
-
end
|
190
|
-
end
|
191
|
-
worksheet_xml.rewind
|
192
|
-
extract ExtractWorksheetTableRelationships, worksheet_xml, File.join(name,'table_rids')
|
193
|
-
if File.exists?(File.join(xml_directory,'xl','worksheets','_rels',"#{File.basename(xml_filename)}.rels"))
|
194
|
-
extract ExtractRelationships, File.join('worksheets','_rels',"#{File.basename(xml_filename)}.rels"), File.join(name,'relationships')
|
195
|
-
rewrite RewriteRelationshipIdToFilename, File.join(name,'table_rids'), File.join(name,'relationships'), File.join(name,'table_filenames')
|
196
|
-
tables = intermediate(name,'tables')
|
197
|
-
table_extractor = ExtractTable.new(name)
|
198
|
-
table_filenames = input(name,'table_filenames')
|
199
|
-
table_filenames.lines.each do |line|
|
200
|
-
extract table_extractor, File.join('worksheets',line.strip), tables
|
201
|
-
end
|
202
|
-
close(tables,table_filenames)
|
203
|
-
else
|
204
|
-
FileUtils.touch File.join(intermediate_directory,name,'relationships')
|
205
|
-
FileUtils.touch File.join(intermediate_directory,name,'table_filenames')
|
206
|
-
FileUtils.touch File.join(intermediate_directory,name,'tables')
|
207
|
-
end
|
208
|
-
close(worksheet_xml)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
def rewrite_worksheets
|
213
|
-
worksheets("Initial rewrite of references and formulae") do |name,xml_filename|
|
214
|
-
rewrite_row_and_column_references(name,xml_filename)
|
215
|
-
rewrite_shared_formulae(name,xml_filename)
|
216
|
-
rewrite_array_formulae(name,xml_filename)
|
217
|
-
combine_formulae_files(name,xml_filename)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def rewrite_row_and_column_references(name,xml_filename)
|
222
|
-
dimensions = input('dimensions')
|
223
|
-
%w{simple_formulae.ast shared_formulae.ast array_formulae.ast}.each do |file|
|
224
|
-
dimensions.rewind
|
225
|
-
i = File.open(File.join(intermediate_directory,name,file),'r')
|
226
|
-
o = File.open(File.join(intermediate_directory,name,"#{file}-nocols"),'w')
|
227
|
-
RewriteWholeRowColumnReferencesToAreas.rewrite(i,name, dimensions, o)
|
228
|
-
close(i,o)
|
229
|
-
end
|
230
|
-
dimensions.close
|
231
|
-
end
|
232
|
-
|
233
|
-
def rewrite_shared_formulae(name,xml_filename)
|
234
|
-
i = File.open(File.join(intermediate_directory,name,'shared_formulae.ast-nocols'),'r')
|
235
|
-
o = File.open(File.join(intermediate_directory,name,"shared_formulae-expanded.ast"),'w')
|
236
|
-
RewriteSharedFormulae.rewrite(i,o)
|
237
|
-
close(i,o)
|
238
|
-
end
|
239
|
-
|
240
|
-
def rewrite_array_formulae(name,xml_filename)
|
241
|
-
r = ReplaceNamedReferences.new
|
242
|
-
r.sheet_name = name
|
243
|
-
replace r, File.join(name,'array_formulae.ast-nocols'), 'named_references.ast', File.join(name,"array_formulae1.ast")
|
244
|
-
|
245
|
-
r = ReplaceTableReferences.new
|
246
|
-
r.sheet_name = name
|
247
|
-
replace r, File.join(name,'array_formulae1.ast'), 'all_tables', File.join(name,"array_formulae2.ast")
|
248
|
-
replace SimplifyArithmetic, File.join(name,'array_formulae2.ast'), File.join(name,'array_formulae3.ast')
|
249
|
-
replace ReplaceRangesWithArrayLiterals, File.join(name,"array_formulae3.ast"), File.join(name,"array_formulae4.ast")
|
250
|
-
rewrite RewriteArrayFormulaeToArrays, File.join(name,"array_formulae4.ast"), File.join(name,"array_formulae5.ast")
|
251
|
-
rewrite RewriteArrayFormulae, File.join(name,'array_formulae5.ast'), File.join(name,"array_formulae-expanded.ast")
|
252
|
-
end
|
253
|
-
|
254
|
-
def combine_formulae_files(name,xml_filename)
|
255
|
-
values = File.join(name,'values.ast')
|
256
|
-
shared_formulae = File.join(name,"shared_formulae-expanded.ast")
|
257
|
-
array_formulae = File.join(name,"array_formulae-expanded.ast")
|
258
|
-
simple_formulae = File.join(name,"simple_formulae.ast-nocols")
|
259
|
-
output = File.join(name,'formulae.ast')
|
260
|
-
rewrite RewriteMergeFormulaeAndValues, values, shared_formulae, array_formulae, simple_formulae, output
|
261
|
-
end
|
262
|
-
|
263
|
-
def merge_table_files
|
264
|
-
tables = []
|
265
|
-
worksheets("Merging table files") do |name,xml_filename|
|
266
|
-
tables << File.join(intermediate_directory,name,'tables')
|
267
|
-
end
|
268
|
-
`sort #{tables.map { |t| " '#{t}' "}.join} > #{File.join(intermediate_directory,'all_tables')}`
|
269
|
-
end
|
270
|
-
|
271
|
-
def simplify_worksheets
|
272
|
-
worksheets("Simplifying") do |name,xml_filename|
|
273
|
-
simplify_worksheet(name,xml_filename)
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def simplify_worksheet(name,xml_filename)
|
278
|
-
replace SimplifyArithmetic, File.join(name,'formulae.ast'), File.join(name,'formulae_simple_arithmetic.ast')
|
279
|
-
replace ReplaceSharedStrings, File.join(name,'formulae_simple_arithmetic.ast'), 'shared_strings', File.join(name,"formulae_no_shared_strings.ast")
|
280
|
-
replace ReplaceSharedStrings, File.join(name,'values.ast'), 'shared_strings', File.join(name,"values_no_shared_strings.ast")
|
281
|
-
r = ReplaceNamedReferences.new
|
282
|
-
r.sheet_name = name
|
283
|
-
replace r, File.join(name,'formulae_no_shared_strings.ast'), 'named_references.ast', File.join(name,"formulae_no_named_references.ast")
|
284
|
-
|
285
|
-
r = ReplaceTableReferences.new
|
286
|
-
r.sheet_name = name
|
287
|
-
replace r, File.join(name,'formulae_no_named_references.ast'), 'all_tables', File.join(name,"formulae_no_table_references.ast")
|
288
|
-
replace ReplaceRangesWithArrayLiterals, File.join(name,"formulae_no_table_references.ast"), File.join(name,"formulae_no_ranges.ast")
|
289
|
-
end
|
290
|
-
|
291
|
-
def replace_blanks
|
292
|
-
references = {}
|
293
|
-
worksheets("Loading formulae") do |name,xml_filename|
|
294
|
-
r = references[name] = {}
|
295
|
-
i = input(name,"formulae_no_indirects_optimised.ast")
|
296
|
-
i.lines do |line|
|
297
|
-
ref = line[/^(.*?)\t/,1]
|
298
|
-
r[ref] = true
|
299
|
-
end
|
300
|
-
end
|
301
|
-
worksheets("Replacing blanks") do |name,xml_filename|
|
302
|
-
#fork do
|
303
|
-
r = ReplaceBlanks.new
|
304
|
-
r.references = references
|
305
|
-
r.default_sheet_name = name
|
306
|
-
replace r, File.join(name,"formulae_no_indirects_optimised.ast"),File.join(name,"formulae_no_blanks.ast")
|
307
|
-
#end
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
def optimise_and_replace_indirect_loop
|
312
|
-
number_of_loops = 4
|
313
|
-
1.upto(number_of_loops) do |pass|
|
314
|
-
puts "Optimise and replace indirects pass #{pass}"
|
315
|
-
start = pass == 1 ? "formulae_no_ranges.ast" : "optimse-output-#{pass-1}.ast"
|
316
|
-
finish = pass == number_of_loops ? "formulae_no_indirects_optimised.ast" : "optimse-output-#{pass}.ast"
|
317
|
-
replace_indirects(start,"replace-indirect-output-#{pass}.ast","replace-indirect-working-#{pass}-")
|
318
|
-
optimise_sheets("replace-indirect-output-#{pass}.ast",finish,"optimse-working-#{pass}-")
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
|
-
def replace_indirects(start_filename,finish_filename,basename)
|
323
|
-
worksheets("Replacing indirects") do |name,xml_filename|
|
324
|
-
counter = 1
|
325
|
-
replace ReplaceIndirectsWithReferences, File.join(name,start_filename), File.join(name,"#{basename}#{counter+1}.ast")
|
326
|
-
counter += 1
|
327
|
-
|
328
|
-
r = ReplaceNamedReferences.new
|
329
|
-
r.sheet_name = name
|
330
|
-
replace r, File.join(name,"#{basename}#{counter}.ast"), 'named_references.ast', File.join(name,"#{basename}#{counter+1}.ast")
|
331
|
-
counter += 1
|
332
|
-
|
333
|
-
r = ReplaceTableReferences.new
|
334
|
-
r.sheet_name = name
|
335
|
-
replace r, File.join(name,"#{basename}#{counter}.ast"), 'all_tables', File.join(name,"#{basename}#{counter+1}.ast")
|
336
|
-
counter += 1
|
337
|
-
|
338
|
-
replace ReplaceRangesWithArrayLiterals, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
|
339
|
-
counter += 1
|
340
|
-
replace ReplaceArraysWithSingleCells, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
|
341
|
-
counter += 1
|
342
|
-
|
343
|
-
# Finally, create the output directory
|
344
|
-
i = File.join(intermediate_directory,name,"#{basename}#{counter}.ast")
|
345
|
-
o = File.join(intermediate_directory,name,finish_filename)
|
346
|
-
`cp '#{i}' '#{o}'`
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
def optimise_sheets(start_filename,finish_filename,basename)
|
351
|
-
counter = 1
|
352
13
|
|
353
|
-
# Setup start
|
354
|
-
worksheets("Setting up for optimise -#{counter}") do |name|
|
355
|
-
i = File.join(intermediate_directory,name,start_filename)
|
356
|
-
o = File.join(intermediate_directory,name,"#{basename}#{counter}.ast")
|
357
|
-
`cp '#{i}' '#{o}'`
|
358
|
-
end
|
359
|
-
|
360
|
-
worksheets("Replacing with calculated values #{counter}-#{counter+1}") do |name,xml_filename|
|
361
|
-
#fork do
|
362
|
-
replace ReplaceFormulaeWithCalculatedValues, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
|
363
|
-
#end
|
364
|
-
end
|
365
|
-
counter += 1
|
366
|
-
Process.waitall
|
367
|
-
|
368
|
-
references = all_formulae("#{basename}#{counter}.ast")
|
369
|
-
inline_ast_decision = lambda do |sheet,cell,references|
|
370
|
-
references_to_keep = @cells_that_can_be_set_at_runtime[sheet]
|
371
|
-
if references_to_keep && (references_to_keep == :all || references_to_keep.include?(cell))
|
372
|
-
false
|
373
|
-
else
|
374
|
-
ast = references[sheet][cell]
|
375
|
-
if ast
|
376
|
-
if [:number,:string,:blank,:null,:error,:boolean_true,:boolean_false,:sheet_reference,:cell].include?(ast.first)
|
377
|
-
# puts "Inlining #{sheet}.#{cell}: #{ast.inspect}"
|
378
|
-
true
|
379
|
-
else
|
380
|
-
false
|
381
|
-
end
|
382
|
-
else
|
383
|
-
true # Always inline blanks
|
384
|
-
end
|
385
|
-
end
|
386
|
-
end
|
387
|
-
r = InlineFormulae.new
|
388
|
-
r.references = references
|
389
|
-
r.inline_ast = inline_ast_decision
|
390
|
-
|
391
|
-
worksheets("Inlining formulae #{counter}-#{counter+1}") do |name,xml_filename|
|
392
|
-
#fork do
|
393
|
-
r.default_sheet_name = name
|
394
|
-
replace r, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
|
395
|
-
#end
|
396
|
-
end
|
397
|
-
counter += 1
|
398
|
-
Process.waitall
|
399
|
-
|
400
|
-
# Finish
|
401
|
-
worksheets("Moving sheets #{counter}-") do |name|
|
402
|
-
o = File.join(intermediate_directory,name,finish_filename)
|
403
|
-
i = File.join(intermediate_directory,name,"#{basename}#{counter}.ast")
|
404
|
-
`cp '#{i}' '#{o}'`
|
405
|
-
end
|
406
|
-
end
|
407
|
-
|
408
|
-
def remove_any_cells_not_needed_for_outputs(formula_in = "formulae_no_blanks.ast", formula_out = "formulae_pruned.ast", values_in = "values_no_shared_strings.ast", values_out = "values_pruned.ast")
|
409
|
-
if cells_to_keep && !cells_to_keep.empty?
|
410
|
-
identifier = IdentifyDependencies.new
|
411
|
-
identifier.references = all_formulae(formula_in)
|
412
|
-
cells_to_keep.each do |sheet_to_keep,cells_to_keep|
|
413
|
-
if cells_to_keep == :all
|
414
|
-
identifier.add_depedencies_for(sheet_to_keep)
|
415
|
-
elsif cells_to_keep.is_a?(Array)
|
416
|
-
cells_to_keep.each do |cell|
|
417
|
-
identifier.add_depedencies_for(sheet_to_keep,cell)
|
418
|
-
end
|
419
|
-
end
|
420
|
-
end
|
421
|
-
r = RemoveCells.new
|
422
|
-
worksheets("Removing cells") do |name,xml_filename|
|
423
|
-
#fork do
|
424
|
-
r.cells_to_keep = identifier.dependencies[name]
|
425
|
-
rewrite r, File.join(name, formula_in), File.join(name, formula_out)
|
426
|
-
rewrite r, File.join(name, values_in), File.join(name, values_out)
|
427
|
-
#end
|
428
|
-
end
|
429
|
-
Process.waitall
|
430
|
-
else
|
431
|
-
worksheets do |name,xml_filename|
|
432
|
-
i = File.join(intermediate_directory,name, formula_in)
|
433
|
-
o = File.join(intermediate_directory,name, formula_out)
|
434
|
-
`cp '#{i}' '#{o}'`
|
435
|
-
i = File.join(intermediate_directory,name, values_in)
|
436
|
-
o = File.join(intermediate_directory,name, values_out)
|
437
|
-
`cp '#{i}' '#{o}'`
|
438
|
-
end
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
def inline_formulae_that_are_only_used_once
|
443
|
-
references = all_formulae("formulae_pruned.ast")
|
444
|
-
counter = CountFormulaReferences.new
|
445
|
-
count = counter.count(references)
|
446
|
-
|
447
|
-
inline_ast_decision = lambda do |sheet,cell,references|
|
448
|
-
references_to_keep = @cells_that_can_be_set_at_runtime[sheet]
|
449
|
-
if references_to_keep && (references_to_keep == :all || references_to_keep.include?(cell))
|
450
|
-
false
|
451
|
-
else
|
452
|
-
count[sheet][cell] == 1
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
r = InlineFormulae.new
|
457
|
-
r.references = references
|
458
|
-
r.inline_ast = inline_ast_decision
|
459
|
-
|
460
|
-
worksheets("Inlining formulae") do |name,xml_filename|
|
461
|
-
#fork do
|
462
|
-
r.default_sheet_name = name
|
463
|
-
replace r, File.join(name,"formulae_pruned.ast"), File.join(name,"formulae_inlined.ast")
|
464
|
-
#end
|
465
|
-
end
|
466
|
-
|
467
|
-
remove_any_cells_not_needed_for_outputs("formulae_inlined.ast", "formulae_inlined_pruned.ast", "values_pruned.ast", "values_pruned2.ast")
|
468
|
-
|
469
|
-
end
|
470
|
-
|
471
|
-
def separate_formulae_elements
|
472
|
-
# First we add the sheet to all references, so that we can then look for common elements accross worksheets
|
473
|
-
r = RewriteCellReferencesToIncludeSheet.new
|
474
|
-
worksheets("Adding the sheet to all references") do |name,xml_filename|
|
475
|
-
r.worksheet = name
|
476
|
-
rewrite r, File.join(name,"formulae_inlined_pruned.ast"), File.join(name,"formulae_inlined_pruned_with_sheets.ast")
|
477
|
-
end
|
478
|
-
|
479
|
-
references = all_formulae("formulae_inlined_pruned_with_sheets.ast")
|
480
|
-
identifier = IdentifyRepeatedFormulaElements.new
|
481
|
-
repeated_elements = identifier.count(references)
|
482
|
-
repeated_elements.delete_if do |element,count|
|
483
|
-
count < 2
|
484
|
-
end
|
485
|
-
o = intermediate('common-elements-1.ast')
|
486
|
-
i = 0
|
487
|
-
repeated_elements.each do |element,count|
|
488
|
-
o.puts "common#{i}\t#{element}"
|
489
|
-
i = i + 1
|
490
|
-
end
|
491
|
-
close(o)
|
492
|
-
|
493
|
-
worksheets("Replacing repeated elements") do |name,xml_filename|
|
494
|
-
replace ReplaceCommonElementsInFormulae, File.join(name,"formulae_inlined_pruned_with_sheets.ast"), "common-elements-1.ast", File.join(name,"formulae_inlined_pruned_replaced-1.ast")
|
495
|
-
end
|
496
|
-
|
497
|
-
r = ReplaceValuesWithConstants.new
|
498
|
-
worksheets("Replacing values with constants") do |name,xml_filename|
|
499
|
-
i = input(name,"formulae_inlined_pruned_replaced-1.ast")
|
500
|
-
o = intermediate(name,"formulae_inlined_pruned_replaced.ast")
|
501
|
-
r.replace(i,o)
|
502
|
-
close(i,o)
|
503
|
-
end
|
504
|
-
|
505
|
-
puts "Replacing values with constants in common elements"
|
506
|
-
i = input("common-elements-1.ast")
|
507
|
-
o = intermediate("common-elements.ast")
|
508
|
-
r.replace(i,o)
|
509
|
-
close(i,o)
|
510
|
-
|
511
|
-
puts "Writing out constants"
|
512
|
-
co = intermediate("value_constants.ast")
|
513
|
-
r.rewriter.constants.each do |ast,constant|
|
514
|
-
co.puts "#{constant}\t#{ast}"
|
515
|
-
end
|
516
|
-
close(co)
|
517
|
-
end
|
518
|
-
|
519
14
|
def write_out_excel_as_code
|
520
15
|
|
521
16
|
all_refs = all_formulae("formulae_inlined_pruned_replaced.ast")
|
@@ -623,28 +118,6 @@ class ExcelToC
|
|
623
118
|
end
|
624
119
|
close(w,o)
|
625
120
|
end
|
626
|
-
|
627
|
-
def settable(name)
|
628
|
-
settable_refs = @cells_that_can_be_set_at_runtime[name]
|
629
|
-
if settable_refs
|
630
|
-
lambda { |ref| (settable_refs == :all) ? true : settable_refs.include?(ref) }
|
631
|
-
else
|
632
|
-
lambda { |ref| false }
|
633
|
-
end
|
634
|
-
end
|
635
|
-
|
636
|
-
def gettable(name)
|
637
|
-
if @cells_to_keep
|
638
|
-
gettable_refs = @cells_to_keep[name]
|
639
|
-
if gettable_refs
|
640
|
-
lambda { |ref| (gettable_refs == :all) ? true : gettable_refs.include?(ref) }
|
641
|
-
else
|
642
|
-
lambda { |ref| false }
|
643
|
-
end
|
644
|
-
else
|
645
|
-
lambda { |ref| true }
|
646
|
-
end
|
647
|
-
end
|
648
121
|
|
649
122
|
def write_build_script
|
650
123
|
o = output("Makefile")
|
@@ -762,8 +235,8 @@ END
|
|
762
235
|
close(o)
|
763
236
|
end
|
764
237
|
|
765
|
-
def
|
766
|
-
return unless
|
238
|
+
def compile_code
|
239
|
+
return unless actually_compile_code || actually_run_tests
|
767
240
|
puts "Compiling the resulting c code"
|
768
241
|
puts `cd #{File.join(output_directory)}; make clean; make`
|
769
242
|
end
|
@@ -774,85 +247,4 @@ END
|
|
774
247
|
puts `cd #{File.join(output_directory)}; ruby "#{output_name.downcase}_test.rb"`
|
775
248
|
end
|
776
249
|
|
777
|
-
|
778
|
-
# UTILITY FUNCTIONS
|
779
|
-
|
780
|
-
def all_formulae(filename)
|
781
|
-
references = {}
|
782
|
-
worksheets do |name,xml_filename|
|
783
|
-
r = references[name] = {}
|
784
|
-
i = input(name,filename)
|
785
|
-
i.lines do |line|
|
786
|
-
line =~ /^(.*?)\t(.*)$/
|
787
|
-
ref, ast = $1, $2
|
788
|
-
r[$1] = eval($2)
|
789
|
-
end
|
790
|
-
end
|
791
|
-
references
|
792
|
-
end
|
793
|
-
|
794
|
-
def c_name_for_worksheet_name(name)
|
795
|
-
unless @worksheet_names
|
796
|
-
w = input("worksheet_c_names")
|
797
|
-
@worksheet_names = Hash[w.readlines.map { |line| line.split("\t").map { |a| a.strip }}]
|
798
|
-
close(w)
|
799
|
-
end
|
800
|
-
@worksheet_names[name]
|
801
|
-
end
|
802
|
-
|
803
|
-
def worksheets(message = "Processing",&block)
|
804
|
-
IO.readlines(File.join(intermediate_directory,'worksheet_names')).each do |line|
|
805
|
-
name, filename = *line.split("\t")
|
806
|
-
filename = File.expand_path(File.join(xml_directory,'xl',filename.strip))
|
807
|
-
puts "#{message} #{name}"
|
808
|
-
block.call(name, filename)
|
809
|
-
end
|
810
|
-
end
|
811
|
-
|
812
|
-
def extract(_klass,xml_name,output_name)
|
813
|
-
i = xml_name.is_a?(String) ? xml(xml_name) : xml_name
|
814
|
-
o = output_name.is_a?(String) ? intermediate(output_name) : output_name
|
815
|
-
_klass.extract(i,o)
|
816
|
-
if xml_name.is_a?(String)
|
817
|
-
close(i)
|
818
|
-
end
|
819
|
-
if output_name.is_a?(String)
|
820
|
-
close(o)
|
821
|
-
end
|
822
|
-
end
|
823
|
-
|
824
|
-
def rewrite(_klass,*args)
|
825
|
-
o = intermediate(args.pop)
|
826
|
-
inputs = args.map { |name| input(name) }
|
827
|
-
_klass.rewrite(*inputs,o)
|
828
|
-
close(*inputs,o)
|
829
|
-
end
|
830
|
-
|
831
|
-
def replace(_klass,*args)
|
832
|
-
o = intermediate(args.pop)
|
833
|
-
inputs = args.map { |name| input(name) }
|
834
|
-
_klass.replace(*inputs,o)
|
835
|
-
close(*inputs,o)
|
836
|
-
end
|
837
|
-
|
838
|
-
def xml(*args)
|
839
|
-
File.open(File.join(xml_directory,'xl',*args),'r')
|
840
|
-
end
|
841
|
-
|
842
|
-
def input(*args)
|
843
|
-
File.open(File.join(intermediate_directory,*args),'r')
|
844
|
-
end
|
845
|
-
|
846
|
-
def intermediate(*args)
|
847
|
-
File.open(File.join(intermediate_directory,*args),'w')
|
848
|
-
end
|
849
|
-
|
850
|
-
def output(*args)
|
851
|
-
File.open(File.join(output_directory,*args),'w')
|
852
|
-
end
|
853
|
-
|
854
|
-
def close(*args)
|
855
|
-
args.map(&:close)
|
856
|
-
end
|
857
|
-
|
858
|
-
end
|
250
|
+
end
|