excel_to_code 0.1.23 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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