excel_to_code 0.1.23 → 0.2.0

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/src/commands/excel_to_c.rb +39 -92
  3. data/src/commands/excel_to_ruby.rb +9 -35
  4. data/src/commands/excel_to_x.rb +515 -536
  5. data/src/compile/c/a.out +0 -0
  6. data/src/compile/c/compile_named_reference_setters.rb +4 -6
  7. data/src/compile/c/compile_to_c.rb +34 -21
  8. data/src/compile/c/compile_to_c_header.rb +7 -7
  9. data/src/compile/c/excel_to_c_runtime.c +8 -4
  10. data/src/compile/c/map_formulae_to_c.rb +85 -86
  11. data/src/compile/c/map_values_to_c.rb +7 -1
  12. data/src/compile/c/map_values_to_c_structs.rb +1 -1
  13. data/src/compile/ruby/compile_to_ruby.rb +14 -11
  14. data/src/compile/ruby/compile_to_ruby_unit_test.rb +17 -10
  15. data/src/compile/ruby/map_formulae_to_ruby.rb +56 -56
  16. data/src/compile/ruby/map_values_to_ruby.rb +14 -2
  17. data/src/excel/area.rb +6 -8
  18. data/src/excel/excel_functions/hlookup.rb +1 -1
  19. data/src/excel/excel_functions/vlookup.rb +1 -1
  20. data/src/excel/formula_peg.rb +1 -1
  21. data/src/excel/formula_peg.txt +1 -1
  22. data/src/excel/reference.rb +4 -3
  23. data/src/excel/table.rb +4 -4
  24. data/src/extract.rb +1 -0
  25. data/src/extract/check_for_unknown_functions.rb +2 -2
  26. data/src/extract/extract_array_formulae.rb +9 -9
  27. data/src/extract/extract_everything.rb +140 -0
  28. data/src/extract/extract_formulae.rb +30 -20
  29. data/src/extract/extract_named_references.rb +37 -22
  30. data/src/extract/extract_relationships.rb +16 -3
  31. data/src/extract/extract_shared_formulae.rb +8 -11
  32. data/src/extract/extract_shared_formulae_targets.rb +1 -6
  33. data/src/extract/extract_shared_strings.rb +21 -8
  34. data/src/extract/extract_simple_formulae.rb +11 -6
  35. data/src/extract/extract_table.rb +26 -13
  36. data/src/extract/extract_values.rb +35 -11
  37. data/src/extract/extract_worksheet_dimensions.rb +13 -3
  38. data/src/extract/extract_worksheet_names.rb +16 -3
  39. data/src/extract/extract_worksheet_table_relationships.rb +16 -4
  40. data/src/extract/simple_extract_from_xml.rb +9 -11
  41. data/src/rewrite.rb +3 -0
  42. data/src/rewrite/ast_copy_formula.rb +5 -1
  43. data/src/rewrite/ast_expand_array_formulae.rb +71 -59
  44. data/src/rewrite/caching_formula_parser.rb +110 -0
  45. data/src/rewrite/rewrite_array_formulae.rb +21 -14
  46. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +41 -13
  47. data/src/rewrite/rewrite_shared_formulae.rb +17 -18
  48. data/src/rewrite/rewrite_values_to_ast.rb +2 -0
  49. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +28 -25
  50. data/src/simplify.rb +1 -0
  51. data/src/simplify/count_formula_references.rb +22 -23
  52. data/src/simplify/emergency_array_formula_replace_indirect_bodge.rb +44 -0
  53. data/src/simplify/identify_dependencies.rb +7 -8
  54. data/src/simplify/identify_repeated_formula_elements.rb +5 -6
  55. data/src/simplify/inline_formulae.rb +48 -48
  56. data/src/simplify/map_formulae_to_values.rb +197 -79
  57. data/src/simplify/remove_cells.rb +13 -6
  58. data/src/simplify/replace_arithmetic_on_ranges.rb +42 -28
  59. data/src/simplify/replace_arrays_with_single_cells.rb +11 -5
  60. data/src/simplify/replace_column_with_column_number.rb +31 -23
  61. data/src/simplify/replace_common_elements_in_formulae.rb +16 -17
  62. data/src/simplify/replace_indirects_with_references.rb +26 -21
  63. data/src/simplify/replace_named_references.rb +26 -31
  64. data/src/simplify/replace_offsets_with_references.rb +33 -34
  65. data/src/simplify/replace_ranges_with_array_literals.rb +48 -20
  66. data/src/simplify/replace_shared_strings.rb +15 -13
  67. data/src/simplify/replace_string_join_on_ranges.rb +7 -9
  68. data/src/simplify/replace_table_references.rb +16 -11
  69. data/src/simplify/replace_values_with_constants.rb +6 -4
  70. data/src/simplify/simplify_arithmetic.rb +33 -19
  71. data/src/simplify/sort_into_calculation_order.rb +13 -13
  72. data/src/simplify/wrap_formulae_that_return_arrays_and_are_not_in_arrays.rb +21 -13
  73. metadata +19 -2
