excel_to_code 0.0.14 → 0.1.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.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
|