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
@@ -7,12 +7,19 @@ class RemoveCells
7
7
  self.new.rewrite(*args)
8
8
  end
9
9
 
10
- def rewrite(input,output)
11
- input.each_line do |line|
12
- ref = line[/^(.*?)\t/,1]
13
- if cells_to_keep.has_key?(ref)
14
- output.puts line
15
- end
10
+ def rewrite(formulae)
11
+ formulae.delete_if do |ref, ast|
12
+ delete_ref?(ref)
16
13
  end
14
+ formulae
15
+ end
16
+
17
+ def delete_ref?(ref)
18
+ sheet = ref.first
19
+ cell = ref.last
20
+ cells_to_keep_in_sheet = cells_to_keep[sheet]
21
+ return true unless cells_to_keep_in_sheet
22
+ return false if cells_to_keep_in_sheet.has_key?(cell)
23
+ true
17
24
  end
18
25
  end
@@ -2,43 +2,57 @@ class ReplaceArithmeticOnRangesAst
2
2
 
3
3
  def map(ast)
4
4
  return ast unless ast.is_a?(Array)
5
- operator = ast[0]
6
- if respond_to?(operator)
7
- send(operator,*ast[1..-1])
8
- else
9
- [operator,*ast[1..-1].map {|a| map(a) }]
10
- end
5
+ arithmetic(ast) if ast.first == :arithmetic
6
+ ast.each { |a| map(a) }
7
+ ast
11
8
  end
12
9
 
13
- def arithmetic(left, operator, right)
10
+ # Format [:artithmetic, left, operator, right]
11
+ # should have removed arithmetic with more than one operator
12
+ # in an earlier transformation
13
+ def arithmetic(ast)
14
+ left, operator, right = ast[1], ast[2], ast[3]
15
+ # Three different options, array on the left, array on the right, or both
16
+ # array on the left first
14
17
  if left.first == :array && right.first != :array
15
- mapped_right = map(right)
16
- array_map(left) do |cell|
17
- [:arithmetic, map(cell), operator, mapped_right]
18
- end
18
+ map(right)
19
+ ast.replace(
20
+ array_map(left) do |cell|
21
+ [:arithmetic, map(cell), operator, right]
22
+ end
23
+ )
24
+
25
+ # array on the right next
19
26
  elsif left.first != :array && right.first == :array
20
- mapped_left = map(left)
21
- array_map(right) do |cell|
22
- [:arithmetic, mapped_left, operator, map(cell)]
23
- end
27
+ map(left)
28
+ ast.replace(
29
+ array_map(right) do |cell|
30
+ [:arithmetic, left, operator, map(cell)]
31
+ end
32
+ )
33
+
34
+ # now array both sides
24
35
  elsif left.first == :array && right.first == :array
25
- left.map.with_index do |row, i|
26
- if row == :array
27
- row
28
- else
29
- row.map.with_index do |cell, j|
30
- if cell == :row
31
- cell
32
- elsif i >= left.length || i >= right.length || j >= left.first.length || j >= right.first.length
33
- [:error, "#VALUE!"]
34
- else
35
- [:arithmetic, map(left[i][j]), operator, map(right[i][j])]
36
+ ast.replace(
37
+ left.map.with_index do |row, i|
38
+ if row == :array
39
+ row
40
+ else
41
+ row.map.with_index do |cell, j|
42
+ if cell == :row
43
+ cell
44
+ elsif i >= left.length || i >= right.length || j >= left.first.length || j >= right.first.length
45
+ [:error, "#VALUE!"]
46
+ else
47
+ [:arithmetic, map(left[i][j]), operator, map(right[i][j])]
48
+ end
36
49
  end
37
50
  end
38
51
  end
39
- end
52
+ )
40
53
  else
41
- [:arithmetic, map(left), operator, map(right)]
54
+ map(left)
55
+ map(right)
42
56
  end
43
57
  end
44
58
 
@@ -1,5 +1,14 @@
1
1
  require_relative '../excel'
2
2
 
3
+ class ReplaceArraysWithSingleCellsAst
4
+
5
+ def map(ast)
6
+ return ast unless ast.first == :array
7
+ ast[1][1]
8
+ end
9
+ end
10
+
11
+
3
12
  class ReplaceArraysWithSingleCells
4
13
 
5
14
  def self.replace(*args)