@@ -15,21 +15,20 @@ class IdentifyRepeatedFormulaElements
15
15
 
16
16
  def count(references)
17
17
  @references = references
18
- references.each do |sheet,cells|
19
- cells.each do |ref,ast|
20
- identify_repeated_formulae(ast)
21
- end
18
+ references.each do |ref,ast|
19
+ identify_repeated_formulae(ast)
22
20
  end
23
21
  return @counted_elements
24
22
  end
25
23
 
24
+ IGNORE_TYPES = {:number => true, :string => true, :blank => true, :null => true, :error => true, :boolean_true => true, :boolean_false => true, :sheet_reference => true, :cell => true, :row => true}
26
25
 
27
26
  def identify_repeated_formulae(ast)
28
27
  string = ast.to_s
29
28
  return unless ast.is_a?(Array)
30
- return if [:number,:string,:blank,:null,:error,:boolean_true,:boolean_false,:sheet_reference,:cell, :row].include?(ast.first)
29
+ return if IGNORE_TYPES.has_key?(ast.first)
31
30
  return if string.length < bothered_threshold
32
- @counted_elements[string] += 1
31
+ @counted_elements[ast] += 1
33
32
  ast.each do |a|
34
33
  identify_repeated_formulae(a)
35
34
  end
@@ -1,67 +1,67 @@
1
1
  class InlineFormulaeAst
2
+
3
+ BLANK = [:blank]
2
4
 
3
5
  attr_accessor :references, :current_sheet_name, :inline_ast
4
- attr_accessor :replacements_made_in_the_last_pass
6
+ attr_accessor :count_replaced
5
7
 
6
- def initialize(references, current_sheet_name, inline_ast = nil)
8
+ def initialize(references = nil, current_sheet_name = nil, inline_ast = nil)
7
9
  @references, @current_sheet_name, @inline_ast = references, [current_sheet_name], inline_ast
8
- @replacements_made_in_the_last_pass = 0
9
- @inline_ast ||= lambda { |sheet,reference,references| true }
10
+ @count_replaced = 0
11
+ @inline_ast ||= lambda { |sheet, ref, references| true } # Default is to always inline
10
12
  end
11
13
 
12
14
  def map(ast)
13
15
  return ast unless ast.is_a?(Array)
14
- operator = ast[0]
15
- if respond_to?(operator)
16
- send(operator,*ast[1..-1])
17
- else
18
- [operator,*ast[1..-1].map {|a| map(a) }]
16
+ if respond_to?(ast[0])
17
+ send(ast[0], ast)
18
+ else # In this case needs to be an else because don't want to map first argument in OFFSET(cell_to_offset_from_shouldn't_be_mapped, rows, columns)
19
+ ast.each { |a| map(a) }
19
20
  end
21
+ ast
20
22
  end
21
23
 
22
- def function(name,*args)
23
- case name
24
- when 'OFFSET'
25
- [:function, name, args.shift, *args.map { |a| map(a) }]
24
+ def function(ast)
25
+ if ast[1] == :OFFSET && (ast[2][0] == :cell || ast[2][0] == :sheet_reference)
26
+ # Don't map the second argument - it should be left as a cell refernce
27
+ ast[3..-1].each {|a| map(a) }
26
28
  else
27
- [:function, name, *args.map { |a| map(a) }]
29
+ # Otherwise good to map all the other arguments
30
+ ast.each { |a| map(a) }
28
31
  end
29
32
  end
30
33
 
