excel_to_code 0.0.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.
Files changed (123) hide show
  1. data/README +41 -0
  2. data/bin/excel_to_c +63 -0
  3. data/bin/excel_to_ruby +9 -0
  4. data/src/commands.rb +2 -0
  5. data/src/commands/excel_to_c.rb +858 -0
  6. data/src/commands/excel_to_ruby.rb +620 -0
  7. data/src/compile.rb +2 -0
  8. data/src/compile/c.rb +5 -0
  9. data/src/compile/c/compile_to_c.rb +62 -0
  10. data/src/compile/c/compile_to_c_header.rb +26 -0
  11. data/src/compile/c/compile_to_c_unit_test.rb +42 -0
  12. data/src/compile/c/excel_to_c_runtime.c +2029 -0
  13. data/src/compile/c/map_formulae_to_c.rb +184 -0
  14. data/src/compile/c/map_sheet_names_to_c_names.rb +19 -0
  15. data/src/compile/c/map_values_to_c.rb +85 -0
  16. data/src/compile/c/map_values_to_c_structs.rb +37 -0
  17. data/src/compile/ruby.rb +3 -0
  18. data/src/compile/ruby/compile_to_ruby.rb +33 -0
  19. data/src/compile/ruby/compile_to_ruby_unit_test.rb +28 -0
  20. data/src/compile/ruby/excel_to_ruby_runtime.rb +1 -0
  21. data/src/compile/ruby/map_formulae_to_ruby.rb +95 -0
  22. data/src/compile/ruby/map_sheet_names_to_ruby_names.rb +19 -0
  23. data/src/compile/ruby/map_values_to_ruby.rb +65 -0
  24. data/src/excel.rb +5 -0
  25. data/src/excel/area.rb +93 -0
  26. data/src/excel/excel_functions.rb +84 -0
  27. data/src/excel/excel_functions/abs.rb +14 -0
  28. data/src/excel/excel_functions/add.rb +18 -0
  29. data/src/excel/excel_functions/and.rb +30 -0
  30. data/src/excel/excel_functions/apply_to_range.rb +17 -0
  31. data/src/excel/excel_functions/average.rb +12 -0
  32. data/src/excel/excel_functions/choose.rb +18 -0
  33. data/src/excel/excel_functions/cosh.rb +9 -0
  34. data/src/excel/excel_functions/count.rb +9 -0
  35. data/src/excel/excel_functions/counta.rb +8 -0
  36. data/src/excel/excel_functions/divide.rb +23 -0
  37. data/src/excel/excel_functions/excel_equal.rb +20 -0
  38. data/src/excel/excel_functions/excel_if.rb +8 -0
  39. data/src/excel/excel_functions/excel_match.rb +51 -0
  40. data/src/excel/excel_functions/find.rb +39 -0
  41. data/src/excel/excel_functions/iferror.rb +10 -0
  42. data/src/excel/excel_functions/index.rb +48 -0
  43. data/src/excel/excel_functions/left.rb +12 -0
  44. data/src/excel/excel_functions/less_than.rb +26 -0
  45. data/src/excel/excel_functions/less_than_or_equal.rb +26 -0
  46. data/src/excel/excel_functions/max.rb +12 -0
  47. data/src/excel/excel_functions/min.rb +12 -0
  48. data/src/excel/excel_functions/mod.rb +15 -0
  49. data/src/excel/excel_functions/more_than.rb +26 -0
  50. data/src/excel/excel_functions/more_than_or_equal.rb +26 -0
  51. data/src/excel/excel_functions/multiply.rb +24 -0
  52. data/src/excel/excel_functions/negative.rb +12 -0
  53. data/src/excel/excel_functions/not_equal.rb +19 -0
  54. data/src/excel/excel_functions/number_argument.rb +30 -0
  55. data/src/excel/excel_functions/pi.rb +7 -0
  56. data/src/excel/excel_functions/pmt.rb +16 -0
  57. data/src/excel/excel_functions/power.rb +18 -0
  58. data/src/excel/excel_functions/round.rb +13 -0
  59. data/src/excel/excel_functions/rounddown.rb +14 -0
  60. data/src/excel/excel_functions/roundup.rb +17 -0
  61. data/src/excel/excel_functions/string_join.rb +19 -0
  62. data/src/excel/excel_functions/subtotal.rb +13 -0
  63. data/src/excel/excel_functions/subtract.rb +18 -0
  64. data/src/excel/excel_functions/sum.rb +8 -0
  65. data/src/excel/excel_functions/sumif.rb +7 -0
  66. data/src/excel/excel_functions/sumifs.rb +74 -0
  67. data/src/excel/excel_functions/sumproduct.rb +32 -0
  68. data/src/excel/excel_functions/vlookup.rb +49 -0
  69. data/src/excel/formula_peg.rb +238 -0
  70. data/src/excel/formula_peg.txt +45 -0
  71. data/src/excel/reference.rb +56 -0
  72. data/src/excel/table.rb +108 -0
  73. data/src/excel_to_code.rb +7 -0
  74. data/src/extract.rb +13 -0
  75. data/src/extract/check_for_unknown_functions.rb +20 -0
  76. data/src/extract/extract_array_formulae.rb +23 -0
  77. data/src/extract/extract_formulae.rb +36 -0
  78. data/src/extract/extract_named_references.rb +38 -0
  79. data/src/extract/extract_relationships.rb +10 -0
  80. data/src/extract/extract_shared_formulae.rb +23 -0
  81. data/src/extract/extract_shared_strings.rb +20 -0
  82. data/src/extract/extract_simple_formulae.rb +18 -0
  83. data/src/extract/extract_table.rb +24 -0
  84. data/src/extract/extract_values.rb +29 -0
  85. data/src/extract/extract_worksheet_dimensions.rb +11 -0
  86. data/src/extract/extract_worksheet_names.rb +10 -0
  87. data/src/extract/extract_worksheet_table_relationships.rb +10 -0
  88. data/src/extract/simple_extract_from_xml.rb +19 -0
  89. data/src/rewrite.rb +10 -0
  90. data/src/rewrite/ast_copy_formula.rb +42 -0
  91. data/src/rewrite/ast_expand_array_formulae.rb +180 -0
  92. data/src/rewrite/rewrite_array_formulae.rb +71 -0
  93. data/src/rewrite/rewrite_array_formulae_to_arrays.rb +18 -0
  94. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +56 -0
  95. data/src/rewrite/rewrite_formulae_to_ast.rb +24 -0
  96. data/src/rewrite/rewrite_merge_formulae_and_values.rb +18 -0
  97. data/src/rewrite/rewrite_relationship_id_to_filename.rb +22 -0
  98. data/src/rewrite/rewrite_shared_formulae.rb +38 -0
  99. data/src/rewrite/rewrite_values_to_ast.rb +28 -0
  100. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +90 -0
  101. data/src/rewrite/rewrite_worksheet_names.rb +20 -0
  102. data/src/simplify.rb +16 -0
  103. data/src/simplify/count_formula_references.rb +58 -0
  104. data/src/simplify/identify_dependencies.rb +56 -0
  105. data/src/simplify/identify_repeated_formula_elements.rb +37 -0
  106. data/src/simplify/inline_formulae.rb +77 -0
  107. data/src/simplify/map_formulae_to_values.rb +157 -0
  108. data/src/simplify/remove_cells.rb +18 -0
  109. data/src/simplify/replace_arrays_with_single_cells.rb +27 -0
  110. data/src/simplify/replace_blanks.rb +58 -0
  111. data/src/simplify/replace_common_elements_in_formulae.rb +19 -0
  112. data/src/simplify/replace_formulae_with_calculated_values.rb +21 -0
  113. data/src/simplify/replace_indirects_with_references.rb +44 -0
  114. data/src/simplify/replace_named_references.rb +82 -0
  115. data/src/simplify/replace_ranges_with_array_literals.rb +54 -0
  116. data/src/simplify/replace_shared_strings.rb +49 -0
  117. data/src/simplify/replace_table_references.rb +71 -0
  118. data/src/simplify/replace_values_with_constants.rb +47 -0
  119. data/src/simplify/simplify_arithmetic.rb +54 -0
  120. data/src/util.rb +2 -0
  121. data/src/util/not_supported_exception.rb +2 -0
  122. data/src/util/try.rb +9 -0
  123. metadata +207 -0
