excel_to_code 0.2.15 → 0.2.16
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/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
|