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.
- 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
|
|