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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 880640b9610ce6658c3ba7a5ff8d6f96048d0705
4
- data.tar.gz: 08f680f1229c0e00d769b3b7024e452a32a5ebd5
3
+ metadata.gz: 4e983a098e0ba3377ae5b0d42b53c5d627e765e8
4
+ data.tar.gz: dd9b3766572dd84f5bf980fb256bfd7ce70fdb49
5
5
  SHA512:
6
- metadata.gz: 244303086ecf46f8be290f2d1c46f4c2b929c21b2bca0f7129d9edeb65e06e068824039250f650d12b3eaa4f85b2e9d08423b042cbf71b68e9a4d9f4c3311e4f
7
- data.tar.gz: d40bf490120ec061bf94fbf65c32daf136edd1d9abefb833c1e1cf8a0c17940d80c6fe5e8ca8fd436c22aad17db8f3cbdc5312a55338317ab16614b7be107c98
6
+ metadata.gz: b829c3fb99bd61216b7df001ad320a9a0ecd6f6157fc07ac0c368417354e853544f25ae8b31b43dde6fb788480c6ac9e8559b6646fd8a5d55bd4a054885c0e62
7
+ data.tar.gz: 6d1f1519d8daf601c6028c6c116eadd8c76dada8edb5153ee42d49946fa97831b18d0d086e46a59b49823c6126e893638f0247c2e30b2de08a3f053a917bc49a
data/TODO CHANGED
@@ -19,7 +19,6 @@ See doc/How_to_add_a_missing_function.md
19
19
 
20
20
  * Optimize IF, CHOOSE, MATCH, VLOOKUP and similar functions so that they don't have to calculate all their arguments
21
21
  * Fix it so that cells that are being reported as empty, that excel would give a numeric value of zero, are fixed
22
- * Fix so that detects when it has finished replacing cells with values, rather than just doing a fixed number of cycles
23
22
 
24
23
  ## Things that are badly written
25
24
 
@@ -36,13 +36,14 @@ class ExcelToC < ExcelToX
36
36
 
37
37
  # Now we have to put all the initial definitions out
38
38
  o.puts "// definitions"
39
+ o.puts "static ExcelValue ORIGINAL_EXCEL_FILENAME = {.type = ExcelString, .string = #{excel_file.inspect} };"
39
40
 
40
41
  i = input("Common elements")
41
42
  c = CompileToCHeader.new
42
43
  c.gettable = lambda { |ref| false }
43
44
  c.rewrite(i,w,o)
44
45
  i.rewind
45
- number_of_refs += i.lines.to_a.size
46
+ number_of_refs += i.each_line.to_a.size
46
47
  close(i)
47
48
 
48
49
  worksheets do |name,xml_filename|
@@ -54,7 +55,7 @@ class ExcelToC < ExcelToX
54
55
  i = input([name,"Formulae"])
55
56
  c.rewrite(i,w,o)
56
57
  i.rewind
57
- number_of_refs += i.lines.to_a.size
58
+ number_of_refs += i.each_line.to_a.size
58
59
  close(i)
59
60
  end
60
61
 
@@ -82,7 +83,7 @@ class ExcelToC < ExcelToX
82
83
  o.puts "// starting the value constants"
83
84
  mapper = MapValuesToCStructs.new
84
85
  i = input("Constants")
85
- i.lines do |line|
86
+ i.each_line do |line|
86
87
  begin
87
88
  ref, formula = line.split("\t")
88
89
  ast = eval(formula)
@@ -344,7 +345,7 @@ END
344
345
 
345
346
  # Getters
346
347
  i = input('Named references to keep')
347
- i.lines.each do |line|
348
+ i.each_line do |line|
348
349
  name = line.strip.split("\t").first
349
350
  o.puts " attach_function '#{name}', [], ExcelValue.by_value"
350
351
  end
@@ -352,7 +353,7 @@ END
352
353
 
353
354
  # Setters
354
355
  i = input('Named references to set')