31
- def sheet_reference(sheet,reference)
32
- return [:error, '#REF!'] unless references[sheet]
33
- if inline_ast.call(sheet,reference.last.upcase.gsub('$',''),references)
34
- @replacements_made_in_the_last_pass += 1
35
- ast = references[sheet][reference.last.upcase.gsub('$','')]
36
- if ast
37
- current_sheet_name.push(sheet)
38
- result = map(ast)
39
- current_sheet_name.pop
40
- else
41
- result = [:blank]
42
- end
43
- else
44
- result = [:sheet_reference,sheet,reference]
45
- end
46
- result
34
+ # Should be of the form [:sheet_reference, sheet_name, reference]
35
+ # FIXME: Can we rely on reference always being a [:cell, ref] at this stage?
36
+ def sheet_reference(ast)
37
+ return unless ast[2][0] == :cell
38
+ sheet = ast[1].to_sym
39
+ ref = ast[2][1].to_s.upcase.gsub('$','').to_sym
40
+ # FIXME: Need to check if valid worksheet and return [:error, "#REF!"] if not
41
+ # Now check user preference on this
42
+ return unless inline_ast.call(sheet,ref, references)
43
+ @count_replaced += 1
44
+ ast_to_inline = references[[sheet, ref]]
45
+ return ast.replace(BLANK) unless ast_to_inline
46
+ current_sheet_name.push(sheet)
47
+ map(ast_to_inline)
48
+ current_sheet_name.pop
49
+ ast.replace(ast_to_inline)
47
50
  end
48
51
 
49
- # TODO: Optimize by replacing contents of references hash with the inlined version
50
- def cell(reference)
51
- if inline_ast.call(current_sheet_name.last,reference.upcase.gsub('$',''),references)
52
- @replacements_made_in_the_last_pass += 1
53
- ast = references[current_sheet_name.last][reference.upcase.gsub('$','')]
54
- if ast
55
- map(ast)
56
- else
57
- [:blank]
58
- end
59
- else
60
- if current_sheet_name.size > 1
61
- [:sheet_reference,current_sheet_name.last,[:cell,reference]]
62
- else
63
- [:cell,reference]
64
- end
52
+ # Format [:cell, ref]
53
+ def cell(ast)
54
+ sheet = current_sheet_name.last
55
+ ref = ast[1].to_s.upcase.gsub('$', '').to_sym
56
+ if inline_ast.call(sheet, ref, references)
57
+ @count_replaced += 1
58
+ ast_to_inline = references[[sheet, ref]]
59
+ return ast.replace(BLANK) unless ast_to_inline
60
+ map(ast_to_inline)
61
+ ast.replace(ast_to_inline)
62
+ # FIXME: Check - is this right? does it work recursively enough?
63
+ elsif current_sheet_name.size > 1
64
+ ast.replace([:sheet_reference, sheet, ast.dup])
65
65
  end
66
66
  end
67
67
 
@@ -76,7 +76,7 @@ class InlineFormulae
76
76
  self.new.replace(*args)
77
77
  end
78
78
 
79
- attr_accessor :replacements_made_in_the_last_pass
79
+ attr_accessor :count_replaced
80
80
 
81
81
  def replace(input,output)
82
82
  rewriter = InlineFormulaeAst.new(references, default_sheet_name, inline_ast)
@@ -89,6 +89,6 @@ class InlineFormulae
89
89
  output.puts line
90
90
  end
91
91
  end
92
- @replacements_made_in_the_last_pass = rewriter.replacements_made_in_the_last_pass
92
+ @count_replaced = rewriter.count_replaced
93
93
  end
94
94
  end
@@ -16,115 +16,214 @@ class MapFormulaeToValues
16
16
  @value_for_ast = MapValuesToRuby.new
17
17
  @calculator = FormulaeCalculator.new
18
18
  @replacements_made_in_the_last_pass = 0
19
- @cache = {}
20
19
  end
21
20
 
22
- def reset
23
- @cache = {}
21
+ def original_excel_filename=(new_filename)
22
+ @original_excel_filename = new_filename
23
+ @calculator.original_excel_filename = new_filename
24
24
  end
25
25
 
26
- # FIXME: Caching works in the odd edge cases of long formula
27
- # but I really need to find the root cause of the problem
28
- def map(ast)
29
- @calculator.original_excel_filename = original_excel_filename
30
- @cache[ast] ||= do_map(ast)
26
+ def reset
27
+ # Not used any more
28
+ # FIXME: Remove references to this method
31
29
  end
32
30
 
33
- def do_map(ast)
34
- return ast unless ast.is_a?(Array)
35
- operator = ast[0]
36
- if respond_to?(operator)
37
- send(operator,*ast[1..-1])
38
- else
39
- [operator,*ast[1..-1].map {|a| map(a) }]
40
- end
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}
32
+
33
+ def map(ast)
34
+ ast[1..-1].each do |a|
35
+ next unless a.is_a?(Array)
36
+ next if DO_NOT_MAP[(a[0])]
37
+ map(a)
38
+ end # Depth first best in this case?
39
+ send(ast[0], ast) if respond_to?(ast[0])
40
+ ast
41
41
  end
