excel_to_code 0.2.15 → 0.2.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/excel_to_c +4 -0
- data/src/commands/excel_to_c.rb +2 -2
- data/src/commands/excel_to_x.rb +25 -6
- data/src/compile/c/a.out +0 -0
- data/src/compile/c/compile_to_c.rb +6 -0
- data/src/compile/c/excel_to_c_runtime.c +131 -8
- data/src/compile/c/excel_to_c_runtime_test.c +475 -434
- data/src/compile/c/map_formulae_to_c.rb +4 -0
- data/src/compile/c/map_values_to_c.rb +4 -0
- data/src/compile/ruby/map_formulae_to_ruby.rb +7 -1
- data/src/compile/ruby/map_values_to_ruby.rb +4 -0
- data/src/excel/area.rb +6 -1
- data/src/excel/excel_functions.rb +2 -0
- data/src/excel/excel_functions/ensure_is_number.rb +7 -0
- data/src/excel/excel_functions/excel_if.rb +1 -0
- data/src/excel/excel_functions/left.rb +1 -0
- data/src/excel/excel_functions/right.rb +2 -1
- data/src/excel/formula_peg.rb +2 -2
- data/src/excel/formula_peg.txt +2 -2
- data/src/excel/reference.rb +1 -0
- data/src/excel_to_code.rb +1 -1
- data/src/extract/extract_data_from_worksheet.rb +4 -0
- data/src/simplify/inline_formulae.rb +3 -1
- data/src/simplify/map_formulae_to_values.rb +52 -17
- data/src/simplify/replace_arithmetic_on_ranges.rb +51 -2
- data/src/simplify/replace_arrays_with_single_cells.rb +60 -4
- data/src/simplify/replace_ranges_with_array_literals.rb +2 -2
- data/src/simplify/simplify_arithmetic.rb +3 -8
- metadata +3 -2
@@ -36,6 +36,7 @@ class MapFormulaeToC < MapValuesToC
|
|
36
36
|
:'COSH' => 'cosh',
|
37
37
|
:'COUNT' => 'count',
|
38
38
|
:'COUNTA' => 'counta',
|
39
|
+
:'ENSURE_IS_NUMBER' => 'ensure_is_number',
|
39
40
|
:'EXP' => 'excel_exp',
|
40
41
|
:'FIND2' => 'find_2',
|
41
42
|
:'FIND3' => 'find',
|
@@ -53,6 +54,7 @@ class MapFormulaeToC < MapValuesToC
|
|
53
54
|
:'LARGE' => 'large',
|
54
55
|
:'LEFT1' => 'left_1',
|
55
56
|
:'LEFT2' => 'left',
|
57
|
+
:'LEN' => 'len',
|
56
58
|
:'LOG1' => 'excel_log',
|
57
59
|
:'LOG2' => 'excel_log_2',
|
58
60
|
:'MATCH2' => 'excel_match_2',
|
@@ -67,6 +69,8 @@ class MapFormulaeToC < MapValuesToC
|
|
67
69
|
:'PV5' => 'pv_5',
|
68
70
|
:'RANK2' => 'rank_2',
|
69
71
|
:'RANK3' => 'rank',
|
72
|
+
:'RIGHT1' => 'right_1',
|
73
|
+
:'RIGHT2' => 'right',
|
70
74
|
:'ROUND' => 'excel_round',
|
71
75
|
:'ROUNDDOWN' => 'rounddown',
|
72
76
|
:'ROUNDUP' => 'roundup',
|
@@ -26,6 +26,7 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
26
26
|
:'COSH' => 'cosh',
|
27
27
|
:'COUNT' => 'count',
|
28
28
|
:'COUNTA' => 'counta',
|
29
|
+
:'ENSURE_IS_NUMBER' => 'ensure_is_number',
|
29
30
|
:'EXP' => 'exp',
|
30
31
|
:'FIND' => 'find',
|
31
32
|
:'FORECAST' => 'forecast',
|
@@ -82,7 +83,12 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
82
83
|
end
|
83
84
|
|
84
85
|
def string_join(*strings)
|
85
|
-
|
86
|
+
strings = strings.map do |s|
|
87
|
+
s = [:string, ""] if s == [:inlined_blank]
|
88
|
+
s = map(s)
|
89
|
+
end
|
90
|
+
|
91
|
+
"string_join(#{strings.join(',')})"
|
86
92
|
end
|
87
93
|
|
88
94
|
def comparison(left,operator,right)
|
data/src/excel/area.rb
CHANGED
@@ -14,9 +14,13 @@ class Area < String
|
|
14
14
|
end
|
15
15
|
|
16
16
|
attr_reader :excel_start, :excel_finish
|
17
|
+
|
18
|
+
def unfix
|
19
|
+
calculate_excel_variables
|
20
|
+
Area.for("#{@excel_start.unfix}:#{@excel_finish.unfix}").calculate_excel_variables
|
21
|
+
end
|
17
22
|
|
18
23
|
def calculate_excel_variables
|
19
|
-
return if @excel_variables_calculated
|
20
24
|
if self =~ /([^:]+):(.*)/
|
21
25
|
@excel_start = Reference.for($1)
|
22
26
|
@excel_finish = Reference.for($2)
|
@@ -25,6 +29,7 @@ class Area < String
|
|
25
29
|
end
|
26
30
|
@excel_start.calculate_excel_variables
|
27
31
|
@excel_finish.calculate_excel_variables
|
32
|
+
self
|
28
33
|
end
|
29
34
|
|
30
35
|
def offset(row,column)
|
@@ -4,6 +4,7 @@ module ExcelFunctions
|
|
4
4
|
return string if string.is_a?(Symbol)
|
5
5
|
return characters if characters.is_a?(Symbol)
|
6
6
|
return nil if string == nil || characters == nil
|
7
|
+
return :value if characters < 0
|
7
8
|
string = "TRUE" if string == true
|
8
9
|
string = "FALSE" if string == false
|
9
10
|
string.to_s.slice(0,characters)
|
@@ -4,10 +4,11 @@ module ExcelFunctions
|
|
4
4
|
return string if string.is_a?(Symbol)
|
5
5
|
return characters if characters.is_a?(Symbol)
|
6
6
|
return nil if string == nil || characters == nil
|
7
|
+
return :value if characters < 0
|
7
8
|
string = "TRUE" if string == true
|
8
9
|
string = "FALSE" if string == false
|
9
10
|
string = string.to_s
|
10
|
-
string.slice(string.length-characters,characters)
|
11
|
+
string.slice(string.length-characters,characters) || ""
|
11
12
|
end
|
12
13
|
|
13
14
|
end
|
data/src/excel/formula_peg.rb
CHANGED
@@ -67,7 +67,7 @@ class Formula < RubyPeg
|
|
67
67
|
|
68
68
|
def comparison
|
69
69
|
node :comparison do
|
70
|
-
(arithmetic || thing) && space && comparator && space && (arithmetic || thing)
|
70
|
+
(arithmetic || string_join || thing) && space && comparator && space && (arithmetic || string_join || thing)
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -111,7 +111,7 @@ class Formula < RubyPeg
|
|
111
111
|
|
112
112
|
def external_reference
|
113
113
|
node :external_reference do
|
114
|
-
terminal(/\[\d+\]!?/) && any_internal_reference
|
114
|
+
terminal(/\[\d+\]!?/) && (any_internal_reference || named_reference)
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
data/src/excel/formula_peg.txt
CHANGED
@@ -9,7 +9,7 @@ row := basic_type ( space `',' space basic_type )*
|
|
9
9
|
basic_type = string | percentage | number | boolean
|
10
10
|
string_join := (arithmetic | thing) (space `"&" space (arithmetic | thing))+
|
11
11
|
arithmetic := thing (space operator space thing)+
|
12
|
-
comparison := (arithmetic | thing) space comparator space (arithmetic | thing)
|
12
|
+
comparison := (arithmetic | string_join | thing) space comparator space (arithmetic | string_join | thing)
|
13
13
|
comparator := '>=' | '<=' | '<>' | '>' | '<' | '='
|
14
14
|
string := `'"' /(""|[^"])*/ `'"'
|
15
15
|
any_reference = external_reference | any_internal_reference
|
@@ -17,7 +17,7 @@ any_internal_reference = table_reference | local_table_reference | she
|
|
17
17
|
percentage := /[-+]?[0-9]+\.?[0-9]*/ `'%'
|
18
18
|
number := /[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/
|
19
19
|
operator := '+' | '-' | '/' | '*' | '^'
|
20
|
-
external_reference := /\[\d+\]!?/ any_internal_reference
|
20
|
+
external_reference := /\[\d+\]!?/ (any_internal_reference | named_reference)
|
21
21
|
table_reference := table_name `'[' (range_structured_reference | short_range_structured_reference | complex_structured_reference | overly_structured_reference | simple_structured_reference) `']'
|
22
22
|
local_table_reference := `'[' (range_structured_reference | short_range_structured_reference | complex_structured_reference | overly_structured_reference | simple_structured_reference) `']'
|
23
23
|
table_name = /[.\p{Word}_]+/
|
data/src/excel/reference.rb
CHANGED
data/src/excel_to_code.rb
CHANGED
@@ -2,6 +2,8 @@ require 'ox'
|
|
2
2
|
|
3
3
|
class ExtractDataFromWorksheet < ::Ox::Sax
|
4
4
|
|
5
|
+
attr_accessor :only_extract_values
|
6
|
+
|
5
7
|
attr_accessor :table_rids
|
6
8
|
attr_accessor :worksheets_dimensions
|
7
9
|
attr_accessor :values
|
@@ -75,6 +77,8 @@ class ExtractDataFromWorksheet < ::Ox::Sax
|
|
75
77
|
@values[key] = @fp.map(ast)
|
76
78
|
when :f
|
77
79
|
@current_element.pop
|
80
|
+
return if only_extract_values
|
81
|
+
|
78
82
|
unless @formula.empty?
|
79
83
|
formula_text = @formula.join.gsub(/[\r\n]+/,'')
|
80
84
|
ast = @fp.parse(formula_text)
|
@@ -28,7 +28,7 @@ class MapFormulaeToValues
|
|
28
28
|
# FIXME: Remove references to this method
|
29
29
|
end
|
30
30
|
|
31
|
-
DO_NOT_MAP = {:number => true, :string => true, :blank => true, :null => true, :error => true, :boolean_true => true, :boolean_false => true, :sheet_reference => true, :cell => true}
|
31
|
+
DO_NOT_MAP = {:number => true, :string => true, :blank => true, :inlined_blank => true, :null => true, :error => true, :boolean_true => true, :boolean_false => true, :sheet_reference => true, :cell => true}
|
32
32
|
|
33
33
|
def map(ast)
|
34
34
|
ast[1..-1].each do |a|
|
@@ -62,14 +62,14 @@ class MapFormulaeToValues
|
|
62
62
|
elsif l == 0
|
63
63
|
case operator.last
|
64
64
|
when :+
|
65
|
-
ast.replace(right)
|
65
|
+
ast.replace(n(right))
|
66
66
|
when :*, :/, :^
|
67
67
|
ast.replace([:number, 0])
|
68
68
|
end
|
69
69
|
elsif r == 0
|
70
70
|
case operator.last
|
71
71
|
when :+, :-
|
72
|
-
ast.replace(left)
|
72
|
+
ast.replace(n(left))
|
73
73
|
when :*
|
74
74
|
ast.replace([:number, 0])
|
75
75
|
when :/
|
@@ -80,19 +80,24 @@ class MapFormulaeToValues
|
|
80
80
|
elsif l == 1
|
81
81
|
case operator.last
|
82
82
|
when :*
|
83
|
-
ast.replace(right)
|
83
|
+
ast.replace(n(right))
|
84
84
|
when :^
|
85
85
|
ast.replace([:number, 1])
|
86
86
|
end
|
87
87
|
elsif r == 1
|
88
88
|
case operator.last
|
89
89
|
when :*, :/, :^
|
90
|
-
ast.replace(left)
|
90
|
+
ast.replace(n(left))
|
91
91
|
end
|
92
92
|
end
|
93
93
|
ast
|
94
94
|
end
|
95
95
|
|
96
|
+
def n(ast)
|
97
|
+
return ast if ast[0] == :function && ast[1] == :ENSURE_IS_NUMBER
|
98
|
+
[:function, :ENSURE_IS_NUMBER, ast]
|
99
|
+
end
|
100
|
+
|
96
101
|
def comparison(ast)
|
97
102
|
left, operator, right = ast[1], ast[2], ast[3]
|
98
103
|
l = value(left)
|
@@ -108,7 +113,9 @@ class MapFormulaeToValues
|
|
108
113
|
|
109
114
|
# [:string_join, stringA, stringB, ...]
|
110
115
|
def string_join(ast)
|
111
|
-
values = ast[1..-1].map
|
116
|
+
values = ast[1..-1].map do |a|
|
117
|
+
value(a, "")
|
118
|
+
end
|
112
119
|
return if values.any? { |a| a == :not_a_value }
|
113
120
|
ast.replace(ast_for_value(@calculator.string_join(*values)))
|
114
121
|
end
|
@@ -123,18 +130,45 @@ class MapFormulaeToValues
|
|
123
130
|
if respond_to?("map_#{name.to_s.downcase}")
|
124
131
|
send("map_#{name.to_s.downcase}",ast)
|
125
132
|
else
|
126
|
-
|
127
|
-
return if values.any? { |a| a == :not_a_value }
|
128
|
-
ast.replace(formula_value(name,*values))
|
133
|
+
normal_function(ast)
|
129
134
|
end
|
130
135
|
end
|
131
136
|
|
137
|
+
def normal_function(ast, inlined_blank = 0)
|
138
|
+
values = ast[2..-1].map { |a| value(a, inlined_blank) }
|
139
|
+
return if values.any? { |a| a == :not_a_value }
|
140
|
+
ast.replace(formula_value( ast[1],*values))
|
141
|
+
end
|
142
|
+
|
143
|
+
def map_right(ast)
|
144
|
+
normal_function(ast, "")
|
145
|
+
end
|
146
|
+
|
147
|
+
def map_left(ast)
|
148
|
+
normal_function(ast, "")
|
149
|
+
end
|
150
|
+
|
151
|
+
def map_mid(ast)
|
152
|
+
normal_function(ast, "")
|
153
|
+
end
|
154
|
+
|
155
|
+
def map_len(ast)
|
156
|
+
normal_function(ast, "")
|
157
|
+
end
|
158
|
+
|
159
|
+
def map_sumifs(ast)
|
160
|
+
sum_value = value(ast[2])
|
161
|
+
values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
|
162
|
+
return if sum_value == :not_a_value
|
163
|
+
return if values.any? { |a| a == :not_a_value }
|
164
|
+
ast.replace(formula_value( ast[1], sum_value, *values))
|
165
|
+
end
|
166
|
+
|
132
167
|
# [:function, "COUNT", range]
|
133
168
|
def map_count(ast)
|
134
|
-
|
135
|
-
return
|
136
|
-
|
137
|
-
ast.replace(ast_for_value(range.size * range.first.size))
|
169
|
+
values = ast[2..-1].map { |a| value(a, nil) }
|
170
|
+
return if values.any? { |a| a == :not_a_value }
|
171
|
+
ast.replace(formula_value( ast[1],*values))
|
138
172
|
end
|
139
173
|
|
140
174
|
# [:function, "INDEX", array, row_number, column_number]
|
@@ -249,10 +283,11 @@ class MapFormulaeToValues
|
|
249
283
|
:"#NUM!" => :num
|
250
284
|
}
|
251
285
|
|
252
|
-
def value(ast)
|
253
|
-
return extract_values_from_array(ast) if ast.first == :array
|
286
|
+
def value(ast, inlined_blank = 0)
|
287
|
+
return extract_values_from_array(ast, inlined_blank) if ast.first == :array
|
254
288
|
case ast.first
|
255
289
|
when :blank; nil
|
290
|
+
when :inlined_blank; inlined_blank
|
256
291
|
when :null; nil
|
257
292
|
when :number; ast[1]
|
258
293
|
when :percentage; ast[1]/100.0
|
@@ -264,10 +299,10 @@ class MapFormulaeToValues
|
|
264
299
|
end
|
265
300
|
end
|
266
301
|
|
267
|
-
def extract_values_from_array(ast)
|
302
|
+
def extract_values_from_array(ast, inlined_blank = 0)
|
268
303
|
ast[1..-1].map do |row|
|
269
304
|
row[1..-1].map do |cell|
|
270
|
-
v = value(cell)
|
305
|
+
v = value(cell, inlined_blank)
|
271
306
|
return :not_a_value if v == :not_a_value
|
272
307
|
v
|
273
308
|
end
|
@@ -2,10 +2,59 @@ class ReplaceArithmeticOnRangesAst
|
|
2
2
|
|
3
3
|
def map(ast)
|
4
4
|
return ast unless ast.is_a?(Array)
|
5
|
-
arithmetic(ast) if ast.first == :arithmetic
|
6
5
|
ast.each { |a| map(a) }
|
6
|
+
arithmetic(ast) if ast.first == :arithmetic
|
7
|
+
comparison(ast) if ast.first == :comparison
|
7
8
|
ast
|
8
9
|
end
|
10
|
+
|
11
|
+
# FIXME: DRY THIS UP
|
12
|
+
def comparison(ast)
|
13
|
+
left, operator, right = ast[1], ast[2], ast[3]
|
14
|
+
# Three different options, array on the left, array on the right, or both
|
15
|
+
# array on the left first
|
16
|
+
if left.first == :array && right.first != :array
|
17
|
+
map(right)
|
18
|
+
ast.replace(
|
19
|
+
array_map(left) do |cell|
|
20
|
+
[:comparison, map(cell), operator, right]
|
21
|
+
end
|
22
|
+
)
|
23
|
+
|
24
|
+
# array on the right next
|
25
|
+
elsif left.first != :array && right.first == :array
|
26
|
+
map(left)
|
27
|
+
ast.replace(
|
28
|
+
array_map(right) do |cell|
|
29
|
+
[:comparison, left, operator, map(cell)]
|
30
|
+
end
|
31
|
+
)
|
32
|
+
|
33
|
+
# now array both sides
|
34
|
+
elsif left.first == :array && right.first == :array
|
35
|
+
ast.replace(
|
36
|
+
left.map.with_index do |row, i|
|
37
|
+
if row == :array
|
38
|
+
row
|
39
|
+
else
|
40
|
+
row.map.with_index do |cell, j|
|
41
|
+
if cell == :row
|
42
|
+
cell
|
43
|
+
elsif i >= left.length || i >= right.length || j >= left[1].length || j >= right[1].length
|
44
|
+
[:error, "#VALUE!"]
|
45
|
+
else
|
46
|
+
[:comparison, map(left[i][j]), operator, map(right[i][j])]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
)
|
52
|
+
else
|
53
|
+
map(left)
|
54
|
+
map(right)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
9
58
|
|
10
59
|
# Format [:artithmetic, left, operator, right]
|
11
60
|
# should have removed arithmetic with more than one operator
|
@@ -41,7 +90,7 @@ class ReplaceArithmeticOnRangesAst
|
|
41
90
|
row.map.with_index do |cell, j|
|
42
91
|
if cell == :row
|
43
92
|
cell
|
44
|
-
elsif i >= left.length || i >= right.length || j >= left.
|
93
|
+
elsif i >= left.length || i >= right.length || j >= left[1].length || j >= right[1].length
|
45
94
|
[:error, "#VALUE!"]
|
46
95
|
else
|
47
96
|
[:arithmetic, map(left[i][j]), operator, map(right[i][j])]
|
@@ -12,7 +12,12 @@ class ReplaceArraysWithSingleCellsAst
|
|
12
12
|
return unless ast.is_a?(Array)
|
13
13
|
if ast.first == :array
|
14
14
|
@need_to_replace = true
|
15
|
-
|
15
|
+
new_ast = try_and_convert_array(ast)
|
16
|
+
return ERROR if new_ast.first == :array
|
17
|
+
return new_ast
|
18
|
+
# Special case, only change if at the top level
|
19
|
+
elsif ast[0] == :function && ast[1] == :CHOOSE && check_choose(ast)
|
20
|
+
# Replacement made in check
|
16
21
|
else
|
17
22
|
do_map(ast)
|
18
23
|
ast
|
@@ -39,11 +44,62 @@ class ReplaceArraysWithSingleCellsAst
|
|
39
44
|
else
|
40
45
|
ast[1..-1].each { |a| do_map(a) }
|
41
46
|
end
|
47
|
+
when :function
|
48
|
+
if ast[1] == :SUMIF && ast[3].first == :array
|
49
|
+
ast[3] = try_and_convert_array(ast[3])
|
50
|
+
elsif ast[1] == :SUMIFS && check_sumifs(ast)
|
51
|
+
# Replacement madein check_sumif function
|
52
|
+
elsif ast[1] == :MATCH && check_match(ast)
|
53
|
+
# Replacement made in check_match function
|
54
|
+
elsif ast[1] == :INDIRECT && check_indirect(ast)
|
55
|
+
# Replacement made in check function
|
56
|
+
else
|
57
|
+
ast[2..-1].each { |a| do_map(a) }
|
58
|
+
end
|
42
59
|
else
|
43
60
|
ast[1..-1].each { |a| do_map(a) }
|
44
61
|
end
|
45
62
|
end
|
46
63
|
|
64
|
+
def check_choose(ast)
|
65
|
+
replacement_made = false
|
66
|
+
i = 2
|
67
|
+
while i < ast.length
|
68
|
+
if ast[i].first == :array
|
69
|
+
replacement_made = true
|
70
|
+
ast[i] = try_and_convert_array(ast[i])
|
71
|
+
end
|
72
|
+
i +=1
|
73
|
+
end
|
74
|
+
replacement_made
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_match(ast)
|
78
|
+
return false unless ast[2].first == :array
|
79
|
+
ast[2] = try_and_convert_array(ast[2])
|
80
|
+
ast[3..-1].each { |a| do_map(a) }
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def check_indirect(ast)
|
85
|
+
return false unless ast[2].first == :array
|
86
|
+
ast[2] = try_and_convert_array(ast[2])
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def check_sumifs(ast)
|
91
|
+
replacement_made = false
|
92
|
+
i = 4
|
93
|
+
while i < ast.length
|
94
|
+
if ast[i].first == :array
|
95
|
+
replacement_made = true
|
96
|
+
ast[i] = try_and_convert_array(ast[i])
|
97
|
+
end
|
98
|
+
i +=2
|
99
|
+
end
|
100
|
+
replacement_made
|
101
|
+
end
|
102
|
+
|
47
103
|
def try_and_convert_array(ast)
|
48
104
|
return ast unless ast.first == :array
|
49
105
|
return ast unless all_references?(ast)
|
@@ -53,7 +109,7 @@ class ReplaceArraysWithSingleCellsAst
|
|
53
109
|
elsif ast[1].length == 2
|
54
110
|
single_column(ast)
|
55
111
|
else
|
56
|
-
|
112
|
+
ast
|
57
113
|
end
|
58
114
|
end
|
59
115
|
|
@@ -78,7 +134,7 @@ class ReplaceArraysWithSingleCellsAst
|
|
78
134
|
sheet == s && column == c
|
79
135
|
end
|
80
136
|
|
81
|
-
match ||
|
137
|
+
match || ast
|
82
138
|
end
|
83
139
|
|
84
140
|
def single_column(ast)
|
@@ -94,6 +150,6 @@ class ReplaceArraysWithSingleCellsAst
|
|
94
150
|
sheet == s && row == r
|
95
151
|
end
|
96
152
|
|
97
|
-
match ||
|
153
|
+
match || ast
|
98
154
|
end
|
99
155
|
end
|