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.
- checksums.yaml +4 -4
- data/src/commands/excel_to_c.rb +39 -92
- data/src/commands/excel_to_ruby.rb +9 -35
- data/src/commands/excel_to_x.rb +515 -536
- data/src/compile/c/a.out +0 -0
- data/src/compile/c/compile_named_reference_setters.rb +4 -6
- data/src/compile/c/compile_to_c.rb +34 -21
- data/src/compile/c/compile_to_c_header.rb +7 -7
- data/src/compile/c/excel_to_c_runtime.c +8 -4
- data/src/compile/c/map_formulae_to_c.rb +85 -86
- data/src/compile/c/map_values_to_c.rb +7 -1
- data/src/compile/c/map_values_to_c_structs.rb +1 -1
- data/src/compile/ruby/compile_to_ruby.rb +14 -11
- data/src/compile/ruby/compile_to_ruby_unit_test.rb +17 -10
- data/src/compile/ruby/map_formulae_to_ruby.rb +56 -56
- data/src/compile/ruby/map_values_to_ruby.rb +14 -2
- data/src/excel/area.rb +6 -8
- data/src/excel/excel_functions/hlookup.rb +1 -1
- data/src/excel/excel_functions/vlookup.rb +1 -1
- data/src/excel/formula_peg.rb +1 -1
- data/src/excel/formula_peg.txt +1 -1
- data/src/excel/reference.rb +4 -3
- data/src/excel/table.rb +4 -4
- data/src/extract.rb +1 -0
- data/src/extract/check_for_unknown_functions.rb +2 -2
- data/src/extract/extract_array_formulae.rb +9 -9
- data/src/extract/extract_everything.rb +140 -0
- data/src/extract/extract_formulae.rb +30 -20
- data/src/extract/extract_named_references.rb +37 -22
- data/src/extract/extract_relationships.rb +16 -3
- data/src/extract/extract_shared_formulae.rb +8 -11
- data/src/extract/extract_shared_formulae_targets.rb +1 -6
- data/src/extract/extract_shared_strings.rb +21 -8
- data/src/extract/extract_simple_formulae.rb +11 -6
- data/src/extract/extract_table.rb +26 -13
- data/src/extract/extract_values.rb +35 -11
- data/src/extract/extract_worksheet_dimensions.rb +13 -3
- data/src/extract/extract_worksheet_names.rb +16 -3
- data/src/extract/extract_worksheet_table_relationships.rb +16 -4
- data/src/extract/simple_extract_from_xml.rb +9 -11
- data/src/rewrite.rb +3 -0
- data/src/rewrite/ast_copy_formula.rb +5 -1
- data/src/rewrite/ast_expand_array_formulae.rb +71 -59
- data/src/rewrite/caching_formula_parser.rb +110 -0
- data/src/rewrite/rewrite_array_formulae.rb +21 -14
- data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +41 -13
- data/src/rewrite/rewrite_shared_formulae.rb +17 -18
- data/src/rewrite/rewrite_values_to_ast.rb +2 -0
- data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +28 -25
- data/src/simplify.rb +1 -0
- data/src/simplify/count_formula_references.rb +22 -23
- data/src/simplify/emergency_array_formula_replace_indirect_bodge.rb +44 -0
- data/src/simplify/identify_dependencies.rb +7 -8
- data/src/simplify/identify_repeated_formula_elements.rb +5 -6
- data/src/simplify/inline_formulae.rb +48 -48
- data/src/simplify/map_formulae_to_values.rb +197 -79
- data/src/simplify/remove_cells.rb +13 -6
- data/src/simplify/replace_arithmetic_on_ranges.rb +42 -28
- data/src/simplify/replace_arrays_with_single_cells.rb +11 -5
- data/src/simplify/replace_column_with_column_number.rb +31 -23
- data/src/simplify/replace_common_elements_in_formulae.rb +16 -17
- data/src/simplify/replace_indirects_with_references.rb +26 -21
- data/src/simplify/replace_named_references.rb +26 -31
- data/src/simplify/replace_offsets_with_references.rb +33 -34
- data/src/simplify/replace_ranges_with_array_literals.rb +48 -20
- data/src/simplify/replace_shared_strings.rb +15 -13
- data/src/simplify/replace_string_join_on_ranges.rb +7 -9
- data/src/simplify/replace_table_references.rb +16 -11
- data/src/simplify/replace_values_with_constants.rb +6 -4
- data/src/simplify/simplify_arithmetic.rb +33 -19
- data/src/simplify/sort_into_calculation_order.rb +13 -13
- data/src/simplify/wrap_formulae_that_return_arrays_and_are_not_in_arrays.rb +21 -13
- metadata +19 -2
@@ -7,12 +7,19 @@ class RemoveCells
|
|
7
7
|
self.new.rewrite(*args)
|
8
8
|
end
|
9
9
|
|
10
|
-
def rewrite(
|
11
|
-
|
12
|
-
ref
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
row
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
cell
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
52
|
+
)
|
40
53
|
else
|
41
|
-
|
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
|
-
|
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 :
|
3
|
+
attr_accessor :count_replaced
|
4
|
+
attr_accessor :replacement_made
|
4
5
|
|
5
6
|
def initialize
|
6
|
-
@
|
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
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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 :
|
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 =~
|
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
|
-
@
|
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,
|
10
|
-
@
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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 :
|
5
|
+
attr_accessor :count_replaced
|
6
|
+
attr_accessor :replacement_made
|
6
7
|
|
7
8
|
def initialize
|
8
|
-
@
|
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
|
-
|
14
|
-
|
15
|
-
|
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(
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@
|
27
|
-
args[0]
|
28
|
-
|
29
|
-
|
30
|
-
|
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 :
|
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 =~
|
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
|
-
@
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
53
|
-
|
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(
|
64
|
-
self.new.replace(
|
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,
|
69
|
-
named_references = NamedReferences.new(named_references
|
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
|