355
- i.lines.each do |line|
356
+ i.each_line do |line|
356
357
  name = line.strip.split("\t").first
357
358
  o.puts " attach_function 'set_#{name}', [ExcelValue.by_value], :void"
358
359
  end
@@ -30,6 +30,7 @@ class ExcelToRuby < ExcelToX
30
30
  o.puts ""
31
31
  o.puts "class #{ruby_module_name}"
32
32
  o.puts " include ExcelFunctions"
33
+ o.puts " def original_excel_filename; #{excel_file.inspect}; end"
33
34
 
34
35
  o.puts
35
36
  o.puts " # Starting common elements"
@@ -66,7 +67,7 @@ class ExcelToRuby < ExcelToX
66
67
  o.puts " # starting initializer"
67
68
  o.puts " def initialize"
68
69
  d = input('Defaults')
69
- d.lines do |line|
70
+ d.each_line do |line|
70
71
  o.puts line
71
72
  end
72
73
  o.puts " end"
@@ -110,9 +110,11 @@ class ExcelToX
110
110
  self.cells_that_can_be_set_at_runtime ||= {}
111
111
 
112
112
  # Make sure that all the cell names are downcase and don't have any $ in them
113
- cells_that_can_be_set_at_runtime.keys.each do |sheet|
114
- next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
115
- cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').upcase }
113
+ if cells_that_can_be_set_at_runtime.is_a?(Hash)
114
+ cells_that_can_be_set_at_runtime.keys.each do |sheet|
115
+ next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
116
+ cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').upcase }
117
+ end
116
118
  end
117
119
 
118
120
  # Make sure that all the cell names are downcase and don't have any $ in them
@@ -168,9 +170,9 @@ class ExcelToX
168
170
  simplify_worksheets # Replacing shared strings and named references with their actual values, tidying arithmetic
169
171
 
170
172
  # In case this hasn't been set by the user
171
- if cells_that_can_be_set_at_runtime.empty?
173
+ if @cells_that_can_be_set_at_runtime.empty?
172
174
  log.info "Creating a good set of cells that should be settable"
173
- create_a_good_set_of_cells_that_should_be_settable_at_runtime
175
+ @cells_that_can_be_set_at_runtime = a_good_set_of_cells_that_should_be_settable_at_runtime
174
176
  end
175
177
 
176
178
  if named_references_that_can_be_set_at_runtime == :where_possible
@@ -313,7 +315,7 @@ class ExcelToX
313
315
  table_filenames = input(name, "Worksheet tables")
314
316
  tables = intermediate(name, "Worksheet tables")
315
317
  table_extractor = ExtractTable.new(name)
316
- table_filenames.lines.each do |line|
318
+ table_filenames.each_line do |line|
317
319
  table_xml = xml(File.join('worksheets',line.strip))
318
320
  table_extractor.extract(table_xml, tables)
319
321
  end
@@ -328,7 +330,7 @@ class ExcelToX
328
330
  worksheets do |name,xml_filename|
329
331
  log.info "Merging table files for #{name}"
330
332
  worksheet_table_file = input([name, "Worksheet tables"])
331
- worksheet_table_file.lines do |line|
333
+ worksheet_table_file.each_line do |line|
332
334
  merged_table_file.puts line
333
335
  end
334
336
  close worksheet_table_file
@@ -477,6 +479,9 @@ class ExcelToX
477
479
  def work_out_which_named_references_can_be_set_at_runtime
478
480
  return unless @named_references_that_can_be_set_at_runtime
479
481
  return unless @named_references_that_can_be_set_at_runtime == :where_possible
482
+ cells_that_can_be_set = @cells_that_can_be_set_at_runtime
483
+ cells_that_can_be_set = a_good_set_of_cells_that_should_be_settable_at_runtime if cells_that_can_be_set == :named_references_only
484
+ cells_that_can_be_set_due_to_named_reference = Hash.new { |h,k| h[k] = Array.new }
480
485
  @named_references_that_can_be_set_at_runtime = []
