You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

443 lines
13 KiB
Ruby

# frozen_string_literal: true
require_relative 'aliaser'
require_relative 'cpp_info'
require_relative 'template'
require_relative 'util'
class LuaBinding
include RecursiveClassDiscovery
def initialize parser
@parser = parser
@ret_lua_classes = []
@templates = {}
end
alias inspect to_s
def parse
@parser.init_cpp_info
c = @parser.tu.cursor
c.visit_children &method(:real_parse)
parse_templates c unless @templates.empty?
@ret_lua_classes.each do |cls|
cls.entries.select{|_,m| m.info_type == :function }.each do |_,m|
# stable sort, because ruby still can't be assed to implement something
# such basic
m.overloads.sort_by&.with_index {|x,i| [x.order, i] }
end
end
end
TEMPLATE_STR = <<EOS
#if LIBSHIT_WITH_LUA
#include <libshit/lua/user_type.hpp>
//$ classes.each do |cls|
//$ if cls.cpp_info.enum?
const char ::Libshit::Lua::TypeName</*$= cls.cpp_info.name */>::TYPE_NAME[] =
/*$= Util.cpp_inspect cls.name */;
//$ else
/*$ if cls.template_args */template <>/*$ end */
const char /*$= cls.cpp_info.name */::TYPE_NAME[] =
/*$= Util.cpp_inspect cls.name */;
//$ end
//$ end
namespace Libshit::Lua
{
//$ classes.each do |cls|
// class /*$= cls.name */
template<>
void TypeRegisterTraits</*$= cls.cpp_info.name */>::Register(TypeBuilder& bld)
{
//$ x = [ cls.cpp_info.name ]
//$ cls.parents.each do |v|
//$ x << v.cpp_info.name unless v.annot.no_inherit
//$ end
//$ if x.size > 1
bld.Inherit</*$= x.join ', ' */>();
//$ end
//$ cls.entries.each do |k,v|
//$ if v.info_type == :constant
bld.Add(/*$= Util.cpp_inspect k */, /*$= v.value_str */);
//$ else
bld.AddFunction<
//$ v.overloads.each_with_index do |m,i|
/*$= m.value_str *//*$= i == (v.overloads.size-1) ? '' : ',' */
//$ end
>(/*$= Util.cpp_inspect k */);
//$ end
//$ end
/*$= Template.template (cls.annot.post_register || "").rstrip, cls */
}
static TypeRegister::StateRegister</*$= cls.cpp_info.name */> libshit_lua_statereg_/*$= cls.cpp_info.id */;
//$ end
}
#endif
EOS
def generate dst
dst.write Template.template TEMPLATE_STR, { classes: @ret_lua_classes }
end
private
def default_name_class c
Util.remove_anon_ns(c.spelling).gsub(/<.*/, ''). # eat template arguments
gsub(/([[:upper:]])([[:upper:]][[:lower:]])/, '\1_\2').
gsub(/([[:lower:]])([[:upper:]])/, '\1_\2').gsub('::', '.').downcase
end
def default_name_fun c
if c.kind == :constructor
'new'
else
c.spelling.gsub(/([[:lower:]])([[:upper:]])/, '\1_\2').downcase
end
end
Annotation = Struct.new *CppFunction::ANNOTATIONS, *%i(
name hidden get set post_register no_inherit smart_object const
template_params fullname klass comparable wrap order implicit
), keyword_init: true
ClassAnnotation = Annotation # TODO
CLASS_ANNOTATION_KEY = 'libshit_luagen'
def get_annotations c, env = {}
Util.collect_annotations(c) do |annot|
Util.map_annotation Annotation, CLASS_ANNOTATION_KEY, annot, env
end
end
ClassInfo = Struct.new :cpp_info, :annot, :name, :entries, :smart_object,
:const, :parents, :has_push_lua, :template_alias,
:template_args, keyword_init: true
def get_class_info c, annot
return false if annot.hidden
ClassInfo.new \
cpp_info: CppClass.new(@parser, c.type),
annot:, name: annot.name || default_name_class(c.type),
entries: {}, smart_object: annot.smart_object, const: annot.const,
parents: [], has_push_lua: false
end
def class_migrate_inheritable mod, ref
return unless mod
%i(smart_object const).each do |v|
mod[v] = ref[v] if ref[v] != nil && mod[v] == nil
end
end
# process lua methods inside classes
def default_hidden c, annot, parent
return false unless annot.implicit
# special cases
case c.kind
when :function_decl
return false
when :constructor
return true if parent.type.abstract_type?
end
acc = c.access_specifier
(acc != :invalid && acc != :public) || c.deleted? || c.override? ||
c.spelling.start_with?('operator')
end
# function handlers begin
MemberInfo = Struct.new :annot, :lua_name, :info_klass, :info_type,
:value_str, :cpp_info, :order, :use_class,
:overloads, # TODO
keyword_init: true
FunctionList = Struct.new :info_type, :overloads
def func_common c, info
info.cpp_info ||= CppFunction.new \
cursor: c, aliaser: @parser.aliaser, annotation: info.annot,
klass: info.info_klass&.cpp_info
info.lua_name ||= default_name_fun(c)
info.info_type = :function
end
def method_tmpl c, info
# TODO deduplicate
ci = info.cpp_info ||= CppFunction.new \
cursor: c, aliaser: @parser.aliaser, annotation: info.annot,
klass: info.info_klass.cpp_info
tp = info.annot.template_params
if !tp
args = ci.template_arguments
if args.size != 1 || args[0].spelling != 'Checker'
Util.print_error 'Must specify template parameters', c
return false
end
ci.template_arguments_map = ['Check::Throw']
elsif tp.is_a? Array
ci.template_arguments_map = tp
else
fail "Invalid template_params #{tp.class}"
end
end
def freestanding_func c, info
if info.annot.klass # user provided
typ = class_by_name(info.annot.klass).cpp_info.type
else
typ = info.cpp_info.arguments.find do |x|
x.type.canonical.spelling != 'Libshit::Lua::StateRef'
end.type
typ = typ.pointee || typ # get rid of &, *
typ = typ.declaration.type # and const, whatever
end
fail 'Invalid base class' unless info.info_klass = class?(typ)
info.value_str ||= info.cpp_info.ptr_value_str
end
def ctor c, info
ci = info.cpp_info
info.value_str ||= "&::Libshit::Lua::TypeTraits<#{ci.klass_name}>::" \
"Make<#{ci.arguments_str 'LuaGetRef'}>"
end
def general_method c, info
ci = info.cpp_info
ci.klass_name = info.use_class if info.use_class
wrap = info.annot.wrap
info.value_str ||=
wrap ? "#{wrap}<#{ci.ptr_value_str}>::Wrap" : ci.ptr_value_str
end
def field c, info
annot = info.annot
fail 'Exactly one of get/set required' if !!annot.get == !!annot.set
ci = info.cpp_info = CppVariable.new parser: @parser, cursor: c,
klass: info.info_klass.cpp_info
info.lua_name ||= "#{annot.get ? 'get' : 'set'}_#{default_name_fun c}"
info.info_type = :function
info.value_str =
begin
key = if annot.get
annot.get == true ? '::Libshit::Lua::GetMember' : annot.get
else
annot.set == true ? '::Libshit::Lua::SetMember' : annot.set
end
"&#{key}<#{ci.klass_name}, #{ci.type_str}, #{ci.ptr_value_str}>"
end
end
def constant c, info
ci = info.cpp_info = CppEnumConstant.new \
cursor: c, klass: info.info_klass.cpp_info
info.lua_name ||= ci.qualified_name
info.info_type = :constant
info.value_str = ci.value_str
end
FUNC_TYPE_HANDLERS = {
function_decl: %i(func_common freestanding_func),
constructor: %i(func_common ctor),
cxx_method: %i(func_common general_method),
function_template: %i(method_tmpl func_common general_method),
field_decl: %i(field),
enum_constant_decl: %i(constant),
}.freeze
def lua_function c, annot, parent, info = MemberInfo.new
return if annot.hidden.nil? ? default_hidden(c, annot, parent) : annot.hidden
info.annot = annot
info.lua_name = annot.name
info.order = annot.order || 0
FUNC_TYPE_HANDLERS[c.kind].each do |h|
send h, c, info
end
fail unless info.info_klass
fail unless info.value_str
fail unless info.info_type
ents = info.info_klass.entries
name = info.lua_name
case info.info_type
when :constant
ents[name].nil? or fail "Duplicate constant #{name}"
ents[name] = info
when :function
ents[name] ||= FunctionList.new :function, []
ents[name].info_type == :function or fail 'function&constant with same name?'
ents[name].overloads << info
else
fail "Unknown info.info_type #{info.info_type.inspect}"
end
rescue
Util.print_error $!, c
end
def parse_method c, kind, parent, parse_class_class
annots = get_annotations c, cls: parse_class_class
if annots.empty?
if kind == :field_decl
annots << Annotation.new(get: true, implicit: true).freeze
# no setter if class or field const
if !parse_class_class.const && !c.type.const_qualified?
annots << Annotation.new(set: true, implicit: true).freeze
end
# only ctors/const methods in const classes. if class not const->everything
elsif kind == :constructor || !parse_class_class.const || c.const? ||
c.static?
annots << Annotation.new(implicit: true).freeze
end
end
annots.each do |a|
lua_function c, a, parent, MemberInfo.new(info_klass: parse_class_class)
end
end
# inside class
def parse_class type, klass
type.declaration.visit_children do |c, par|
kind = c.kind
case kind
when :cxx_base_specifier
if c.public? && (lc = class?(c.type))
klass.parents << lc
end
when :using_declaration
c.referenced.overloaded_decls.each do |v|
if v.kind != :type_alias_decl
parse_method v, v.kind, c.semantic_parent, klass
end
end
end
next :continue unless %i(cxx_method function_template constructor
field_decl enum_constant_decl).member? kind
klass.has_push_lua = true if c.spelling == 'PushLua'
parse_method c, kind, c.semantic_parent, klass
:continue
end
if klass.smart_object && !type.abstract_type? && !klass.has_push_lua
Util.print_warning 'Missing LIBSHIT_DYNAMIC_OBJECT', type.declaration
end
klass
end
# top level parse
def real_parse c, parent
return :continue unless @parser.filter[c, parent]
case c.kind
when :namespace; return :recurse
when :class_decl, :struct_decl, :enum_decl
# ignore fwd decls/templates
if c.definition? && c.specialized_template.null?
x = class? c.type
@ret_lua_classes << parse_class(c.type, x) if x
return :recurse # support inner classes
end
when :function_decl
get_annotations(c).each {|a| lua_function c, a, c.semantic_parent }
when :type_alias_decl
get_annotations(c).each do |a|
t = c.typedef_type
if x = class?(t)
@templates[t] = c.spelling
x.name = a.fullname || "#{x.name}_#{a.name || c.spelling}"
x.template_alias = a
# hack!
x.template_args =
t.spelling.gsub(/^[:0-9a-zA-Z ]*</, '').gsub(/>[ *&]*$/, '')
@ret_lua_classes << parse_class(t, x)
else
Util.print_error 'is not a lua class', c
end
end
end
:continue
rescue
Util.print_error $!, c
:continue
end
def path_isbase base, o
o[0, base.size] == base
end
def parse_templates c
cwd = Pathname.pwd.cleanpath
base = cwd.join('src').each_filename.to_a
basetest = cwd.join('test').each_filename.to_a
libshittest = cwd.join('libshit/test').each_filename.to_a
cache = {}
c.visit_children do |c2, par|
file = c2.location.file
if cache[file].nil?
this = cwd.join(file).cleanpath.each_filename.to_a
cache[file] = path_isbase(base, this) || path_isbase(basetest, this) ||
path_isbase(libshittest, this)
end
next :continue unless cache[file]
case c2.kind
when :namespace; next :recurse
when :struct_decl
fake_class = nil
c2.visit_children do |c3, par3|
kind = c3.kind
next :continue if kind == :namespace_ref || kind == :type_ref
if kind == :type_alias_decl && c3.spelling == 'FakeClass'
fake_class = c3.typedef_type
next :continue
end
next :break unless fake_class
if kind == :cxx_method
anns = get_annotations c3
anns << Annotation.new(implicit: true).freeze if anns.empty?
anns.each do |a|
if klass = class?(fake_class)
mi = MemberInfo.new info_klass: klass,
use_class: @parser.aliaser.type_name(par3)
lua_function c3, a, c3.semantic_parent, mi
else
Util.print_error 'FakeClass is not lua class', c
end
end
end
:continue
rescue
Util.print_error $!, c
:continue
end
end
:continue
end
end
end