excel_to_code 0.1.8 → 0.1.10

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/TODO +0 -1
  3. data/src/commands/excel_to_c.rb +6 -5
  4. data/src/commands/excel_to_ruby.rb +2 -1
  5. data/src/commands/excel_to_x.rb +69 -37
  6. data/src/compile/c/a.out +0 -0
  7. data/src/compile/c/compile_named_reference_setters.rb +1 -1
  8. data/src/compile/c/compile_to_c.rb +1 -1
  9. data/src/compile/c/compile_to_c_header.rb +1 -1
  10. data/src/compile/c/excel_to_c_runtime.c +117 -1
  11. data/src/compile/c/map_formulae_to_c.rb +4 -0
  12. data/src/compile/c/map_sheet_names_to_c_names.rb +1 -1
  13. data/src/compile/c/map_values_to_c.rb +2 -1
  14. data/src/compile/ruby/compile_to_ruby.rb +2 -2
  15. data/src/compile/ruby/compile_to_ruby_unit_test.rb +1 -1
  16. data/src/compile/ruby/map_formulae_to_ruby.rb +5 -0
  17. data/src/compile/ruby/map_values_to_ruby.rb +2 -1
  18. data/src/excel/excel_functions.rb +10 -0
  19. data/src/excel/excel_functions/cell.rb +14 -0
  20. data/src/excel/excel_functions/mid.rb +20 -0
  21. data/src/excel/excel_functions/negative.rb +2 -0
  22. data/src/excel/excel_functions/pv.rb +37 -0
  23. data/src/excel/excel_functions/text.rb +25 -0
  24. data/src/excel/excel_functions/trim.rb +8 -0
  25. data/src/excel/table.rb +1 -0
  26. data/src/extract/check_for_unknown_functions.rb +1 -1
  27. data/src/rewrite/ast_expand_array_formulae.rb +4 -6
  28. data/src/rewrite/rewrite_array_formulae.rb +2 -2
  29. data/src/rewrite/rewrite_array_formulae_to_arrays.rb +1 -1
  30. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +1 -1
  31. data/src/rewrite/rewrite_formulae_to_ast.rb +1 -1
  32. data/src/rewrite/rewrite_merge_formulae_and_values.rb +2 -2
  33. data/src/rewrite/rewrite_named_reference_names.rb +1 -1
  34. data/src/rewrite/rewrite_relationship_id_to_filename.rb +1 -1
  35. data/src/rewrite/rewrite_shared_formulae.rb +2 -2
  36. data/src/rewrite/rewrite_values_to_ast.rb +1 -1
  37. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +2 -2
  38. data/src/rewrite/rewrite_worksheet_names.rb +6 -3
  39. data/src/simplify.rb +2 -0
  40. data/src/simplify/inline_formulae.rb +18 -1
  41. data/src/simplify/map_formulae_to_values.rb +18 -6
  42. data/src/simplify/remove_cells.rb +1 -1
  43. data/src/simplify/replace_arrays_with_single_cells.rb +1 -1
  44. data/src/simplify/replace_column_with_column_number.rb +58 -0
  45. data/src/simplify/replace_common_elements_in_formulae.rb +1 -1
  46. data/src/simplify/replace_formulae_with_calculated_values.rb +7 -1
  47. data/src/simplify/replace_indirects_with_references.rb +16 -3
  48. data/src/simplify/replace_named_references.rb +1 -1
  49. data/src/simplify/replace_offsets_with_references.rb +66 -0
  50. data/src/simplify/replace_ranges_with_array_literals.rb +1 -1
  51. data/src/simplify/replace_shared_strings.rb +1 -1
  52. data/src/simplify/replace_table_references.rb +2 -2
  53. data/src/simplify/replace_values_with_constants.rb +1 -1
  54. data/src/simplify/simplify_arithmetic.rb +1 -1
  55. metadata +9 -2
@@ -51,6 +51,9 @@ class MapFormulaeToC < MapValuesToC
51
51
  'MIN' => 'min',
52
52
  'MOD' => 'mod',