481
486
  all_named_references = named_references
482
487
  @named_references_to_keep.each do |name|
@@ -484,23 +489,34 @@ class ExcelToX
484
489
  if ref.first == :sheet_reference
485
490
  sheet = ref[1]
486
491
  cell = ref[2][1].gsub('$','')
487
- s = @cells_that_can_be_set_at_runtime[sheet]
488
- @named_references_that_can_be_set_at_runtime << name if s && s.include?(cell)
492
+ s = cells_that_can_be_set[sheet]
493
+ if s && s.include?(cell)
494
+ @named_references_that_can_be_set_at_runtime << name
495
+ cells_that_can_be_set_due_to_named_reference[sheet] << cell
496
+ cells_that_can_be_set_due_to_named_reference[sheet].uniq!
497
+ end
489
498
  elsif ref.first.is_a?(Array)
490
499
  ref = ref.first
491
- p ref
492
- settable = ref.all? do |row|
493
- ref.all? do |column|
494
- p column
495
- sheet = column[1]
496
- cell = column[2][1].gsub('$','')
497
- s = @cells_that_can_be_set_at_runtime[sheet]
498
- s && s.include?(cell)
500
+ settable = ref.all? do |r|
501
+ sheet = r[1]
502
+ cell = r[2][1].gsub('$','')
503
+ s = cells_that_can_be_set[sheet]
504
+ s && s.include?(cell)
505
+ end
506
+ if settable
507
+ @named_references_that_can_be_set_at_runtime << name
508
+ ref.each do |r|
509
+ sheet = r[1]
510
+ cell = r[2][1].gsub('$','')
511
+ cells_that_can_be_set_due_to_named_reference[sheet] << cell
512
+ cells_that_can_be_set_due_to_named_reference[sheet].uniq!
499
513
  end
500
514
  end
501
- @named_references_that_can_be_set_at_runtime << name if settable
502
515
  end
503
516
  end
517
+ if @cells_that_can_be_set_at_runtime == :named_references_only
518
+ @cells_that_can_be_set_at_runtime = cells_that_can_be_set_due_to_named_reference
519
+ end
504
520
  end
505
521
 
506
522
  # FIXME: Feels like a kludge
@@ -510,7 +526,7 @@ class ExcelToX
510
526
 
511
527
  i = input('Named references')
512
528
  o = intermediate('Named references to keep')
513
- i.lines.each do |line|
529
+ i.each_line do |line|
514
530
  sheet, name, ref = *line.split("\t")
515
531
  key = sheet.length != 0 ? [sheet, name] : name
516
532
  o.puts line if named_references_to_keep.include?(key) || named_references_that_can_be_set_at_runtime.include?(key)
@@ -519,7 +535,7 @@ class ExcelToX
519
535
 
520
536
  i.rewind
521
537
  o = intermediate('Named references to set')
522
- i.lines.each do |line|
538
+ i.each_line do |line|
523
539
  sheet, name, ref = *line.split("\t")
524
540
  key = sheet.length != 0 ? [sheet, name] : name
525
541
  o.puts line if named_references_that_can_be_set_at_runtime.include?(key)
@@ -550,25 +566,35 @@ class ExcelToX
550
566
  end
551
567
  end
552
568
 
553
- # FIXME: This should work out how often it needs to operate, rather than having a hardwired 4
554
569
  def replace_formulae_with_their_results
555
- 4.times do
556
- replace_indirects
570
+ number_of_passes = 0
571
+ begin
572
+ number_of_passes += 1
573
+ @replacements_made_in_the_last_pass = 0
574
+ replace_indirects_and_offsets
557
575
  replace_formulae_with_calculated_values
558
576
  replace_references_to_values_with_values
559
- end
577
+ log.info "Pass #{number_of_passes}: Made #{@replacements_made_in_the_last_pass} replacements"
578
+ if number_of_passes > 20
579
+ log.warn "Made more than 20 passes, so aborting"
580
+ break
581
+ end
582
+ end while @replacements_made_in_the_last_pass > 0
560
583
  end