42
42
 
43
- def prefix(operator,argument)
44
- argument_value = value(map(argument))
45
- return [:prefix, operator, map(argument)] if argument_value == :not_a_value
46
- return ast_for_value(argument_value || 0) if operator == "+"
47
- ast_for_value(@calculator.negative(argument_value))
43
+ # [:prefix, operator, argument]
44
+ def prefix(ast)
45
+ operator, argument = ast[1], ast[2]
46
+ argument_value = value(argument)
47
+ return if argument_value == :not_a_value
48
+ return ast.replace(ast_for_value(argument_value || 0)) if operator == "+"
49
+ ast.replace(ast_for_value(@calculator.negative(argument_value)))
48
50
  end
49
51
 
50
- def arithmetic(left,operator,right)
51
- l = value(map(left))
52
- r = value(map(right))
53
- if (l != :not_a_value) && (r != :not_a_value)
54
- formula_value(operator.last,l,r)
55
- else
56
- [:arithmetic,map(left),operator,map(right)]
52
+ # [:arithmetic, left, operator, right]
53
+ def arithmetic(ast)
54
+ left, operator, right = ast[1], ast[2], ast[3]
55
+ l = @calculator.number_argument(value(left))
56
+ r = @calculator.number_argument(value(right))
57
+ if (l == :not_a_value) && (r == :not_a_value)
58
+ return ast
59
+ elsif (l != :not_a_value) && (r != :not_a_value)
60
+ ast.replace(formula_value(operator.last,l,r))
61
+ # SPECIAL CASES
62
+ elsif l == 0
63
+ case operator.last
64
+ when :+
65
+ ast.replace(right)
66
+ when :*, :/, :^
67
+ ast.replace([:number, 0])
68
+ end
69
+ elsif r == 0
70
+ case operator.last
71
+ when :+, :-
72
+ ast.replace(left)
73
+ when :*
74
+ ast.replace([:number, 0])
75
+ when :/
76
+ ast.replace([:error, :'#DIV/0!'])
77
+ when :^
78
+ ast.replace([:number, 1])
79
+ end
80
+ elsif l == 1
81
+ case operator.last
82
+ when :*
83
+ ast.replace(right)
84
+ when :^
85
+ ast.replace([:number, 1])
86
+ end
87
+ elsif r == 1
88
+ case operator.last
89
+ when :*, :/, :^
90
+ ast.replace(left)
91
+ end
57
92
  end
93
+ ast
58
94
  end
59
-
60
- alias :comparison :arithmetic
61
95
 
62
- def percentage(number)
63
- ast_for_value(value([:percentage, number]))
96
+ def comparison(ast)
97
+ left, operator, right = ast[1], ast[2], ast[3]
98
+ l = value(left)
99
+ r = value(right)
100
+ return ast if (l == :not_a_value) || (r == :not_a_value)
101
+ ast.replace(formula_value(operator.last,l,r))
64
102
  end
65
-
66
- def string_join(*args)
67
- values = args.map { |a| value(map(a)) } # FIXME: These eval statements are really bugging me. Must find a better solution
68
- if values.any? { |a| a == :not_a_value }
69
- [:string_join,*args.map { |a| map(a) }]
70
- else
71
- ast_for_value(@calculator.string_join(*values))
72
- end
103
+
104
+ # [:percentage, number]
105
+ def percentage(ast)
106
+ ast.replace(ast_for_value(value([:percentage, ast[1]])))
73
107
  end
74
108
 
75
- FUNCTIONS_THAT_SHOULD_NOT_BE_CONVERTED = %w{TODAY RAND RANDBETWEEN INDIRECT}
109
+ # [:string_join, stringA, stringB, ...]
110
+ def string_join(ast)
111
+ values = ast[1..-1].map { |a| value(a) }
112
+ return if values.any? { |a| a == :not_a_value }
113
+ ast.replace(ast_for_value(@calculator.string_join(*values)))
114
+ end
76
115
 
