excel_to_code 0.1.23 → 0.2.0

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/src/commands/excel_to_c.rb +39 -92
  3. data/src/commands/excel_to_ruby.rb +9 -35
  4. data/src/commands/excel_to_x.rb +515 -536
  5. data/src/compile/c/a.out +0 -0
  6. data/src/compile/c/compile_named_reference_setters.rb +4 -6
  7. data/src/compile/c/compile_to_c.rb +34 -21
  8. data/src/compile/c/compile_to_c_header.rb +7 -7
  9. data/src/compile/c/excel_to_c_runtime.c +8 -4
  10. data/src/compile/c/map_formulae_to_c.rb +85 -86
  11. data/src/compile/c/map_values_to_c.rb +7 -1
  12. data/src/compile/c/map_values_to_c_structs.rb +1 -1
  13. data/src/compile/ruby/compile_to_ruby.rb +14 -11
  14. data/src/compile/ruby/compile_to_ruby_unit_test.rb +17 -10
  15. data/src/compile/ruby/map_formulae_to_ruby.rb +56 -56
  16. data/src/compile/ruby/map_values_to_ruby.rb +14 -2
  17. data/src/excel/area.rb +6 -8
  18. data/src/excel/excel_functions/hlookup.rb +1 -1
  19. data/src/excel/excel_functions/vlookup.rb +1 -1
  20. data/src/excel/formula_peg.rb +1 -1
  21. data/src/excel/formula_peg.txt +1 -1
  22. data/src/excel/reference.rb +4 -3
  23. data/src/excel/table.rb +4 -4
  24. data/src/extract.rb +1 -0
  25. data/src/extract/check_for_unknown_functions.rb +2 -2
  26. data/src/extract/extract_array_formulae.rb +9 -9
  27. data/src/extract/extract_everything.rb +140 -0
  28. data/src/extract/extract_formulae.rb +30 -20
  29. data/src/extract/extract_named_references.rb +37 -22
  30. data/src/extract/extract_relationships.rb +16 -3
  31. data/src/extract/extract_shared_formulae.rb +8 -11
  32. data/src/extract/extract_shared_formulae_targets.rb +1 -6
  33. data/src/extract/extract_shared_strings.rb +21 -8
  34. data/src/extract/extract_simple_formulae.rb +11 -6
  35. data/src/extract/extract_table.rb +26 -13
  36. data/src/extract/extract_values.rb +35 -11
  37. data/src/extract/extract_worksheet_dimensions.rb +13 -3
  38. data/src/extract/extract_worksheet_names.rb +16 -3
  39. data/src/extract/extract_worksheet_table_relationships.rb +16 -4
  40. data/src/extract/simple_extract_from_xml.rb +9 -11
  41. data/src/rewrite.rb +3 -0
  42. data/src/rewrite/ast_copy_formula.rb +5 -1
  43. data/src/rewrite/ast_expand_array_formulae.rb +71 -59
  44. data/src/rewrite/caching_formula_parser.rb +110 -0
  45. data/src/rewrite/rewrite_array_formulae.rb +21 -14
  46. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +41 -13
  47. data/src/rewrite/rewrite_shared_formulae.rb +17 -18
  48. data/src/rewrite/rewrite_values_to_ast.rb +2 -0
  49. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +28 -25
  50. data/src/simplify.rb +1 -0
  51. data/src/simplify/count_formula_references.rb +22 -23
  52. data/src/simplify/emergency_array_formula_replace_indirect_bodge.rb +44 -0
  53. data/src/simplify/identify_dependencies.rb +7 -8
  54. data/src/simplify/identify_repeated_formula_elements.rb +5 -6
  55. data/src/simplify/inline_formulae.rb +48 -48
  56. data/src/simplify/map_formulae_to_values.rb +197 -79
  57. data/src/simplify/remove_cells.rb +13 -6
  58. data/src/simplify/replace_arithmetic_on_ranges.rb +42 -28
  59. data/src/simplify/replace_arrays_with_single_cells.rb +11 -5
  60. data/src/simplify/replace_column_with_column_number.rb +31 -23
  61. data/src/simplify/replace_common_elements_in_formulae.rb +16 -17
  62. data/src/simplify/replace_indirects_with_references.rb +26 -21
  63. data/src/simplify/replace_named_references.rb +26 -31
  64. data/src/simplify/replace_offsets_with_references.rb +33 -34
  65. data/src/simplify/replace_ranges_with_array_literals.rb +48 -20
  66. data/src/simplify/replace_shared_strings.rb +15 -13
  67. data/src/simplify/replace_string_join_on_ranges.rb +7 -9
  68. data/src/simplify/replace_table_references.rb +16 -11
  69. data/src/simplify/replace_values_with_constants.rb +6 -4
  70. data/src/simplify/simplify_arithmetic.rb +33 -19
  71. data/src/simplify/sort_into_calculation_order.rb +13 -13
  72. data/src/simplify/wrap_formulae_that_return_arrays_and_are_not_in_arrays.rb +21 -13
  73. metadata +19 -2
