excel_to_code 0.3.17 → 0.3.18.beta.1
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 +5 -5
- data/README.md +67 -34
- data/bin/excel_to_c +8 -78
- data/bin/excel_to_go +41 -0
- data/bin/excel_to_ruby +2 -69
- data/src/commands.rb +2 -0
- data/src/commands/common_command_line_options.rb +81 -0
- data/src/commands/excel_to_c.rb +3 -0
- data/src/commands/excel_to_go.rb +91 -0
- data/src/commands/excel_to_x.rb +77 -11
- data/src/compile.rb +1 -0
- data/src/compile/c/a.out +0 -0
- data/src/compile/c/a.out.dSYM/Contents/Resources/DWARF/a.out +0 -0
- data/src/compile/c/compile_to_c.rb +2 -0
- data/src/compile/c/excel_to_c_runtime.c +691 -145
- data/src/compile/c/excel_to_c_runtime_test.c +226 -20
- data/src/compile/c/map_formulae_to_c.rb +62 -23
- data/src/compile/c/run_c_unit_tests +3 -0
- data/src/compile/cd.rb +6 -0
- data/src/compile/go.rb +3 -0
- data/src/compile/go/compile_to_go.rb +85 -0
- data/src/compile/go/compile_to_go_test.rb +73 -0
- data/src/compile/go/excel.go +171 -0
- data/src/compile/go/excel_test.go +54 -0
- data/src/compile/go/map_values_to_go.rb +67 -0
- data/src/compile/ruby/map_formulae_to_ruby.rb +30 -12
- data/src/excel/excel_functions.rb +26 -1
- data/src/excel/excel_functions/ceiling.rb +23 -0
- data/src/excel/excel_functions/countif.rb +15 -0
- data/src/excel/excel_functions/countifs.rb +10 -0
- data/src/excel/excel_functions/floor.rb +14 -0
- data/src/excel/excel_functions/hyperlink.rb +9 -0
- data/src/excel/excel_functions/na.rb +7 -0
- data/src/excel/excel_functions/not.rb +13 -0
- data/src/excel/excel_functions/or.rb +30 -0
- data/src/excel/excel_functions/product.rb +8 -0
- data/src/excel/excel_functions/rate.rb +16 -0
- data/src/excel/excel_functions/replace.rb +13 -0
- data/src/excel/excel_functions/scurve.rb +73 -0
- data/src/excel/excel_functions/sqrt.rb +11 -0
- data/src/excel/excel_functions/string_argument.rb +37 -0
- data/src/excel/excel_functions/sumifs.rb +19 -8
- data/src/excel/excel_functions/text.rb +3 -3
- data/src/excel/formula_peg.rb +1 -1
- data/src/excel/formula_peg.txt +2 -3
- data/src/excel/table.rb +15 -15
- data/src/excel_to_code.rb +1 -4
- data/src/extract/extract_data_from_worksheet.rb +8 -1
- data/src/rewrite/ast_expand_array_formulae.rb +4 -0
- data/src/rewrite/caching_formula_parser.rb +16 -11
- data/src/simplify.rb +1 -0
- data/src/simplify/inline_formulae.rb +16 -0
- data/src/simplify/replace_arithmetic_on_ranges.rb +14 -1
- data/src/simplify/replace_arrays_with_single_cells.rb +42 -15
- data/src/simplify/replace_cell_addresses_with_references.rb +70 -0
- data/src/simplify/replace_column_with_column_number.rb +8 -1
- data/src/simplify/replace_table_references.rb +40 -19
- data/src/simplify/simplify_arithmetic.rb +15 -10
- data/src/version.rb +4 -0
- metadata +115 -43
- data/TODO +0 -25
@@ -1,10 +1,10 @@
|
|
1
1
|
require_relative 'map_values_to_ruby'
|
2
2
|
|
3
3
|
class MapFormulaeToRuby < MapValuesToRuby
|
4
|
-
|
4
|
+
|
5
5
|
attr_accessor :sheet_names
|
6
6
|
attr_accessor :worksheet
|
7
|
-
|
7
|
+
|
8
8
|
FUNCTIONS = {
|
9
9
|
:'*' => 'multiply',
|
10
10
|
:'+' => 'add',
|
@@ -21,6 +21,8 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
21
21
|
:'AND' => 'excel_and',
|
22
22
|
:'AVERAGE' => 'average',
|
23
23
|
:'AVERAGEIFS' => 'averageifs',
|
24
|
+
:'CEILING' => 'ceiling',
|
25
|
+
:'_xlfn.CEILING.MATH' => 'ceiling',
|
24
26
|
:'CELL' => 'cell',
|
25
27
|
:'CHAR' => 'char',
|
26
28
|
:'CHOOSE' => 'choose',
|
@@ -28,11 +30,15 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
28
30
|
:'COSH' => 'cosh',
|
29
31
|
:'COUNT' => 'count',
|
30
32
|
:'COUNTA' => 'counta',
|
33
|
+
:'COUNTIF' => 'countif',
|
34
|
+
:'COUNTIFS' => 'countifs',
|
31
35
|
:'ENSURE_IS_NUMBER' => 'ensure_is_number',
|
32
36
|
:'EXP' => 'exp',
|
33
37
|
:'FIND' => 'find',
|
38
|
+
:'FLOOR' => 'floor',
|
34
39
|
:'FORECAST' => 'forecast',
|
35
40
|
:'HLOOKUP' => 'hlookup',
|
41
|
+
:'HYPERLINK' => 'hyperlink',
|
36
42
|
:'IF' => 'excel_if',
|
37
43
|
:'IFERROR' => 'iferror',
|
38
44
|
:'INDEX' => 'index',
|
@@ -54,17 +60,24 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
54
60
|
:'MIN' => 'min',
|
55
61
|
:'MMULT' => 'mmult',
|
56
62
|
:'MOD' => 'mod',
|
63
|
+
:'NA' => 'na',
|
64
|
+
:'NOT' => 'excel_not',
|
57
65
|
:'NPV' => 'npv',
|
58
66
|
:'NUMBER_OR_ZERO' => 'number_or_zero',
|
67
|
+
:'OR' => 'excel_or',
|
59
68
|
:'PI' => 'pi',
|
60
69
|
:'PMT' => 'pmt',
|
61
70
|
:'POWER' => 'power',
|
71
|
+
:'PRODUCT' => 'product',
|
62
72
|
:'PV' => 'pv',
|
63
73
|
:'RANK' => 'rank',
|
74
|
+
:'RATE' => 'rate',
|
75
|
+
:'REPLACE' => 'replace',
|
64
76
|
:'RIGHT' => 'right',
|
65
77
|
:'ROUND' => 'round',
|
66
78
|
:'ROUNDDOWN' => 'rounddown',
|
67
79
|
:'ROUNDUP' => 'roundup',
|
80
|
+
:'SQRT' => 'sqrt',
|
68
81
|
:'SUBSTITUTE' => 'substitute',
|
69
82
|
:'SUBTOTAL' => 'subtotal',
|
70
83
|
:'SUM' => 'sum',
|
@@ -75,18 +88,23 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
75
88
|
:'TRIM' => 'trim',
|
76
89
|
:'VALUE' => 'value',
|
77
90
|
:'VLOOKUP' => 'vlookup',
|
78
|
-
:'^' => 'power'
|
91
|
+
:'^' => 'power',
|
92
|
+
:'_xlfn.FORECAST.LINEAR' => 'forecast',
|
93
|
+
:'curve' => 'curve',
|
94
|
+
:'halfscurve' => 'halfscurve',
|
95
|
+
:'lcurve' => 'lcurve',
|
96
|
+
:'scurve' => 'scurve'
|
79
97
|
}
|
80
|
-
|
98
|
+
|
81
99
|
def prefix(symbol,ast)
|
82
100
|
return map(ast) if symbol == "+"
|
83
101
|
return "negative(#{map(ast)})"
|
84
102
|
end
|
85
|
-
|
103
|
+
|
86
104
|
def brackets(*contents)
|
87
105
|
"(#{contents.map { |a| map(a) }.join(',')})"
|
88
106
|
end
|
89
|
-
|
107
|
+
|
90
108
|
def arithmetic(left,operator,right)
|
91
109
|
"#{FUNCTIONS[operator.last]}(#{map(left)},#{map(right)})"
|
92
110
|
end
|
@@ -103,7 +121,7 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
103
121
|
def comparison(left,operator,right)
|
104
122
|
"#{FUNCTIONS[operator.last]}(#{map(left)},#{map(right)})"
|
105
123
|
end
|
106
|
-
|
124
|
+
|
107
125
|
def function(function_name,*arguments)
|
108
126
|
if FUNCTIONS.has_key?(function_name)
|
109
127
|
"#{FUNCTIONS[function_name]}(#{arguments.map { |a| map(a) }.join(",")})"
|
@@ -111,21 +129,21 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
111
129
|
raise NotSupportedException.new("Function #{function_name} not supported")
|
112
130
|
end
|
113
131
|
end
|
114
|
-
|
132
|
+
|
115
133
|
def cell(reference)
|
116
134
|
reference.to_s.downcase.gsub('$','')
|
117
135
|
end
|
118
|
-
|
136
|
+
|
119
137
|
def sheet_reference(sheet,reference)
|
120
138
|
"#{sheet_names[sheet]}_#{map(reference)}"
|
121
139
|
end
|
122
|
-
|
140
|
+
|
123
141
|
def array(*rows)
|
124
142
|
"[#{rows.map {|r| map(r)}.join(",")}]"
|
125
143
|
end
|
126
|
-
|
144
|
+
|
127
145
|
def row(*cells)
|
128
146
|
"[#{cells.map {|r| map(r)}.join(",")}]"
|
129
147
|
end
|
130
|
-
|
148
|
+
|
131
149
|
end
|
@@ -3,6 +3,7 @@ end
|
|
3
3
|
|
4
4
|
# Support functions
|
5
5
|
require_relative 'excel_functions/number_argument'
|
6
|
+
require_relative 'excel_functions/string_argument'
|
6
7
|
require_relative 'excel_functions/reset'
|
7
8
|
|
8
9
|
# Constants
|
@@ -31,6 +32,7 @@ require_relative 'excel_functions/negative'
|
|
31
32
|
require_relative 'excel_functions/abs'
|
32
33
|
require_relative 'excel_functions/and'
|
33
34
|
require_relative 'excel_functions/mod'
|
35
|
+
require_relative 'excel_functions/not'
|
34
36
|
|
35
37
|
# Array arithmetic
|
36
38
|
require_relative 'excel_functions/sum'
|
@@ -71,7 +73,6 @@ require_relative 'excel_functions/int'
|
|
71
73
|
|
72
74
|
# Other functions
|
73
75
|
|
74
|
-
|
75
76
|
require_relative 'excel_functions/cell'
|
76
77
|
|
77
78
|
require_relative 'excel_functions/trim'
|
@@ -123,3 +124,27 @@ require_relative 'excel_functions/char'
|
|
123
124
|
require_relative 'excel_functions/address'
|
124
125
|
|
125
126
|
require_relative 'excel_functions/number_or_zero'
|
127
|
+
|
128
|
+
require_relative 'excel_functions/countif'
|
129
|
+
|
130
|
+
require_relative 'excel_functions/hyperlink'
|
131
|
+
|
132
|
+
require_relative 'excel_functions/na'
|
133
|
+
|
134
|
+
require_relative 'excel_functions/or'
|
135
|
+
|
136
|
+
require_relative 'excel_functions/scurve'
|
137
|
+
|
138
|
+
require_relative 'excel_functions/floor'
|
139
|
+
|
140
|
+
require_relative 'excel_functions/sqrt'
|
141
|
+
|
142
|
+
require_relative 'excel_functions/product'
|
143
|
+
|
144
|
+
require_relative 'excel_functions/countifs'
|
145
|
+
|
146
|
+
require_relative 'excel_functions/replace'
|
147
|
+
|
148
|
+
require_relative 'excel_functions/rate'
|
149
|
+
|
150
|
+
require_relative 'excel_functions/ceiling'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
def ceiling(number, multiple, mode = 0)
|
4
|
+
return number if number.is_a?(Symbol)
|
5
|
+
return multiple if multiple.is_a?(Symbol)
|
6
|
+
return mode if mode.is_a?(Symbol)
|
7
|
+
number = number_argument(number)
|
8
|
+
multiple = number_argument(multiple)
|
9
|
+
mode = number_argument(mode)
|
10
|
+
return :value unless number.is_a?(Numeric)
|
11
|
+
return :value unless multiple.is_a?(Numeric)
|
12
|
+
return :value unless mode.is_a?(Numeric)
|
13
|
+
return 0 if multiple == 0
|
14
|
+
if mode == 0 || number > 0
|
15
|
+
whole, remainder = number.divmod(multiple)
|
16
|
+
num_steps = remainder > 0 ? whole + 1 : whole
|
17
|
+
num_steps * multiple
|
18
|
+
else # Need to round negative away from zero
|
19
|
+
-ceiling(-number, multiple, 0)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
def countif(count_range, criteria)
|
4
|
+
return 0 if criteria.kind_of?(Array) # Who knows why, Excel
|
5
|
+
count_range = [count_range] unless count_range.kind_of?(Array)
|
6
|
+
rows = count_range.size
|
7
|
+
if count_range.first && count_range.first.kind_of?(Array)
|
8
|
+
sum_range = Array.new(rows, Array.new(count_range.first.size,1))
|
9
|
+
else
|
10
|
+
sum_range = Array.new(rows, 1)
|
11
|
+
end
|
12
|
+
return sumif(count_range, criteria, sum_range)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
def floor(number, multiple)
|
4
|
+
return number if number.is_a?(Symbol)
|
5
|
+
return multiple if multiple.is_a?(Symbol)
|
6
|
+
number ||= 0
|
7
|
+
return :value unless number.is_a?(Numeric)
|
8
|
+
return :value unless multiple.is_a?(Numeric)
|
9
|
+
return :div0 if multiple == 0
|
10
|
+
return :num if multiple < 0
|
11
|
+
number - (number % multiple)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
def excel_or(*args)
|
4
|
+
# Flatten arrays
|
5
|
+
args = args.flatten
|
6
|
+
|
7
|
+
# If an argument is an error, return that
|
8
|
+
error = args.find {|a| a.is_a?(Symbol)}
|
9
|
+
return error if error
|
10
|
+
|
11
|
+
# Replace 1 and 0 with true and false
|
12
|
+
args.map! do |a|
|
13
|
+
case a
|
14
|
+
when 1; true
|
15
|
+
when 0; false
|
16
|
+
else; a
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Remove anything not boolean
|
21
|
+
args.delete_if { |a| !(a.is_a?(TrueClass) || a.is_a?(FalseClass)) }
|
22
|
+
|
23
|
+
# Return an error if nothing less
|
24
|
+
return :value if args.empty?
|
25
|
+
|
26
|
+
# Now calculate and return
|
27
|
+
args.any? {|a| a == true }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
def rate(periods, payment, presentValue, finalValue)
|
4
|
+
raise NotSupportedException.new("rate() function has not been implemented fully. Edit src/excel/excel_functions/rate.rb") unless (payment || 0) == 0
|
5
|
+
return periods if periods.is_a?(Symbol)
|
6
|
+
return presentValue if presentValue.is_a?(Symbol)
|
7
|
+
return finalValue if finalValue.is_a?(Symbol)
|
8
|
+
|
9
|
+
return :num unless periods.is_a?(Numeric)
|
10
|
+
return :num unless presentValue.is_a?(Numeric)
|
11
|
+
return :num unless finalValue.is_a?(Numeric)
|
12
|
+
|
13
|
+
((finalValue.to_f/(-presentValue.to_f)) ** (1.0/periods))-1.0
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
def replace(string, start, length, replacement)
|
4
|
+
return string if string.is_a?(Symbol)
|
5
|
+
return start if start.is_a?(Symbol)
|
6
|
+
return length if length.is_a?(Symbol)
|
7
|
+
return replacement if replacement.is_a?(Symbol)
|
8
|
+
string = string.to_s
|
9
|
+
return string + replacement if start >= string.length
|
10
|
+
string_join(left(string,start-1),replacement,right(string,string.length - (start - 1 + length)))
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
def scurve(currentYear, startValue, endValue, duration, startYear = 2018.0)
|
4
|
+
currentYear = number_argument(currentYear)
|
5
|
+
startValue = number_argument(startValue)
|
6
|
+
endValue = number_argument(endValue)
|
7
|
+
duration = number_argument(duration)
|
8
|
+
startYear = number_argument(startYear)
|
9
|
+
|
10
|
+
return currentYear if currentYear.is_a?(Symbol)
|
11
|
+
return startValue if startValue.is_a?(Symbol)
|
12
|
+
return endValue if endValue.is_a?(Symbol)
|
13
|
+
return duration if duration.is_a?(Symbol)
|
14
|
+
return startYear if startYear.is_a?(Symbol)
|
15
|
+
|
16
|
+
return startValue if currentYear < startYear
|
17
|
+
|
18
|
+
x = (currentYear - startYear) / duration.to_f
|
19
|
+
x0 = 0.0
|
20
|
+
a = endValue - startValue
|
21
|
+
sc = 0.999
|
22
|
+
eps = 1.0 - sc
|
23
|
+
mu = 0.5
|
24
|
+
beta = (mu - 1.0) / Math.log(1.0 / sc - 1)
|
25
|
+
scurve = a * (((Math.exp(-(x - mu) / beta) + 1) ** -1) - ((Math.exp(-(x0 - mu) / beta) + 1) ** -1)) + startValue
|
26
|
+
end
|
27
|
+
|
28
|
+
def halfscurve(currentYear, startValue, endValue , duration, startYear = 2018)
|
29
|
+
currentYear = number_argument(currentYear)
|
30
|
+
startValue = number_argument(startValue)
|
31
|
+
endValue = number_argument(endValue)
|
32
|
+
duration = number_argument(duration)
|
33
|
+
startYear = number_argument(startYear)
|
34
|
+
|
35
|
+
return currentYear if currentYear.is_a?(Symbol)
|
36
|
+
return startValue if startValue.is_a?(Symbol)
|
37
|
+
return endValue if endValue.is_a?(Symbol)
|
38
|
+
return duration if duration.is_a?(Symbol)
|
39
|
+
return startYear if startYear.is_a?(Symbol)
|
40
|
+
return startValue if currentYear < startYear
|
41
|
+
|
42
|
+
scurve(currentYear + duration, startValue, endValue, duration * 2, startYear) - ((endValue - startValue) / 2.0)
|
43
|
+
end
|
44
|
+
|
45
|
+
def lcurve(currentYear, startValue, endValue , duration, startYear = 2018)
|
46
|
+
currentYear = number_argument(currentYear)
|
47
|
+
startValue = number_argument(startValue)
|
48
|
+
endValue = number_argument(endValue)
|
49
|
+
duration = number_argument(duration)
|
50
|
+
startYear = number_argument(startYear)
|
51
|
+
|
52
|
+
return currentYear if currentYear.is_a?(Symbol)
|
53
|
+
return startValue if startValue.is_a?(Symbol)
|
54
|
+
return endValue if endValue.is_a?(Symbol)
|
55
|
+
return duration if duration.is_a?(Symbol)
|
56
|
+
return startYear if startYear.is_a?(Symbol)
|
57
|
+
|
58
|
+
return startValue if currentYear < startYear
|
59
|
+
return endValue if currentYear > (startYear + duration)
|
60
|
+
return startValue if currentYear < startYear
|
61
|
+
startValue + (endValue - startValue) / duration * (currentYear - startYear)
|
62
|
+
end
|
63
|
+
|
64
|
+
def curve(curveType, currentYear, startValue, endValue, duration, startYear = 2018)
|
65
|
+
if curveType == "s"
|
66
|
+
return scurve(currentYear, startValue, endValue, duration, startYear)
|
67
|
+
elsif curveType == "hs"
|
68
|
+
return halfscurve(currentYear, startValue, endValue, duration, startYear)
|
69
|
+
else
|
70
|
+
return lcurve(currentYear, startValue, endValue, duration, startYear)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ExcelFunctions
|
2
|
+
|
3
|
+
# This is a support function for mapping arguments that are not strings to their
|
4
|
+
# Excel string equivalent. e.g., true = TRUE, :div0 = "DIV/0!"
|
5
|
+
def string_argument(a)
|
6
|
+
case a
|
7
|
+
when Symbol
|
8
|
+
return {
|
9
|
+
:name => "#NAME?",
|
10
|
+
:value => "#VALUE!",
|
11
|
+
:div0 => "#DIV/0!",
|
12
|
+
:ref => "#REF!",
|
13
|
+
:na => "#N/A",
|
14
|
+
:num => "#NUM!",
|
15
|
+
}[a] || :value
|
16
|
+
when String
|
17
|
+
return a
|
18
|
+
when nil
|
19
|
+
return ""
|
20
|
+
when true
|
21
|
+
return "TRUE"
|
22
|
+
when false
|
23
|
+
return "FALSE"
|
24
|
+
when Numeric
|
25
|
+
if a.round == a
|
26
|
+
return a.to_i.to_s
|
27
|
+
else
|
28
|
+
return a.to_s
|
29
|
+
end
|
30
|
+
when Array
|
31
|
+
return string_argument(a[0][0])
|
32
|
+
else
|
33
|
+
return :value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|