77
- def function(name,*args)
78
- if FUNCTIONS_THAT_SHOULD_NOT_BE_CONVERTED.include?(name)
79
- [:function,name,*args.map { |a| map(a) }]
80
- elsif respond_to?("map_#{name.downcase}")
81
- send("map_#{name.downcase}",*args)
116
+ # [:function, function_name, arg1, arg2, ...]
117
+ def function(ast)
118
+ name = ast[1]
119
+ return if name == :INDIRECT
120
+ return if name == :OFFSET
121
+ return if name == :COLUMN
122
+ if respond_to?("map_#{name.to_s.downcase}")
123
+ send("map_#{name.to_s.downcase}",ast)
82
124
  else
83
- values = args.map { |a| value(map(a)) }
84
- if values.any? { |a| a == :not_a_value }
85
- [:function,name,*args.map { |a| map(a) }]
86
- else
87
- formula_value(name,*values)
88
- end
125
+ values = ast[2..-1].map { |a| value(a) }
126
+ return if values.any? { |a| a == :not_a_value }
127
+ ast.replace(formula_value(name,*values))
89
128
  end
90
129
  end
91
130
 
92
- def map_count(range)
93
- return [:function, "COUNT", range] unless [:array, :cell, :sheet_reference].include?(range.first)
131
+ # [:function, "COUNT", range]
132
+ def map_count(ast)
133
+ range = ast[2]
134
+ return unless [:array, :cell, :sheet_reference].include?(range.first)
94
135
  range = array_as_values(range)
95
- ast_for_value(range.size * range.first.size)
136
+ ast.replace(ast_for_value(range.size * range.first.size))
96
137
  end
97
138
 
98
- def map_index(array,row_number,column_number = :not_specified)
99
- return map_index_with_only_two_arguments(array,row_number) if column_number == :not_specified
139
+ # [:function, "INDEX", array, row_number, column_number]
140
+ def map_index(ast)
141
+ return map_index_with_only_two_arguments(ast) if ast.length == 4
100
142
 
101
- array_mapped = map(array)
102
- row_as_number = value(map(row_number))
103
- column_as_number = value(map(column_number))
143
+ array_mapped = ast[2]
144
+ row_as_number = value(ast[3])
145
+ column_as_number = value(ast[4])
104
146
 
105
- return [:function, "INDEX", array_mapped, map(row_number), map(column_number)] if row_as_number == :not_a_value || column_as_number == :not_a_value
147
+ return if row_as_number == :not_a_value
148
+ return if column_as_number == :not_a_value
106
149
 
107
- array_as_values = array_as_values(array)
108
- return [:function, "INDEX", array_mapped, map(row_number), map(column_number)] unless array_as_values
150
+ array_as_values = array_as_values(array_mapped)
151
+ return unless array_as_values
109
152
 
110
- result = @calculator.send(MapFormulaeToRuby::FUNCTIONS["INDEX"],array_as_values,row_as_number,column_as_number)
153
+ result = @calculator.send(MapFormulaeToRuby::FUNCTIONS[:INDEX],array_as_values,row_as_number,column_as_number)
111
154
  result = [:number, 0] if result == [:blank]
112
155
  result = ast_for_value(result)
113
- result
156
+ ast.replace(result)
114
157
  end
115
158
 
116
- def map_index_with_only_two_arguments(array,row_number)
117
- array_mapped = map(array)
118
- row_as_number = value(map(row_number))
119
- return [:function, "INDEX", array_mapped, map(row_number)] if row_as_number == :not_a_value
120
- array_as_values = array_as_values(array)
121
- return [:function, "INDEX", array_mapped, map(row_number)] unless array_as_values
122
- result = @calculator.send(MapFormulaeToRuby::FUNCTIONS["INDEX"],array_as_values,row_as_number)
159
+ # [:function, "INDEX", array, row_number]
160
+ def map_index_with_only_two_arguments(ast)
161
+ array_mapped = ast[2]
162
+ row_as_number = value(ast[3])
163
+ return if row_as_number == :not_a_value
164
+ array_as_values = array_as_values(array_mapped)
165
+ return unless array_as_values
166
+ result = @calculator.send(MapFormulaeToRuby::FUNCTIONS[:INDEX],array_as_values,row_as_number)
123
167
  result = [:number, 0] if result == [:blank]
124
168
  result = ast_for_value(result)
125
- result
169
+ ast.replace(result)
126
170
  end
