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
@@ -1,42 +1,39 @@
|
|
1
1
|
class ReplaceOffsetsWithReferencesAst
|
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
|
7
8
|
end
|
8
9
|
|
10
|
+
def replace(ast)
|
11
|
+
@replacement_made = false
|
12
|
+
map(ast)
|
13
|
+
@replacement_made
|
14
|
+
end
|
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
|
-
def function(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
else
|
34
|
-
puts "#{[:function, "OFFSET", reference, row_offset, column_offset, height, width]} not replaced"
|
35
|
-
[:function, "OFFSET", reference, row_offset, column_offset, height, width]
|
36
|
-
end
|
37
|
-
else
|
38
|
-
puts "#{[:function, "OFFSET", reference, row_offset, column_offset, height, width]} not replaced"
|
39
|
-
[:function, "OFFSET", reference, row_offset, column_offset, height, width]
|
23
|
+
def function(ast)
|
24
|
+
name = ast[1]
|
25
|
+
args = ast[2..-1]
|
26
|
+
return unless ast[1] == :OFFSET
|
27
|
+
reference = ast[2]
|
28
|
+
row_offset = ast[3]
|
29
|
+
column_offset = ast[4]
|
30
|
+
height = ast[5] || [:number, 1]
|
31
|
+
width = ast[6] || [:number, 1]
|
32
|
+
return unless [row_offset, column_offset, height, width].all? { |a| a.first == :number }
|
33
|
+
if reference.first == :cell
|
34
|
+
ast.replace(offset_cell(reference, row_offset, column_offset, height, width))
|
35
|
+
elsif reference.first == :sheet_reference && reference[2].first == :cell
|
36
|
+
ast.replace([:sheet_reference, reference[1], offset_cell(reference[2], row_offset, column_offset, height, width)])
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
@@ -48,8 +45,10 @@ class ReplaceOffsetsWithReferencesAst
|
|
48
45
|
height = height[1].to_i
|
49
46
|
width = width[1].to_i
|
50
47
|
|
51
|
-
@
|
52
|
-
|
48
|
+
@count_replaced += 1
|
49
|
+
@replacement_made = true
|
50
|
+
|
51
|
+
reference = Reference.for(reference).unfix
|
53
52
|
start_reference = reference.offset(row_offset.to_i, column_offset.to_i)
|
54
53
|
end_reference = reference.offset(row_offset.to_i + height.to_i - 1, column_offset.to_i + width.to_i - 1)
|
55
54
|
if start_reference == end_reference
|
@@ -68,19 +67,19 @@ class ReplaceOffsetsWithReferences
|
|
68
67
|
self.new.replace(*args)
|
69
68
|
end
|
70
69
|
|
71
|
-
attr_accessor :
|
70
|
+
attr_accessor :count_replaced
|
72
71
|
|
73
72
|
def replace(input,output)
|
74
73
|
rewriter = ReplaceOffsetsWithReferencesAst.new
|
75
74
|
input.each_line do |line|
|
76
75
|
# Looks to match lines with references
|
77
|
-
if line =~
|
76
|
+
if line =~ /:OFFSET/
|
78
77
|
ref, ast = line.split("\t")
|
79
78
|
output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
|
80
79
|
else
|
81
80
|
output.puts line
|
82
81
|
end
|
83
82
|
end
|
84
|
-
@
|
83
|
+
@count_replaced = rewriter.count_replaced
|
85
84
|
end
|
86
85
|
end
|
@@ -1,40 +1,68 @@
|
|
1
1
|
require_relative '../excel'
|
2
2
|
|
3
3
|
class ReplaceRangesWithArrayLiteralsAst
|
4
|
+
def initialize
|
5
|
+
@cache = {}
|
6
|
+
end
|
7
|
+
|
4
8
|
def map(ast)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
r = do_map(ast)
|
10
|
+
ast.replace(r) unless r.object_id == ast.object_id
|
11
|
+
ast
|
12
|
+
end
|
13
|
+
|
14
|
+
def do_map(ast)
|
15
|
+
return ast unless ast.is_a?(Array)
|
16
|
+
case ast[0]
|
17
|
+
when :sheet_reference; return sheet_reference(ast)
|
18
|
+
when :area; return area(ast)
|
12
19
|
else
|
13
|
-
|
20
|
+
ast.each.with_index do |a,i|
|
21
|
+
next unless a.is_a?(Array)
|
22
|
+
case a[0]
|
23
|
+
when :sheet_reference; ast[i] = sheet_reference(a)
|
24
|
+
when :area; ast[i] = area(a)
|
25
|
+
else do_map(a)
|
26
|
+
end
|
27
|
+
end
|
14
28
|
end
|
29
|
+
ast
|
15
30
|
end
|
16
31
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# Don't convert single cell ranges
|
23
|
-
return a[1][1] if a.size == 2 && a[1].size == 2
|
24
|
-
a
|
32
|
+
# Of the form [:sheet_reference, sheet, reference]
|
33
|
+
def sheet_reference(ast)
|
34
|
+
@cache[ast] || calculate_expansion_for(ast)
|
35
|
+
end
|
25
36
|
|
37
|
+
def calculate_expansion_for(ast)
|
38
|
+
sheet = ast[1]
|
39
|
+
reference = ast[2]
|
40
|
+
return ast unless reference.first == :area
|
41
|
+
area = Area.for("#{reference[1]}:#{reference[2]}")
|
42
|
+
a = area.to_array_literal(sheet)
|
43
|
+
|
44
|
+
# Don't convert single cell ranges
|
45
|
+
result = if a.size == 2 && a[1].size == 2
|
46
|
+
a[1][1]
|
26
47
|
else
|
27
|
-
|
48
|
+
a
|
28
49
|
end
|
50
|
+
@cache[ast.dup] = result
|
29
51
|
end
|
30
52
|
|
31
|
-
|
53
|
+
# Of the form [:area, start, finish]
|
54
|
+
def area(ast)
|
55
|
+
start = ast[1]
|
56
|
+
finish = ast[2]
|
32
57
|
area = Area.for("#{start}:#{finish}")
|
33
58
|
a = area.to_array_literal
|
34
59
|
|
35
60
|
# Don't convert single cell ranges
|
36
|
-
|
37
|
-
|
61
|
+
if a.size == 2 && a[1].size == 2
|
62
|
+
a[1][1]
|
63
|
+
else
|
64
|
+
a
|
65
|
+
end
|
38
66
|
end
|
39
67
|
|
40
68
|
end
|
@@ -3,26 +3,28 @@ class ReplaceSharedStringAst
|
|
3
3
|
attr_accessor :shared_strings
|
4
4
|
|
5
5
|
def initialize(shared_strings)
|
6
|
-
@shared_strings = shared_strings
|
7
|
-
s[/^(.*?)\n$/,1]
|
8
|
-
end
|
6
|
+
@shared_strings = shared_strings
|
9
7
|
end
|
10
8
|
|
9
|
+
# This is convoluted so as to always
|
10
|
+
# return the same shared string
|
11
11
|
def map(ast)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
return ast unless ast.is_a?(Array)
|
13
|
+
return shared_string(ast) if ast[0] == :shared_string
|
14
|
+
ast.each.with_index do |a, i|
|
15
|
+
next unless a.is_a?(Array)
|
16
|
+
if a[0] == :shared_string
|
17
|
+
ast[i] = shared_string(a)
|
16
18
|
else
|
17
|
-
|
19
|
+
map(a)
|
18
20
|
end
|
19
|
-
else
|
20
|
-
return ast
|
21
21
|
end
|
22
|
+
ast
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
-
|
25
|
+
# Format [:shared_string, string_id]
|
26
|
+
def shared_string(ast)
|
27
|
+
ast.replace(shared_strings[ast[1].to_i])
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
@@ -35,7 +37,7 @@ class ReplaceSharedStrings
|
|
35
37
|
|
36
38
|
# Rewrites ast with shared strings to strings
|
37
39
|
def replace(values,shared_strings,output)
|
38
|
-
rewriter = ReplaceSharedStringAst.new(shared_strings
|
40
|
+
rewriter = ReplaceSharedStringAst.new(shared_strings)
|
39
41
|
values.each_line do |line|
|
40
42
|
# Looks to match shared string lines
|
41
43
|
if line =~ /\[:shared_string/
|
@@ -2,17 +2,15 @@ class ReplaceStringJoinOnRangesAST
|
|
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
|
+
string_join(ast) if ast[0] == :string_join
|
6
|
+
ast.each { |a| map(a) }
|
7
|
+
ast
|
11
8
|
end
|
12
9
|
|
13
|
-
def string_join(
|
10
|
+
def string_join(ast)
|
11
|
+
strings = ast[1..-1]
|
14
12
|
# Make sure there is actually a conversion to do
|
15
|
-
return
|
13
|
+
return unless strings.any? { |s| s.first == :array }
|
16
14
|
# Now work out the largest dimensions
|
17
15
|
# Arrays look like this [:array, [:row, 1, 2, 3], [:row, 4, 5, 6]]
|
18
16
|
max_rows = 0
|
@@ -37,7 +35,7 @@ class ReplaceStringJoinOnRangesAST
|
|
37
35
|
end
|
38
36
|
result << row
|
39
37
|
end
|
40
|
-
result
|
38
|
+
ast.replace(result)
|
41
39
|
end
|
42
40
|
|
43
41
|
def select_from(maybe_array, row_index, column_index)
|
@@ -8,25 +8,30 @@ class ReplaceTableReferenceAst
|
|
8
8
|
|
9
9
|
def map(ast)
|
10
10
|
return ast unless ast.is_a?(Array)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
else
|
15
|
-
[operator,*ast[1..-1].map {|a| map(a) }]
|
11
|
+
case ast[0]
|
12
|
+
when :table_reference; table_reference(ast)
|
13
|
+
when :local_table_reference; local_table_reference(ast)
|
16
14
|
end
|
15
|
+
ast.each { |a| map(a) }
|
16
|
+
ast
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
# Of the format [:table_reference, table_name, table_reference]
|
20
|
+
def table_reference(ast)
|
21
|
+
table_name = ast[1]
|
22
|
+
table_reference = ast[2]
|
23
|
+
return ast.replace([:error, :"#REF!"]) unless tables.has_key?(table_name.downcase)
|
24
|
+
ast.replace(tables[table_name.downcase].reference_for(table_name,table_reference,worksheet,referring_cell))
|
22
25
|
end
|
23
26
|
|
24
|
-
|
27
|
+
# Of the format [:local_table_reference, table_reference]
|
28
|
+
def local_table_reference(ast)
|
29
|
+
table_reference = ast[1]
|
25
30
|
table = tables.values.find do |table|
|
26
31
|
table.includes?(worksheet,referring_cell)
|
27
32
|
end
|
28
|
-
return [:error,"#REF!"] unless table
|
29
|
-
table.reference_for(table.name,table_reference,worksheet,referring_cell)
|
33
|
+
return ast.replace([:error, :"#REF!"]) unless table
|
34
|
+
ast.replace(table.reference_for(table.name,table_reference,worksheet,referring_cell))
|
30
35
|
end
|
31
36
|
|
32
37
|
end
|
@@ -6,17 +6,19 @@ class MapValuesToConstants
|
|
6
6
|
count = 0
|
7
7
|
@constants = Hash.new do |hash,key|
|
8
8
|
count += 1
|
9
|
-
hash[key] = "
|
9
|
+
hash[key] = "constant#{count}"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
POTENTIAL_CONSTANTS = { :number => true, :percentage => true, :string => true}
|
14
|
+
|
13
15
|
def map(ast)
|
14
16
|
return ast unless ast.is_a?(Array)
|
15
17
|
operator = ast[0]
|
16
|
-
if
|
17
|
-
[:constant, constants[ast]]
|
18
|
+
if POTENTIAL_CONSTANTS.has_key?(operator)
|
19
|
+
ast.replace([:constant, constants[ast.dup]])
|
18
20
|
else
|
19
|
-
|
21
|
+
ast.each { |a| map(a) }
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
@@ -2,31 +2,45 @@ class SimplifyArithmeticAst
|
|
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) }]
|
5
|
+
case ast[0]
|
6
|
+
when :brackets; brackets(ast)
|
7
|
+
when :arithmetic; arithmetic(ast)
|
10
8
|
end
|
9
|
+
ast.each { |a| map(a) }
|
10
|
+
ast
|
11
11
|
end
|
12
12
|
|
13
|
-
def brackets(
|
14
|
-
raise NotSupportedException.new("Multiple arguments not supported in brackets #{args.inspect}") if
|
15
|
-
map(
|
13
|
+
def brackets(ast)
|
14
|
+
raise NotSupportedException.new("Multiple arguments not supported in brackets #{args.inspect}") if ast.size > 2
|
15
|
+
ast.replace(map(ast[1]))
|
16
16
|
end
|
17
17
|
|
18
|
-
def arithmetic(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
def arithmetic(ast)
|
19
|
+
case ast.size
|
20
|
+
when 2; return map(ast[1]) # Not really arithmetic
|
21
|
+
when 4; # Normal arithmetic that doesn't need re-arranging
|
22
|
+
ast.each { |a| map(a) }
|
23
|
+
else
|
24
|
+
# This sets the operator precedence
|
25
|
+
i = nil
|
26
|
+
[{:'^' => 1},{:'*' => 2,:'/' => 2},{:'+' => 3,:'-' => 3}].each do |op|
|
27
|
+
i = ast.find_index { |a| a[0] == :operator && op.has_key?(a[1])}
|
28
|
+
break if i
|
29
|
+
end
|
30
|
+
if i
|
31
|
+
# Now we need to wrap that operation in its own arithmetic clause
|
32
|
+
old_clause = ast[(i-1)..(i+1)]
|
33
|
+
# Make sure we do any mapping
|
34
|
+
old_clause.each { |a| map(a) }
|
35
|
+
# Now create a new clause
|
36
|
+
new_clause = [:arithmetic, *old_clause]
|
37
|
+
# And insert it back into the ast
|
38
|
+
ast[(i-1)..(i+1)] = [new_clause]
|
39
|
+
# Redo the mapping
|
40
|
+
map(ast)
|
41
|
+
end
|
28
42
|
end
|
29
|
-
|
43
|
+
ast.each { |a| map(a) }
|
30
44
|
end
|
31
45
|
|
32
46
|
end
|
@@ -9,21 +9,20 @@ class SortIntoCalculationOrder
|
|
9
9
|
def sort(references)
|
10
10
|
@current_sheet = []
|
11
11
|
@ordered_references = []
|
12
|
+
@counted = {}
|
12
13
|
@references = references
|
13
14
|
|
14
15
|
# First we find the references that are at the top of the tree
|
15
16
|
references_with_counts = CountFormulaReferences.new.count(references)
|
16
17
|
tops = []
|
17
|
-
references_with_counts.each do |
|
18
|
-
|
19
|
-
|
20
|
-
tops << [sheet, cell]
|
21
|
-
end
|
18
|
+
references_with_counts.each do |reference, count|
|
19
|
+
next unless count == 0
|
20
|
+
tops << reference
|
22
21
|
end
|
23
22
|
# Then we have to work through those tops
|
24
23
|
# recursively adding the cells that they depend on
|
25
|
-
tops.each do |
|
26
|
-
add_ordered_references_for
|
24
|
+
tops.each do |reference|
|
25
|
+
add_ordered_references_for reference
|
27
26
|
end
|
28
27
|
@ordered_references
|
29
28
|
end
|
@@ -32,10 +31,11 @@ class SortIntoCalculationOrder
|
|
32
31
|
sheet = ref.first
|
33
32
|
cell = ref.last
|
34
33
|
current_sheet.push(sheet)
|
35
|
-
ast = @references[
|
34
|
+
ast = @references[ref]
|
36
35
|
map(ast)
|
37
36
|
current_sheet.pop
|
38
|
-
ordered_references << ref
|
37
|
+
@ordered_references << ref
|
38
|
+
@counted[ref] = true
|
39
39
|
end
|
40
40
|
|
41
41
|
def map(ast)
|
@@ -51,14 +51,14 @@ class SortIntoCalculationOrder
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def sheet_reference(sheet,reference)
|
54
|
-
ref = [sheet, reference.last.gsub('$','')]
|
55
|
-
return if @
|
54
|
+
ref = [sheet, reference.last.to_s.gsub('$','').to_sym]
|
55
|
+
return if @counted.has_key?(ref)
|
56
56
|
add_ordered_references_for(ref)
|
57
57
|
end
|
58
58
|
|
59
59
|
def cell(reference)
|
60
|
-
ref = [current_sheet.last, reference.gsub('$','')]
|
61
|
-
return if @
|
60
|
+
ref = [current_sheet.last, reference.to_s.gsub('$','').to_sym]
|
61
|
+
return if @counted.has_key?(ref)
|
62
62
|
add_ordered_references_for(ref)
|
63
63
|
end
|
64
64
|
|