53
53
  'PMT' => 'pmt',
54
+ 'PV3' => 'pv_3',
55
+ 'PV4' => 'pv_4',
56
+ 'PV5' => 'pv_5',
54
57
  'ROUND' => 'excel_round',
55
58
  'ROUNDDOWN' => 'rounddown',
56
59
  'ROUNDUP' => 'roundup',
@@ -60,6 +63,7 @@ class MapFormulaeToC < MapValuesToC
60
63
  'SUMIF2' => 'sumif_2',
61
64
  'SUMIF3' => 'sumif',
62
65
  'SUMIFS' => 'sumifs',
66
+ 'TEXT2' => 'text',
63
67
  'SUMPRODUCT' => 'sumproduct',
64
68
  'VLOOKUP3' => 'vlookup_3',
65
69
  'VLOOKUP4' => 'vlookup',
@@ -6,7 +6,7 @@ class MapSheetNamesToCNames
6
6
 
7
7
  def rewrite(input,output)
8
8
  c_names_assigned = {}
9
- input.lines do |line|
9
+ input.each_line do |line|
10
10
  excel_worksheet_name = line.split("\t").first
11
11
  c_name = excel_worksheet_name.downcase.gsub(/[^a-z0-9]+/,'_')
12
12
  c_name = "s"+c_name if c_name[0] !~ /[a-z]/
@@ -65,7 +65,8 @@ class MapValuesToC
65
65
  "#VALUE!" => "VALUE",
66
66
  "#DIV/0!" => "DIV0",
67
67
  "#REF!" => "REF",
68
- "#N/A" => "NA"
68
+ "#N/A" => "NA",
69
+ "#NUM!" => "NUM"
69
70
  }
70
71
 
71
72
  REVERSE_ERRORS = ERRORS.invert
@@ -15,7 +15,7 @@ class CompileToRuby
15
15
  mapper.worksheet = worksheet
16
16
  mapper.sheet_names = Hash[sheet_names_file.readlines.map { |line| line.strip.split("\t")}]
17
17
  c_name = mapper.sheet_names[worksheet]
18
- input.lines do |line|
18
+ input.each_line do |line|
19
19
  begin
20
20
  ref, formula = line.split("\t")
21
21
  name = c_name ? "#{c_name}_#{ref.downcase}" : ref.downcase
@@ -32,4 +32,4 @@ class CompileToRuby
32
32
  end
33
33
  end
34
34
 
35
- end
35
+ end
@@ -17,7 +17,7 @@ class CompileToRubyUnitTest
17
17
 
18
18
  def rewrite(input, sloppy, c_name, refs_to_test, o)
19
19
  mapper = MapValuesToRuby.new
20
- input.lines do |line|
20
+ input.each_line do |line|
21
21
  ref, formula = line.split("\t")
22
22
  next unless refs_to_test.include?(ref.upcase)
23
23
  ast = eval(formula)
@@ -19,6 +19,7 @@ class MapFormulaeToRuby < MapValuesToRuby
19
19
  'ABS' => 'abs',
20
20
  'AND' => 'excel_and',
21
21
  'AVERAGE' => 'average',
22
+ 'CELL' => 'cell',
22
23
  'CHOOSE' => 'choose',
23
24
  'COSH' => 'cosh',
24
25
  'COUNT' => 'count',
@@ -32,10 +33,12 @@ class MapFormulaeToRuby < MapValuesToRuby
32
33
  'LEFT' => 'left',
33
34
  'MATCH' => 'excel_match',
34
35
  'MAX' => 'max',
36
+ 'MID' => 'mid',
35
37
  'MIN' => 'min',
36
38
  'MOD' => 'mod',
37
39
  'PI' => 'pi',
38
40
  'PMT' => 'pmt',
41
+ 'PV' => 'pv',
39
42
  'ROUND' => 'round',
40
43
  'ROUNDDOWN' => 'rounddown',
41
44
  'ROUNDUP' => 'roundup',
@@ -44,6 +47,8 @@ class MapFormulaeToRuby < MapValuesToRuby
44
47
  'SUMIF' => 'sumif',
