1# Copyright (c) 2024 Huawei Device Co., Ltd.
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14require 'set'
15require 'delegate'
16require_relative 'enums.rb'
17
18module Es2pandaLibApi
19  class Arg
20    @primitive_types = nil
21
22    # Containers
23    @es2panda_arg = nil
24    @lib_args = nil
25    @lib_cast = nil
26    @return_args = nil
27    @is_ast_node = false
28    @is_ast_node_add_children = false
29    @is_ast_type = false
30    @is_var_type = false
31    @is_enum_type = false
32    @is_scope_type = false
33    @is_code_gen = false
34
35    # Flags
36    @need_var_cast = false
37
38    def check_ptr_depth(change_type, ptr_depth)
39      !((change_type.es2panda_arg['min_ptr_depth'] && ptr_depth < change_type.es2panda_arg['min_ptr_depth']) ||
40      (change_type.es2panda_arg['max_ptr_depth'] && ptr_depth > change_type.es2panda_arg['max_ptr_depth']))
41    end
42
43    def delete_ref(type)
44      type['ref_depth'] = 0 if type.respond_to?('ref_depth')
45    end
46
47    def initialize(arg_info)
48      @primitive_types = [
49        'char',
50        'char16_t',
51        'short',
52        'int',
53        'long',
54        'long long',
55        'float',
56        'double',
57        'long double',
58        'bool',
59        'void',
60        'size_t',
61        'uint32_t',
62        'int32_t',
63        'int64_t',
64        'es2panda_AstNode',
65        'es2panda_Type',
66        'es2panda_Scope'
67      ]
68      @es2panda_arg = arg_info
69
70      found_change_type_link = Es2pandaLibApi.change_types.find do |x|
71        x.es2panda_arg.type.respond_to?('name') && x.es2panda_arg.type.name == @es2panda_arg['type']['name'] &&
72          (!x.es2panda_arg.type.respond_to?('namespace') ||
73            x.es2panda_arg.type.namespace == @es2panda_arg['type'].namespace) &&
74          (!x.es2panda_arg.type.respond_to?('current_class') ||
75            x.es2panda_arg.type['current_class'] == @es2panda_arg['type']['current_class']) &&
76          check_ptr_depth(x, @es2panda_arg['type']['ptr_depth'] || 0)
77      end
78
79      unless found_change_type_link
80        @is_ast_node = Es2pandaLibApi.ast_nodes.include?(@es2panda_arg['type'].name) &&
81                       (!@es2panda_arg['type']['namespace'] || @es2panda_arg['type']['namespace'] == 'ir')
82        @is_ast_node_add_children = Es2pandaLibApi.ast_node_additional_children.include?(@es2panda_arg['type'].name) &&
83                                    (!@es2panda_arg['type']['namespace'] || @es2panda_arg['type']['namespace'] == 'ir')
84        @is_ast_type = Es2pandaLibApi.ast_types.include?(@es2panda_arg['type'].name) &&
85                       (!@es2panda_arg['type']['namespace'] || @es2panda_arg['type']['namespace'] == 'checker')
86        @is_var_type = Es2pandaLibApi.ast_variables.any? { |variable| variable[1] == @es2panda_arg['type'].name } &&
87                       (!@es2panda_arg['type']['namespace'] || @es2panda_arg['type']['namespace'] == 'varbinder')
88        @is_enum_type = Es2pandaLibApi.enums.include?(@es2panda_arg['type'].name)
89        @is_scope_type = Es2pandaLibApi.scopes.include?(@es2panda_arg['type'].name) &&
90                         (!@es2panda_arg['type']['namespace'] || @es2panda_arg['type']['namespace'] == 'varbinder')
91        @is_code_gen = Es2pandaLibApi.code_gen_children.include?(@es2panda_arg['type'].name) &&
92                       (!@es2panda_arg['type']['namespace'] || @es2panda_arg['type']['namespace'] == 'compiler')
93
94        if @is_ast_type
95          found_change_type_link = Es2pandaLibApi.change_types.find do |x|
96            x.es2panda_arg.type == '|AstType|' && check_ptr_depth(x, @es2panda_arg['type'].ptr_depth || 0)
97          end
98        end
99
100        if @is_ast_node
101          found_change_type_link = Es2pandaLibApi.change_types.find do |x|
102            x.es2panda_arg.type == '|AstNode|' && check_ptr_depth(x, @es2panda_arg['type'].ptr_depth || 0)
103          end
104        end
105
106        if @is_ast_node_add_children
107          found_change_type_link = Es2pandaLibApi.change_types.find do |x|
108            x.es2panda_arg.type ==
109              '|AstNodeAdditionalChildren|' && check_ptr_depth(x, @es2panda_arg['type'].ptr_depth || 0)
110          end
111        end
112
113        if @is_var_type
114          found_change_type_link = Es2pandaLibApi.change_types.find do |x|
115            x.es2panda_arg.type == '|Variable|' && check_ptr_depth(x, @es2panda_arg['type'].ptr_depth || 0)
116          end
117        end
118
119        if @is_enum_type
120          found_change_type_link = Es2pandaLibApi.change_types.find do |x|
121            x.es2panda_arg.type == '|Enum|' && check_ptr_depth(x, @es2panda_arg['type'].ptr_depth || 0)
122          end
123        end
124
125        if @is_scope_type
126          found_change_type_link = Es2pandaLibApi.change_types.find do |x|
127            x.es2panda_arg.type == '|Scope|' && check_ptr_depth(x, @es2panda_arg['type'].ptr_depth || 0)
128          end
129        end
130
131        if @is_code_gen
132          found_change_type_link = Es2pandaLibApi.change_types.find do |x|
133            x.es2panda_arg.type == '|CodeGen|' && check_ptr_depth(x, @es2panda_arg['type'].ptr_depth || 0)
134          end
135        end
136      end
137
138      unless found_change_type_link || @primitive_types.include?(@es2panda_arg['type'].name)
139        raise "Unsupported type: '" + @es2panda_arg['type'].name + "' ptr depth: " +
140              (@es2panda_arg['type'].ptr_depth || 0).to_s
141      end
142
143      ptr_depth = @es2panda_arg['type']['ptr_depth'] || 0
144
145      if found_change_type_link && !check_ptr_depth(found_change_type_link, ptr_depth)
146        raise 'invalid ptr_depth: ' + ptr_depth.to_s + ', type: ' + @es2panda_arg['type'].name
147      end
148
149      if found_change_type_link && @es2panda_arg['name'] == 'returnType' &&
150         !found_change_type_link.cast.respond_to?('reverse_cast')
151        raise 'Unsupported return type: ' + @es2panda_arg['type'].name + ' ptr depth: ' +
152              (@es2panda_arg['type'].ptr_depth || 0).to_s
153      end
154
155      if found_change_type_link
156        found_change_type = Marshal.load(Marshal.dump(found_change_type_link))
157
158        placeholders = find_placeholders(found_change_type.es2panda_arg)
159
160        replacements = placeholders.map do |path, placeholder|
161          value = get_value_by_path(@es2panda_arg, path)
162          [placeholder, value]
163        end
164
165        found_change_type.es2panda_arg = Marshal.load(Marshal.dump(@es2panda_arg))
166        found_change_type = deep_replace(found_change_type, replacements)
167
168        clever_replacements = []
169        template_args = []
170
171        if found_change_type&.new_args&.length && found_change_type.new_args.length > 1 &&
172           @es2panda_arg['type'].respond_to?('template_args')
173          accessor = (@es2panda_arg['type']['ptr_depth'] || 0).zero? ? '.' : '->'
174          clever_replacements += [['|accessor|', accessor]]
175
176          @es2panda_arg['type']['template_args'].each_with_index do |template_arg_raw, i|
177            template_arg = Arg.new({ 'name' => @es2panda_arg['name'] + "Element#{i + 1}",
178                                     'type' => template_arg_raw['type'] })
179            raise 'Unsupported double+ nested complex types: ' + @es2panda_arg.to_s if template_arg.lib_args.length > 1
180
181            template_args += [template_arg]
182          end
183
184          found_change_type&.new_args&.map! do |arg|
185            tmp = Arg.new(arg)
186            raise 'Unsupported double+ nested complex types: ' + arg_info.to_s if tmp.lib_args.length > 1
187
188            tmp.lib_args[0]['increase_ptr_depth'] = arg['increase_ptr_depth'] if (arg['increase_ptr_depth'] || 0) != 0
189            tmp.lib_args[0]
190          end
191
192          found_change_type&.return_args&.map! do |arg|
193            tmp = Arg.new(arg)
194            raise 'Unsupported double+ nested complex types: ' + arg_info if tmp.lib_args.length > 1
195
196            tmp.lib_args[0]['increase_ptr_depth'] = arg['increase_ptr_depth'] if (arg['increase_ptr_depth'] || 0) != 0
197            tmp.lib_args[0]
198          end
199          correct_depths(found_change_type)
200
201          template_args.each_with_index do |template_arg, i|
202            clever_replacements += [["|template_nested_expression_#{i + 1}|",
203                                     template_arg.lib_cast['expression'] + "\n"]]
204            clever_replacements += [["|reverse_template_nested_expression_#{i + 1}_start|",
205                                     template_arg.lib_cast['reverse_cast']&.start&.gsub('?const?', '') || '']]
206            clever_replacements += [["|reverse_template_nested_expression_#{i + 1}_end|",
207                                     (template_arg.lib_cast['reverse_cast']&.end || '')]]
208            found_change_type.new_args[i]['local_var_name'] = template_arg.lib_cast['var_name']
209          end
210
211          @es2panda_arg['type']['ref_depth'] == 2 &&
212            found_change_type.cast['var_name'] = 'std::move(' + found_change_type.cast['var_name'] + ')'
213        end
214
215        clever_placeholders = find_placeholders(found_change_type)
216        clever_replacements += clever_placeholders.map do |_path, placeholder|
217          if placeholder.include?('.')
218            value = get_placeholder_value(found_change_type, placeholder)
219            [placeholder, value]
220          else
221            is_known_replacement = clever_replacements.any? do |sub_array|
222              sub_array[0] =~ /_nested_expression_|\|accessor\|/
223            end
224            raise 'Unknown placeholder: ' + placeholder + "\n" unless is_known_replacement
225          end
226        end
227
228        found_change_type = deep_replace(found_change_type, clever_replacements)
229
230        @lib_args = found_change_type&.new_args
231        @lib_cast = found_change_type&.cast
232        @return_args = found_change_type&.return_args || nil
233
234      end
235
236      if @lib_args.nil?
237        @lib_args = []
238        @lib_args << @es2panda_arg
239      end
240
241      @lib_args.each do |arg|
242        delete_ref(arg['type'])
243      end
244
245      if @lib_cast.nil?
246        @lib_cast = {}
247        @lib_cast['var_name'] = @es2panda_arg['name']
248      else
249        @need_var_cast = true
250      end
251    end
252
253    def find_placeholders(data, path = [])
254      placeholders = []
255      if data.is_a?(OpenStruct)
256        data.each_pair do |key, value|
257          placeholders += find_placeholders(value, path + [key.to_s])
258        end
259      elsif data.is_a?(Array)
260        data.each_with_index do |value, index|
261          placeholders += find_placeholders(value, path + [index])
262        end
263      elsif data.is_a?(String)
264        data.scan(/\|(.+?)\|/) do |match|
265          placeholders << [path.join('.'), '|' + match[0] + '|']
266        end
267      end
268      placeholders
269    end
270
271    def get_value_by_path(data, path)
272      path.split('.').reduce(data) do |current, key|
273        current.is_a?(Array) ? current[key.to_i] : current[key]
274      end
275    end
276
277    def get_placeholder_value(found_change_type, placeholder)
278      path_to_value = placeholder.gsub('|', '').gsub('_int', '').gsub(' - 1', '')
279      value = get_value_by_path(found_change_type, path_to_value) || 0
280      if value.is_a?(Integer)
281        if placeholder.end_with?('ptr_depth|')
282          value = '*' * value
283        elsif placeholder.end_with?('ref_depth|')
284          value = '&' * value
285        elsif placeholder.end_with?('ptr_depth_int|')
286          value = value
287        elsif placeholder.end_with?('ptr_depth - 1|')
288          value = '*' * (value - 1)
289        elsif placeholder.end_with?('namespace|')
290          value = ''
291        else
292          raise 'Unknown integer found for placeholer: ' + placeholder + ', res: ' + value.to_s + "\n"
293        end
294      end
295      value
296    end
297
298    # replacements = [[from1, to1], [from2, to2], ...]
299    def deep_replace(data, replacements)
300      if data.is_a?(OpenStruct)
301        data.each_pair do |key, value|
302          data[key] = deep_replace(value, replacements)
303        end
304      elsif data.is_a?(Array)
305        data.map! { |value| deep_replace(value, replacements) }
306      elsif data.is_a?(String)
307        found_replacement = replacements.find { |first, _| first == data }
308        if found_replacement
309          data = if found_replacement[1].is_a?(String)
310                   found_replacement[1]
311                 else
312                   found_replacement[1].dup
313                 end
314        else
315          replacements.each { |from, to| data.gsub!(from, to) if to.is_a?(String) }
316        end
317      end
318      data
319    end
320
321    def correct_depths(data)
322      if data.is_a?(OpenStruct)
323        if data.respond_to?(:increase_ptr_depth)
324          ptr_depth = data.type.ptr_depth || 0
325          ptr_depth += data.increase_ptr_depth.to_i
326          data.type['ptr_depth'] = ptr_depth
327          data.delete_field(:increase_ptr_depth)
328        else
329          data.each_pair do |key, value|
330            data[key] = correct_depths(value)
331          end
332        end
333      elsif data.is_a?(Array)
334        data.map! { |value| correct_depths(value) }
335      end
336      data
337    end
338
339    attr_reader :es2panda_arg
340
341    attr_reader :lib_args
342
343    attr_reader :lib_cast
344
345    attr_reader :return_args
346
347    def lib_args_to_str
348      @lib_args.map do |lib_arg|
349        Arg.arg_to_str(lib_arg)
350      end&.join(', ')
351    end
352
353    def self.arg_value(arg)
354      ptr_depth = arg['type']['ptr_depth'] || 0
355      '*' * ptr_depth + arg['name']
356    end
357
358    def self.arg_to_str(arg)
359      type_to_str(arg['type']) + arg['name']
360    end
361
362    def self.type_to_str(type)
363      res = type['name']
364      ptr_depth = type['ptr_depth'] || 0
365      ref_depth = type['ref_depth'] || 0
366      res + ' ' + '*' * ptr_depth + '&' * ref_depth
367    end
368
369    attr_reader :is_change_type
370
371    def updater_allowed
372      @is_ast_type || @is_ast_node || @is_ast_node_add_children || @is_var_type || @is_enum_type ||
373        @is_scope_type || @is_code_gen
374    end
375  end
376
377  class Type
378    @raw_type = nil
379    @lib_type = nil
380    @return_args = nil
381    @cast = nil
382
383    def initialize(type_info)
384      @raw_type = type_info
385      tmp_arg = Arg.new({ 'name' => 'returnType', 'type' => @raw_type })
386
387      @return_args = tmp_arg.return_args if tmp_arg.lib_args.length != 1
388      @lib_type = tmp_arg.lib_args[0]['type']
389      @cast = tmp_arg.lib_cast
390    end
391
392    def lib_type_to_str
393      Arg.type_to_str(@lib_type)
394    end
395
396    def arena_item_type
397      type = Marshal.load(Marshal.dump(@lib_type))
398      type['ptr_depth'] -= 1
399      tmp_arg = Arg.new({ 'name' => '', 'type' => type })
400      Arg.type_to_str(tmp_arg.lib_args[0]['type'])
401    end
402
403    def call_cast
404      if @cast
405        @cast['call_cast']
406      else
407        ''
408      end
409    end
410
411    def constructor_cast
412      if @cast
413        @cast['constructor_cast']
414      else
415        ''
416      end
417    end
418
419    def cast
420      if @cast
421        @cast['reverse_cast']
422      else
423        ''
424      end
425    end
426
427    def return_args_to_str
428      res = ''
429      @return_args&.map do |arg|
430        res += ', ' + Arg.arg_to_str(arg)
431      end
432      res
433    end
434
435    attr_reader :raw_type
436    attr_reader :lib_type
437  end
438
439  class ClassData < SimpleDelegator
440    def class_name
441      Es2pandaLibApi.classes.find { |_name, data| data == self }[0]
442    end
443
444    def call_cast
445      name = class_name
446      class_type = Type.new(OpenStruct.new({ 'name' => name, 'ptr_depth' => 1 }))
447      class_type.call_cast
448    end
449
450    def constructor_type
451      name = class_name
452      Type.new(OpenStruct.new({ 'name' => name, 'ptr_depth' => 1 }))
453    end
454
455    def constructor_cast
456      name = class_name
457      class_type = Type.new(OpenStruct.new({ 'name' => name, 'ptr_depth' => 1 }))
458      class_type.constructor_cast
459    end
460
461    def updater_allowed
462      name = class_name
463      class_arg = Arg.new(OpenStruct.new({ 'name' => 'empty',
464                                           'type' => OpenStruct.new({ 'name' => name, 'ptr_depth' => 1 }) }))
465      class_arg.updater_allowed
466    end
467
468    def usings_map
469      dig(:usings)&.map { |using| [using['name'], using['type']] }.to_h || {}
470    end
471
472    def replace_with_usings(type, usings)
473      if usings[type&.name]
474        new_type = usings[type.name]
475        new_type['ref_depth'] = type['ref_depth'] || 0
476        new_type['ptr_depth'] = type['ptr_depth'] || 0
477        return new_type
478      end
479      type
480    end
481
482    def error_catch_log(mode, function, err)
483      if mode == 'constructor'
484        Es2pandaLibApi.log('error', "Error: '#{err.message}'\nConstructor: #{function.name}\nRaw:\n---\n"\
485                                                                          "#{function.raw_declaration}\n---\n\n")
486        Es2pandaLibApi.log('backtrace', err.backtrace.join("\n"), "\n")
487        Es2pandaLibApi.stat_add_unsupported_type(err.message) if err.message.include?('Unsupported type')
488        Es2pandaLibApi.stat_add_constructor(0)
489        Es2pandaLibApi.stat_add_class(0, function.name)
490      elsif mode == 'method'
491        Es2pandaLibApi.log('error', "Error: '#{err.message}'\nClass: #{function.name}\nRaw:\n---\n"\
492                                                                    "#{function.raw_declaration}\n---\n\n")
493        Es2pandaLibApi.log('backtrace', err.backtrace.join("\n"), "\n\n")
494        Es2pandaLibApi.stat_add_unsupported_type(err.message) if err.message.include?('Unsupported type')
495        Es2pandaLibApi.stat_add_method(0)
496      else
497        raise 'Unreachable'
498      end
499    end
500
501    def class_constructors
502      res = []
503      usings = usings_map
504      dig(:constructors)&.each do |constructor|
505        if check_no_gen_constructor(constructor)
506          args = []
507          begin
508            constructor_cast
509            constructor.args&.each do |arg|
510              arg['type'] = replace_with_usings(arg['type'], usings)
511              arg['type']['current_class'] = constructor.name
512              args << Arg.new(arg)
513            end
514          rescue StandardError => e
515            error_catch_log('constructor', constructor, e)
516          else
517            Es2pandaLibApi.stat_add_constructor(1)
518            Es2pandaLibApi.stat_add_class(1, class_name)
519
520            Es2pandaLibApi.log('info', "Supported constructor for class '#{class_name}'\n")
521
522            res << { 'args' => args, 'raw_decl' => constructor.raw_declaration }
523          end
524        else
525          Es2pandaLibApi.log('info', "Banned constructor for class '#{class_name}'\n")
526        end
527      end
528      res
529    end
530
531    def check_no_gen_constructor(constructor)
532      res = false
533      Es2pandaLibApi.no_gen_constructor_info['postfix_contains']&.each do |postfix|
534        res ||= constructor.postfix&.include?(postfix)
535      end
536      Es2pandaLibApi.no_gen_constructor_info['name_starts_with']&.each do |name_starts_with|
537        res ||= constructor.name&.start_with?(name_starts_with)
538      end
539      Es2pandaLibApi.no_gen_constructor_info['arg_type']&.each do |arg_type|
540        constructor.args&.each do |arg|
541          res ||= Es2pandaLibApi.check_fit(arg.type, arg_type)
542        end
543      end
544      Es2pandaLibApi.no_gen_constructor_info['call_class']&.each do |call_class|
545        res ||= (call_class['name'] == class_name)
546      end
547      !res
548    end
549
550    def check_no_gen_method(method)
551      res = false # = Will be generated
552      Es2pandaLibApi.no_gen_method_info['postfix_contains']&.each do |postfix|
553        res ||= method.postfix&.include?(postfix)
554      end
555      Es2pandaLibApi.no_gen_method_info['name_starts_with']&.each do |name_starts_with|
556        res ||= method.name&.start_with?(name_starts_with)
557      end
558      Es2pandaLibApi.no_gen_method_info['arg_type']&.each do |arg_type|
559        method.args&.each do |arg|
560          res ||= Es2pandaLibApi.check_fit(arg.type, arg_type)
561        end
562      end
563      Es2pandaLibApi.no_gen_method_info['return_type']&.each do |return_type|
564        res ||= begin
565                  method.return_type['name'] == return_type['name'] && (!return_type.respond_to?('namespace') ||
566        return_type['namespace'] == method.return_type['namespace'])
567                end
568      end
569      Es2pandaLibApi.no_gen_method_info['call_class']&.each do |call_class|
570        res ||= (call_class['name'] == class_name)
571      end
572      !res
573    end
574
575    def get_return_expr(return_type, call_cast, const, method, args)
576      return_expr = ''
577
578      if return_type.raw_type['name'] != 'void' && return_type.raw_type['name'] != 'ArenaVector' &&
579         return_type.raw_type['name'] != 'ArenaSet' && return_type.raw_type['name'] != 'pair' &&
580         return_type.raw_type['name'] != 'vector'
581        return_expr += 'auto res = '
582      end
583
584      return_expr += return_type.cast['start']&.gsub('?const?', const) if return_type.cast
585
586      return_expr += '(' + call_cast['start']&.gsub('?const?', const) + method['name'] + '('
587      return_expr += args&.map do |arg|
588        arg.lib_cast['var_name'] if arg.lib_cast
589      end&.join(', ')
590      return_expr += ')' + (call_cast['end']&.gsub('?const?', const) || '') + ')'
591
592      return_expr += return_type.cast['end'] || '' if return_type.cast
593
594      return_expr += if return_type.raw_type['name'] == 'void'
595                       ';'
596                     elsif const == ''
597                       ";\n\treturn res;"
598                     else
599                       ";\n\treturn const_cast<const " + return_type.lib_type_to_str + '>(res);'
600                     end
601      return_expr
602    end
603
604    def get_new_method_name(function_overload, name, const)
605      function_name = name + (const != '' ? 'Const' : '')
606      overload_name = function_name
607
608      if function_overload.include?(function_name)
609        overload_name += function_overload[function_name].to_s
610        function_overload[function_name] += 1
611      else
612        function_overload[function_name] = 1
613      end
614      overload_name
615    end
616
617    def get_const_modifier(return_type)
618      if return_type&.raw_type&.prefix&.include?('const') && return_type&.lib_type&.respond_to?('ptr_depth') &&
619         return_type&.lib_type&.ptr_depth&.positive?
620        'const'
621      else
622        ''
623      end
624    end
625
626    def class_methods
627      res = []
628      function_overload = {}
629      usings = usings_map
630      dig(:methods)&.each do |method|
631        if check_no_gen_method(method)
632          begin
633            return_type = Type.new(replace_with_usings(method.return_type, usings))
634            const = get_const_modifier(return_type)
635
636            args = []
637            method.args&.each do |arg|
638              arg['type'] = replace_with_usings(arg['type'], usings)
639              args << Arg.new(arg)
640            end
641
642            return_expr = get_return_expr(return_type, call_cast, const, method, args)
643          rescue StandardError => e
644            error_catch_log('method', method, e)
645          else
646            Es2pandaLibApi.stat_add_method(1)
647            Es2pandaLibApi.log('info', 'supported method: ', method.name, ' class: ', class_name, "\n")
648
649            res << { 'name' => method.name, 'const' => const, 'return_arg_to_str' => return_type.return_args_to_str,
650                     'overload_name' => get_new_method_name(function_overload, method.name, const), 'args' => args,
651                     'return_type' => return_type, 'return_expr' => return_expr, 'raw_decl' => method.raw_declaration }
652          end
653        else
654          Es2pandaLibApi.log('info', "Banned method\n")
655        end
656      end
657      res
658    end
659  end
660
661  @ast_nodes = Set.new(%w[AstNode Expression Statement TypeNode])
662  @ast_types = Set.new(['Type'])
663  @scopes = Set.new(['Scope'])
664  @classes = {}
665  @includes = Set.new
666  @change_types = []
667  @enums = Set.new(['AstNodeType'])
668
669  @all_methods = 0.0
670  @all_constructors = 0.0
671  @all_classes = Set.new
672  @classes_with_supported_constructor = Set.new
673  @unsupported_types = {}
674
675  @supported_methods = 0.0
676  @supported_constructors = 0.0
677
678  def log(debug_level, *args)
679    info_log = false
680    debug_log = false
681    error_log = true
682    backtrace_log = true
683    stat_log = true
684
685    if debug_level == 'info' && info_log
686      print args.join('').to_s
687    elsif debug_level == 'debug' && debug_log
688      print args.join('').to_s
689    elsif debug_level == 'error' && error_log
690      print args.join('').to_s
691    elsif debug_level == 'backtrace' && backtrace_log
692      print args.join('').to_s
693    elsif debug_level == 'stat' && stat_log
694      print args.join('').to_s
695    end
696  end
697
698  def no_gen_constructor_info
699    { 'name_starts_with' =>
700      %w[AstNode ClassElement TypedStatement Annotated Scope Type],
701      'postfix_contains' =>
702      ['= delete', 'override'],
703      'arg_type' =>
704      [{ 'name' => 'Tag' },
705       { 'name' => 'Number', 'namespace' => 'lexer' },
706       { 'name' => 'Property', 'namespace' => 'AstDumper' },
707       { 'name' => 'TSChecker', 'namespace' => 'checker' },
708       { 'name' => 'ArenaVector', 'template_args' => [{ 'type' => { 'name' => 'pair' } }] },
709       { 'name' => 'initializer_list' }],
710      'call_class' =>
711      [{ 'name' => 'AstNode' }, { 'name' => 'ClassElement' }, { 'name' => 'TypedStatement' }, { 'name' => 'Annotated' },
712       { 'name' => 'Scope' }, { 'name' => 'Type' }] }
713  end
714
715  def no_gen_method_info
716    { 'name_starts_with' =>
717      ['~', 'HasFloatingPoint', 'AddChildLambda', 'operator=', 'NumericConditionalCheck', 'CompileComputed'],
718      'postfix_contains' =>
719      ['= delete', 'override'],
720      'return_type' =>
721      [{ 'name' => 'ETSChecker' }, { 'name' => 'ArenaAllocator' },
722       { 'name' => 'Allocator' }, { 'name' => 'Program' }, { 'name' => 'Tag' },
723       { 'name' => 'Number', 'namespace' => 'lexer' }, { 'name' => 'Property', 'namespace' => 'AstDumper' },
724       { 'name' => 'TSChecker', 'namespace' => 'checker' }],
725      'arg_type' =>
726      [{ 'name' => 'Tag' }, { 'name' => 'Number', 'namespace' => 'lexer' },
727       { 'name' => 'Property', 'namespace' => 'AstDumper' }, { 'name' => 'TSChecker', 'namespace' => 'checker' },
728       { 'name' => 'ArenaVector', 'template_args' => [{ 'type' => { 'name' => 'pair' } }] },
729       { 'name' => 'initializer_list' }],
730      'call_class' =>
731      [{ 'name' => 'Annotated' }] }
732  end
733
734  def check_fit(data, pattern)
735    return true if data.nil? || pattern.nil?
736
737    if pattern.is_a?(OpenStruct) || pattern.is_a?(Hash)
738      pattern.each_pair do |key, value|
739        return false unless check_fit(data[key], value)
740      end
741    elsif pattern.is_a?(Array)
742      pattern.each_with_index do |value, i|
743        return false unless check_fit(data[i], value)
744      end
745    elsif pattern.is_a?(String) || pattern.is_a?(Integer)
746      return false if data != pattern
747    end
748
749    true
750  end
751
752  def stat_add_method(support)
753    @all_methods += 1
754    @supported_methods += support
755  end
756
757  def stat_add_constructor(support)
758    @all_constructors += 1
759    @supported_constructors += support
760  end
761
762  def stat_add_class(support, class_name)
763    @all_classes << class_name
764    @classes_with_supported_constructor << class_name if support != 0
765  end
766
767  def stat_add_unsupported_type(err_msg)
768    @unsupported_types[err_msg] ||= 0
769    @unsupported_types[err_msg] += 1
770  end
771
772  def print_stats
773    Es2pandaLibApi.log('stat', "--------------\n")
774    Es2pandaLibApi.log('stat', 'Supported methods: ', @supported_methods, ' / ', @all_methods, ' ( ',
775                       @supported_methods / @all_methods * 100, " % )\n")
776    Es2pandaLibApi.log('stat', 'Supported constructors: ', @supported_constructors, ' / ', @all_constructors, ' ( ',
777                       @supported_constructors / @all_constructors * 100, " % )\n")
778    Es2pandaLibApi.log('stat', "Classes with supported constructor: #{@classes_with_supported_constructor.size} / "\
779        "#{@all_classes.size} ( #{@classes_with_supported_constructor.size.to_f / @all_classes.size * 100} % )\n")
780    Es2pandaLibApi.log('stat', "--------------\n")
781
782    return if @unsupported_types.empty?
783
784    Es2pandaLibApi.log('stat', "Unsupported types for constructor: \n")
785    sorted_items = @unsupported_types.sort_by { |_key, value| -value }
786    sorted_items.each do |key, value|
787      Es2pandaLibApi.log('stat', "#{key}: #{value}\n")
788    end
789  end
790
791  def ast_nodes
792    @ast_nodes
793  end
794
795  def ast_types
796    @ast_types
797  end
798
799  def scopes
800    @scopes
801  end
802
803  def ast_variables
804    [%w[NO Variable],
805     %w[LOCAL LocalVariable],
806     %w[GLOBAL GlobalVariable],
807     %w[MODULE ModuleVariable],
808     %w[ENUM EnumVariable]]
809  end
810
811  def ast_node_additional_children
812    %w[
813      TypedStatement
814      ClassElement
815      AnnotatedExpression
816      Literal
817      LoopStatement
818      MaybeOptionalExpression
819      Property
820    ]
821  end
822
823  def code_gen_children
824    %w[
825      CodeGen
826      PandaGen
827      ETSGen
828    ]
829  end
830
831  def classes
832    @classes
833  end
834
835  def includes
836    @includes
837  end
838
839  def change_types
840    @change_types
841  end
842
843  def enums
844    @enums
845  end
846
847  def wrap_data(data)
848    return unless data
849
850    data.macros&.each do |macros|
851      case macros.name
852      when 'AST_NODE_MAPPING'
853        @ast_nodes.merge(Set.new(macros.values&.map { |x| x[1] }))
854      when 'AST_NODE_REINTERPRET_MAPPING'
855        @ast_nodes.merge(Set.new(macros.values&.map { |x| x[2..3] }&.flatten))
856      when 'TYPE_MAPPING'
857        @ast_types.merge(Set.new(macros.values&.map { |x| x[1] }&.flatten))
858      end
859    end
860
861    data.varbinder&.macros&.each do |macros|
862      case macros.name
863      when 'SCOPE_TYPES'
864        @scopes.merge(Set.new(macros.values&.map { |x| x[1] }))
865      end
866    end
867
868    data.ast_node_reinterpret_mapping&.each do |mapping|
869      @ast_nodes << mapping[2]
870      @ast_nodes << mapping[3]
871    end
872
873    data.paths&.each do |include|
874      @includes << include
875    end
876
877    data.change_types&.each do |change_type|
878      @change_types << change_type
879    end
880
881    data['ir']&.class_definitions&.each do |class_definition|
882      @classes[class_definition.name] = ClassData.new(class_definition&.public)
883    end
884
885    data.enums&.each do |enum|
886      @enums << enum.name
887    end
888
889    Enums.wrap_data(data)
890  end
891
892  module_function :wrap_data, :classes, :ast_nodes, :includes, :change_types, :enums, :ast_types,
893                  :stat_add_constructor, :stat_add_method, :print_stats, :no_gen_method_info, :no_gen_constructor_info,
894                  :stat_add_class, :stat_add_unsupported_type, :ast_node_additional_children, :scopes, :ast_variables,
895                  :code_gen_children, :check_fit, :log
896end
897
898def Gen.on_require(data)
899  Es2pandaLibApi.wrap_data(data)
900end
901