libshit

Just some random shit
git clone https://git.neptards.moe/neptards/libshit.git
Log | Files | Refs | Submodules | README | LICENSE

lua.rb (13043B)


      1 # frozen_string_literal: true
      2 
      3 require_relative 'aliaser'
      4 require_relative 'cpp_info'
      5 require_relative 'template'
      6 require_relative 'util'
      7 
      8 class LuaBinding
      9   include RecursiveClassDiscovery
     10   def initialize parser
     11     @parser = parser
     12     @ret_lua_classes = []
     13     @templates = {}
     14   end
     15   alias inspect to_s
     16 
     17   def parse
     18     @parser.init_cpp_info
     19     c = @parser.tu.cursor
     20     c.visit_children &method(:real_parse)
     21     parse_templates c unless @templates.empty?
     22 
     23     @ret_lua_classes.each do |cls|
     24       cls.entries.select{|_,m| m.info_type == :function }.each do |_,m|
     25         # stable sort, because ruby still can't be assed to implement something
     26         # such basic
     27         m.overloads.sort_by&.with_index {|x,i| [x.order, i] }
     28       end
     29     end
     30   end
     31 
     32   TEMPLATE_STR = <<EOS
     33 #if LIBSHIT_WITH_LUA
     34 #include <libshit/lua/user_type.hpp>
     35 
     36 //$ classes.each do |cls|
     37 //$   if cls.cpp_info.enum?
     38 const char ::Libshit::Lua::TypeName</*$= cls.cpp_info.name */>::TYPE_NAME[] =
     39   /*$= Util.cpp_inspect cls.name */;
     40 //$   else
     41 /*$ if cls.template_args */template <>/*$ end */
     42 const char /*$= cls.cpp_info.name */::TYPE_NAME[] =
     43   /*$= Util.cpp_inspect cls.name */;
     44 //$   end
     45 //$ end
     46 
     47 namespace Libshit::Lua
     48 {
     49 //$ classes.each do |cls|
     50 
     51   // class /*$= cls.name */
     52   template<>
     53   void TypeRegisterTraits</*$= cls.cpp_info.name */>::Register(TypeBuilder& bld)
     54   {
     55 //$   x = [ cls.cpp_info.name ]
     56 //$   cls.parents.each do |v|
     57 //$     x << v.cpp_info.name unless v.annot.no_inherit
     58 //$   end
     59 //$   if x.size > 1
     60     bld.Inherit</*$= x.join ', ' */>();
     61 //$   end
     62 
     63 //$   cls.entries.each do |k,v|
     64 //$     if v.info_type == :constant
     65     bld.Add(/*$= Util.cpp_inspect k */, /*$= v.value_str */);
     66 //$     else
     67     bld.AddFunction<
     68 //$       v.overloads.each_with_index do |m,i|
     69       /*$= m.value_str *//*$= i == (v.overloads.size-1) ? '' : ',' */
     70 //$       end
     71     >(/*$= Util.cpp_inspect k */);
     72 //$     end
     73 //$   end
     74 /*$= Template.template (cls.annot.post_register || "").rstrip, cls */
     75   }
     76   static TypeRegister::StateRegister</*$= cls.cpp_info.name */> libshit_lua_statereg_/*$= cls.cpp_info.id */;
     77 //$ end
     78 
     79 }
     80 #endif
     81 EOS
     82 
     83   def generate dst
     84     dst.write Template.template TEMPLATE_STR, { classes: @ret_lua_classes }
     85   end
     86 
     87   private
     88   def default_name_class c
     89     Util.remove_anon_ns(c.spelling).gsub(/<.*/, ''). # eat template arguments
     90       gsub(/([[:upper:]])([[:upper:]][[:lower:]])/, '\1_\2').
     91       gsub(/([[:lower:]])([[:upper:]])/, '\1_\2').gsub('::', '.').downcase
     92   end
     93 
     94   def default_name_fun c
     95     if c.kind == :constructor
     96       'new'
     97     else
     98       c.spelling.gsub(/([[:lower:]])([[:upper:]])/, '\1_\2').downcase
     99     end
    100   end
    101 
    102 
    103   Annotation = Struct.new *CppFunction::ANNOTATIONS, *%i(
    104     name hidden get set post_register no_inherit smart_object const
    105     template_params fullname klass comparable wrap order implicit
    106   ), keyword_init: true
    107   ClassAnnotation = Annotation # TODO
    108   CLASS_ANNOTATION_KEY = 'libshit_luagen'
    109 
    110   def get_annotations c, env = {}
    111     Util.collect_annotations(c) do |annot|
    112       Util.map_annotation Annotation, CLASS_ANNOTATION_KEY, annot, env
    113     end
    114   end
    115 
    116 
    117   ClassInfo = Struct.new :cpp_info, :annot, :name, :entries, :smart_object,
    118                          :const, :parents, :has_push_lua, :template_alias,
    119                          :template_args, keyword_init: true
    120   def get_class_info c, annot
    121     return false if annot.hidden
    122 
    123     ClassInfo.new \
    124       cpp_info: CppClass.new(@parser, c.type),
    125       annot:, name: annot.name || default_name_class(c.type),
    126       entries: {}, smart_object: annot.smart_object, const: annot.const,
    127       parents: [], has_push_lua: false
    128   end
    129 
    130   def class_migrate_inheritable mod, ref
    131     return unless mod
    132 
    133     %i(smart_object const).each do |v|
    134       mod[v] = ref[v] if ref[v] != nil && mod[v] == nil
    135     end
    136   end
    137 
    138 
    139   # process lua methods inside classes
    140   def default_hidden c, annot, parent
    141     return false unless annot.implicit
    142 
    143     # special cases
    144     case c.kind
    145     when :function_decl
    146       return false
    147     when :constructor
    148       return true if parent.type.abstract_type?
    149     end
    150 
    151     acc = c.access_specifier
    152     (acc != :invalid && acc != :public) || c.deleted? || c.override? ||
    153       c.spelling.start_with?('operator')
    154   end
    155 
    156   # function handlers begin
    157   MemberInfo = Struct.new :annot, :lua_name, :info_klass, :info_type,
    158                           :value_str, :cpp_info, :order, :use_class,
    159                           :overloads, # TODO
    160                           keyword_init: true
    161   FunctionList = Struct.new :info_type, :overloads
    162   def func_common c, info
    163     info.cpp_info ||= CppFunction.new \
    164       cursor: c, aliaser: @parser.aliaser, annotation: info.annot,
    165       klass: info.info_klass&.cpp_info
    166     info.lua_name ||= default_name_fun(c)
    167     info.info_type = :function
    168   end
    169 
    170   def method_tmpl c, info
    171     # TODO deduplicate
    172     ci = info.cpp_info ||= CppFunction.new \
    173       cursor: c, aliaser: @parser.aliaser, annotation: info.annot,
    174       klass: info.info_klass.cpp_info
    175 
    176     tp = info.annot.template_params
    177     if !tp
    178       args = ci.template_arguments
    179       if args.size != 1 || args[0].spelling != 'Checker'
    180         Util.print_error 'Must specify template parameters', c
    181         return false
    182       end
    183 
    184       ci.template_arguments_map = ['Check::Throw']
    185     elsif tp.is_a? Array
    186       ci.template_arguments_map = tp
    187     else
    188       fail "Invalid template_params #{tp.class}"
    189     end
    190   end
    191 
    192   def freestanding_func c, info
    193     if info.annot.klass # user provided
    194       typ = class_by_name(info.annot.klass).cpp_info.type
    195     else
    196       typ = info.cpp_info.arguments.find do |x|
    197         x.type.canonical.spelling != 'Libshit::Lua::StateRef'
    198       end.type
    199       typ = typ.pointee || typ # get rid of &, *
    200       typ = typ.declaration.type # and const, whatever
    201     end
    202 
    203     fail 'Invalid base class' unless info.info_klass = class?(typ)
    204 
    205     info.value_str ||= info.cpp_info.ptr_value_str
    206   end
    207 
    208   def ctor c, info
    209     ci = info.cpp_info
    210     info.value_str ||= "&::Libshit::Lua::TypeTraits<#{ci.klass_name}>::" \
    211                        "Make<#{ci.arguments_str 'LuaGetRef'}>"
    212   end
    213 
    214   def general_method c, info
    215     ci = info.cpp_info
    216     ci.klass_name = info.use_class if info.use_class
    217     wrap = info.annot.wrap
    218     info.value_str ||=
    219       wrap ? "#{wrap}<#{ci.ptr_value_str}>::Wrap" : ci.ptr_value_str
    220   end
    221 
    222   def field c, info
    223     annot = info.annot
    224     fail 'Exactly one of get/set required' if !!annot.get == !!annot.set
    225 
    226     ci = info.cpp_info = CppVariable.new parser: @parser, cursor: c,
    227                                          klass: info.info_klass.cpp_info
    228     info.lua_name ||= "#{annot.get ? 'get' : 'set'}_#{default_name_fun c}"
    229     info.info_type = :function
    230 
    231     info.value_str =
    232       begin
    233         key = if annot.get
    234                 annot.get == true ? '::Libshit::Lua::GetMember' : annot.get
    235               else
    236                 annot.set == true ? '::Libshit::Lua::SetMember' : annot.set
    237               end
    238         "&#{key}<#{ci.klass_name}, #{ci.type_str}, #{ci.ptr_value_str}>"
    239       end
    240   end
    241 
    242   def constant c, info
    243     ci = info.cpp_info = CppEnumConstant.new \
    244       cursor: c, klass: info.info_klass.cpp_info
    245     info.lua_name ||= ci.qualified_name
    246     info.info_type = :constant
    247     info.value_str = ci.value_str
    248   end
    249 
    250   FUNC_TYPE_HANDLERS = {
    251     function_decl: %i(func_common freestanding_func),
    252     constructor: %i(func_common ctor),
    253     cxx_method: %i(func_common general_method),
    254     function_template: %i(method_tmpl func_common general_method),
    255     field_decl: %i(field),
    256     enum_constant_decl: %i(constant),
    257   }.freeze
    258 
    259   def lua_function c, annot, parent, info = MemberInfo.new
    260     return if annot.hidden.nil? ? default_hidden(c, annot, parent) : annot.hidden
    261 
    262     info.annot = annot
    263     info.lua_name = annot.name
    264     info.order = annot.order || 0
    265     FUNC_TYPE_HANDLERS[c.kind].each do |h|
    266       send h, c, info
    267     end
    268 
    269     fail unless info.info_klass
    270     fail unless info.value_str
    271     fail unless info.info_type
    272 
    273     ents = info.info_klass.entries
    274     name = info.lua_name
    275     case info.info_type
    276     when :constant
    277       ents[name].nil? or fail "Duplicate constant #{name}"
    278       ents[name] = info
    279 
    280     when :function
    281       ents[name] ||= FunctionList.new :function, []
    282       ents[name].info_type == :function or fail 'function&constant with same name?'
    283       ents[name].overloads << info
    284 
    285     else
    286       fail "Unknown info.info_type #{info.info_type.inspect}"
    287     end
    288   rescue
    289     Util.print_error $!, c
    290   end
    291 
    292   def parse_method c, kind, parent, parse_class_class
    293     annots = get_annotations c, cls: parse_class_class
    294     if annots.empty?
    295       if kind == :field_decl
    296         annots << Annotation.new(get: true, implicit: true).freeze
    297         # no setter if class or field const
    298         if !parse_class_class.const && !c.type.const_qualified?
    299           annots << Annotation.new(set: true, implicit: true).freeze
    300         end
    301 
    302       # only ctors/const methods in const classes. if class not const->everything
    303       elsif kind == :constructor || !parse_class_class.const || c.const? ||
    304             c.static?
    305         annots << Annotation.new(implicit: true).freeze
    306       end
    307     end
    308 
    309     annots.each do |a|
    310       lua_function c, a, parent, MemberInfo.new(info_klass: parse_class_class)
    311     end
    312   end
    313 
    314   # inside class
    315   def parse_class type, klass
    316     type.declaration.visit_children do |c, par|
    317       kind = c.kind
    318       case kind
    319       when :cxx_base_specifier
    320         if c.public? && (lc = class?(c.type))
    321           klass.parents << lc
    322         end
    323 
    324       when :using_declaration
    325         c.referenced.overloaded_decls.each do |v|
    326           if v.kind != :type_alias_decl
    327             parse_method v, v.kind, c.semantic_parent, klass
    328           end
    329         end
    330       end
    331 
    332       next :continue unless %i(cxx_method function_template constructor
    333                                field_decl enum_constant_decl).member? kind
    334       klass.has_push_lua = true if c.spelling == 'PushLua'
    335 
    336       parse_method c, kind, c.semantic_parent, klass
    337       :continue
    338     end
    339 
    340     if klass.smart_object && !type.abstract_type? && !klass.has_push_lua
    341       Util.print_warning 'Missing LIBSHIT_DYNAMIC_OBJECT', type.declaration
    342     end
    343     klass
    344   end
    345 
    346   # top level parse
    347   def real_parse c, parent
    348     return :continue unless @parser.filter[c, parent]
    349     case c.kind
    350     when :namespace; return :recurse
    351     when :class_decl, :struct_decl, :enum_decl
    352       # ignore fwd decls/templates
    353       if c.definition? && c.specialized_template.null?
    354         x = class? c.type
    355         @ret_lua_classes << parse_class(c.type, x) if x
    356         return :recurse # support inner classes
    357       end
    358 
    359     when :function_decl
    360       get_annotations(c).each {|a| lua_function c, a, c.semantic_parent }
    361 
    362     when :type_alias_decl
    363       get_annotations(c).each do |a|
    364         t = c.typedef_type
    365 
    366         if x = class?(t)
    367           @templates[t] = c.spelling
    368           x.name = a.fullname || "#{x.name}_#{a.name || c.spelling}"
    369           x.template_alias = a
    370           # hack!
    371           x.template_args =
    372             t.spelling.gsub(/^[:0-9a-zA-Z ]*</, '').gsub(/>[ *&]*$/, '')
    373           @ret_lua_classes << parse_class(t, x)
    374         else
    375           Util.print_error 'is not a lua class', c
    376         end
    377       end
    378     end
    379 
    380     :continue
    381   rescue
    382     Util.print_error $!, c
    383     :continue
    384   end
    385 
    386   def path_isbase base, o
    387     o[0, base.size] == base
    388   end
    389 
    390   def parse_templates c
    391     cwd = Pathname.pwd.cleanpath
    392     base = cwd.join('src').each_filename.to_a
    393     basetest = cwd.join('test').each_filename.to_a
    394     libshittest = cwd.join('libshit/test').each_filename.to_a
    395 
    396     cache = {}
    397     c.visit_children do |c2, par|
    398       file = c2.location.file
    399       if cache[file].nil?
    400         this = cwd.join(file).cleanpath.each_filename.to_a
    401         cache[file] = path_isbase(base, this) || path_isbase(basetest, this) ||
    402                       path_isbase(libshittest, this)
    403       end
    404       next :continue unless cache[file]
    405 
    406       case c2.kind
    407       when :namespace; next :recurse
    408       when :struct_decl
    409         fake_class = nil
    410         c2.visit_children do |c3, par3|
    411           kind = c3.kind
    412           next :continue if kind == :namespace_ref || kind == :type_ref
    413           if kind == :type_alias_decl && c3.spelling == 'FakeClass'
    414             fake_class = c3.typedef_type
    415             next :continue
    416           end
    417           next :break unless fake_class
    418 
    419           if kind == :cxx_method
    420             anns = get_annotations c3
    421             anns << Annotation.new(implicit: true).freeze if anns.empty?
    422             anns.each do |a|
    423               if klass = class?(fake_class)
    424                 mi = MemberInfo.new info_klass: klass,
    425                                     use_class: @parser.aliaser.type_name(par3)
    426                 lua_function c3, a, c3.semantic_parent, mi
    427               else
    428                 Util.print_error 'FakeClass is not lua class', c
    429               end
    430             end
    431           end
    432 
    433           :continue
    434         rescue
    435           Util.print_error $!, c
    436           :continue
    437         end
    438       end
    439       :continue
    440     end
    441   end
    442 end