561
584
 
562
- # There is no support for INDIRECT in the ruby or c runtime
585
+ # There is no support for INDIRECT or OFFSET in the ruby or c runtime
563
586
  # However, in many cases it isn't needed, because we can work
564
- # out the value of the indirect at compile time and eliminate it
565
- def replace_indirects
587
+ # out the value of the indirect or OFFSET at compile time and eliminate it
588
+ def replace_indirects_and_offsets
566
589
  worksheets do |name,xml_filename|
567
- log.info "Replacing indirects in #{name}"
590
+ log.info "Replacing INDIRECT, OFFSET and COLUMN functions in #{name}"
568
591
 
569
592
  # First of all we replace any indirects where their values can be calculated at compile time with those
570
- # calculated values (e.g., INDIRECT("A"&1) can be turned into A1)
571
- replace ReplaceIndirectsWithReferences, [name, 'Formulae'], [name, 'Formulae']
593
+ # calculated values (e.g., INDIRECT("A"&1) can be turned into A1 and OFFSET(A1,1,1,2,2) can be turned into B2:C3)
594
+ [ReplaceIndirectsWithReferences.new, ReplaceOffsetsWithReferences.new, ReplaceColumnWithColumnNumber.new].each do |r|
595
+ replace r, [name, 'Formulae'], [name, 'Formulae']
596
+ @replacements_made_in_the_last_pass += r.replacements_made_in_the_last_pass
597
+ end
572
598
 
573
599
  # The result of the indirect might be a named reference, which we need to simplify
574
600
  r = ReplaceNamedReferences.new
@@ -589,7 +615,10 @@ class ExcelToX
589
615
  # If a formula's value can be calculated at compile time, it is replaced with its calculated value (e.g., 1+1 gets replaced with 2)
590
616
  def replace_formulae_with_calculated_values
591
617
  worksheets do |name,xml_filename|
592
- replace ReplaceFormulaeWithCalculatedValues, [name, 'Formulae'], [name, 'Formulae']
618
+ r = ReplaceFormulaeWithCalculatedValues.new
619
+ r.excel_file = excel_file
620
+ replace r, [name, 'Formulae'], [name, 'Formulae']
621
+ @replacements_made_in_the_last_pass += r.replacements_made_in_the_last_pass
593
622
  end
594
623
  end
595
624
 
@@ -623,6 +652,7 @@ class ExcelToX
623
652
  r.default_sheet_name = name
624
653
  replace r, [name, 'Formulae'], [name, 'Formulae']
625
654
  end
655
+ @replacements_made_in_the_last_pass += r.replacements_made_in_the_last_pass
626
656
  end
627
657
 
628
658
  # If 'cells to keep' are specified, then other cells are removed, unless
@@ -773,10 +803,11 @@ class ExcelToX
773
803
  # or in cells_that_can_be_set_at_runtime, then we assume that
774
804
  # all value cells should be settable if they are referenced by
775
805
  # any other forumla.
776
- def create_a_good_set_of_cells_that_should_be_settable_at_runtime
806
+ def a_good_set_of_cells_that_should_be_settable_at_runtime
777
807
  references = all_formulae
778
808
  counter = CountFormulaReferences.new
779
809
  count = counter.count(references)
810
+ settable_cells = {}
780
811
 
781
812
  count.each do |sheet,keys|
782
813
  keys.each do |ref,count|
@@ -784,11 +815,12 @@ class ExcelToX
784
815
  ast = references[sheet][ref]
785
816
  next unless ast
786
817
  if [:blank,:number,:null,:string,:shared_string,:constant,:percentage,:error,:boolean_true,:boolean_false].include?(ast.first)
787
- @cells_that_can_be_set_at_runtime[sheet] ||= []
788
- @cells_that_can_be_set_at_runtime[sheet] << ref.upcase
818
+ settable_cells[sheet] ||= []
819
+ settable_cells[sheet] << ref.upcase
789
820
  end