45
48
  'SUMIFS' => 'sumifs',
46
49
  'SUMPRODUCT' => 'sumproduct',
50
+ 'TEXT' => 'text',
51
+ 'TRIM' => 'trim',
47
52
  'VLOOKUP' => 'vlookup',
48
53
  '^' => 'power'
49
54
  }
@@ -45,7 +45,8 @@ class MapValuesToRuby
45
45
  "#VALUE!" => ":value",
46
46
  "#DIV/0!" => ":div0",
47
47
  "#REF!" => ":ref",
48
- "#N/A" => ":na"
48
+ "#N/A" => ":na",
49
+ "#NUM!" => ":num"
49
50
  }
50
51
 
51
52
  REVERSE_ERRORS = ERRORS.invert
@@ -69,3 +69,13 @@ require_relative 'excel_functions/int'
69
69
 
70
70
  # Other functions
71
71
 
72
+
73
+ require_relative 'excel_functions/cell'
74
+
75
+ require_relative 'excel_functions/trim'
76
+
77
+ require_relative 'excel_functions/mid'
78
+
79
+ require_relative 'excel_functions/pv'
80
+
81
+ require_relative 'excel_functions/text'
@@ -0,0 +1,14 @@
1
+ module ExcelFunctions
2
+
3
+ def cell(info_type, reference = nil)
4
+ return info_type if info_type.is_a?(Symbol)
5
+ return :value unless info_type.is_a?(String)
6
+ case info_type.downcase
7
+ when 'filename'
8
+ original_excel_filename
9
+ else
10
+ :value
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,20 @@
1
+ module ExcelFunctions
2
+
3
+ def mid(text, start_num, num_chars)
4
+ start_num = number_argument(start_num)
5
+ num_chars = number_argument(num_chars)
6
+
7
+ return text if text.is_a?(Symbol)
8
+ return start_num if start_num.is_a?(Symbol)
9
+ return num_chars if num_chars.is_a?(Symbol)
10
+
11
+ text = text.to_s
12
+
13
+ return :value if start_num < 1
14
+ return :value if num_chars < 0
15
+
16
+ return "" if start_num > text.length
17
+ text[start_num - 1, num_chars]
18
+ end
19
+
20
+ end
@@ -1,6 +1,8 @@
1
1
  module ExcelFunctions
2
2
 
3
3
  def negative(a)
4
+ return a.map { |c| negative(c) } if a.is_a?(Array)
5
+
4
6
  a = number_argument(a)
5
7
 
6
8
  return a if a.is_a?(Symbol)
@@ -0,0 +1,37 @@
1
+ module ExcelFunctions
2
+
3
+ def pv(rate, nper, pmt, fv = nil, type = nil)
4
+ # Turn the remainder into numbers
5
+ rate = number_argument(rate)
6
+ nper = number_argument(nper)
7
+ pmt = number_argument(pmt)
8
+ fv = number_argument(fv)
9
+ type = number_argument(type)
10
+
11
+ # Check for errors
12
+ return rate if rate.is_a?(Symbol)
13
+ return nper if nper.is_a?(Symbol)
14
+ return pmt if pmt.is_a?(Symbol)
15
+ return fv if fv.is_a?(Symbol)
16
+
17
+ return :value unless (type == 1 || type == 0)
18
+ return :value unless rate >= 0
19
+
20
+ # Sum the payments
21
+ if rate > 0
22
+ present_value = -pmt * ((1 - ((1 + rate)**-nper))/rate)
23
+ else
24
+ present_value = -pmt * nper
25
+ end
26
+
27
+ # Adjust for the type, which governs whether payments at the beginning or end of the period
28
+ present_value = present_value * ( 1 + rate) if type == 1
29
+
30
+ # Add on the final value
31
+ present_value += -fv / ((1 + rate)**(nper))
32
+
33
+ # Return the answer
34
+ present_value
35
+ end
36
+
37
+ end
@@ -0,0 +1,25 @@
1
+ module ExcelFunctions
2
+
3
+ def text(number, format)
4
+ number ||= 0
5
+ return "" unless format
6
+
7
+ if number.is_a?(String)
8
+ begin
9
+ number = Float(number)
10
+ rescue ArgumentError => e
11
+ # Ignore
12
+ end
13
+ end
14
+ return number unless number.is_a?(Numeric)
15
+ return format if format.is_a?(Symbol)
16
+
17
+ case format
18
+ when '0%'
19
+ "#{(number * 100).round}%"
20
+ else
21
+ format
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,8 @@
1
+ module ExcelFunctions
2
+
3
+ def trim(text)
4
+ return text unless text.is_a?(String)
5
+ text.strip.gsub(/ +/,' ')
6
+ end
7
+
8
+ end
data/src/excel/table.rb CHANGED
@@ -9,6 +9,7 @@ class Table
9
9
  @name, @worksheet, @area, @number_of_total_rows, @column_name_array = name, worksheet, Area.for(reference), number_of_total_rows.to_i, column_name_array.map { |c| c.strip.downcase }
