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
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
|