790
821
  end
791
- end
822
+ end
823
+ return settable_cells
792
824
  end
793
825
 
794
826
  # UTILITY FUNCTIONS
@@ -820,7 +852,7 @@ class ExcelToX
820
852
  worksheets do |name,xml_filename|
821
853
  r = references[name] = {}
822
854
  i = input([name,'Formulae'])
823
- i.lines do |line|
855
+ i.each_line do |line|
824
856
  line =~ /^(.*?)\t(.*)$/
825
857
  ref, ast = $1, $2
826
858
  r[ref] = eval(ast)
@@ -841,7 +873,7 @@ class ExcelToX
841
873
  def worksheets(&block)
842
874
  unless @worksheet_filenames
843
875
  worksheet_names = input('Worksheet names')
844
- @worksheet_filenames = worksheet_names.lines.map do |line|
876
+ @worksheet_filenames = worksheet_names.each_line.map do |line|
845
877
  name, filename = *line.split("\t")
846
878
  [name, filename.strip]
847
879
  end
data/src/compile/c/a.out CHANGED
Binary file
@@ -74,7 +74,7 @@ class CompileNamedReferenceSetters
74
74
  mapper.sheet_names = Hash[sheet_names.readlines.map { |line| line.strip.split("\t")}]
75
75
  mapper.cells_that_can_be_set_at_runtime = cells_that_can_be_set_at_runtime
76
76
 
77
- named_references.lines do |line|
77
+ named_references.each_line do |line|
78
78
  name, reference = line.split("\t")
79
79
  ast = eval(reference)
80
80
  output.puts "void set_#{name}(ExcelValue newValue) {"
@@ -19,7 +19,7 @@ class CompileToC
19
19
  mapper.worksheet = worksheet
20
20
  mapper.sheet_names = Hash[sheet_names_file.readlines.map { |line| line.strip.split("\t")}]
21
21
  c_name = mapper.sheet_names[worksheet] || worksheet
22
- input.lines do |line|
22
+ input.each_line do |line|
23
23
  begin
24
24
  ref, formula = line.split("\t")
25
25
  ast = eval(formula)
@@ -12,7 +12,7 @@ class CompileToCHeader
12
12
  c_name = Hash[sheet_names_file.readlines.map { |line| line.strip.split("\t")}][worksheet]
13
13
  self.gettable ||= lambda { |ref| true }
14
14
  self.settable ||= lambda { |ref| false }
15
- input.lines do |line|
15
+ input.each_line do |line|
16
16
  begin
17
17
  ref, formula = line.split("\t")
18
18
  static_or_not = (gettable.call(ref) || settable.call(ref)) ? "" : "static "
@@ -67,6 +67,9 @@ static ExcelValue mod(ExcelValue a_v, ExcelValue b_v);
67
67
  static ExcelValue negative(ExcelValue a_v);
68
68
  static ExcelValue pmt(ExcelValue rate_v, ExcelValue number_of_periods_v, ExcelValue present_value_v);
69
69
  static ExcelValue power(ExcelValue a_v, ExcelValue b_v);
70
+ static ExcelValue pv_3(ExcelValue a_v, ExcelValue b_v, ExcelValue c_v);
71
+ static ExcelValue pv_4(ExcelValue a_v, ExcelValue b_v, ExcelValue c_v, ExcelValue d_v);
72
+ static ExcelValue pv_5(ExcelValue a_v, ExcelValue b_v, ExcelValue c_v, ExcelValue d_v, ExcelValue e_v);
70
73
  static ExcelValue excel_round(ExcelValue number_v, ExcelValue decimal_places_v);
71
74
  static ExcelValue rounddown(ExcelValue number_v, ExcelValue decimal_places_v);
72
75
  static ExcelValue roundup(ExcelValue number_v, ExcelValue decimal_places_v);