10
10
  @area.calculate_excel_variables
11
11
  @data_area = Area.for("#{@area.excel_start.offset(1,0)}:#{@area.excel_finish.offset(-@number_of_total_rows,0)}")
12
+ @data_area.calculate_excel_variables
12
13
  end
13
14
 
14
15
  def reference_for(table_name,structured_reference,calling_worksheet,calling_cell)
@@ -10,7 +10,7 @@ class CheckForUnknownFunctions
10
10
 
11
11
  def check(input,output)
12
12
  self.settable ||= lambda { |ref| false }
13
- input.lines do |line|
13
+ input.each_line do |line|
14
14
  line.scan(/\[:function, "(.*?)"/).each do |match|
15
15
  output.puts $1 unless MapFormulaeToRuby::FUNCTIONS.has_key?($1)
16
16
  end
@@ -114,13 +114,11 @@ class AstExpandArrayFormulae
114
114
 
115
115
  private
116
116
 
117
- def no_need_to_array?(args,ok_to_be_an_array)
117
+ def no_need_to_array?(args, ok_to_be_an_array)
118
118
  ok_to_be_an_array.each_with_index do |array_ok,i|
119
- unless array_ok
120
- if args[i].first == :array
121
- return false
122
- end
123
- end
119
+ next if array_ok
120
+ break unless args[i]
121
+ return false if args[i].first == :array
124
122
  end
125
123
  true
126
124
  end
@@ -45,7 +45,7 @@ class RewriteArrayFormulae
45
45
  end
46
46
 
47
47
  def rewrite(input,output)
48
- input.lines do |line|
48
+ input.each_line do |line|
49
49
  ref, array_range, formula = line.split("\t")
50
50
  array_formula(formula,array_range,output)
51
51
  end
@@ -68,4 +68,4 @@ class RewriteArrayFormulae
68
68
  end
69
69
  end
70
70
 
71
- end
71
+ end
@@ -8,7 +8,7 @@ class RewriteArrayFormulaeToArrays
8
8
 
9
9
  def rewrite(input,output)
10
10
  mapper = AstExpandArrayFormulae.new
11
- input.lines do |line|
11
+ input.each_line do |line|
12
12
  content = line.split("\t")
13
13
  ast = eval(content.pop)
14
14
  output.puts "#{content.join("\t")}\t#{mapper.map(ast).inspect}"
@@ -42,7 +42,7 @@ class RewriteCellReferencesToIncludeSheet
42
42
  def rewrite(input,output)
43
43
  mapper = RewriteCellReferencesToIncludeSheetAst.new
44
44
  mapper.worksheet = worksheet
45
- input.lines do |line|
45
+ input.each_line do |line|
46
46
  if line =~ /(:area|:cell)/
47
47
  content = line.split("\t")
48
48
  ast = eval(content.pop)
@@ -9,7 +9,7 @@ class RewriteFormulaeToAst
9
9
  # input should be in the form: 'thing\tthing\tformula\n' where the last field is always a forumla
10
10
  # output will be in the form 'thing\tthing\tast\n'
11
11
  def rewrite(input,output)
12
- input.lines.with_index do |line,i|
12
+ input.each_line.with_index do |line,i|
13
13
  line =~ /^(.*\t)(.*?)$/
14
14
  output.write $1
15
15
  ast = Formula.parse($2)
@@ -12,7 +12,7 @@ class RewriteMergeFormulaeAndValues
12
12
  array_formula = Hash[array_formula.readlines.map { |line| [line[/(.*?)\t/,1],line]}]
13
13
  simple_formulae = Hash[simple_formulae.readlines.map { |line| [line[/(.*?)\t/,1],line]}]
14
14
 
15
- values.lines do |line|
15
+ values.each_line do |line|
16
16
  ref = line[/(.*?)\t/,1]
17
17
  @references_to_add_if_they_are_not_already_present.delete(ref)
18
18
  output.puts simple_formulae[ref] || array_formula[ref] || shared_formulae[ref] || line
@@ -23,4 +23,4 @@ class RewriteMergeFormulaeAndValues
23
23
  end
24
24
 
25
25
  end
26
-
26
+
@@ -16,7 +16,7 @@ class RewriteNamedReferenceNames
16
16
  worksheet_names = Hash[worksheet_names.readlines.map { |line| line.strip.split("\t")}]
17
17
  c_names_assigned = worksheet_names.invert
18
18
 
19
- named_references.lines do |line|
19
+ named_references.each_line do |line|
20
20
  sheet, name, reference = line.split("\t")
21
21
  sheet = worksheet_names[sheet]
22
22
  if sheet
@@ -7,7 +7,7 @@ class RewriteRelationshipIdToFilename
7
7
  def rewrite(input, relationships_file, output)
8
8
  relationships_file.rewind
9
9
  relationships = Hash[relationships_file.readlines.map { |line| line.split("\t")}]
10
- input.lines do |line|
10
+ input.each_line do |line|
11
11
  parts = line.split("\t")
12
12
  rid = parts.pop.strip
13
13
  if relationships.has_key?(rid)
@@ -6,8 +6,8 @@ class RewriteSharedFormulae
6
6
  end
7
7
 
8
8
  def rewrite(input, shared_targets, output)
9
- shared_targets = shared_targets.lines.map(&:strip).to_a
10
- input.lines do |line|
9
+ shared_targets = shared_targets.each_line.map(&:strip).to_a
10
+ input.each_line do |line|
11
11
  ref, copy_range, formula = line.split("\t")
12
12
  share_formula(ref, formula, copy_range, shared_targets, output)
13
13
  end
@@ -9,7 +9,7 @@ class RewriteValuesToAst
9
9
  # input should be in the form: 'thing\tthing\tformula\n' where the last field is always a forumla
10
10
  # output will be in the form 'thing\tthing\tast\n'
11
11
  def rewrite(input,output)
12
- input.lines do |line|
12
+ input.each_line do |line|
13
13
  line =~ /^(.*?)\t(.*?)\t(.*)\n/
14
14
  ref, type, value = $1, $2, $3
15
15
  ast = case type
@@ -74,7 +74,7 @@ class RewriteWholeRowColumnReferencesToAreas
74
74
  end
75
75
 
76
76
  def rewrite(input,output)
77
- input.lines do |line|
77
+ input.each_line do |line|
78
78
  if line =~ /(:column_range|:row_range)/
79
79
  content = line.split("\t")
80
80
  ast = eval(content.pop)
@@ -97,4 +97,4 @@ class RewriteWholeRowColumnReferencesToAreas
97
97
  @mapper ||= MapColumnAndRowRangeAst.new(sheet_name,dimensions)
98
98
  end
99
99
 
100
- end
100
+ end
@@ -10,11 +10,14 @@ class RewriteWorksheetNames
10
10
  # relationship_id\tfilename\n
11
11
  # Outputs worksheet names in the form:
12
12
  # name\tfilename\n
13
+ # Only includes actual worksheets (ignores chartsheets and the like)
13
14
  def rewrite(worksheet_names,relationships,output)
14
15
  relationships = Hash[relationships.readlines.map { |line| line.split("\t")}]
15
- worksheet_names.lines do |line|
16
+ worksheet_names.each_line do |line|
16
17
  rid, name = line.split("\t")
17
- output.puts "#{name.strip}\t#{relationships[rid].strip}"
18
+ filename = relationships[rid].strip
19
+ next unless filename =~ /^worksheets/i
20
+ output.puts "#{name.strip}\t#{filename}"
18
21
  end
19
22
  end
20
- end
23
+ end
data/src/simplify.rb CHANGED
@@ -5,6 +5,8 @@ require_relative "simplify/replace_ranges_with_array_literals"
5
5
  require_relative "simplify/inline_formulae"
6
6
  require_relative "simplify/replace_formulae_with_calculated_values"
7
7
  require_relative "simplify/replace_indirects_with_references"
8
+ require_relative "simplify/replace_offsets_with_references"
9
+ require_relative "simplify/replace_column_with_column_number"
8
10
  require_relative "simplify/simplify_arithmetic"
9
11
  require_relative "simplify/identify_dependencies"
10
12
  require_relative "simplify/remove_cells"
@@ -1,9 +1,11 @@
1
1
  class InlineFormulaeAst
2
2
 
3
3
  attr_accessor :references, :current_sheet_name, :inline_ast
4
+ attr_accessor :replacements_made_in_the_last_pass
4
5
 
5
6
  def initialize(references, current_sheet_name, inline_ast = nil)
6
7
  @references, @current_sheet_name, @inline_ast = references, [current_sheet_name], inline_ast
8
+ @replacements_made_in_the_last_pass = 0
7
9
  @inline_ast ||= lambda { |sheet,reference,references| true }
8
10
  end
9
11
 
@@ -16,9 +18,20 @@ class InlineFormulaeAst
16
18
  [operator,*ast[1..-1].map {|a| map(a) }]
17
19
  end
18
20
  end
21
+
22
+ def function(name,*args)
23
+ case name
24
+ when 'OFFSET'
25
+ [:function, name, args.shift, *args.map { |a| map(a) }]
26
+ else
27
+ [:function, name, *args.map { |a| map(a) }]
28
+ end
29
+ end
19
30
 
20
31
  def sheet_reference(sheet,reference)
32
+ return [:error, '#REF!'] unless references[sheet]
21
33
  if inline_ast.call(sheet,reference.last.upcase.gsub('$',''),references)
34
+ @replacements_made_in_the_last_pass += 1
22
35
  ast = references[sheet][reference.last.upcase.gsub('$','')]
23
36
  if ast
24
37
  current_sheet_name.push(sheet)
@@ -36,6 +49,7 @@ class InlineFormulaeAst
36
49
  # TODO: Optimize by replacing contents of references hash with the inlined version
37
50
  def cell(reference)
38
51
  if inline_ast.call(current_sheet_name.last,reference.upcase.gsub('$',''),references)
52
+ @replacements_made_in_the_last_pass += 1
39
53
  ast = references[current_sheet_name.last][reference.upcase.gsub('$','')]
40
54
  if ast
41
55
  map(ast)
@@ -61,10 +75,12 @@ class InlineFormulae
61
75
  def self.replace(*args)
62
76
  self.new.replace(*args)
63
77
  end
78
+
79
+ attr_accessor :replacements_made_in_the_last_pass
64
80
 
65
81
  def replace(input,output)
66
82
  rewriter = InlineFormulaeAst.new(references, default_sheet_name, inline_ast)
67
- input.lines do |line|
83
+ input.each_line do |line|
68
84
  # Looks to match lines with references
69
85
  if line =~ /\[:cell/
70
86
  ref, ast = line.split("\t")
@@ -73,5 +89,6 @@ class InlineFormulae
73
89
  output.puts line
74
90
  end
75
91
  end
92
+ @replacements_made_in_the_last_pass = rewriter.replacements_made_in_the_last_pass
76
93
  end
77
94
  end