127
-
171
+
172
+ # [:function, :SUM, a, b, c...]
173
+ def map_sum(ast)
174
+ values = ast[2..-1].map { |a| value(a) }
175
+ return partially_map_sum(ast) if values.any? { |a| a == :not_a_value }
176
+ ast.replace(formula_value(:SUM,*values))
177
+ end
178
+
179
+ def partially_map_sum(ast)
180
+ number_total = 0
181
+ not_number_array = []
182
+ ast[2..-1].each do |a|
183
+ result = filter_numbers_and_not(a)
184
+ number_total += result.first
185
+ not_number_array.concat(result.last)
186
+ end
187
+ if number_total == 0 && not_number_array.empty?
188
+ ast.replace([:number, number_total])
189
+ # FIXME: Will I be haunted by this? What if doing a sum of something that isn't a number
190
+ # and so what is expected is a VALUE error?. YES. This doesn't work well.
191
+ #elsif number_total == 0 && not_number_array.size == 1
192
+ # p not_number_array[0]
193
+ # ast.replace(not_number_array[0])
194
+ else
195
+ new_ast = [:function, :SUM].concat(not_number_array)
196
+ new_ast.push([:number, number_total]) unless number_total == 0
197
+ ast.replace(new_ast)
198
+ end
199
+ ast
200
+ end
201
+
202
+ def filter_numbers_and_not(ast)
203
+ number_total = 0
204
+ not_number_array = []
205
+ case ast.first
206
+ when :array
207
+ array_as_values(ast).each do |row|
208
+ row.each do |c|
209
+ result = filter_numbers_and_not(c)
210
+ number_total += result.first
211
+ not_number_array.concat(result.last)
212
+ end
213
+ end
214
+ when :blank, :number, :percentage, :string, :boolean_true, :boolean_false
215
+ number = @calculator.number_argument(value(ast))
216
+ if number.is_a?(Symbol)
217
+ not_number_array.push(ast)
218
+ else
219
+ number_total += number
220
+ end
221
+ else
222
+ not_number_array.push(ast)
223
+ end
224
+ [number_total, not_number_array]
225
+ end
226
+
128
227
  def array_as_values(array_mapped)
129
228
  case array_mapped.first
130
229
  when :array
@@ -139,11 +238,29 @@ class MapFormulaeToValues
139
238
  nil
140
239
  end
141
240
  end
241
+
242
+ ERRORS = {
243
+ :"#NAME?" => :name,
244
+ :"#VALUE!" => :value,
245
+ :"#DIV/0!" => :div0,
246
+ :"#REF!" => :ref,
247
+ :"#N/A" => :na,
248
+ :"#NUM!" => :num
249
+ }
142
250
 
143
251
  def value(ast)
144
252
  return extract_values_from_array(ast) if ast.first == :array
145
- return :not_a_value unless @value_for_ast.respond_to?(ast.first)
146
- eval(@value_for_ast.send(*ast))
253
+ case ast.first
254
+ when :blank; nil
255
+ when :null; nil
256
+ when :number; ast[1]
257
+ when :percentage; ast[1]/100.0
258
+ when :string; ast[1]
259
+ when :error; ERRORS[ast[1]]
260
+ when :boolean_true; true
261
+ when :boolean_false; false
262
+ else return :not_a_value
263
+ end
147
264
  end
148
265
 
149
266
  def extract_values_from_array(ast)
@@ -157,15 +274,15 @@ class MapFormulaeToValues
157
274
  end
158
275
 
159
276
  def formula_value(ast_name,*arguments)
160
- raise NotSupportedException.new("#{ast_name.inspect} function not recognised in #{MapFormulaeToRuby::FUNCTIONS.inspect}") unless MapFormulaeToRuby::FUNCTIONS.has_key?(ast_name)
277
+ raise NotSupportedException.new("#{ast_name} function not recognised in #{MapFormulaeToRuby::FUNCTIONS.inspect}") unless MapFormulaeToRuby::FUNCTIONS.has_key?(ast_name)
161
278
  ast_for_value(@calculator.send(MapFormulaeToRuby::FUNCTIONS[ast_name],*arguments))
162
279
  end
163
280
 
164
281
  def ast_for_value(value)
165
282
  return value if value.is_a?(Array) && value.first.is_a?(Symbol)
166
283
  @replacements_made_in_the_last_pass += 1
167
- case value
168
- when Numeric; [:number,value.inspect]
284
+ ast = case value
285
+ when Numeric; [:number,value]
169
286
  when true; [:boolean_true]
170
287
  when false; [:boolean_false]
171
288
  when Symbol;
@@ -177,6 +294,7 @@ class MapFormulaeToValues
177
294
  else
178
295
  raise NotSupportedException.new("Ast for #{value.inspect} of class #{value.class} not recognised")
179
296
  end
297
+ CachingFormulaParser.map(ast)
180
298
  end
181
299
 
182
300
  end