@@ -2,6 +2,10 @@ class ExtractArrayFormulaForCell
2
2
 
3
3
  attr_accessor :row_offset, :column_offset
4
4
 
5
+ def initialize
6
+ @fc = CachingFormulaParser.instance
7
+ end
8
+
5
9
  def map(ast)
6
10
  case ast.first
7
11
  when :array; map_array(ast)
@@ -15,7 +19,7 @@ class ExtractArrayFormulaForCell
15
19
  if ast.length == 2
16
20
  @row_offset = 0
17
21
  else
18
- return [:error, "#N/A"]
22
+ return @fc.map([:error, :"#N/A"])
19
23
  end
20
24
  end
21
25
 
@@ -23,36 +27,39 @@ class ExtractArrayFormulaForCell
23
27
  if ast[1].length == 2
24
28
  @column_offset = 0
25
29
  else
26
- return [:error, "#N/A"]
30
+ return @fc.map([:error, :"#N/A"])
27
31
  end
28
32
  end
29
33
 
30
34
  ast[@row_offset+1][@column_offset+1] # plus ones to skip tthe [:array,[:row,"cell"]] symbols
31
35
  end
32
36
 
33
- FUNCTIONS_THAT_CAN_RETURN_ARRAYS = %w{INDEX MMULT}
37
+ FUNCTIONS_THAT_CAN_RETURN_ARRAYS = { INDEX: true, MMULT: true}
34
38
 
35
39
  def map_function(ast)
36
- return ast unless FUNCTIONS_THAT_CAN_RETURN_ARRAYS.include?(ast[1])
37
- [:function, "INDEX", ast, [:number, (@row_offset+1).to_s], [:number, (column_offset+1).to_s]]
40
+ return ast unless FUNCTIONS_THAT_CAN_RETURN_ARRAYS.has_key?(ast[1])
41
+ [:function, :INDEX, ast, @fc.map([:number, (@row_offset+1)]), @fc.map([:number, (column_offset+1)])]
38
42
  end
39
43
 
40
44
  end
41
45
 
42
46
  class RewriteArrayFormulae
43
- def self.rewrite(input,output)
44
- new.rewrite(input,output)
47
+ def self.rewrite(*args)
48
+ new.rewrite(*args)
45
49
  end
46
50
 
47
- def rewrite(input,output)
48
- input.each_line do |line|
49
- ref, array_range, formula = line.split("\t")
50
- array_formula(formula,array_range,output)
51
+ def rewrite(input)
52
+ @output = {}
53
+ input.each do |ref, details|
54
+ sheet = ref[0]
55
+ array_range = details[0]
56
+ ast = details[1]
57
+ array_formula(sheet, array_range, ast)
51
58
  end
59
+ @output
52
60
  end
53
61
 
54
- def array_formula(formula,array_range,output)
55
- array_ast = eval(formula)
62
+ def array_formula(sheet, array_range, array_ast)
56
63
  array_range = "#{array_range}:#{array_range}" unless array_range.include?(':')
57
64
  array_range = Area.for(array_range)
58
65
  array_range.calculate_excel_variables
@@ -64,7 +71,7 @@ class RewriteArrayFormulae
64
71
  mapper.row_offset = row
65
72
  mapper.column_offset = column
66
73
  ref = start_reference.offset(row,column)
67
- output.puts "#{ref}\t#{mapper.map(array_ast).inspect}"
74
+ @output[[sheet.to_sym, ref.to_sym]] = mapper.map(array_ast)
68
75
  end
