excel_to_code 0.1.8 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
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