@@ -0,0 +1,157 @@
1
+ require_relative '../compile'
2
+ require_relative '../excel/excel_functions'
3
+ require_relative '../util'
4
+
5
+ class FormulaeCalculator
6
+ include ExcelFunctions
7
+ end
8
+
9
+ class MapFormulaeToValues
10
+
11
+ def initialize
12
+ @value_for_ast = MapValuesToRuby.new
13
+ @calculator = FormulaeCalculator.new
14
+ end
15
+
16
+ def map(ast)
17
+ return ast unless ast.is_a?(Array)
18
+ operator = ast[0]
19
+ if respond_to?(operator)
20
+ send(operator,*ast[1..-1])
21
+ else
22
+ [operator,*ast[1..-1].map {|a| map(a) }]
23
+ end
24
+ end
25
+
26
+ def prefix(operator,argument)
27
+ argument_value = value(map(argument))
28
+ return [:prefix, operator, map(argument)] if argument_value == :not_a_value
29
+ return ast_for_value(argument_value || 0) if operator == "+"
30
+ ast_for_value(@calculator.negative(argument_value))
31
+ end
32
+
33
+ def arithmetic(left,operator,right)
34
+ l = value(map(left))
35
+ r = value(map(right))
36
+ if (l != :not_a_value) && (r != :not_a_value)
37
+ formula_value(operator.last,l,r)
38
+ else
39
+ [:arithmetic,map(left),operator,map(right)]
40
+ end
41
+ end
42
+
43
+ alias :comparison :arithmetic
44
+
45
+ def percentage(number)
46
+ ast_for_value(value([:percentage, number]))
47
+ end
48
+
49
+ def string_join(*args)
50
+ values = args.map { |a| value(map(a)) } # FIXME: These eval statements are really bugging me. Must find a better solution
51
+ if values.any? { |a| a == :not_a_value }
52
+ [:string_join,*args.map { |a| map(a) }]
53
+ else
54
+ ast_for_value(@calculator.string_join(*values))
55
+ end
56
+ end
57
+
58
+ FUNCTIONS_THAT_SHOULD_NOT_BE_CONVERTED = %w{TODAY RAND RANDBETWEEN INDIRECT}
59
+
60
+ def function(name,*args)
61
+ if FUNCTIONS_THAT_SHOULD_NOT_BE_CONVERTED.include?(name)
62
+ [:function,name,*args.map { |a| map(a) }]
63
+ elsif respond_to?("map_#{name.downcase}")
64
+ send("map_#{name.downcase}",*args)
65
+ else
66
+ values = args.map { |a| value(map(a)) }
67
+ if values.any? { |a| a == :not_a_value }
68
+ [:function,name,*args.map { |a| map(a) }]
69
+ else
70
+ formula_value(name,*values)
71
+ end
72
+ end
73
+ end
74
+
75
+ def map_index(array,row_number,column_number = :not_specified)
76
+ return map_index_with_only_two_arguments(array,row_number) if column_number == :not_specified
77
+
78
+ array_mapped = map(array)
79
+ row_as_number = value(map(row_number))
80
+ column_as_number = value(map(column_number))
81
+
82
+ return [:function, "INDEX", array_mapped, map(row_number), map(column_number)] if row_as_number == :not_a_value || column_as_number == :not_a_value
83
+
84
+ array_as_values = array_as_values(array)
85
+ return [:function, "INDEX", array_mapped, map(row_number), map(column_number)] unless array_as_values
86
+
87
+ result = @calculator.send(MapFormulaeToRuby::FUNCTIONS["INDEX"],array_as_values,row_as_number,column_as_number)
88
+ result = ast_for_value(result) unless result.is_a?(Array)
89
+ result
90
+ end
91
+
92
+ def map_index_with_only_two_arguments(array,row_number)
93
+ array_mapped = map(array)
94
+ row_as_number = value(map(row_number))
95
+ return [:function, "INDEX", array_mapped, map(row_number)] if row_as_number == :not_a_value
96
+ array_as_values = array_as_values(array)
97
+ return [:function, "INDEX", array_mapped, map(row_number)] unless array_as_values
98
+ result = @calculator.send(MapFormulaeToRuby::FUNCTIONS["INDEX"],array_as_values,row_as_number)
99
+ result = ast_for_value(result) unless result.is_a?(Array)
100
+ result
101
+ end
102
+
103
+ def array_as_values(array_mapped)
104
+ case array_mapped.first
105
+ when :array
106
+ array_mapped[1..-1].map do |row|
107
+ row[1..-1].map do |cell|
108
+ cell
109
+ end
110
+ end
111
+ when :cell, :sheet_reference, :blank, :number, :percentage, :string, :error, :boolean_true, :boolean_false
112
+ [[array_mapped]]
113
+ else
114
+ nil
115
+ end
116
+ end
117
+
118
+
119
+
120
+ def value(ast)
121
+ return extract_values_from_array(ast) if ast.first == :array
122
+ return :not_a_value unless @value_for_ast.respond_to?(ast.first)
123
+ eval(@value_for_ast.send(*ast))
124
+ end
125
+
126
+ def extract_values_from_array(ast)
127
+ ast[1..-1].map do |row|
128
+ row[1..-1].map do |cell|
129
+ v = value(cell)
130
+ return :not_a_value if v == :not_a_value
131
+ v
132
+ end
133
+ end
134
+ end
135
+
136
+ def formula_value(ast_name,*arguments)
137
+ raise NotSupportedException.new("#{ast_name.inspect} function not recognised in #{MapFormulaeToRuby::FUNCTIONS.inspect}") unless MapFormulaeToRuby::FUNCTIONS.has_key?(ast_name)
138
+ ast_for_value(@calculator.send(MapFormulaeToRuby::FUNCTIONS[ast_name],*arguments))
139
+ end
140
+
141
+ def ast_for_value(value)
142
+ case value
143
+ when Numeric; [:number,value.inspect]
144
+ when true; [:boolean_true]
145
+ when false; [:boolean_false]
146
+ when Symbol;
147
+ raise NotSupportedException.new("Error #{value.inspect} not recognised") unless MapFormulaeToRuby::REVERSE_ERRORS[value.inspect]
148
+ [:error,MapFormulaeToRuby::REVERSE_ERRORS[value.inspect]]
149
+ when String; [:string,value]
150
+ when Array; [:array,*value.map { |row| [:row, *row.map { |c| ast_for_value(c) }]}]
151
+ when nil; [:blank]
152
+ else
153
+ raise NotSupportedException.new("Ast for #{value.inspect} of class #{value.class} not recognised")
154
+ end
155
+ end
156
+
157
+ end
@@ -0,0 +1,18 @@
1
+
2
+ class RemoveCells
3
+
4
+ attr_accessor :cells_to_keep
5
+
6
+ def self.rewrite(*args)
7
+ self.new.rewrite(*args)
8
+ end
9
+
10
+ def rewrite(input,output)
11
+ input.lines do |line|
12
+ ref = line[/^(.*?)\t/,1]
13
+ if cells_to_keep.has_key?(ref)
14
+ output.puts line
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ require_relative '../excel'
2
+
3
+ class ReplaceArraysWithSingleCells
4
+
5
+ def self.replace(*args)
6
+ self.new.replace(*args)
7
+ end
8
+
9
+ def replace(input,output)
10
+
11
+ input.lines do |line|
12
+ # Looks to match shared string lines
13
+ if line =~ /\[:array/
14
+ content = line.split("\t")
15
+ ast = eval(content.pop)
16
+ if ast.first == :array
17
+ output.puts "#{content.join("\t")}\t#{ast[1][1].inspect}"
18
+ else
19
+ output.puts line
20
+ end
21
+ else
22
+ output.puts line
23
+ end
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,58 @@
1
+ class ReplaceBlanksAst
2
+
3
+ attr_accessor :references, :default_sheet_name
4
+
5
+ def initialize(references, default_sheet_name)
6
+ @references, @default_sheet_name = references, default_sheet_name
7
+ end
8
+
9
+ def map(ast)
10
+ return ast unless ast.is_a?(Array)
11
+ operator = ast[0]
12
+ if respond_to?(operator)
13
+ send(operator,*ast[1..-1])
14
+ else
15
+ [operator,*ast[1..-1].map {|a| map(a) }]
16
+ end
17
+ end
18
+
19
+ def sheet_reference(sheet,reference)
20
+ if references[sheet].has_key?(reference.last.gsub('$',''))
21
+ [:sheet_reference,sheet,reference]
22
+ else
23
+ [:blank]
24
+ end
25
+ end
26
+
27
+ def cell(reference)
28
+ if references[default_sheet_name].has_key?(reference.gsub('$',''))
29
+ [:cell,reference]
30
+ else
31
+ [:blank]
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+
38
+ class ReplaceBlanks
39
+
40
+ attr_accessor :references, :default_sheet_name
41
+
42
+ def self.replace(*args)
43
+ self.new.replace(*args)
44
+ end
45
+
46
+ def replace(input,output)
47
+ rewriter = ReplaceBlanksAst.new(references,default_sheet_name)
48
+ input.lines do |line|
49
+ # Looks to match lines with references
50
+ if line =~ /\[:cell/
51
+ ref, ast = line.split("\t")
52
+ output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
53
+ else
54
+ output.puts line
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ class ReplaceCommonElementsInFormulae
2
+
3
+ def self.replace(*args)
4
+ self.new.replace(*args)
5
+ end
6
+
7
+ def replace(input,common,output)
8
+ common = common.readlines.map do |a|
9
+ ref, element = a.split("\t")
10
+ [element.strip,"[:cell, \"#{ref}\"]",ref]
11
+ end.sort
12
+ input.lines do |line|
13
+ common.each do |element,cell,ref|
14
+ line.gsub!(element,cell)
15
+ end
16
+ output.puts line
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'map_formulae_to_values'
2
+
3
+ class ReplaceFormulaeWithCalculatedValues
4
+
5
+ def self.replace(*args)
6
+ self.new.replace(*args)
7
+ end
8
+
9
+ def replace(input,output)
10
+ rewriter = MapFormulaeToValues.new
11
+ input.lines do |line|
12
+ begin
13
+ ref, ast = line.split("\t")
14
+ output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
15
+ rescue Exception => e
16
+ puts "Exception at line #{line}"
17
+ raise
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,44 @@
1
+ require_relative '../excel/formula_peg'
2
+
3
+ class ReplaceIndirectsWithReferencesAst
4
+
5
+ def map(ast)
6
+ return ast unless ast.is_a?(Array)
7
+ operator = ast[0]
8
+ if respond_to?(operator)
9
+ send(operator,*ast[1..-1])
10
+ else
11
+ [operator,*ast[1..-1].map {|a| map(a) }]
12
+ end
13
+ end
14
+
15
+ def function(name,*args)
16
+ if name == "INDIRECT" && args.size == 1 && args[0][0] == :string
17
+ Formula.parse(args[0][1]).to_ast[1]
18
+ else
19
+ puts "indirect #{[:function,name,*args.map { |a| map(a) }].inspect} not replaced" if name == "INDIRECT"
20
+ [:function,name,*args.map { |a| map(a) }]
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+ class ReplaceIndirectsWithReferences
27
+
28
+ def self.replace(*args)
29
+ self.new.replace(*args)
30
+ end
31
+
32
+ def replace(input,output)
33
+ rewriter = ReplaceIndirectsWithReferencesAst.new
34
+ input.lines do |line|
35
+ # Looks to match lines with references
36
+ if line =~ /"INDIRECT"/
37
+ ref, ast = line.split("\t")
38
+ output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
39
+ else
40
+ output.puts line
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,82 @@
1
+ class NamedReferences
2
+
3
+ attr_accessor :named_references
4
+
5
+ def initialize(refs)
6
+ @named_references = {}
7
+ refs.each do |line|
8
+ sheet, name, reference = line.split("\t")
9
+ @named_references[sheet.downcase] ||= {}
10
+ @named_references[sheet.downcase][name.downcase] = eval(reference)
11
+ end
12
+ end
13
+
14
+ def reference_for(sheet,named_reference)
15
+ sheet = sheet.downcase
16
+ named_reference = named_reference.downcase
17
+ if @named_references.has_key?(sheet)
18
+ @named_references[sheet][named_reference] || @named_references[""][named_reference] || [:error, "#NAME?"]
19
+ else
20
+ @named_references[""][named_reference] || [:error, "#NAME?"]
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ class ReplaceNamedReferencesAst
27
+
28
+ attr_accessor :named_references, :default_sheet_name
29
+
30
+ def initialize(named_references, default_sheet_name)
31
+ @named_references, @default_sheet_name = named_references, default_sheet_name
32
+ end
33
+
34
+ def map(ast)
35
+ return ast unless ast.is_a?(Array)
36
+ operator = ast[0]
37
+ if respond_to?(operator)
38
+ send(operator,*ast[1..-1])
39
+ else
40
+ [operator,*ast[1..-1].map {|a| map(a) }]
41
+ end
42
+ end
43
+
44
+ def sheet_reference(sheet,reference)
45
+ if reference.first == :named_reference
46
+ named_references.reference_for(sheet,reference.last)
47
+ else
48
+ [:sheet_reference,sheet,reference]
49
+ end
50
+ end
51
+
52
+ def named_reference(name)
53
+ named_references.reference_for(default_sheet_name,name)
54
+ end
55
+
56
+ end
57
+
58
+
59
+ class ReplaceNamedReferences
60
+
61
+ attr_accessor :sheet_name
62
+
63
+ def self.replace(values,sheet_name,named_references,output)
64
+ self.new.replace(values,sheet_name,named_references,output)
65
+ end
66
+
67
+ # Rewrites ast with named references
68
+ def replace(values,named_references,output)
69
+ named_references = NamedReferences.new(named_references.readlines)
70
+ rewriter = ReplaceNamedReferencesAst.new(named_references,sheet_name)
71
+ values.lines do |line|
72
+ # Looks to match shared string lines
73
+ if line =~ /\[:named_reference/
74
+ cols = line.split("\t")
75
+ ast = cols.pop
76
+ output.puts "#{cols.join("\t")}\t#{rewriter.map(eval(ast)).inspect}"
77
+ else
78
+ output.puts line
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../excel'
2
+
3
+ class ReplaceRangesWithArrayLiteralsAst
4
+ def map(ast)
5
+ if ast.is_a?(Array)
6
+ operator = ast.shift
7
+ if respond_to?(operator)
8
+ send(operator,*ast)
9
+ else
10
+ [operator,*ast.map {|a| map(a) }]
11
+ end
12
+ else
13
+ return ast
14
+ end
15
+ end
16
+
17
+ def sheet_reference(sheet,reference)
18
+ if reference.first == :area
19
+ area = Area.for("#{reference[1]}:#{reference[2]}")
20
+ area.to_array_literal(sheet)
21
+ else
22
+ [:sheet_reference,sheet,reference]
23
+ end
24
+ end
25
+
26
+ def area(start,finish)
27
+ area = Area.for("#{start}:#{finish}")
28
+ area.to_array_literal
29
+ end
30
+
31
+ end
32
+
33
+ class ReplaceRangesWithArrayLiterals
34
+
35
+ def self.replace(*args)
36
+ self.new.replace(*args)
37
+ end
38
+
39
+ def replace(input,output)
40
+ rewriter = ReplaceRangesWithArrayLiteralsAst.new
41
+
42
+ input.lines do |line|
43
+ # Looks to match shared string lines
44
+ if line =~ /\[:area/
45
+ content = line.split("\t")
46
+ ast = eval(content.pop)
47
+ output.puts "#{content.join("\t")}\t#{rewriter.map(ast).inspect}"
48
+ else
49
+ output.puts line
50
+ end
51
+ end
52
+ end
53
+
54
+ end