69
76
  end
70
77
 
@@ -3,30 +3,58 @@ require_relative '../excel'
3
3
  class RewriteCellReferencesToIncludeSheetAst
4
4
 
5
5
  attr_accessor :worksheet
6
+
7
+ def initialize
8
+ @fp = CachingFormulaParser.instance
9
+ end
6
10
 
11
+ # FIXME: UGh.
7
12
  def map(ast)
8
- if ast.is_a?(Array)
9
- operator = ast.shift
10
- if respond_to?(operator)
11
- send(operator,*ast)
13
+ r = do_map(ast)
14
+ ast.replace(r) unless r.object_id == ast.object_id
15
+ ast
16
+ end
17
+
18
+ def do_map(ast)
19
+ return ast unless ast.is_a?(Array)
20
+ return cell(ast) if ast[0] == :cell
21
+ return area(ast) if ast[0] == :area
22
+ return sheet_reference(ast) if ast[0] == :sheet_reference
23
+ ast.each.with_index do |a,i|
24
+ next unless a.is_a?(Array)
25
+ case a[0]
26
+ when :cell
27
+ ast[i] = cell(a)
28
+ when :area
29
+ ast[i] = area(a)
30
+ when :sheet_reference
31
+ ast[i] = sheet_reference(a)
12
32
  else
13
- [operator,*ast.map {|a| map(a) }]
33
+ do_map(a)
14
34
  end
15
- else
16
- return ast
17
35
  end
36
+ ast
18
37
  end
19
38
 
20
- def cell(ref)
21
- [:sheet_reference, worksheet, [:cell, ref]]
39
+ def cell(ast)
40
+ ast[1] = ast[1].to_s.gsub('$','').to_sym
41
+ @fp.map([:sheet_reference, worksheet, ast.dup])
22
42
  end
23
43
 
24
- def area(start,finish)
25
- [:sheet_reference, worksheet, [:area, start, finish]]
44
+ def area(ast)
45
+ ast[1] = ast[1].to_s.gsub('$','').to_sym
46
+ ast[2] = ast[2].to_s.gsub('$','').to_sym
47
+ @fp.map([:sheet_reference, worksheet, ast.dup])
26
48
  end
27
49
 
28
- def sheet_reference(sheet_name,reference)
29
- [:sheet_reference, sheet_name, reference]
50
+ def sheet_reference(ast)
51
+ if ast[2][0] == :cell
52
+ ast[2][1] = ast[2][1].to_s.gsub('$','').to_sym
53
+ elsif ast[2][1] == :area
54
+ ast[2][1] = ast[2][1].to_s.gsub('$','').to_sym
55
+ ast[2][2] = ast[2][2].to_s.gsub('$','').to_sym
56
+ end
57
+ @fp.map(ast)
30
58
  end
31
59
 
32
60
  end
@@ -1,45 +1,44 @@
1
1
  require_relative 'ast_copy_formula'
2
2
 
3
3
  class RewriteSharedFormulae
4
- def self.rewrite(input, shared_targets, output)
5
- new.rewrite(input, shared_targets, output)
4
+ def self.rewrite(*args)
5
+ new.rewrite(*args)
6
6
  end
7
7
 
8
- def rewrite(input, shared_target_file, output)
9
- shared_targets = Hash.new
10
- shared_target_file.each_line do |l|
11
- ref, number = *l.split("\t")
12
- shared_targets[ref.strip] = number.strip
13
- end
8
+ def rewrite(formula_shared, formula_shared_targets)
9
+ @output = {}
10
+ @formula_shared_targets = formula_shared_targets
14
11
 
15
- input.each_line do |line|
16
- ref, copy_range, shared_formula_identifier, formula = line.split("\t")
17
- share_formula(ref, formula, copy_range, shared_formula_identifier, shared_targets, output)
12
+ formula_shared.each do |ref, a|
13
+ copy_range = a[0]
14
+ shared_formula_identifier = a[1]
15
+ shared_ast = a[2]
16
+ share_formula(ref, shared_ast, copy_range, shared_formula_identifier)
18
17
  end
18
+ @output
19
19
  end
20
20
 
