44class ErbToRubyTransformer
55 def initialize
66 @parser = Temple ::ERB ::Parser . new
7- @indent_level = 0
8- @current_line = [ ]
97 @in_control_block = false
10- @output_tmp_var = "tmp0"
11- @is_first_output = true
8+ @output_tmp_var = "joern__buffer"
9+ @in_do_block = false
10+ @inner_buffer = "joern__inner_buffer"
11+ @current_counter = 0
12+ @current_lambda_vars = ""
1213 @output = [ ]
13- @no_control_struct = true
14- @open_heredoc = false
14+ @static_buff = [ ]
1515 end
1616
1717 def transform ( input )
1818 ast = @parser . call ( input )
19- content = "#{ @output_tmp_var } = \" \" \n #{ visit ( ast ) } "
20- if @in_control_block
19+ @output << "#{ @output_tmp_var } = \" \" "
20+ visit ( ast )
21+ @output << "return #{ @output_tmp_var } "
22+
23+ if @in_control_block || @in_do_block
2124 raise ::StandardError , "Invalid ERB Syntax"
2225 end
2326 <<~RUBY
24- #{ content }
25- return #{ @output_tmp_var }
27+ #{ @output . join ( "\n " ) }
2628 RUBY
2729 end
2830
@@ -32,77 +34,104 @@ def visit(node)
3234 case node . first
3335 when :multi
3436 node [ 1 ..-1 ] . each do |child |
35- transformed = visit ( child )
36- unless transformed . strip . empty?
37- if @is_first_output
38- @open_heredoc = true
39- @current_line << "#{ @output_tmp_var } += <<-HEREDOC\n "
40- @is_first_output = false
41- end
42- @current_line << transformed
43- end
44- end
45-
46- if @open_heredoc
47- @current_line << "\n HEREDOC\n "
48- @open_heredoc = false
37+ visit ( child )
4938 end
50-
51- flush_current_line ( @output ) unless @current_line . empty?
52- @output . join ( "\n " )
5339 when :static
54- "#{ node [ 1 ] . to_s } "
40+ unless node [ 1 ] . to_s != nil && node [ 1 ] . to_s . strip . empty?
41+ @static_buff << "\" #{ node [ 1 ] . to_s . gsub ( '"' , '\"' ) . strip } \" "
42+ end
5543 when :dynamic
56- "#{ node [ 1 ] . to_s } "
44+ unless node [ 1 ] . to_s != nil && node [ 1 ] . to_s . strip . empty?
45+ @output << "\" #{ node [ 1 ] . to_s . gsub ( '"' , '\"' ) } \" "
46+ end
5747 when :escape
48+ unless @static_buff . empty?
49+ buffer_to_use = if @in_do_block then "#{ @inner_buffer } " else "#{ @output_tmp_var } " end
50+ @output << "#{ buffer_to_use } << \" #{ @static_buff . join ( '\n' ) . gsub ( /(?<!\\ )"/ , '' ) } \" "
51+ @static_buff = [ ] # clear static buffer
52+ end
53+
5854 escape_enabled = node [ 1 ]
5955 inner_node = node [ 2 ]
6056 code = inner_node [ 1 ] . to_s . strip
61- template_call = if escape_enabled then "joern__template_out_raw" else "joern__template_out_escape" end
62- "\# {#{ template_call } (#{ code } )}"
57+
58+ # Do block with variable found, lower
59+ if is_do_block ( code )
60+ lower_do_block ( code )
61+ elsif @in_do_block
62+ template_call = if escape_enabled then "joern__template_out_raw" else "joern__template_out_escape" end
63+ @output << "#{ @inner_buffer } << #{ template_call } (#{ code } )"
64+ else
65+ template_call = if escape_enabled then "joern__template_out_raw" else "joern__template_out_escape" end
66+ @output << "#{ @output_tmp_var } << #{ template_call } (#{ code } )"
67+ end
6368 when :code
69+ unless @static_buff . empty?
70+ buffer_to_use = if @in_do_block then "#{ @inner_buffer } " else "buffer" end
71+ @output << "#{ buffer_to_use } << \" #{ @static_buff . join ( '\n' ) . gsub ( /(?<!\\ )"/ , '' ) } \" "
72+ @static_buff = [ ] # clear static buffer
73+ end
6474 code = node [ 1 ] . to_s . strip
6575 # Using this to determine if we should throw a StandardError for "invalid" ERB
6676 if is_control_struct_start ( code )
6777 @in_control_block = true
78+ @output << code
6879 elsif code . start_with? ( "end" )
69- @in_control_block = false
70- end
71-
72- if @open_heredoc
73- @open_heredoc = false
74- @current_line << "\n HEREDOC"
80+ if @in_do_block
81+ @in_do_block = false
82+ @output << "#{ @inner_buffer } "
83+ @output << "end"
84+ @output << "#{ @output_tmp_var } << #{ current_lambda } .call(#{ @current_lambda_vars } )"
85+ else
86+ @in_control_block = false
87+ @output << "end"
88+ end
89+ else
90+ if is_do_block ( code )
91+ lower_do_block ( code )
92+ end
7593 end
76-
77- @current_line << "\n #{ node [ 1 ] . to_s . strip } \n "
78- @is_first_output = true
79- ""
8094 when :newline
81- ""
8295 else
8396 RubyAstGen ::Logger ::debug ( "Invalid node type: #{ node } " )
84- ""
8597 end
8698 end
8799
88- def indent
89- " " * @indent_level
100+
101+ def is_control_struct_start ( line )
102+ line . start_with? ( 'if' , 'unless' , 'elsif' , 'else' , /@?\w +\. each\s do/ )
90103 end
91104
92- def flush_current_line ( output )
93- unless @current_line . empty?
94- line = @current_line . join . rstrip
95- output << line unless line . empty?
96- @current_line . clear
97- end
105+ def lambda_incrementor ( )
106+ new_lambda = "rails_lambda_#{ @current_counter } "
107+ @current_counter += 1
108+ new_lambda
98109 end
99110
100- def is_control_struct_start ( line )
101- line . start_with? ( 'if' , 'unless' , 'elsif' , 'else' , /@?\w +\. each\s do/ )
111+ def current_lambda ( )
112+ "rails_lambda_#{ @current_counter -1 } "
113+ end
114+
115+ def lower_do_block ( code )
116+ if ( code_match = code . match ( /do\s *(?:\| ([^|]*)\| )?/ ) )
117+ @current_lambda_vars = code_match [ 1 ]
118+ before_do , _ = code . split ( /\b do\b / )
119+ unless before_do . nil?
120+ method_call = before_do . strip
121+ call_name , rest = method_call . split ( ' ' , 2 )
122+ if rest != nil && !rest . start_with? ( '(' ) && !rest . end_with? ( ')' )
123+ method_call = "#{ call_name } (#{ rest } )"
124+ end
125+ @output << "#{ @output_tmp_var } << #{ method_call } "
126+ end
127+ @in_do_block = true
128+ @output << "#{ lambda_incrementor } = lambda do |#{ @current_lambda_vars } |"
129+ @output << "#{ @inner_buffer } = \" \" "
130+ end
102131 end
103132
104- def is_control_struct ( line )
105- is_control_struct_start ( line ) || line . start_with? ( 'end' )
133+ def is_do_block ( code )
134+ code . match ( /do \s *(?: \| ([^|]*) \| )?/ )
106135 end
107136end
108137
0 commit comments