excel_to_code 0.0.1
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/README +41 -0
- data/bin/excel_to_c +63 -0
- data/bin/excel_to_ruby +9 -0
- data/src/commands.rb +2 -0
- data/src/commands/excel_to_c.rb +858 -0
- data/src/commands/excel_to_ruby.rb +620 -0
- data/src/compile.rb +2 -0
- data/src/compile/c.rb +5 -0
- data/src/compile/c/compile_to_c.rb +62 -0
- data/src/compile/c/compile_to_c_header.rb +26 -0
- data/src/compile/c/compile_to_c_unit_test.rb +42 -0
- data/src/compile/c/excel_to_c_runtime.c +2029 -0
- data/src/compile/c/map_formulae_to_c.rb +184 -0
- data/src/compile/c/map_sheet_names_to_c_names.rb +19 -0
- data/src/compile/c/map_values_to_c.rb +85 -0
- data/src/compile/c/map_values_to_c_structs.rb +37 -0
- data/src/compile/ruby.rb +3 -0
- data/src/compile/ruby/compile_to_ruby.rb +33 -0
- data/src/compile/ruby/compile_to_ruby_unit_test.rb +28 -0
- data/src/compile/ruby/excel_to_ruby_runtime.rb +1 -0
- data/src/compile/ruby/map_formulae_to_ruby.rb +95 -0
- data/src/compile/ruby/map_sheet_names_to_ruby_names.rb +19 -0
- data/src/compile/ruby/map_values_to_ruby.rb +65 -0
- data/src/excel.rb +5 -0
- data/src/excel/area.rb +93 -0
- data/src/excel/excel_functions.rb +84 -0
- data/src/excel/excel_functions/abs.rb +14 -0
- data/src/excel/excel_functions/add.rb +18 -0
- data/src/excel/excel_functions/and.rb +30 -0
- data/src/excel/excel_functions/apply_to_range.rb +17 -0
- data/src/excel/excel_functions/average.rb +12 -0
- data/src/excel/excel_functions/choose.rb +18 -0
- data/src/excel/excel_functions/cosh.rb +9 -0
- data/src/excel/excel_functions/count.rb +9 -0
- data/src/excel/excel_functions/counta.rb +8 -0
- data/src/excel/excel_functions/divide.rb +23 -0
- data/src/excel/excel_functions/excel_equal.rb +20 -0
- data/src/excel/excel_functions/excel_if.rb +8 -0
- data/src/excel/excel_functions/excel_match.rb +51 -0
- data/src/excel/excel_functions/find.rb +39 -0
- data/src/excel/excel_functions/iferror.rb +10 -0
- data/src/excel/excel_functions/index.rb +48 -0
- data/src/excel/excel_functions/left.rb +12 -0
- data/src/excel/excel_functions/less_than.rb +26 -0
- data/src/excel/excel_functions/less_than_or_equal.rb +26 -0
- data/src/excel/excel_functions/max.rb +12 -0
- data/src/excel/excel_functions/min.rb +12 -0
- data/src/excel/excel_functions/mod.rb +15 -0
- data/src/excel/excel_functions/more_than.rb +26 -0
- data/src/excel/excel_functions/more_than_or_equal.rb +26 -0
- data/src/excel/excel_functions/multiply.rb +24 -0
- data/src/excel/excel_functions/negative.rb +12 -0
- data/src/excel/excel_functions/not_equal.rb +19 -0
- data/src/excel/excel_functions/number_argument.rb +30 -0
- data/src/excel/excel_functions/pi.rb +7 -0
- data/src/excel/excel_functions/pmt.rb +16 -0
- data/src/excel/excel_functions/power.rb +18 -0
- data/src/excel/excel_functions/round.rb +13 -0
- data/src/excel/excel_functions/rounddown.rb +14 -0
- data/src/excel/excel_functions/roundup.rb +17 -0
- data/src/excel/excel_functions/string_join.rb +19 -0
- data/src/excel/excel_functions/subtotal.rb +13 -0
- data/src/excel/excel_functions/subtract.rb +18 -0
- data/src/excel/excel_functions/sum.rb +8 -0
- data/src/excel/excel_functions/sumif.rb +7 -0
- data/src/excel/excel_functions/sumifs.rb +74 -0
- data/src/excel/excel_functions/sumproduct.rb +32 -0
- data/src/excel/excel_functions/vlookup.rb +49 -0
- data/src/excel/formula_peg.rb +238 -0
- data/src/excel/formula_peg.txt +45 -0
- data/src/excel/reference.rb +56 -0
- data/src/excel/table.rb +108 -0
- data/src/excel_to_code.rb +7 -0
- data/src/extract.rb +13 -0
- data/src/extract/check_for_unknown_functions.rb +20 -0
- data/src/extract/extract_array_formulae.rb +23 -0
- data/src/extract/extract_formulae.rb +36 -0
- data/src/extract/extract_named_references.rb +38 -0
- data/src/extract/extract_relationships.rb +10 -0
- data/src/extract/extract_shared_formulae.rb +23 -0
- data/src/extract/extract_shared_strings.rb +20 -0
- data/src/extract/extract_simple_formulae.rb +18 -0
- data/src/extract/extract_table.rb +24 -0
- data/src/extract/extract_values.rb +29 -0
- data/src/extract/extract_worksheet_dimensions.rb +11 -0
- data/src/extract/extract_worksheet_names.rb +10 -0
- data/src/extract/extract_worksheet_table_relationships.rb +10 -0
- data/src/extract/simple_extract_from_xml.rb +19 -0
- data/src/rewrite.rb +10 -0
- data/src/rewrite/ast_copy_formula.rb +42 -0
- data/src/rewrite/ast_expand_array_formulae.rb +180 -0
- data/src/rewrite/rewrite_array_formulae.rb +71 -0
- data/src/rewrite/rewrite_array_formulae_to_arrays.rb +18 -0
- data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +56 -0
- data/src/rewrite/rewrite_formulae_to_ast.rb +24 -0
- data/src/rewrite/rewrite_merge_formulae_and_values.rb +18 -0
- data/src/rewrite/rewrite_relationship_id_to_filename.rb +22 -0
- data/src/rewrite/rewrite_shared_formulae.rb +38 -0
- data/src/rewrite/rewrite_values_to_ast.rb +28 -0
- data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +90 -0
- data/src/rewrite/rewrite_worksheet_names.rb +20 -0
- data/src/simplify.rb +16 -0
- data/src/simplify/count_formula_references.rb +58 -0
- data/src/simplify/identify_dependencies.rb +56 -0
- data/src/simplify/identify_repeated_formula_elements.rb +37 -0
- data/src/simplify/inline_formulae.rb +77 -0
- data/src/simplify/map_formulae_to_values.rb +157 -0
- data/src/simplify/remove_cells.rb +18 -0
- data/src/simplify/replace_arrays_with_single_cells.rb +27 -0
- data/src/simplify/replace_blanks.rb +58 -0
- data/src/simplify/replace_common_elements_in_formulae.rb +19 -0
- data/src/simplify/replace_formulae_with_calculated_values.rb +21 -0
- data/src/simplify/replace_indirects_with_references.rb +44 -0
- data/src/simplify/replace_named_references.rb +82 -0
- data/src/simplify/replace_ranges_with_array_literals.rb +54 -0
- data/src/simplify/replace_shared_strings.rb +49 -0
- data/src/simplify/replace_table_references.rb +71 -0
- data/src/simplify/replace_values_with_constants.rb +47 -0
- data/src/simplify/simplify_arithmetic.rb +54 -0
- data/src/util.rb +2 -0
- data/src/util/not_supported_exception.rb +2 -0
- data/src/util/try.rb +9 -0
- metadata +207 -0
data/README
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# excel_to_code
|
2
|
+
|
3
|
+
Converts some excel spreadsheets (.xlsx, not .xls) into some other programming languages (currently ruby or c).
|
4
|
+
This allows the excel spreadsheets to be run programatically, without excel.
|
5
|
+
|
6
|
+
Its cannonical source is at http://github.com/tamc/excel2code
|
7
|
+
|
8
|
+
# Running excel_to_code
|
9
|
+
|
10
|
+
To just have a go:
|
11
|
+
|
12
|
+
./bin/excel_to_c <excel_file_name>
|
13
|
+
|
14
|
+
NB:For small spreadsheets this will take a minute or so. For large spreadsheets it is best to run it overnight.
|
15
|
+
|
16
|
+
for more detail:
|
17
|
+
|
18
|
+
./bin/excel_to_c --compile --run-tests --settable <name of input worksheet> --prune-except <name of output worksheet> <excel file name>
|
19
|
+
|
20
|
+
this should work:
|
21
|
+
|
22
|
+
./bin/excel_to_c --help
|
23
|
+
|
24
|
+
# Testing excel_to_code
|
25
|
+
|
26
|
+
1. Make sure you have ruby 1.9.2 or later installed
|
27
|
+
2. gem install bundler # May need to use sudo
|
28
|
+
3. bundle
|
29
|
+
4. rspec spec/*
|
30
|
+
|
31
|
+
# Hacking excel_to_code
|
32
|
+
|
33
|
+
There are some how to guides in the doc folder.
|
34
|
+
|
35
|
+
# Limitations
|
36
|
+
|
37
|
+
1. Not tested at all on Windows
|
38
|
+
2. INDIRECT formula must be convertable at runtime into a standard formula
|
39
|
+
3. Doesn't implement all functions (see doc/Which_functions_are_implemented.md)
|
40
|
+
4. Doesn't implement references that involve range unions and lists
|
41
|
+
5. Sometimes gives cells as being empty, when excel would give the cell as having a numeric value of zero
|
data/bin/excel_to_c
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require_relative '../src/commands/excel_to_c'
|
4
|
+
|
5
|
+
command = ExcelToC.new
|
6
|
+
|
7
|
+
opts = OptionParser.new do |opts|
|
8
|
+
opts.banner = <<-END
|
9
|
+
|
10
|
+
A command to approximately translate excel files into c code.
|
11
|
+
Usage: excel_to_c [options] input_excel_file <output_directory>
|
12
|
+
input_excel_file is the name of a .xlsx excel file (i.e., produced by Excel 2007+, not an xls file)
|
13
|
+
output_directory is 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
|
+
Support: http://github.com/tamc/excel2code
|
16
|
+
END
|
17
|
+
|
18
|
+
opts.separator ""
|
19
|
+
opts.separator "Specific options:"
|
20
|
+
|
21
|
+
opts.on('-s','--settable WORKSHEET','Make it possible to set the values of cells in this worksheet at runtime. By default no values are settable.') do |sheet|
|
22
|
+
command.cells_that_can_be_set_at_runtime = { sheet => :all }
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-o','--output-name NAME','Filename to give to c version of code (and associated ruby interface). Defaults to a folder with the same name as the excel file.') do |name|
|
26
|
+
command.output_name = name
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-p','--prune-except WORKSHEET',"Remove all cells except those on this worksheet, or that are required to calculate values on that worksheet. By default keeps all cells.") do |sheet|
|
30
|
+
command.cells_to_keep = { sheet => :all }
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-c','--compile',"Compile the generated c code") do
|
34
|
+
command.actually_compile_c_code = true
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-r','--run-tests',"Compile the generated c code and then run the tests") do
|
38
|
+
command.actually_compile_c_code = true
|
39
|
+
command.actually_run_tests = true
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-h", "--help", "Show this message") do
|
43
|
+
puts opts
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
opts.parse!(ARGV)
|
50
|
+
rescue OptionParser::ParseError => e
|
51
|
+
STDERR.puts e.message, "\n", opts
|
52
|
+
exit(-1)
|
53
|
+
end
|
54
|
+
|
55
|
+
unless ARGV.size > 0
|
56
|
+
puts opts
|
57
|
+
exit(-1)
|
58
|
+
end
|
59
|
+
|
60
|
+
command.excel_file = ARGV[0]
|
61
|
+
command.output_directory = ARGV[1] if ARGV[1]
|
62
|
+
|
63
|
+
command.go!
|
data/bin/excel_to_ruby
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative '../src/commands/excel_to_ruby'
|
3
|
+
command = ExcelToRuby.new
|
4
|
+
command.excel_file = ARGV[0]
|
5
|
+
command.compiled_module_name = "ExcelSpreadsheet"
|
6
|
+
command.output_directory = ARGV[1]
|
7
|
+
command.values_that_can_be_set_at_runtime = { ARGV[2] => :all } if ARGV[2]
|
8
|
+
command.outputs_to_keep = { ARGV[3] => :all } if ARGV[3]
|
9
|
+
command.go!
|
data/src/commands.rb
ADDED
@@ -0,0 +1,858 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'fileutils'
|
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
|
90
|
+
|
91
|
+
def go!
|
92
|
+
# This sorts out the attributes
|
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
|
117
|
+
write_out_excel_as_code
|
118
|
+
write_build_script
|
119
|
+
write_fuby_ffi_interface
|
120
|
+
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
|
+
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
|
+
|
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
|
+
def write_out_excel_as_code
|
520
|
+
|
521
|
+
all_refs = all_formulae("formulae_inlined_pruned_replaced.ast")
|
522
|
+
|
523
|
+
number_of_refs = 0
|
524
|
+
|
525
|
+
# Probably a better way of getting the runtime file to be compiled with the created file
|
526
|
+
puts `cp #{File.join(File.dirname(__FILE__),'..','compile','c','excel_to_c_runtime.c')} #{File.join(output_directory,'excel_to_c_runtime.c')}`
|
527
|
+
|
528
|
+
# Output the workbook preamble
|
529
|
+
w = input("worksheet_c_names")
|
530
|
+
o = output("#{output_name.downcase}.c")
|
531
|
+
o.puts "// #{excel_file} approximately translated into C"
|
532
|
+
o.puts '#include "excel_to_c_runtime.c"'
|
533
|
+
o.puts
|
534
|
+
|
535
|
+
# Now we have to put all the initial definitions out
|
536
|
+
o.puts "// definitions"
|
537
|
+
|
538
|
+
i = input("common-elements.ast")
|
539
|
+
c = CompileToCHeader.new
|
540
|
+
c.gettable = lambda { |ref| false }
|
541
|
+
c.rewrite(i,w,o)
|
542
|
+
i.rewind
|
543
|
+
number_of_refs += i.lines.to_a.size
|
544
|
+
close(i)
|
545
|
+
|
546
|
+
worksheets("Compiling definitions") do |name,xml_filename|
|
547
|
+
w.rewind
|
548
|
+
c = CompileToCHeader.new
|
549
|
+
c.settable = settable(name)
|
550
|
+
c.gettable = gettable(name)
|
551
|
+
c.worksheet = name
|
552
|
+
i = input(name,"formulae_inlined_pruned_replaced.ast")
|
553
|
+
c.rewrite(i,w,o)
|
554
|
+
i.rewind
|
555
|
+
number_of_refs += i.lines.to_a.size
|
556
|
+
close(i)
|
557
|
+
end
|
558
|
+
|
559
|
+
o.puts "// end of definitions"
|
560
|
+
o.puts
|
561
|
+
o.puts "// Used to decide whether to recalculate a cell"
|
562
|
+
o.puts "static int variable_set[#{number_of_refs}];"
|
563
|
+
o.puts "void reset() {"
|
564
|
+
o.puts " int i;"
|
565
|
+
o.puts " cell_counter = 0;"
|
566
|
+
o.puts " for(i = 0; i < #{number_of_refs}; i++) {"
|
567
|
+
o.puts " variable_set[i] = 0;"
|
568
|
+
o.puts " }"
|
569
|
+
o.puts "};"
|
570
|
+
o.puts
|
571
|
+
|
572
|
+
# Output the value constants
|
573
|
+
o.puts "// starting the value constants"
|
574
|
+
mapper = MapValuesToCStructs.new
|
575
|
+
i = input("value_constants.ast")
|
576
|
+
i.lines do |line|
|
577
|
+
begin
|
578
|
+
ref, formula = line.split("\t")
|
579
|
+
ast = eval(formula)
|
580
|
+
calculation = mapper.map(ast)
|
581
|
+
o.puts "static ExcelValue #{ref} = #{calculation};"
|
582
|
+
rescue Exception => e
|
583
|
+
puts "Exception at line #{line}"
|
584
|
+
raise
|
585
|
+
end
|
586
|
+
end
|
587
|
+
close(i)
|
588
|
+
o.puts "// ending the value constants"
|
589
|
+
o.puts
|
590
|
+
|
591
|
+
variable_set_counter = 0
|
592
|
+
|
593
|
+
# output the common elements
|
594
|
+
o.puts "// starting common elements"
|
595
|
+
w.rewind
|
596
|
+
c = CompileToC.new
|
597
|
+
c.variable_set_counter = variable_set_counter
|
598
|
+
c.gettable = lambda { |ref| false }
|
599
|
+
i = input("common-elements.ast")
|
600
|
+
c.rewrite(i,w,o)
|
601
|
+
close(i)
|
602
|
+
o.puts "// ending common elements"
|
603
|
+
o.puts
|
604
|
+
|
605
|
+
variable_set_counter = c.variable_set_counter
|
606
|
+
|
607
|
+
c = CompileToC.new
|
608
|
+
c.variable_set_counter = variable_set_counter
|
609
|
+
# Output the elements from each worksheet in turn
|
610
|
+
worksheets("Compiling worksheet") do |name,xml_filename|
|
611
|
+
w.rewind
|
612
|
+
c.settable = settable(name)
|
613
|
+
c.gettable = gettable(name)
|
614
|
+
c.worksheet = name
|
615
|
+
|
616
|
+
i = input(name,"formulae_inlined_pruned_replaced.ast")
|
617
|
+
ruby_name = c_name_for_worksheet_name(name)
|
618
|
+
o.puts "// start #{name}"
|
619
|
+
c.rewrite(i,w,o)
|
620
|
+
o.puts "// end #{name}"
|
621
|
+
o.puts
|
622
|
+
close(i)
|
623
|
+
end
|
624
|
+
close(w,o)
|
625
|
+
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
|
+
|
649
|
+
def write_build_script
|
650
|
+
o = output("Makefile")
|
651
|
+
name = output_name.downcase
|
652
|
+
|
653
|
+
# Target for shared library
|
654
|
+
o.puts "lib#{name}.dylib: #{name}.o"
|
655
|
+
o.puts "\tgcc -shared -o lib#{name}.dylib #{name}.o"
|
656
|
+
o.puts
|
657
|
+
|
658
|
+
# Target for compiled version
|
659
|
+
o.puts "#{name}.o:"
|
660
|
+
o.puts "\tgcc -Wall -fPIC -c #{name}.c"
|
661
|
+
o.puts
|
662
|
+
|
663
|
+
# Target for cleaning
|
664
|
+
o.puts "clean:"
|
665
|
+
o.puts "\trm #{name}.o"
|
666
|
+
o.puts "\trm lib#{name}.dylib"
|
667
|
+
|
668
|
+
close(o)
|
669
|
+
end
|
670
|
+
|
671
|
+
def write_fuby_ffi_interface
|
672
|
+
all_formulae = all_formulae('formulae_inlined_pruned_replaced.ast')
|
673
|
+
name = output_name.downcase
|
674
|
+
o = output("#{name}.rb")
|
675
|
+
code = <<END
|
676
|
+
require 'ffi'
|
677
|
+
|
678
|
+
module #{name.capitalize}
|
679
|
+
extend FFI::Library
|
680
|
+
ffi_lib File.join(File.dirname(__FILE__),'lib#{name}.dylib')
|
681
|
+
ExcelType = enum :ExcelEmpty, :ExcelNumber, :ExcelString, :ExcelBoolean, :ExcelError, :ExcelRange
|
682
|
+
|
683
|
+
class ExcelValue < FFI::Struct
|
684
|
+
layout :type, ExcelType,
|
685
|
+
:number, :double,
|
686
|
+
:string, :string,
|
687
|
+
:array, :pointer,
|
688
|
+
:rows, :int,
|
689
|
+
:columns, :int
|
690
|
+
end
|
691
|
+
|
692
|
+
END
|
693
|
+
o.puts code
|
694
|
+
o.puts
|
695
|
+
o.puts " # use this function to reset all cell values"
|
696
|
+
o.puts " attach_function 'reset', [], :void"
|
697
|
+
|
698
|
+
worksheets("Adding references to ruby shim for") do |name,xml_filename|
|
699
|
+
o.puts
|
700
|
+
o.puts " # start of #{name}"
|
701
|
+
c_name = c_name_for_worksheet_name(name)
|
702
|
+
|
703
|
+
# Put in place the setters, if any
|
704
|
+
settable_refs = @cells_that_can_be_set_at_runtime[name]
|
705
|
+
if settable_refs
|
706
|
+
settable_refs = all_formulae[name].keys if settable_refs == :all
|
707
|
+
settable_refs.each do |ref|
|
708
|
+
o.puts " attach_function 'set_#{c_name}_#{ref.downcase}', [ExcelValue.by_value], :void"
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
if !cells_to_keep || cells_to_keep.empty? || cells_to_keep[name] == :all
|
713
|
+
getable_refs = all_formulae[name].keys
|
714
|
+
elsif !cells_to_keep[name] && settable_refs
|
715
|
+
getable_refs = settable_refs
|
716
|
+
else
|
717
|
+
getable_refs = cells_to_keep[name] || []
|
718
|
+
end
|
719
|
+
|
720
|
+
getable_refs.each do |ref|
|
721
|
+
o.puts " attach_function '#{c_name}_#{ref.downcase}', [], ExcelValue.by_value"
|
722
|
+
end
|
723
|
+
|
724
|
+
o.puts " # end of #{name}"
|
725
|
+
end
|
726
|
+
o.puts "end"
|
727
|
+
close(o)
|
728
|
+
end
|
729
|
+
|
730
|
+
def write_tests
|
731
|
+
name = output_name.downcase
|
732
|
+
o = output("#{name}_test.rb")
|
733
|
+
o.puts "# coding: utf-8"
|
734
|
+
o.puts "# Test for #{name}"
|
735
|
+
o.puts "require 'rubygems'"
|
736
|
+
o.puts "gem 'minitest'"
|
737
|
+
o.puts "require 'test/unit'"
|
738
|
+
o.puts "require_relative '#{output_name.downcase}'"
|
739
|
+
o.puts
|
740
|
+
o.puts "class Test#{name.capitalize} < Test::Unit::TestCase"
|
741
|
+
o.puts " def spreadsheet; @spreadsheet ||= init_spreadsheet; end"
|
742
|
+
o.puts " def init_spreadsheet; #{name.capitalize} end"
|
743
|
+
|
744
|
+
all_formulae = all_formulae('formulae_inlined_pruned_replaced.ast')
|
745
|
+
|
746
|
+
worksheets("Adding tests for") do |name,xml_filename|
|
747
|
+
i = input(name,"values_pruned2.ast")
|
748
|
+
o.puts
|
749
|
+
o.puts " # start of #{name}"
|
750
|
+
c_name = c_name_for_worksheet_name(name)
|
751
|
+
if !cells_to_keep || cells_to_keep.empty? || cells_to_keep[name] == :all
|
752
|
+
refs_to_test = all_formulae[name].keys
|
753
|
+
else
|
754
|
+
refs_to_test = cells_to_keep[name]
|
755
|
+
end
|
756
|
+
if refs_to_test && !refs_to_test.empty?
|
757
|
+
CompileToCUnitTest.rewrite(i, c_name, refs_to_test, o)
|
758
|
+
end
|
759
|
+
close(i)
|
760
|
+
end
|
761
|
+
o.puts "end"
|
762
|
+
close(o)
|
763
|
+
end
|
764
|
+
|
765
|
+
def compile_c_code
|
766
|
+
return unless actually_compile_c_code || actually_run_tests
|
767
|
+
puts "Compiling the resulting c code"
|
768
|
+
puts `cd #{File.join(output_directory)}; make clean; make`
|
769
|
+
end
|
770
|
+
|
771
|
+
def run_tests
|
772
|
+
return unless actually_run_tests
|
773
|
+
puts "Running the resulting tests"
|
774
|
+
puts `cd #{File.join(output_directory)}; ruby "#{output_name.downcase}_test.rb"`
|
775
|
+
end
|
776
|
+
|
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
|