21
- def share_formula(ref, formula, copy_range, shared_formula_identifier, shared_targets, output)
22
- shared_ast = eval(formula)
21
+ def share_formula(ref, shared_ast, copy_range, shared_formula_identifier)
23
22
  copier = AstCopyFormula.new
24
23
  copy_range = Area.for(copy_range)
25
24
  copy_range.calculate_excel_variables
26
25
 
27
26
  start_reference = copy_range.excel_start
28
27
 
29
- r = Reference.for(ref)
28
+ r = Reference.for(ref.last)
30
29
  r.calculate_excel_variables
31
30
 
32
31
  offset_from_formula_to_start_rows = start_reference.excel_row_number - r.excel_row_number
33
32
  offset_from_formula_to_start_columns = start_reference.excel_column_number - r.excel_column_number
34
33
 
35
34
  copy_range.offsets.each do |row,column|
36
- new_ref = start_reference.offset(row,column)
37
- next unless shared_targets.include?(new_ref)
38
- next unless shared_formula_identifier == shared_targets[new_ref]
35
+ new_ref = [ref.first.to_sym, start_reference.offset(row,column).to_sym]
36
+ next unless @formula_shared_targets.include?(new_ref)
37
+ next unless shared_formula_identifier == @formula_shared_targets[new_ref]
39
38
  copier.rows_to_move = row + offset_from_formula_to_start_rows
40
39
  copier.columns_to_move = column + offset_from_formula_to_start_columns
41
40
  ast = copier.copy(shared_ast)
42
- output.puts "#{new_ref}\t#{ast.inspect}"
41
+ @output[new_ref] = ast
43
42
  end
44
43
  end
45
44
 
@@ -1,5 +1,7 @@
1
1
  require_relative '../excel/formula_peg'
2
2
 
3
+ #FIXME: NOT NEEDED
4
+
3
5
  class RewriteValuesToAst
4
6
 
5
7
  def self.rewrite(input,output)
@@ -20,6 +20,10 @@ class WorksheetDimension
20
20
  def map_column(start,finish)
21
21
  ["#{start}#{first_row}","#{finish}#{last_row}"]
22
22
  end
23
+
24
+ def inspect
25
+ area.inspect
26
+ end
23
27
 
24
28
  end
25
29
 
@@ -29,37 +33,36 @@ class MapColumnAndRowRangeAst
29
33
 
30
34
  def initialize(default_worksheet_name,worksheet_dimensions)
31
35
  @default_worksheet_name, @worksheet_dimensions = default_worksheet_name, worksheet_dimensions
36
+ @worksheet_names = []
32
37
  end
33
38
 
34
39
  def map(ast)
35
- if ast.is_a?(Array)
36
- operator = ast.shift
37
- if respond_to?(operator)
38
- send(operator,*ast)
39
- else
40
- [operator,*ast.map {|a| map(a) }]
41
- end
42
- else
43
- return ast
40
+ return ast unless ast.is_a?(Array)
41
+ case ast[0]
42
+ when :sheet_reference; sheet_reference(ast)
43
+ when :row_range; row_range(ast)
44
+ when :column_range; column_range(ast)
44
45
  end
46
+ ast.each {|e| map(e)}
45
47
  end
46
48
 
47
- def sheet_reference(sheet_name,reference)
48
- if reference.first == :row_range
49
- [:sheet_reference,sheet_name,[:area,*worksheet_dimensions[sheet_name].map_row(reference[1],reference[2])]]
50
- elsif reference.first == :column_range
51
- [:sheet_reference,sheet_name,[:area,*worksheet_dimensions[sheet_name].map_column(reference[1],reference[2])]]
52
- else
53
- [:sheet_reference,sheet_name,reference]
54
- end
49
+ # Of the form [:sheet_reference, sheet_name, reference]
50
+ def sheet_reference(ast)
51
+ @worksheet_names.push(ast[1].to_sym) #FIXME: Remove once all symbols
52
+ map(ast[2])
53
+ @worksheet_names.pop
55
54
  end
56
55
 
57
- def row_range(start,finish)
58
- [:area,*worksheet_dimensions[default_worksheet_name].map_row(start,finish)]
56
+ # Of the form [:row_range, start, finish]
57
+ def row_range(ast)
58
+ worksheet = @worksheet_names.last || @default_worksheet_name
59
+ ast.replace([:area,*worksheet_dimensions[worksheet].map_row(ast[1],ast[2])])
59
60
  end
