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