excel_to_code 0.0.14 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -0
- data/src/commands/excel_to_c.rb +149 -3
- data/src/commands/excel_to_x.rb +143 -9
- data/src/compile/c.rb +1 -0
- data/src/compile/c/compile_named_reference_setters.rb +90 -0
- data/src/compile/c/compile_to_c.rb +2 -2
- data/src/compile/c/compile_to_c_unit_test.rb +3 -68
- data/src/compile/c/excel_to_c_runtime.c +10 -2
- data/src/compile/c/map_sheet_names_to_c_names.rb +1 -1
- data/src/rewrite.rb +1 -0
- data/src/rewrite/rewrite_named_reference_names.rb +35 -0
- metadata +6 -3
data/README.md
CHANGED
@@ -39,3 +39,4 @@ There are some how to guides in the doc folder.
|
|
39
39
|
3. Doesn't implement all functions (see doc/Which_functions_are_implemented.md)
|
40
40
|
4. Doesn't implement references that involve range unions and lists
|
41
41
|
5. Sometimes gives cells as being empty, when excel would give the cell as having a numeric value of zero
|
42
|
+
6. The generated C version does not multithread and will give bad results if you try
|
data/src/commands/excel_to_c.rb
CHANGED
@@ -58,6 +58,9 @@ class ExcelToC < ExcelToX
|
|
58
58
|
close(i)
|
59
59
|
end
|
60
60
|
|
61
|
+
# Need to make sure there are enough refs for named references as well
|
62
|
+
number_of_refs += named_references_to_keep.size
|
63
|
+
|
61
64
|
o.puts "// end of definitions"
|
62
65
|
o.puts
|
63
66
|
o.puts "// Used to decide whether to recalculate a cell"
|
@@ -126,6 +129,30 @@ class ExcelToC < ExcelToX
|
|
126
129
|
o.puts
|
127
130
|
close(i)
|
128
131
|
end
|
132
|
+
|
133
|
+
# Output the named references
|
134
|
+
|
135
|
+
# Getters
|
136
|
+
o.puts "// Start of named references"
|
137
|
+
i = input('Named references to keep')
|
138
|
+
w.rewind
|
139
|
+
c.gettable = lambda { |ref| true }
|
140
|
+
c.settable = lambda { |ref| false }
|
141
|
+
c.worksheet = ""
|
142
|
+
c.rewrite(i,w,o)
|
143
|
+
close(i)
|
144
|
+
|
145
|
+
# Setters
|
146
|
+
i = input('Named references to set')
|
147
|
+
w.rewind # Worksheet C names
|
148
|
+
|
149
|
+
c = CompileNamedReferenceSetters.new
|
150
|
+
c.cells_that_can_be_set_at_runtime = cells_that_can_be_set_at_runtime
|
151
|
+
c.rewrite(i,w,o)
|
152
|
+
|
153
|
+
close(i)
|
154
|
+
o.puts "// End of named references"
|
155
|
+
|
129
156
|
close(w,o)
|
130
157
|
end
|
131
158
|
|
@@ -161,6 +188,102 @@ class ExcelToC < ExcelToX
|
|
161
188
|
|
162
189
|
code = <<END
|
163
190
|
require 'ffi'
|
191
|
+
require 'singleton'
|
192
|
+
|
193
|
+
class #{ruby_module_name}Shim
|
194
|
+
|
195
|
+
# WARNING: this is not thread safe
|
196
|
+
def initialize
|
197
|
+
reset
|
198
|
+
end
|
199
|
+
|
200
|
+
def reset
|
201
|
+
#{ruby_module_name}.reset
|
202
|
+
end
|
203
|
+
|
204
|
+
def method_missing(name, *arguments)
|
205
|
+
if arguments.size == 0
|
206
|
+
get(name)
|
207
|
+
elsif arguments.size == 1
|
208
|
+
set(name, arguments.first)
|
209
|
+
else
|
210
|
+
super
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def get(name)
|
215
|
+
return 0 unless #{ruby_module_name}.respond_to?(name)
|
216
|
+
excel_value = #{ruby_module_name}.send(name)
|
217
|
+
case excel_value[:type]
|
218
|
+
when :ExcelNumber; excel_value[:number]
|
219
|
+
when :ExcelString; excel_value[:string].read_string.force_encoding("utf-8")
|
220
|
+
when :ExcelBoolean; excel_value[:number] == 1
|
221
|
+
when :ExcelEmpty; nil
|
222
|
+
when :ExcelRange
|
223
|
+
r = excel_value[:rows]
|
224
|
+
c = excel_value[:columns]
|
225
|
+
p = excel_value[:array]
|
226
|
+
s = #{ruby_module_name}::ExcelValue.size
|
227
|
+
a = Array.new(r) { Array.new(c) }
|
228
|
+
(0...r).each do |row|
|
229
|
+
(0...c).each do |column|
|
230
|
+
a[row][column] = ruby_from_excel_value(#{ruby_module_name}::ExcelValue.new(p + (((row*c)+column)*s)))
|
231
|
+
end
|
232
|
+
end
|
233
|
+
return a
|
234
|
+
when :ExcelError; [:value,:name,:div0,:ref,:na][excel_value[:number]]
|
235
|
+
else
|
236
|
+
raise Exception.new("ExcelValue type \u0023{excel_value[:type].inspect} not recognised")
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def set(name, ruby_value)
|
241
|
+
name = name.to_s
|
242
|
+
name = "set_\#{name[0..-2]}" if name.end_with?('=')
|
243
|
+
return false unless #{ruby_module_name}.respond_to?(name)
|
244
|
+
Getsetranges.send(name, excel_value_from_ruby_value(ruby_value))
|
245
|
+
end
|
246
|
+
|
247
|
+
def excel_value_from_ruby_value(ruby_value, excel_value = #{ruby_module_name}::ExcelValue.new)
|
248
|
+
case ruby_value
|
249
|
+
when Numeric
|
250
|
+
excel_value[:type] = :ExcelNumber
|
251
|
+
excel_value[:number] = ruby_value
|
252
|
+
when String
|
253
|
+
excel_value[:type] = :ExcelString
|
254
|
+
excel_value[:string] = FFI::MemoryPointer.from_string(ruby_value.encode('utf-8'))
|
255
|
+
when TrueClass, FalseClass
|
256
|
+
excel_value[:type] = :ExcelBoolean
|
257
|
+
excel_value[:number] = ruby_value ? 1 : 0
|
258
|
+
when nil
|
259
|
+
excel_value[:type] = :ExcelEmpty
|
260
|
+
when Array
|
261
|
+
excel_value[:type] = :ExcelRange
|
262
|
+
# Presumed to be a row unless specified otherwise
|
263
|
+
if ruby_value.first.is_a?(Array)
|
264
|
+
excel_value[:rows] = ruby_value.size
|
265
|
+
excel_value[:columns] = ruby_value.first.size
|
266
|
+
else
|
267
|
+
excel_value[:rows] = 1
|
268
|
+
excel_value[:columns] = ruby_value.size
|
269
|
+
end
|
270
|
+
ruby_values = ruby_value.flatten
|
271
|
+
pointer = FFI::MemoryPointer.new(#{ruby_module_name}::ExcelValue, ruby_values.size)
|
272
|
+
excel_value[:array] = pointer
|
273
|
+
ruby_values.each.with_index do |v,i|
|
274
|
+
excel_value_from_ruby_value(v, #{ruby_module_name}::ExcelValue.new(pointer[i]))
|
275
|
+
end
|
276
|
+
when Symbol
|
277
|
+
excel_value[:type] = :ExcelError
|
278
|
+
excel_value[:number] = [:value, :name, :div0, :ref, :na].index(ruby_value)
|
279
|
+
else
|
280
|
+
raise Exception.new("Ruby value \u0023{ruby_value.inspect} not translatable into excel")
|
281
|
+
end
|
282
|
+
excel_value
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
164
287
|
|
165
288
|
module #{ruby_module_name}
|
166
289
|
extend FFI::Library
|
@@ -170,7 +293,7 @@ module #{ruby_module_name}
|
|
170
293
|
class ExcelValue < FFI::Struct
|
171
294
|
layout :type, ExcelType,
|
172
295
|
:number, :double,
|
173
|
-
:string, :
|
296
|
+
:string, :pointer,
|
174
297
|
:array, :pointer,
|
175
298
|
:rows, :int,
|
176
299
|
:columns, :int
|
@@ -196,6 +319,7 @@ END
|
|
196
319
|
end
|
197
320
|
end
|
198
321
|
|
322
|
+
# Put in place the getters
|
199
323
|
if !cells_to_keep || cells_to_keep.empty? || cells_to_keep[name] == :all
|
200
324
|
getable_refs = all_formulae[name].keys
|
201
325
|
elsif !cells_to_keep[name] && settable_refs
|
@@ -210,6 +334,28 @@ END
|
|
210
334
|
|
211
335
|
o.puts " # end of #{name}"
|
212
336
|
end
|
337
|
+
|
338
|
+
# Now put in place the getters and setters for the named references
|
339
|
+
o.puts " # Start of named references"
|
340
|
+
|
341
|
+
# Getters
|
342
|
+
i = input('Named references to keep')
|
343
|
+
i.lines.each do |line|
|
344
|
+
name = line.strip.split("\t").first
|
345
|
+
o.puts " attach_function '#{name}', [], ExcelValue.by_value"
|
346
|
+
end
|
347
|
+
close(i)
|
348
|
+
|
349
|
+
# Setters
|
350
|
+
i = input('Named references to set')
|
351
|
+
i.lines.each do |line|
|
352
|
+
name = line.strip.split("\t").first
|
353
|
+
o.puts " attach_function 'set_#{name}', [ExcelValue.by_value], :void"
|
354
|
+
end
|
355
|
+
|
356
|
+
close(i)
|
357
|
+
o.puts " # End of named references"
|
358
|
+
|
213
359
|
o.puts "end"
|
214
360
|
close(o)
|
215
361
|
end
|
@@ -225,8 +371,8 @@ END
|
|
225
371
|
o.puts "require_relative '#{output_name.downcase}'"
|
226
372
|
o.puts
|
227
373
|
o.puts "class Test#{ruby_module_name} < Test::Unit::TestCase"
|
228
|
-
o.puts " def
|
229
|
-
o.puts " def init_spreadsheet; #{ruby_module_name} end"
|
374
|
+
o.puts " def worksheet; @worksheet ||= init_spreadsheet; end"
|
375
|
+
o.puts " def init_spreadsheet; #{ruby_module_name}Shim.new end"
|
230
376
|
|
231
377
|
all_formulae = all_formulae()
|
232
378
|
|
data/src/commands/excel_to_x.rb
CHANGED
@@ -32,6 +32,18 @@ class ExcelToX
|
|
32
32
|
# 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
|
33
33
|
# should be setable, or an array of cell names on that sheet that should be settable (e.g., A1)
|
34
34
|
attr_accessor :cells_that_can_be_set_at_runtime
|
35
|
+
|
36
|
+
# Optional attribute. Specifies which named references to be turned into setters
|
37
|
+
#
|
38
|
+
# Should be an array of strings. Each string is a named reference. Case sensitive.
|
39
|
+
# To specify a named reference scoped to a worksheet, use ['worksheet', 'named reference'] instead
|
40
|
+
# of a string.
|
41
|
+
#
|
42
|
+
# Each named reference then has a function in the resulting C code of the form
|
43
|
+
# void set_named_reference_mangled_into_a_c_function(ExcelValue newValue)
|
44
|
+
#
|
45
|
+
# By default, no named references are output
|
46
|
+
attr_accessor :named_references_that_can_be_set_at_runtime
|
35
47
|
|
36
48
|
# Optional attribute. Specifies which cells must appear in the final generated code.
|
37
49
|
# The default is that all cells in the original spreadsheet appear in the final code.
|
@@ -45,6 +57,18 @@ class ExcelToX
|
|
45
57
|
# 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
|
46
58
|
# should be lept, or an array of cell names on that sheet that should be kept (e.g., A1)
|
47
59
|
attr_accessor :cells_to_keep
|
60
|
+
|
61
|
+
# Optional attribute. Specifies which named references should be included in the output
|
62
|
+
# Should be an array of strings. Each string is a named reference. Case sensitive.
|
63
|
+
#
|
64
|
+
# To specify a named reference scoped to a worksheet, use ['worksheet', 'named reference'] instead
|
65
|
+
# of a string.
|
66
|
+
#
|
67
|
+
# Each named reference then has a function in the resulting C code of the form
|
68
|
+
# ExcelValue named_reference_mangled_into_a_c_function()
|
69
|
+
#
|
70
|
+
# By default, no named references are output
|
71
|
+
attr_accessor :named_references_to_keep
|
48
72
|
|
49
73
|
# Optional attribute. Boolean. Not relevant to all types of code output
|
50
74
|
# * true - the generated c code is compiled
|
@@ -128,16 +152,22 @@ class ExcelToX
|
|
128
152
|
# * Mergining all the different types of formulae and values into a single file
|
129
153
|
rewrite_worksheets
|
130
154
|
|
155
|
+
# These perform a series of transformations to the information
|
156
|
+
# with the intent of removing any redundant calculations
|
157
|
+
# that are in the excel.
|
158
|
+
simplify_worksheets # Replacing shared strings and named references with their actual values, tidying arithmetic
|
159
|
+
|
160
|
+
transfer_named_references_to_keep_into_cells_to_keep
|
161
|
+
transfer_named_references_that_can_be_set_at_runtime_into_cells_that_can_be_set_at_runtime
|
162
|
+
|
131
163
|
# In case this hasn't been set by the user
|
132
164
|
if cells_that_can_be_set_at_runtime.empty?
|
133
165
|
log.info "Creating a good set of cells that should be settable"
|
134
166
|
create_a_good_set_of_cells_that_should_be_settable_at_runtime
|
135
167
|
end
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
# that are in the excel.
|
140
|
-
simplify_worksheets # Replacing shared strings and named references with their actual values, tidying arithmetic
|
168
|
+
|
169
|
+
filter_named_references
|
170
|
+
|
141
171
|
replace_formulae_with_their_results
|
142
172
|
remove_any_cells_not_needed_for_outputs
|
143
173
|
inline_formulae_that_are_only_used_once
|
@@ -190,8 +220,9 @@ class ExcelToX
|
|
190
220
|
def extract_named_references
|
191
221
|
extract ExtractNamedReferences, 'workbook.xml', 'Named references'
|
192
222
|
apply_rewrite RewriteFormulaeToAst, 'Named references'
|
223
|
+
replace ReplaceRangesWithArrayLiterals, 'Named references', 'Named references'
|
193
224
|
end
|
194
|
-
|
225
|
+
|
195
226
|
# Excel keeps a list of worksheet names. To get the mapping between
|
196
227
|
# human and computer name correct we have to look in the workbook
|
197
228
|
# relationships files. We also need to mangle the name into something
|
@@ -202,7 +233,7 @@ class ExcelToX
|
|
202
233
|
rewrite RewriteWorksheetNames, 'Worksheet names', 'Workbook relationships', 'Worksheet names'
|
203
234
|
rewrite MapSheetNamesToCNames, 'Worksheet names', 'Worksheet C names'
|
204
235
|
end
|
205
|
-
|
236
|
+
|
206
237
|
# We want a central list of the maximum extent of each worksheet
|
207
238
|
# so that we can convert column (e.g., C:F) and row (e.g., 13:18)
|
208
239
|
# references into equivalent area references (e.g., C1:F30)
|
@@ -293,6 +324,10 @@ class ExcelToX
|
|
293
324
|
end
|
294
325
|
end
|
295
326
|
|
327
|
+
# In Excel we can have references like A:Z and 5:20 which mean all cells in columns
|
328
|
+
# A to Z and all cells in rows 5 to 20 respectively. This function translates these
|
329
|
+
# into more conventional references (e.g., A5:Z20) based on the maximum area that
|
330
|
+
# has been used on a worksheet
|
296
331
|
def rewrite_row_and_column_references(name,xml_filename)
|
297
332
|
dimensions = input('Worksheet dimensions')
|
298
333
|
|
@@ -344,6 +379,104 @@ class ExcelToX
|
|
344
379
|
end
|
345
380
|
required_refs
|
346
381
|
end
|
382
|
+
|
383
|
+
# Returns a hash of named references, and the ast of their links
|
384
|
+
# where the named reference is global the key will be a string of
|
385
|
+
# its name and case sensitive.
|
386
|
+
# where the named reference is coped to a worksheet, the key will be
|
387
|
+
# a two element array. The first element will be the sheet name. The
|
388
|
+
# second will be the name.
|
389
|
+
def named_references
|
390
|
+
return @named_references if @named_references
|
391
|
+
@named_references = {}
|
392
|
+
i = input('Named references')
|
393
|
+
i.lines.each do |line|
|
394
|
+
sheet, name, ref = *line.split("\t")
|
395
|
+
key = sheet.size != 0 ? [sheet, name] : name
|
396
|
+
@named_references[key] = eval(ref)
|
397
|
+
end
|
398
|
+
close(i)
|
399
|
+
@named_references
|
400
|
+
end
|
401
|
+
|
402
|
+
# This makes sure that cells_to_keep includes named_references_to_keep
|
403
|
+
def transfer_named_references_to_keep_into_cells_to_keep
|
404
|
+
log.debug "Started transfering named references to keep into cells to keep"
|
405
|
+
return unless @named_references_to_keep
|
406
|
+
@cells_to_keep ||= {}
|
407
|
+
all_named_references = named_references
|
408
|
+
@named_references_to_keep.each do |name|
|
409
|
+
ref = all_named_references[name]
|
410
|
+
if ref
|
411
|
+
add_ref_to_hash(ref, @cells_to_keep)
|
412
|
+
else
|
413
|
+
log.warn "Named reference #{name} not found"
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
def transfer_named_references_that_can_be_set_at_runtime_into_cells_that_can_be_set_at_runtime
|
419
|
+
log.debug "Started transfering named references that can be set at runtime into cells that can be set at runtime"
|
420
|
+
return unless @named_references_that_can_be_set_at_runtime
|
421
|
+
@cells_that_can_be_set_at_runtime ||= {}
|
422
|
+
all_named_references = named_references
|
423
|
+
@named_references_that_can_be_set_at_runtime.each do |name|
|
424
|
+
ref = all_named_references[name]
|
425
|
+
if ref
|
426
|
+
add_ref_to_hash(ref, @cells_that_can_be_set_at_runtime)
|
427
|
+
else
|
428
|
+
log.warn "Named reference #{name} not found"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def add_ref_to_hash(ref, hash)
|
434
|
+
if ref.first == :sheet_reference
|
435
|
+
sheet = ref[1]
|
436
|
+
cell = ref[2][1].gsub('$','')
|
437
|
+
hash[sheet] ||= []
|
438
|
+
return if hash[sheet] == :all
|
439
|
+
hash[sheet] << cell unless hash[sheet].include?(cell)
|
440
|
+
elsif ref.first == :array
|
441
|
+
ref.shift
|
442
|
+
ref.each do |row|
|
443
|
+
row.shift
|
444
|
+
row.each do |cell|
|
445
|
+
add_ref_to_hash(cell, hash)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
else
|
449
|
+
log.error "Weird reference in named reference #{ref}"
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# FIXME: Feels like a kludge
|
454
|
+
def filter_named_references
|
455
|
+
@named_references_to_keep ||= []
|
456
|
+
@named_references_that_can_be_set_at_runtime ||= []
|
457
|
+
|
458
|
+
i = input('Named references')
|
459
|
+
o = intermediate('Named references to keep')
|
460
|
+
i.lines.each do |line|
|
461
|
+
sheet, name, ref = *line.split("\t")
|
462
|
+
key = sheet.length != 0 ? [sheet, name] : name
|
463
|
+
o.puts line if named_references_to_keep.include?(key) || named_references_that_can_be_set_at_runtime.include?(key)
|
464
|
+
end
|
465
|
+
close(o)
|
466
|
+
|
467
|
+
i.rewind
|
468
|
+
o = intermediate('Named references to set')
|
469
|
+
i.lines.each do |line|
|
470
|
+
sheet, name, ref = *line.split("\t")
|
471
|
+
key = sheet.length != 0 ? [sheet, name] : name
|
472
|
+
o.puts line if named_references_that_can_be_set_at_runtime.include?(key)
|
473
|
+
end
|
474
|
+
close(o)
|
475
|
+
|
476
|
+
# FIXME: Might result in getter and setter having different names
|
477
|
+
rewrite RewriteNamedReferenceNames, 'Named references to keep', 'Worksheet C names', 'Named references to keep'
|
478
|
+
rewrite RewriteNamedReferenceNames, 'Named references to set', 'Worksheet C names', 'Named references to set'
|
479
|
+
end
|
347
480
|
|
348
481
|
def simplify_worksheets
|
349
482
|
worksheets do |name,xml_filename|
|
@@ -583,7 +716,8 @@ class ExcelToX
|
|
583
716
|
close(output)
|
584
717
|
end
|
585
718
|
|
586
|
-
# If
|
719
|
+
# If nothing has been specified in named_refernces_that_can_be_set_at_runtime
|
720
|
+
# or in cells_that_can_be_set_at_runtime, then we assume that
|
587
721
|
# all value cells should be settable if they are referenced by
|
588
722
|
# any other forumla.
|
589
723
|
def create_a_good_set_of_cells_that_should_be_settable_at_runtime
|
@@ -636,7 +770,7 @@ class ExcelToX
|
|
636
770
|
i.lines do |line|
|
637
771
|
line =~ /^(.*?)\t(.*)$/
|
638
772
|
ref, ast = $1, $2
|
639
|
-
r[
|
773
|
+
r[ref] = eval(ast)
|
640
774
|
end
|
641
775
|
end
|
642
776
|
references
|
data/src/compile/c.rb
CHANGED
@@ -0,0 +1,90 @@
|
|
1
|
+
class MapNamedReferenceToCSetter
|
2
|
+
|
3
|
+
attr_accessor :sheet_names
|
4
|
+
attr_accessor :cells_that_can_be_set_at_runtime
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
reset
|
8
|
+
end
|
9
|
+
|
10
|
+
def reset
|
11
|
+
@new_value_name = "newValue"
|
12
|
+
end
|
13
|
+
|
14
|
+
def map(ast)
|
15
|
+
if ast.is_a?(Array)
|
16
|
+
operator = ast[0]
|
17
|
+
if respond_to?(operator)
|
18
|
+
send(operator,*ast[1..-1])
|
19
|
+
else
|
20
|
+
raise NotSupportedException.new("#{operator} in #{ast.inspect} not supported")
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise NotSupportedException.new("#{ast} not supported")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def cell(reference)
|
28
|
+
reference.downcase.gsub('$','')
|
29
|
+
end
|
30
|
+
|
31
|
+
def sheet_reference(sheet,reference)
|
32
|
+
s = sheet_names[sheet]
|
33
|
+
c = map(reference)
|
34
|
+
return " // #{s}_#{c} not settable" unless settable(sheet, c)
|
35
|
+
" set_#{s}_#{c}(#{@new_value_name});"
|
36
|
+
end
|
37
|
+
|
38
|
+
def array(*rows)
|
39
|
+
counter = -1
|
40
|
+
|
41
|
+
result = rows.map do |r|
|
42
|
+
r.shift if r.first == :row
|
43
|
+
r.map do |c|
|
44
|
+
counter += 1
|
45
|
+
@new_value_name = "array[#{counter}]"
|
46
|
+
map(c)
|
47
|
+
end
|
48
|
+
end.flatten.join("\n")
|
49
|
+
|
50
|
+
@new_value_name = "newValue"
|
51
|
+
|
52
|
+
" ExcelValue *array = newValue.array;\n#{result}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def settable(sheet, reference)
|
56
|
+
settable_refs = cells_that_can_be_set_at_runtime[sheet]
|
57
|
+
return false unless settable_refs
|
58
|
+
return true if settable_refs == :all
|
59
|
+
settable_refs.include?(reference.upcase)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class CompileNamedReferenceSetters
|
65
|
+
|
66
|
+
attr_accessor :cells_that_can_be_set_at_runtime
|
67
|
+
|
68
|
+
def self.rewrite(*args)
|
69
|
+
new.rewrite(*args)
|
70
|
+
end
|
71
|
+
|
72
|
+
def rewrite(named_references, sheet_names, output)
|
73
|
+
mapper = MapNamedReferenceToCSetter.new
|
74
|
+
mapper.sheet_names = Hash[sheet_names.readlines.map { |line| line.strip.split("\t")}]
|
75
|
+
mapper.cells_that_can_be_set_at_runtime = cells_that_can_be_set_at_runtime
|
76
|
+
|
77
|
+
named_references.lines do |line|
|
78
|
+
name, reference = line.split("\t")
|
79
|
+
ast = eval(reference)
|
80
|
+
output.puts "void set_#{name}(ExcelValue newValue) {"
|
81
|
+
output.puts mapper.map(ast)
|
82
|
+
output.puts "}"
|
83
|
+
output.puts
|
84
|
+
end
|
85
|
+
output
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
end
|
@@ -24,7 +24,7 @@ class CompileToC
|
|
24
24
|
ref, formula = line.split("\t")
|
25
25
|
ast = eval(formula)
|
26
26
|
calculation = mapper.map(ast)
|
27
|
-
name = "#{c_name}_#{ref.downcase}"
|
27
|
+
name = c_name ? "#{c_name}_#{ref.downcase}" : ref.downcase
|
28
28
|
static_or_not = gettable.call(ref) ? "" : "static "
|
29
29
|
if settable.call(ref)
|
30
30
|
output.puts "ExcelValue #{name}_default() {"
|
@@ -58,4 +58,4 @@ class CompileToC
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
end
|
61
|
+
end
|
@@ -1,72 +1,7 @@
|
|
1
|
-
|
1
|
+
require_relative "../ruby/compile_to_ruby_unit_test"
|
2
2
|
|
3
|
-
|
4
|
-
attr_accessor :delta
|
3
|
+
class CompileToCUnitTest < CompileToRubyUnitTest
|
5
4
|
|
6
|
-
|
7
|
-
@epsilon = 0.001
|
8
|
-
@delta = 0.001
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.rewrite(*args)
|
12
|
-
self.new.rewrite(*args)
|
13
|
-
end
|
14
|
-
|
15
|
-
def rewrite(input, sloppy, c_name, refs_to_test, output)
|
16
|
-
input.lines do |line|
|
17
|
-
begin
|
18
|
-
ref, formula = line.split("\t")
|
19
|
-
next unless refs_to_test.include?(ref.upcase)
|
20
|
-
output.puts "def test_#{c_name}_#{ref.downcase}"
|
21
|
-
output.puts " r = spreadsheet.#{c_name}_#{ref.downcase}"
|
22
|
-
ast = eval(formula)
|
23
|
-
case ast.first
|
24
|
-
when :number, :percentage
|
25
|
-
unless sloppy
|
26
|
-
output.puts " assert_equal(:ExcelNumber,r[:type])"
|
27
|
-
output.puts " assert_equal(#{ast.last.to_f.to_s},r[:number])"
|
28
|
-
else
|
29
|
-
if ast.last.to_f == 0
|
30
|
-
output.puts " pass if r[:type] == :ExcelEmpty"
|
31
|
-
end
|
32
|
-
|
33
|
-
output.puts " assert_equal(:ExcelNumber,r[:type])"
|
34
|
-
|
35
|
-
if ast.last.to_f <= 1
|
36
|
-
output.puts " assert_in_delta(#{ast.last.to_f.to_s},r[:number],#{@delta})"
|
37
|
-
else
|
38
|
-
output.puts " assert_in_epsilon(#{ast.last.to_f.to_s},r[:number],#{@epsilon})"
|
39
|
-
end
|
40
|
-
end
|
41
|
-
when :error
|
42
|
-
output.puts " assert_equal(:ExcelError,r[:type])"
|
43
|
-
when :string
|
44
|
-
output.puts " assert_equal(:ExcelString,r[:type])"
|
45
|
-
output.puts " assert_equal(#{ast.last.inspect},r[:string].force_encoding('utf-8'))"
|
46
|
-
when :boolean_true
|
47
|
-
output.puts " assert_equal(:ExcelBoolean,r[:type])"
|
48
|
-
output.puts " assert_equal(1,r[:number])"
|
49
|
-
when :boolean_false
|
50
|
-
output.puts " assert_equal(:ExcelBoolean,r[:type])"
|
51
|
-
output.puts " assert_equal(0,r[:number])"
|
52
|
-
when :blank
|
53
|
-
unless sloppy
|
54
|
-
output.puts " assert_equal(:ExcelEmpty,r[:type])"
|
55
|
-
else
|
56
|
-
output.puts " pass if r[:type] == :ExcelEmpty"
|
57
|
-
output.puts " assert_equal(:ExcelNumber,r[:type])"
|
58
|
-
output.puts " assert_in_delta(0.0,r[:number],#{@delta})"
|
59
|
-
end
|
60
|
-
else
|
61
|
-
raise NotSupportedException.new("#{ast} type can't be tested")
|
62
|
-
end
|
63
|
-
output.puts "end"
|
64
|
-
output.puts
|
65
|
-
rescue Exception => e
|
66
|
-
puts "Exception at line #{line}"
|
67
|
-
raise
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
5
|
+
# Now produces tests just like the Ruby version
|
71
6
|
|
72
7
|
end
|
@@ -1117,6 +1117,7 @@ static ExcelValue string_join(int number_of_arguments, ExcelValue *arguments) {
|
|
1117
1117
|
used_length = used_length + current_string_length;
|
1118
1118
|
}
|
1119
1119
|
string = realloc(string,used_length+1);
|
1120
|
+
string[used_length] = '\0';
|
1120
1121
|
return new_excel_string(string);
|
1121
1122
|
}
|
1122
1123
|
|
@@ -1878,13 +1879,20 @@ int test_functions() {
|
|
1878
1879
|
// ... should return a string by combining its arguments
|
1879
1880
|
// inspect_excel_value(string_join(2, string_join_array_1));
|
1880
1881
|
assert(string_join(2, string_join_array_1).string[6] == 'w');
|
1882
|
+
assert(string_join(2, string_join_array_1).string[11] == '\0');
|
1881
1883
|
// ... should cope with an arbitrary number of arguments
|
1882
1884
|
assert(string_join(3, string_join_array_2).string[11] == '!');
|
1885
|
+
assert(string_join(3, string_join_array_3).string[12] == '\0');
|
1883
1886
|
// ... should convert values to strings as it goes
|
1884
1887
|
assert(string_join(2, string_join_array_3).string[4] == '1');
|
1885
|
-
|
1886
|
-
assert(string_join(2, string_join_array_3).string[
|
1888
|
+
assert(string_join(2, string_join_array_3).string[5] == '0');
|
1889
|
+
assert(string_join(2, string_join_array_3).string[6] == '\0');
|
1890
|
+
// ... should convert integer values into strings without decimal points, and float values with decimal points
|
1891
|
+
assert(string_join(2, string_join_array_4).string[4] == '1');
|
1892
|
+
assert(string_join(2, string_join_array_4).string[5] == '0');
|
1893
|
+
assert(string_join(2, string_join_array_4).string[6] == '.');
|
1887
1894
|
assert(string_join(2, string_join_array_4).string[7] == '5');
|
1895
|
+
assert(string_join(2, string_join_array_4).string[8] == '\0');
|
1888
1896
|
// ... should convert TRUE and FALSE into strings
|
1889
1897
|
assert(string_join(3,string_join_array_5).string[4] == 'T');
|
1890
1898
|
|
@@ -10,7 +10,7 @@ class MapSheetNamesToCNames
|
|
10
10
|
excel_worksheet_name = line.split("\t").first
|
11
11
|
c_name = excel_worksheet_name.downcase.gsub(/[^a-z0-9]+/,'_')
|
12
12
|
c_name = "s"+c_name if c_name[0] !~ /[a-z]/
|
13
|
-
c_name =
|
13
|
+
c_name = c_name + "2" if c_names_assigned.has_key?(c_name)
|
14
14
|
c_name.succ! while c_names_assigned.has_key?(c_name)
|
15
15
|
output.puts "#{excel_worksheet_name}\t#{c_name}"
|
16
16
|
c_names_assigned[c_name] = excel_worksheet_name
|
data/src/rewrite.rb
CHANGED
@@ -8,3 +8,4 @@ require_relative "rewrite/rewrite_values_to_ast"
|
|
8
8
|
require_relative "rewrite/rewrite_relationship_id_to_filename"
|
9
9
|
require_relative "rewrite/rewrite_merge_formulae_and_values"
|
10
10
|
require_relative "rewrite/rewrite_cell_references_to_include_sheet"
|
11
|
+
require_relative "rewrite/rewrite_named_reference_names"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class RewriteNamedReferenceNames
|
2
|
+
|
3
|
+
def self.rewrite(*args)
|
4
|
+
self.new.rewrite(*args)
|
5
|
+
end
|
6
|
+
|
7
|
+
# Expects named reference in the form:
|
8
|
+
# sheet_name_or_blank_for_global\tnamed_reference_name\treference\n
|
9
|
+
# Outputs named references in the form:
|
10
|
+
# name\treference\n
|
11
|
+
# where name incorporates the sheet name and has been rewritten in a way
|
12
|
+
# that works as a c function name and (hopefully) won't clash with any
|
13
|
+
# existing names
|
14
|
+
# FIXME: but could still clash with function names and methods in the ruby shim
|
15
|
+
def rewrite(named_references, worksheet_names, output)
|
16
|
+
worksheet_names = Hash[worksheet_names.readlines.map { |line| line.strip.split("\t")}]
|
17
|
+
c_names_assigned = worksheet_names.invert
|
18
|
+
|
19
|
+
named_references.lines do |line|
|
20
|
+
sheet, name, reference = line.split("\t")
|
21
|
+
sheet = worksheet_names[sheet]
|
22
|
+
if sheet
|
23
|
+
c_name = "#{sheet}_#{name}"
|
24
|
+
else
|
25
|
+
c_name = name
|
26
|
+
c_name = "n"+c_name if c_name[0] !~ /[a-zA-Z]/
|
27
|
+
end
|
28
|
+
c_name = c_name.downcase.gsub(/[^a-z0-9]+/,'_')
|
29
|
+
c_name = c_name + "2" if c_names_assigned.has_key?(c_name)
|
30
|
+
c_name.succ! while c_names_assigned.has_key?(c_name)
|
31
|
+
output.puts "#{c_name}\t#{reference}"
|
32
|
+
c_names_assigned[c_name] = c_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: excel_to_code
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubypeg
|
@@ -90,7 +90,8 @@ description: ! "# excel_to_code\n\nConverts some excel spreadsheets (.xlsx, not
|
|
90
90
|
INDIRECT formula must be convertable at runtime into a standard formula\n3. Doesn't
|
91
91
|
implement all functions (see doc/Which_functions_are_implemented.md)\n4. Doesn't
|
92
92
|
implement references that involve range unions and lists\n5. Sometimes gives cells
|
93
|
-
as being empty, when excel would give the cell as having a numeric value of zero\
|
93
|
+
as being empty, when excel would give the cell as having a numeric value of zero\n6.
|
94
|
+
The generated C version does not multithread and will give bad results if you try\n"
|
94
95
|
email: tamc@greenonblack.com
|
95
96
|
executables:
|
96
97
|
- excel_to_c
|
@@ -104,6 +105,7 @@ files:
|
|
104
105
|
- src/commands/excel_to_ruby.rb
|
105
106
|
- src/commands/excel_to_x.rb
|
106
107
|
- src/commands.rb
|
108
|
+
- src/compile/c/compile_named_reference_setters.rb
|
107
109
|
- src/compile/c/compile_to_c.rb
|
108
110
|
- src/compile/c/compile_to_c_header.rb
|
109
111
|
- src/compile/c/compile_to_c_unit_test.rb
|
@@ -194,6 +196,7 @@ files:
|
|
194
196
|
- src/rewrite/rewrite_cell_references_to_include_sheet.rb
|
195
197
|
- src/rewrite/rewrite_formulae_to_ast.rb
|
196
198
|
- src/rewrite/rewrite_merge_formulae_and_values.rb
|
199
|
+
- src/rewrite/rewrite_named_reference_names.rb
|
197
200
|
- src/rewrite/rewrite_relationship_id_to_filename.rb
|
198
201
|
- src/rewrite/rewrite_shared_formulae.rb
|
199
202
|
- src/rewrite/rewrite_values_to_ast.rb
|