60
61
 
61
- def column_range(start,finish)
62
- [:area,*worksheet_dimensions[default_worksheet_name].map_column(start,finish)]
62
+ # Of the form [:column_range, start, finish]
63
+ def column_range(ast)
64
+ worksheet = @worksheet_names.last || @default_worksheet_name
65
+ ast.replace([:area,*worksheet_dimensions[worksheet].map_column(ast[1],ast[2])])
63
66
  end
64
67
 
65
68
  end
@@ -86,10 +89,10 @@ class RewriteWholeRowColumnReferencesToAreas
86
89
  end
87
90
 
88
91
  def worksheet_dimensions=(worksheet_dimensions)
89
- @dimensions = Hash[worksheet_dimensions.readlines.map do |line|
90
- worksheet_name, area = line.split("\t")
91
- [worksheet_name,WorksheetDimension.new(area)]
92
- end]
92
+ @dimensions = {}
93
+ worksheet_dimensions.each do |name, area|
94
+ @dimensions[name] = WorksheetDimension.new(area)
95
+ end
93
96
  @mapper = nil
94
97
  end
95
98
 
data/src/simplify.rb CHANGED
@@ -19,3 +19,4 @@ require_relative "simplify/sort_into_calculation_order"
19
19
  require_relative "simplify/replace_arithmetic_on_ranges"
20
20
  require_relative "simplify/wrap_formulae_that_return_arrays_and_are_not_in_arrays"
21
21
  require_relative "simplify/replace_string_join_on_ranges"
22
+ require_relative "simplify/emergency_array_formula_replace_indirect_bodge.rb"
@@ -1,5 +1,6 @@
1
1
  class CountFormulaReferences
2
2
 
3
+ # FIXME: Do we need these accessors
3
4
  attr_accessor :references
4
5
  attr_accessor :dependencies
5
6
  attr_accessor :current_sheet
@@ -8,25 +9,22 @@ class CountFormulaReferences
8
9
  @references = references
9
10
  @dependencies = dependencies
10
11
  dependencies.default_proc = lambda do |hash,key|
11
- hash[key] = {}
12
+ hash[key] = 0
12
13
  end
13
14
  @current_sheet = []
14
15
  end
15
16
 
16
17
  def count(references)
18
+ # FIXME: Why do we have this references instance variable?
17
19
  @references = references
18
- references.each do |sheet,cells|
19
- current_sheet << sheet
20
- cells.each do |ref,ast|
21
- count_dependencies_for(sheet,ref,ast)
22
- end
23
- current_sheet.pop
20
+ references.each do |full_ref,ast|
21
+ @dependencies[full_ref] ||= 0
22
+ count_dependencies_for(full_ref.first,full_ref.last,ast)
24
23
  end
25
24
  return dependencies
26
25
  end
27
26
 
28
27
  def count_dependencies_for(sheet,ref,ast)
29
- @dependencies[sheet][ref] ||= 0
30
28
  current_sheet.push(sheet)
31
29
  map(ast)
32
30
  current_sheet.pop
@@ -34,26 +32,27 @@ class CountFormulaReferences
34
32
 
35
33
  def map(ast)
36
34
  return ast unless ast.is_a?(Array)
37
- operator = ast[0]
38
- if respond_to?(operator)
39
- send(operator,*ast[1..-1])
40
- else
41
- ast[1..-1].each do |a|
42
- map(a)
43
- end
35
+ case ast[0]
36
+ when :sheet_reference; sheet_reference(ast)
37
+ when :cell; cell(ast)
38
+ else; ast.each { |a| map(a) }
44
39
  end
40
+ ast
45
41
  end
46
42
 
47
- def sheet_reference(sheet,reference)
48
- ref = reference.last.gsub('$','')
49
- @dependencies[sheet][ref] ||= 0
50
- @dependencies[sheet][ref] += 1
43
+ # Format [:sheet_reference, sheet, reference]
44
+ def sheet_reference(ast)
45
+ sheet = ast[1]
46
+ reference = ast[2]
47
+ ref = reference.last.to_s.gsub('$','').to_sym
48
+ @dependencies[[sheet, ref]] += 1
51
49
  end