@@ -77,6 +80,7 @@ static ExcelValue sumifs(ExcelValue sum_range_v, int number_of_arguments, ExcelV
77
80
  static ExcelValue sumif(ExcelValue check_range_v, ExcelValue criteria_v, ExcelValue sum_range_v );
78
81
  static ExcelValue sumif_2(ExcelValue check_range_v, ExcelValue criteria_v);
79
82
  static ExcelValue sumproduct(int number_of_arguments, ExcelValue *arguments);
83
+ static ExcelValue text(ExcelValue number_v, ExcelValue format_v);
80
84
  static ExcelValue vlookup_3(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue column_number_v);
81
85
  static ExcelValue vlookup(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue column_number_v, ExcelValue match_type_v);
82
86
 
@@ -122,7 +126,7 @@ static ExcelValue new_excel_number(double number) {
122
126
  static ExcelValue new_excel_string(char *string) {
123
127
  cell_counter++;
124
128
  HEAPCHECK
125
- ExcelValue new_cell = cells[cell_counter];
129
+ ExcelValue new_cell = cells[cell_counter];
126
130
  new_cell.type = ExcelString;
127
131
  new_cell.string = string;
128
132
  return new_cell;
@@ -1071,6 +1075,57 @@ static ExcelValue pmt(ExcelValue rate_v, ExcelValue number_of_periods_v, ExcelVa
1071
1075
  return new_excel_number(-present_value*(rate*(pow((1+rate),number_of_periods)))/((pow((1+rate),number_of_periods))-1));
1072
1076
  }
1073
1077
 
1078
+ static ExcelValue pv_3(ExcelValue rate_v, ExcelValue nper_v, ExcelValue pmt_v) {
1079
+ return pv_4(rate_v, nper_v, pmt_v, ZERO);
1080
+ }
1081
+
1082
+ static ExcelValue pv_4(ExcelValue rate_v, ExcelValue nper_v, ExcelValue pmt_v, ExcelValue fv_v) {
1083
+ return pv_5(rate_v, nper_v, pmt_v, fv_v, ZERO);
1084
+ }
1085
+
1086
+ static ExcelValue pv_5(ExcelValue rate_v, ExcelValue nper_v, ExcelValue pmt_v, ExcelValue fv_v, ExcelValue type_v ) {
1087
+ CHECK_FOR_PASSED_ERROR(rate_v)
1088
+ CHECK_FOR_PASSED_ERROR(nper_v)
1089
+ CHECK_FOR_PASSED_ERROR(pmt_v)
1090
+ CHECK_FOR_PASSED_ERROR(fv_v)
1091
+ CHECK_FOR_PASSED_ERROR(type_v)
1092
+
1093
+ NUMBER(rate_v, rate)
1094
+ NUMBER(nper_v, nper)
1095
+ NUMBER(pmt_v, payment)
1096
+ NUMBER(fv_v, fv)
1097
+ NUMBER(type_v, start_of_period)
1098
+ CHECK_FOR_CONVERSION_ERROR
1099
+
1100
+ if(rate< 0) {
1101
+ return VALUE;
1102
+ }
1103
+
1104
+ double present_value = 0;
1105
+
1106
+ // Sum up the payments
1107
+ if(rate == 0) {
1108
+ present_value = -payment * nper;
1109
+ } else {
1110
+ present_value = -payment * ((1-pow(1+rate,-nper))/rate);
1111
+ }
1112
+
1113
+ // Adjust for beginning or end of period
1114
+ if(start_of_period == 0) {
1115
+ // Do Nothing
1116
+ } else if(start_of_period == 1) {
1117
+ present_value = present_value * (1+rate);
1118
+ } else {
1119
+ return VALUE;
1120
+ }
1121
+
1122
+ // Add on the final value
1123
+ present_value = present_value - (fv/pow(1+rate,nper));
1124
+
1125
+ return new_excel_number(present_value);
1126
+ }
1127
+
1128
+
1074
1129
  static ExcelValue power(ExcelValue a_v, ExcelValue b_v) {
1075
1130
  CHECK_FOR_PASSED_ERROR(a_v)
1076
1131
  CHECK_FOR_PASSED_ERROR(b_v)
@@ -1517,6 +1572,56 @@ static ExcelValue sumproduct(int number_of_arguments, ExcelValue *arguments) {
1517
1572
  return new_excel_number(sum);
1518
1573
  }
1519
1574
 
1575
+ static ExcelValue text(ExcelValue number_v, ExcelValue format_v) {
1576
+ CHECK_FOR_PASSED_ERROR(number_v)
1577
+ CHECK_FOR_PASSED_ERROR(format_v)
1578
+
1579
+ char *s;
1580
+ char *p;
1581
+ double n;
1582
+ ExcelValue result;
1583
+
1584
+ if(number_v.type == ExcelEmpty) {
1585
+ number_v = ZERO;
1586
+ }
1587
+
1588
+ if(format_v.type == ExcelEmpty) {
1589
+ return new_excel_string("");
1590
+ }
1591
+
1592
+ if(number_v.type == ExcelString) {
1593
+ s = number_v.string;
1594
+ if (s == NULL || *s == '\0' || isspace(*s)) {
1595
+ number_v = ZERO;
1596
+ }
1597
+ n = strtod (s, &p);
1598
+ if(*p == '\0') {
1599
+ number_v = new_excel_number(n);
1600
+ }
1601
+ }
1602
+
1603
+ if(number_v.type != ExcelNumber) {
1604
+ return number_v;
1605
+ }
1606
+
1607
+ if(format_v.type != ExcelString) {
1608
+ return format_v;
1609
+ }
1610
+
1611
+ if(format_v.string == "0%") {
1612
+ // FIXME: Too little?
1613
+ s = malloc(100);
1614
+ free_later(s);
1615
+ sprintf(s, "%d%%",(int) round(number_v.number*100));
1616
+ result = new_excel_string(s);
1617
+ } else {
1618
+ return format_v;
1619
+ }
1620
+
1621
+ // inspect_excel_value(result);
1622
+ return result;
1623
+ }
1624
+
1520
1625
  static ExcelValue vlookup_3(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue column_number_v) {
1521
1626
  return vlookup(lookup_value_v,lookup_table_v,column_number_v,TRUE);
1522
1627
  }
@@ -2165,7 +2270,18 @@ int test_functions() {
2165
2270
  ExcelValue sum_array_0_v = new_excel_range(sum_array_0,3,1);
2166
2271
  ExcelValue sum_array_1[] = {sum_array_0_v};
2167
2272
  assert(sum(1,sum_array_1).number == 1253.8718091935484);
2273
+
2274
+ // Test PV
2275
+ assert((int) pv_3(new_excel_number(0.03), new_excel_number(12), new_excel_number(100)).number == -995);
2276
+ assert((int) pv_4(new_excel_number(0.03), new_excel_number(12), new_excel_number(-100), new_excel_number(100)).number == 925);
2277
+ assert((int) pv_5(new_excel_number(0.03), new_excel_number(12), new_excel_number(-100), new_excel_number(-100), new_excel_number(1)).number == 1095);
2168
2278
 
2279
+ // Test TEXT
2280
+ assert(strcmp(text(new_excel_number(1.0), new_excel_string("0%")).string, "100%") == 0);
2281
+ assert(strcmp(text(new_excel_string("1"), new_excel_string("0%")).string, "100%") == 0);
2282
+ assert(strcmp(text(BLANK, new_excel_string("0%")).string, "0%") == 0);
2283
+ assert(strcmp(text(new_excel_number(1.0), BLANK).string, "") == 0);
2284
+ assert(strcmp(text(new_excel_string("ASGASD"), new_excel_string("0%")).string, "ASGASD") == 0);
2169
2285
  // Release memory
2170
2286
  free_all_allocated_memory();
2171
2287