@@ -8,16 +17,13 @@ class ReplaceArraysWithSingleCells
8
17
 
9
18
  def replace(input,output)
10
19
 
20
+ replacer = ReplaceArraysWithSingleCellsAst.new
11
21
  input.each_line do |line|
12
22
  # Looks to match shared string lines
13
23
  if line =~ /\[:array/
14
24
  content = line.split("\t")
15
25
  ast = eval(content.pop)
16
- if ast.first == :array
17
- output.puts "#{content.join("\t")}\t#{ast[1][1].inspect}"
18
- else
19
- output.puts line
20
- end
26
+ output.puts "#{content.join("\t")}\t#{replacer.map(ast).inspect}"
21
27
  else
22
28
  output.puts line
23
29
  end
@@ -1,34 +1,42 @@
1
1
  class ReplaceColumnWithColumnNumberAST
2
2
 
3
- attr_accessor :replacements_made_in_the_last_pass
3
+ attr_accessor :count_replaced
4
+ attr_accessor :replacement_made
4
5
 
5
6
  def initialize
6
- @replacements_made_in_the_last_pass = 0
7
+ @count_replaced = 0
8
+ end
9
+
10
+ def replace(ast)
11
+ @replacement_made = false
12
+ map(ast)
13
+ @replacement_made
7
14
  end
8
15
 
9
16
  def map(ast)
10
17
  return ast unless ast.is_a?(Array)
11
- operator = ast[0]
12
- if respond_to?(operator)
13
- send(operator,*ast[1..-1])
14
- else
15
- [operator,*ast[1..-1].map {|a| map(a) }]
16
- end
18
+ function(ast) if ast[0] == :function
19
+ ast.each { |a| map(a) }
20
+ ast
17
21
  end
18
22
 
19
- def function(name,*args)
20
- if name == "COLUMN" && args.size == 1 && [:cell, :sheet_reference].include?(args[0][0])
21
- if args[0][0] == :cell
22
- reference = Reference.for(args[0][1])
23
- elsif args[0][0] == :sheet_reference
24
- reference = Reference.for(args[0][2][1])
25
- end
26
- reference.calculate_excel_variables
27
- @replacements_made_in_the_last_pass += 1
28
- [:number, reference.excel_column_number.to_s]
29
- else
30
- [:function,name,*args.map { |a| map(a) }]
23
+ # Should be of the form [:function, "COLUMN", [:sheet_reference, sheet, ref]]
24
+
25
+ REF_TYPES = {:cell => true, :sheet_reference => true}
26
+
27
+ def function(ast)
28
+ return unless ast[1] == :COLUMN
29
+ return unless ast.size == 3
30
+ return unless REF_TYPES.has_key?(ast[2][0])
31
+ if ast[2][0] == :cell
32
+ reference = Reference.for(ast[2][1])
33
+ elsif ast[2][0] == :sheet_reference
34
+ reference = Reference.for(ast[2][2][1])
31
35
  end
36
+ reference.calculate_excel_variables
37
+ @count_replaced += 1
38
+ @replacement_made = true
39
+ ast.replace( CachingFormulaParser.map([:number, reference.excel_column_number]))
32
40
  end
33
41
 
34
42
  end
@@ -40,19 +48,19 @@ class ReplaceColumnWithColumnNumber
40
48
  self.new.replace(*args)
41
49
  end
42
50
 
43
- attr_accessor :replacements_made_in_the_last_pass
51
+ attr_accessor :count_replaced
44
52
 
45
53
  def replace(input,output)
46
54
  rewriter = ReplaceColumnWithColumnNumberAST.new
47
55
  input.each_line do |line|
48
56
  # Looks to match lines with references
49
- if line =~ /"COLUMN"/
57
+ if line =~ /:COLUMN/
50
58
  ref, ast = line.split("\t")
51
59
  output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
52
60
  else
53
61
  output.puts line
54
62
  end
55
63
  end
56
- @replacements_made_in_the_last_pass = rewriter.replacements_made_in_the_last_pass
64
+ @count_replaced = rewriter.count_replaced
57
65
  end
58
66
  end
@@ -5,29 +5,28 @@ class ReplaceCommonElementsInFormulae
5
5
  end
6
6
 
7
7
  attr_accessor :common_elements
8
+ attr_accessor :common_elements_used
8
9
 
9
- def replace(input,common,output)
10
- @common_elements ||= {}
11
- common.readlines.map do |a|
12
- ref, element = a.split("\t")
13
- @common_elements[element.strip] = [:cell, ref]
14
- end
15
- input.each_line do |line|
16
- ref, formula = line.split("\t")
17
- output.puts "#{ref}\t#{replace_repeated_formulae(eval(formula)).inspect}"
10
+ def replace(input, common_elements)
11
+ @common_elements_used ||= Hash.new { |h, k| h[k] = 0 }
12
+ @common_elements = common_elements
13
+ input.each do |ref, ast|
14
+ replace_repeated_formulae(ast)
18
15
  end
16
+ input
19
17
  end
18
+
19
+ VALUES = {:number => true, :string => true, :blank => true, :null => true, :error => true, :boolean_true => true, :boolean_false => true, :sheet_reference => true, :cell => true, :row => true}
20
20
 
21
21
  def replace_repeated_formulae(ast)
22
22
  return ast unless ast.is_a?(Array)
23
- return ast if [:number,:string,:blank,:null,:error,:boolean_true,:boolean_false,:sheet_reference,:cell, :row].include?(ast.first)
24
- string = ast.inspect
25
- return ast if string.length < 20
26
- if @common_elements.has_key?(string)
27
- return @common_elements[string]
28
- end
29
- ast.map do |a|
30
- replace_repeated_formulae(a)
23
+ return ast if VALUES.has_key?(ast.first)
24
+ replacement = @common_elements[ast]
25
+ if replacement
26
+ @common_elements_used[replacement] += 1
27
+ ast.replace(replacement)
28
+ else
29
+ ast.each { |a| replace_repeated_formulae(a) }
31
30
  end
32
31
  end
33
32
 
@@ -2,32 +2,37 @@ require_relative '../excel/formula_peg'
2
2
 
3
3
  class ReplaceIndirectsWithReferencesAst
4
4
 
5
- attr_accessor :replacements_made_in_the_last_pass
5
+ attr_accessor :count_replaced
6
+ attr_accessor :replacement_made
6
7
 
7
8
  def initialize
8
- @replacements_made_in_the_last_pass = 0
9
+ @count_replaced = 0
10
+ end
11
+
12
+ def replace(ast)
13
+ @replacement_made = false
14
+ map(ast)
15
+ @replacement_made
9
16
  end
10
17
 
11
18
  def map(ast)
12
19
  return ast unless ast.is_a?(Array)
13
- operator = ast[0]
14
- if respond_to?(operator)
15
- send(operator,*ast[1..-1])
16
- else
17
- [operator,*ast[1..-1].map {|a| map(a) }]
18
- end
20
+ function(ast) if ast[0] == :function
21
+ ast.each { |a| map(a) }
22
+ ast
19
23
  end
20
24
 
21
- def function(name,*args)
22
- if name == "INDIRECT" && args[0][0] == :string
23
- @replacements_made_in_the_last_pass += 1
24
- Formula.parse(args[0][1]).to_ast[1]
25
- elsif name == "INDIRECT" && args[0][0] == :error
26
- @replacements_made_in_the_last_pass += 1
27
- args[0]
28
- else
29
- puts "indirect #{[:function,name,*args.map { |a| map(a) }].inspect} not replaced" if name == "INDIRECT"
30
- [:function,name,*args.map { |a| map(a) }]
25
+ def function(ast)
26
+ return unless ast[1] == :INDIRECT
27
+ args = ast[2..-1]
28
+ if args[0][0] == :string
29
+ @count_replaced += 1
30
+ @replacement_made = true
31
+ ast.replace(CachingFormulaParser.parse(args[0][1]))
32
+ elsif args[0][0] == :error
33
+ @count_replaced += 1
34
+ @replacement_made = true
35
+ ast.replace(args[0])
31
36
  end
32
37
  end
33
38
  end
@@ -39,19 +44,19 @@ class ReplaceIndirectsWithReferences
39
44
  self.new.replace(*args)
40
45
  end
41
46
 
42
- attr_accessor :replacements_made_in_the_last_pass
47
+ attr_accessor :count_replaced
43
48
 
44
49
  def replace(input,output)
45
50
  rewriter = ReplaceIndirectsWithReferencesAst.new
46
51
  input.each_line do |line|
47
52
  # Looks to match lines with references
48
- if line =~ /"INDIRECT"/
53
+ if line =~ /:INDIRECT/
49
54
  ref, ast = line.split("\t")
50
55
  output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
51
56
  else
52
57
  output.puts line
53
58
  end
54
59
  end
55
- @replacements_made_in_the_last_pass = rewriter.replacements_made_in_the_last_pass
60
+ @count_replaced = rewriter.count_replaced
56
61
  end
57
62
  end
@@ -3,22 +3,15 @@ class NamedReferences
3
3
  attr_accessor :named_references
4
4
 
5
5
  def initialize(refs)
6
- @named_references = {}
7
- refs.each do |line|
8
- sheet, name, reference = line.split("\t")
9
- @named_references[sheet.downcase] ||= {}
10
- @named_references[sheet.downcase][name.downcase] = eval(reference)
11
- end
6
+ @named_references = refs
12
7
  end
13
8
 
14
9
  def reference_for(sheet,named_reference)
15
10
  sheet = sheet.downcase
16
- named_reference = named_reference.downcase
17
- if @named_references.has_key?(sheet)
18
- @named_references[sheet][named_reference] || @named_references[""][named_reference] || [:error, "#NAME?"]
19
- else
20
- @named_references[""][named_reference] || [:error, "#NAME?"]
21
- end
11
+ named_reference = named_reference.downcase.to_sym
12
+ @named_references[[sheet, named_reference]] ||
13
+ @named_references[named_reference] ||
14
+ [:error, :"#NAME?"]
22
15
  end
23
16
 
24
17
  end
@@ -27,30 +20,32 @@ class ReplaceNamedReferencesAst
27
20
 
28
21
  attr_accessor :named_references, :default_sheet_name
29
22
 
30
- def initialize(named_references, default_sheet_name)
23
+ def initialize(named_references, default_sheet_name = nil)
31
24
  @named_references, @default_sheet_name = named_references, default_sheet_name
25
+ @named_references = NamedReferences.new(@named_references) unless @named_references.is_a?(NamedReferences)
32
26
  end
33
27
 
34
28
  def map(ast)
35
29
  return ast unless ast.is_a?(Array)
36
- operator = ast[0]
37
- if respond_to?(operator)
38
- send(operator,*ast[1..-1])
39
- else
40
- [operator,*ast[1..-1].map {|a| map(a) }]
30
+ case ast[0]
31
+ when :sheet_reference; sheet_reference(ast)
32
+ when :named_reference; named_reference(ast)
41
33
  end
34
+ ast.each { |a| map(a) }
35
+ ast
42
36
  end
43
37
 
44
- def sheet_reference(sheet,reference)
45
- if reference.first == :named_reference
46
- named_references.reference_for(sheet,reference.last)
47
- else
48
- [:sheet_reference,sheet,reference]
49
- end
38
+ # Format [:sheet_reference, sheet, reference]
39
+ def sheet_reference(ast)
40
+ reference = ast[2]
41
+ return unless reference.first == :named_reference
42
+ sheet = ast[1]
43
+ ast.replace(named_references.reference_for(sheet, reference.last))
50
44
  end
51
45
 
52
- def named_reference(name)
53
- named_references.reference_for(default_sheet_name,name)
46
+ # Format [:named_reference, name]
47
+ def named_reference(ast)
48
+ ast.replace(named_references.reference_for(default_sheet_name, ast[1]))
54
49
  end
55
50
 
56
51
  end
@@ -58,15 +53,15 @@ end
58
53
 
59
54
  class ReplaceNamedReferences
60
55
 
61
- attr_accessor :sheet_name
56
+ attr_accessor :sheet_name, :named_references
62
57
 
63
- def self.replace(values,sheet_name,named_references,output)
64
- self.new.replace(values,sheet_name,named_references,output)
58
+ def self.replace(*args)
59
+ self.new.replace(*args)
65
60
  end
66
61
 
67
62
  # Rewrites ast with named references
68
- def replace(values,named_references,output)
69
- named_references = NamedReferences.new(named_references.readlines)
63
+ def replace(values,output)
64
+ named_references = NamedReferences.new(@named_references)
70
65
  rewriter = ReplaceNamedReferencesAst.new(named_references,sheet_name)
71
66
  values.each_line do |line|
72
67
  # Looks to match shared string lines