52
50
 
53
- def cell(reference)
54
- ref = reference.gsub('$','')
55
- @dependencies[current_sheet.last][ref] ||= 0
56
- @dependencies[current_sheet.last][ref] += 1
51
+ # Format [:cell, reference]
52
+ def cell(ast)
53
+ reference = ast[1]
54
+ ref = reference.to_s.gsub('$','').to_sym
55
+ @dependencies[[current_sheet.last, ref]] += 1
57
56
  end
58
57
 
59
58
  end
@@ -0,0 +1,44 @@
1
+ require_relative '../excel/formula_peg'
2
+
3
+ class EmergencyArrayFormulaReplaceIndirectBodge
4
+
5
+ attr_accessor :current_sheet_name
6
+ attr_accessor :references
7
+
8
+ def initialize
9
+ @indirect_replacer = ReplaceIndirectsWithReferencesAst.new
10
+ @formulae_to_value_replacer = MapFormulaeToValues.new
11
+ @inline_formulae_replacer = InlineFormulaeAst.new
12
+ end
13
+
14
+ def replace(ast)
15
+ map(ast)
16
+ ast
17
+ end
18
+
19
+ def map(ast)
20
+ return ast unless ast.is_a?(Array)
21
+ function(ast) if ast[0] == :function && ast[1] == :INDIRECT
22
+ ast.each { |a| map(a) }
23
+ ast
24
+ end
25
+
26
+ def function(ast)
27
+ new_ast = deep_copy(ast)
28
+ @inline_formulae_replacer.references = @references
29
+ @inline_formulae_replacer.current_sheet_name = [@current_sheet_name]
30
+ @inline_formulae_replacer.map(new_ast)
31
+ @formulae_to_value_replacer.map(new_ast)
32
+ @indirect_replacer.replace(new_ast)
33
+ ast.replace(new_ast)
34
+ ast
35
+ end
36
+
37
+ def deep_copy(ast)
38
+ return ast if ast.is_a?(Symbol)
39
+ return ast.dup unless ast.is_a?(Array)
40
+ ast.map do |a|
41
+ deep_copy(a)
42
+ end
43
+ end
44
+ end
@@ -15,9 +15,9 @@ class IdentifyDependencies
15
15
 
16
16
  def add_depedencies_for(sheet,cell = :all)
17
17
  if cell == :all
18
- return unless references.has_key?(sheet)
19
- references[sheet].each do |ref,ast|
20
- recursively_add_dependencies_for(sheet,ref)
18
+ references.each do |ref,ast|
19
+ next unless ref.first == sheet
20
+ recursively_add_dependencies_for(sheet,ref.last)
21
21
  end
22
22
  else
23
23
  recursively_add_dependencies_for(sheet,cell)
@@ -27,8 +27,7 @@ class IdentifyDependencies
27
27
  def recursively_add_dependencies_for(sheet,cell)
28
28
  return if dependencies[sheet].has_key?(cell)
29
29
  dependencies[sheet][cell] = true
30
- return unless references.has_key?(sheet)
31
- ast = references[sheet][cell]
30
+ ast = references[[sheet,cell]]
32
31
  return unless ast
33
32
  current_sheet.push(sheet)
34
33
  map(ast)
@@ -41,16 +40,16 @@ class IdentifyDependencies
41
40
  if respond_to?(operator)
42
41
  send(operator,*ast[1..-1])
43
42
  else
44
- [operator,*ast[1..-1].map {|a| map(a) }]
43
+ ast.each {|a| map(a) }
45
44
  end
46
45
  end
47
46
 
48
47
  def sheet_reference(sheet,reference)
49
- recursively_add_dependencies_for(sheet,reference.last.gsub('$',''))
48
+ recursively_add_dependencies_for(sheet,Reference.for(reference.last).unfix.to_sym)
50
49
  end
51
50
 
52
51
  def cell(reference)
53
- recursively_add_dependencies_for(current_sheet.last,reference.gsub('$',''))
52
+ recursively_add_dependencies_for(current_sheet.last,Reference.for(reference).unfix.to_sym)
54
53
  end
55
54
 
56
55
  end