From 099235d132b17b2b735cfd46b196cc954f8cba51 Mon Sep 17 00:00:00 2001 From: Michal Kottman Date: Mon, 22 Nov 2010 01:02:59 +0100 Subject: [PATCH] Implicit conversion constructor implementation Now, Lqt does what the C++ compiler does for you - in a place where const reference or const pointer to classes like QString or QVariant, you can pass other value for which the class has a constructor (like Lua string for QString or numbers for QVariant), and Lqt automatically generates a temporary instance. This simplifies programming, because now you can use Lua strings where QString is expected, and also helps with QVariant, QBrush, QPen, and some other GUI classes. The temporaries are garbage collected by Lua. --- common/lqt_common.cpp | 40 ++++++++++++-- common/lqt_common.hpp | 7 +++ common/lqt_qt.cpp | 23 ++++++++ common/lqt_qt.hpp | 4 ++ generator/class_types.lua | 76 ++++++++++++++++++-------- generator/classes.lua | 133 ++++++++++++++++++++++++++++++++++++++++++---- generator/qt_internal.lua | 5 +- generator/qtypes.lua | 9 +++- generator/virtuals.lua | 4 +- 9 files changed, 263 insertions(+), 38 deletions(-) diff --git a/common/lqt_common.cpp b/common/lqt_common.cpp index 75c1b08..bcff4b9 100644 --- a/common/lqt_common.cpp +++ b/common/lqt_common.cpp @@ -174,7 +174,7 @@ int lqtL_createenumlist (lua_State *L, lqt_Enumlist list[]) { } static int lqtL_gcfunc (lua_State *L) { - if (!lua_isuserdata(L, 1) && lua_islightuserdata(L, 1)) return 0; + if (!lua_isuserdata(L, 1) || lua_islightuserdata(L, 1)) return 0; lua_getfenv(L, 1); // (1) if (!lua_istable(L, -1)) { lua_pop(L, 1); // (0) @@ -432,9 +432,9 @@ void lqtL_pushudata (lua_State *L, const void *p, const char *name) { void lqtL_passudata (lua_State *L, const void *p, const char *name) { lqtL_pushudata(L, p, name); - // FIXME: these should be added, but it is not safe for now - //lua_getfield(L, -1, "delete"); - //lua_setfield(L, -2, "__gc"); + // used only when passing temporaries - should be deleted afterwards + lua_getfield(L, -1, "delete"); + lua_setfield(L, -2, "__gc"); return; } @@ -712,3 +712,35 @@ void lqtL_register_super(lua_State *L) { lua_pop(L, -1); } } + +// returns true if the value at index `n` can be converted to `to_type` +bool lqtL_canconvert(lua_State *L, int n, const char *to_type) { + if (lqtL_testudata(L, n, to_type)) + return true; + int oldtop = lua_gettop(L); + luaL_getmetatable(L, to_type); + lua_getfield(L, -1, "__test"); + if (lua_isnil(L, -1)) { + lua_settop(L, oldtop); + return false; + } + lqt_testfunc func = (lqt_testfunc) lua_touserdata(L, -1); + lua_settop(L, oldtop); + return func(L, n); +} + +// converts the value at index `n` to `to_type` and returns a pointer to it +void *lqtL_convert(lua_State *L, int n, const char *to_type) { + if (lqtL_testudata(L, n, to_type)) + return lqtL_toudata(L, n, to_type); + int oldtop = lua_gettop(L); + luaL_getmetatable(L, to_type); + lua_getfield(L, -1, "__convert"); + if (lua_isnil(L, -1)) { + lua_settop(L, oldtop); + return false; + } + lqt_convertfunc func = (lqt_convertfunc) lua_touserdata(L, -1); + lua_settop(L, oldtop); + return func(L, n); +} diff --git a/common/lqt_common.hpp b/common/lqt_common.hpp index 0b5f927..5d64951 100644 --- a/common/lqt_common.hpp +++ b/common/lqt_common.hpp @@ -90,6 +90,13 @@ void * lqtL_checkudata (lua_State *, int, const char *); void lqtL_eraseudata (lua_State *, int, const char *); #define lqtL_isudata lqtL_testudata + +bool lqtL_canconvert(lua_State *L, int n, const char *to_type); +void *lqtL_convert(lua_State *L, int n, const char *to_type); +typedef bool (*lqt_testfunc) (lua_State *L, int n); +typedef void* (*lqt_convertfunc) (lua_State *L, int n); + + void lqtL_pushenum (lua_State *, int, const char *); bool lqtL_isenum (lua_State *, int, const char *); int lqtL_toenum (lua_State *, int, const char *); diff --git a/common/lqt_qt.cpp b/common/lqt_qt.cpp index 32bf86e..7ee809a 100644 --- a/common/lqt_qt.cpp +++ b/common/lqt_qt.cpp @@ -231,3 +231,26 @@ void lqtL_qobject_custom (lua_State *L) { lua_pushcfunction(L, lqtL_connect); lua_setfield(L, -2, "connect"); } + + +QList lqtL_getStringList(lua_State *L, int i) { + QList ret; + int n = lua_objlen(L, i); + for (int i=0; i &table) { + const int n = table.size(); + lua_createtable(L, n, 0); + for (int i=0; i lqtL_getStringList(lua_State *L, int i); +void lqtL_pushStringList(lua_State *L, const QList &table); #endif // __LQT_QT_HPP diff --git a/generator/class_types.lua b/generator/class_types.lua index 6d5f7be..2794fd4 100644 --- a/generator/class_types.lua +++ b/generator/class_types.lua @@ -3,13 +3,19 @@ lqt = lqt or {} lqt.classes = lqt.classes or {} +-- Field explanation: +-- * push - push the instance of a class onto Lua stack +-- * get - retrieve the instance from Lua stack +-- * test - true if the stack index is an instance +-- * bound - true for generated classes (false for native types) +-- * foreign - comes from other module (like QtCore) -local pointer_t = function(fn) +local pointer_t = function(fn, foreign) local cn = string.gsub(fn, '::', '.') return { -- the argument is a pointer to class push = function(n) - return 'lqtL_passudata(L, '..n..', "'..cn..'*")', 1 + return 'lqtL_pushudata(L, '..n..', "'..cn..'*")', 1 end, get = function(n) return 'static_cast<'..fn..'*>' @@ -19,26 +25,39 @@ local pointer_t = function(fn) return 'lqtL_isudata(L, '..n..', "'..cn..'*")', 1 end, onstack = cn..'*,', + bound = true, + foreign = foreign, } end -local pointer_const_t = function(fn) +local pointer_const_t = function(fn, foreign) local cn = string.gsub(fn, '::', '.') return { -- the argument is a pointer to constant class instance push = function(n) - return 'lqtL_passudata(L, '..n..', "'..cn..'*")', 1 + return 'lqtL_pushudata(L, '..n..', "'..cn..'*")', 1 end, get = function(n) - return 'static_cast<'..fn..'*>' - ..'(lqtL_toudata(L, '..n..', "'..cn..'*"))', 1 + local val + if typesystem.can_convert[cn] then + val = 'lqtL_convert(L, '..n..', "'..cn..'*")' + else + val = 'lqtL_toudata(L, '..n..', "'..cn..'*")' + end + return 'static_cast<'..fn..'*>('..val..')', 1 end, test = function(n) - return 'lqtL_isudata(L, '..n..', "'..cn..'*")', 1 + if typesystem.can_convert[cn] then + return 'lqtL_canconvert(L, '..n..', "'..cn..'*")', 1 + else + return 'lqtL_isudata(L, '..n..', "'..cn..'*")', 1 + end end, onstack = cn..'*,', + bound = true, + foreign = foreign, } end -local ref_t = function(fn) +local ref_t = function(fn, foreign) local cn = string.gsub(fn, '::', '.') return { -- the argument is a reference to class @@ -53,9 +72,11 @@ local ref_t = function(fn) return 'lqtL_isudata(L, '..n..', "'..cn..'*")', 1 end, onstack = cn..'*,', + bound = true, + foreign = foreign, } end -local instance_t = function(fn) +local instance_t = function(fn, foreign) local cn = string.gsub(fn, '::', '.') return { -- the argument is the class itself @@ -70,9 +91,11 @@ local instance_t = function(fn) return 'lqtL_isudata(L, '..n..', "'..cn..'*")', 1 end, onstack = cn..'*,', + bound = true, + foreign = foreign, } end -local const_ref_t = function(fn) +local const_ref_t = function(fn, foreign) local cn = string.gsub(fn, '::', '.') return { -- the argument is a pointer to class @@ -80,28 +103,39 @@ local const_ref_t = function(fn) return 'lqtL_copyudata(L, &'..n..', "'..cn..'*")', 1, string.gsub(fn, ' const&$', '') end, get = function(n) - return '*static_cast<'..fn..'*>' - ..'(lqtL_toudata(L, '..n..', "'..cn..'*"))', 1 + local val + if typesystem.can_convert[cn] then + val = 'lqtL_convert(L, '..n..', "'..cn..'*")' + else + val = 'lqtL_toudata(L, '..n..', "'..cn..'*")' + end + return '*static_cast<'..fn..'*>('..val..')', 1 end, test = function(n) - return 'lqtL_isudata(L, '..n..', "'..cn..'*")', 1 + if typesystem.can_convert[cn] then + return 'lqtL_canconvert(L, '..n..', "'..cn..'*")', 1 + else + return 'lqtL_isudata(L, '..n..', "'..cn..'*")', 1 + end end, onstack = cn..'*,', + bound = true, + foreign = foreign, } end local const_ptr_ref_t = pointer_const_t -lqt.classes.insert = function(cname) +lqt.classes.insert = function(cname, foreign) if typesystem[cname]==nil then - typesystem[cname..'*'] = pointer_t(cname) - typesystem[cname..' const*'] = pointer_const_t(cname) - typesystem[cname..'&'] = ref_t(cname) + typesystem[cname..'*'] = pointer_t(cname, foreign) + typesystem[cname..' const*'] = pointer_const_t(cname, foreign) + typesystem[cname..'&'] = ref_t(cname, foreign) - typesystem[cname] = instance_t(cname) - typesystem[cname..' const'] = instance_t(cname) - typesystem[cname..' const&'] = const_ref_t(cname) - typesystem[cname..'* const&'] = const_ptr_ref_t(cname) + typesystem[cname] = instance_t(cname, foreign) + typesystem[cname..' const'] = instance_t(cname, foreign) + typesystem[cname..' const&'] = const_ref_t(cname, foreign) + typesystem[cname..'* const&'] = const_ptr_ref_t(cname, foreign) return true else diff --git a/generator/classes.lua b/generator/classes.lua index cbceeda..bffc950 100644 --- a/generator/classes.lua +++ b/generator/classes.lua @@ -5,9 +5,14 @@ require 'signalslot' module('classes', package.seeall) +-- collection of all functions local functions = {} +-- collection of bound classes local classes = {} +-- list of files to be included local cpp_files = {} +-- table of classes by their cname +local classlist = {} --- Copies functions from the index. function copy_functions(index) @@ -105,6 +110,9 @@ function copy_classes(index) end end templates.finish(index) + for c in pairs(classes) do + classlist[c.xarg.cname] = c + end end function fix_methods_wrappers() @@ -314,12 +322,91 @@ function fill_copy_constructor() end end +function fill_implicit_constructor() + typesystem.can_convert = {} + for c in pairs(classes) do + for _,f in ipairs(c) do + if f.label:match("^Function") then + -- find non-explicit constructor, which has 1 argument of type different + -- from class, i.e. an implicit conversion constructor + if f.xarg.name == c.xarg.name + and #f == 1 + and (not f.xarg.access or f.xarg.access == "public") + and f[1].xarg.type_base ~= c.xarg.name + and not f[1].xarg.type_base:match('Private$') + and not f.xarg.explicit + and not c.abstract + then + local class_name = c.xarg.cname + local from_type = f[1].xarg.type_base + typesystem.can_convert[class_name] = typesystem.can_convert[class_name] or { from = {}, class = c } + typesystem.can_convert[class_name].from[ from_type ] = f + local safe_from = from_type:gsub('[<>*]', '_'):gsub('::', '_LQT_') + end + end + end + end +end + +local function generate_implicit_code(class_name, t) + local c = t.class + local fullname = c.xarg.fullname + local luaname = fullname:gsub('::', '.') + + local test_header = 'bool lqt_canconvert_' .. class_name .. '(lua_State *L, int n)' + local convert_header = 'void* lqt_convert_' .. class_name .. '(lua_State *L, int n)' + + local test_code = "" + local convert_code = "" + local tests = {} + + for _, f in pairs(t.from) do + local typ = f[1].xarg.type_name + if not typesystem[typ] then + ignore(typ, "implicit constructor - unknown type", _) + else + local test = typesystem[typ].test('n') + if not tests[test] then + tests[test] = true + test_code = test_code..' if ('..test..')\n' + test_code = test_code..' return true;\n' + + local newtype = fullname .. '(arg)' + if c.shell then newtype = 'lqt_shell_'..c.xarg.cname..'(L,arg)' end + convert_code = convert_code + ..' if ('..test..') {\n' + ..' '..typ..' arg = '..typesystem[typ].get('n')..';\n' + ..' '..fullname..' *ret = new '..newtype..';\n' + ..' lqtL_passudata(L, ret, "'..luaname..'*");\n' + ..' return ret;\n }\n' + end + end + end + test_code = test_code .. ' return false;' + convert_code = convert_code..' return NULL;' + + c.implicit = { + headers = { test = test_header, convert = convert_header }, + test = test_header .. '{\n' .. test_code .. '\n}', + convert = convert_header .. '{\n' .. convert_code .. '\n}' + } +end + + +function fill_implicit_wrappers() + for class_name, t in pairs(typesystem.can_convert) do + if not t.class.abstract then + generate_implicit_code(class_name, t) + end + end +end + local put_class_in_filesystem = lqt.classes.insert function fill_typesystem_with_classes() for c in pairs(classes) do - classes[c] = put_class_in_filesystem(c.xarg.fullname) --, true) + classes[c] = put_class_in_filesystem(c.xarg.fullname) end end @@ -598,13 +685,19 @@ function print_single_class(c) local n = c.xarg.cname local lua_name = string.gsub(c.xarg.fullname, '::', '.') local cppname = module_name..'_meta_'..n..'.cpp' - table.insert(cpp_files, cppname) -- global cpp_files + table.insert(cpp_files, n) -- global cpp_files local fmeta = assert(io.open(module_name.._src..cppname, 'w')) local print_meta = function(...) fmeta:write(...) fmeta:write'\n' end print_meta('#include "'..module_name..'_head_'..n..'.hpp'..'"\n\n') + + if c.implicit then + print_meta(c.implicit.test) + print_meta(c.implicit.convert) + end + print_meta(c.wrappers) if c.virtual_overloads then print_meta(c.virtual_overloads) @@ -614,6 +707,18 @@ function print_single_class(c) ..lua_name..'*", lqt_metatable' ..c.xarg.id..', lqt_base' ..c.xarg.id..');') + + if c.implicit then + print_meta('\tluaL_getmetatable(L, "'..lua_name..'*");') + print_meta('\tlua_pushliteral(L, "__test");') + print_meta('\tlua_pushlightuserdata(L, (void*)&lqt_canconvert_'..c.xarg.cname..');') + print_meta('\tlua_rawset(L, -3);') + print_meta('\tlua_pushliteral(L, "__convert");') + print_meta('\tlua_pushlightuserdata(L, (void*)&lqt_convert_'..c.xarg.cname..');') + print_meta('\tlua_rawset(L, -3);') + print_meta('\tlua_pop(L, 1);') + end + print_meta'\treturn 0;' print_meta'}' print_meta'' @@ -661,7 +766,11 @@ function print_merged_build() local mergename = module_name..'_merged_build' local merged = assert(io.open(path..mergename..'.cpp', 'w')) for _, p in ipairs(cpp_files) do - merged:write('#include "'..p..'"\n') + merged:write('#include "',module_name,'_head_',p,'.hpp"\n') + end + merged:write('\n') + for _, p in ipairs(cpp_files) do + merged:write('#include "',module_name,'_meta_',p,'.cpp"\n') end local pro_file = assert(io.open(path..mergename..'.pro', 'w')) @@ -690,15 +799,18 @@ function print_class_list() local n = c.xarg.cname if n=='QObject' then qobject_present = true end print_single_class(c) - table.insert(big_picture, 'luaopen_'..n) - table.insert(type_list_t, 'add_class(\''..c.xarg.fullname..'\', types)\n') + table.insert(big_picture, n) + table.insert(type_list_t, 'add_class \''..c.xarg.fullname..'\'\n') end local type_list_f = assert(io.open(module_name.._src..module_name..'_types.lua', 'w')) type_list_f:write([[ #!/usr/bin/lua local types = (...) or {} -local add_class = lqt.classes.insert or error('module lqt.classes not loaded') +assert(lqt.classes.insert, 'module lqt.classes not loaded') +local function add_class(class) + lqt.classes.insert(class, true) +end ]]) for k, v in ipairs(type_list_t) do type_list_f:write(v) @@ -716,12 +828,12 @@ local add_class = lqt.classes.insert or error('module lqt.classes not loaded') print_meta('#include "lqt_common.hpp"') print_meta('#include "'..module_name..'_slot.hpp'..'"\n\n') for _, p in ipairs(big_picture) do - print_meta('extern "C" LQT_EXPORT int '..p..' (lua_State *);') + print_meta('extern "C" LQT_EXPORT int luaopen_'..p..' (lua_State *);') end print_meta('void lqt_create_enums_'..module_name..' (lua_State *);') print_meta('extern "C" LQT_EXPORT int luaopen_'..module_name..' (lua_State *L) {') for _, p in ipairs(big_picture) do - print_meta('\t'..p..'(L);') + print_meta('\tluaopen_'..p..'(L);') end print_meta('\tlqt_create_enums_'..module_name..'(L);') if qobject_present then @@ -752,18 +864,19 @@ function process(index, typesystem, filterfiles) classes = loadfile(f)(classes) end - fill_typesystem_with_classes() - virtuals.fill_virtuals(classes) -- does that, destructor ("~") excluded distinguish_methods() -- does that fill_public_destr() -- does that: checks if destructor is public fill_copy_constructor() -- does that: checks if copy contructor is public or protected + fill_implicit_constructor() fix_methods_wrappers() get_qobjects() + fill_typesystem_with_classes() fill_wrappers() virtuals.fill_virtual_overloads(classes) -- does that virtuals.fill_shell_classes(classes) -- does that + fill_implicit_wrappers() signalslot.process(functions) end diff --git a/generator/qt_internal.lua b/generator/qt_internal.lua index edeac49..08848cc 100644 --- a/generator/qt_internal.lua +++ b/generator/qt_internal.lua @@ -24,7 +24,7 @@ for c in pairs(classes) do or c.xarg.fullname=='QTextStreamManipulator' -- compiles or c.xarg.fullname=='QtConcurrent::ThreadEngineSemaphore' -- compiles or c.xarg.fullname=='QTextObject' -- private/protected destcrutor - or c.xarg.fullname=='QTextCodec' -- private/protected destcrutor + or c.xarg.fullname:match('^QTextCodec') -- private/protected destcrutor or c.xarg.fullname=='QTextBlockGroup' -- private/protected destcrutor or c.xarg.fullname=='QSessionManager' -- private/protected destcrutor or c.xarg.fullname=='QClipboard' -- private/protected destcrutor @@ -66,8 +66,11 @@ for c in pairs(classes) do or c.xarg.fullname=='QHashData' -- not in the docs at all. free_helper is not present during compilation or string.match(c.xarg.fullname, '^QtConcurrent') -- does not make sense anyway, because we should duplicate the lua_State or string.match(c.xarg.fullname, '^QAccessible') -- causes a lot of headaches, and not necessarry anyway (yet) + or string.match(c.xarg.fullname, 'Private$') -- should not bind these ) then ret1[c] = true + else + ignore(c.xarg.fullname, "blacklisted", "filter") end end diff --git a/generator/qtypes.lua b/generator/qtypes.lua index 1cebc54..cc05b1c 100644 --- a/generator/qtypes.lua +++ b/generator/qtypes.lua @@ -82,11 +82,18 @@ qt_types['QRectF const&'] = qt_types['QRectF'] qt_types['QByteArray'] = { get = function(i) return 'QByteArray(lua_tostring(L, '..i..'), lua_objlen(L, '..i..'))', 1, 'QByteArray' end, push = function(i) return 'lua_pushlstring(L, '..i..'.constData(), '..i..'.size())', 1 end, - test = function(i) return 'lua_isstring(L, '..i..')', 1 end, + test = function(i) return 'lqtL_isstring(L, '..i..')', 1 end, onstack = 'string,', } qt_types['QByteArray const&'] = qt_types['QByteArray'] +qt_types['QList'] = { + get = function(i) return 'lqtL_getStringList(L, '..i..')', 1, 'QList' end, + push = function(i) return 'lqtL_pushStringList(L, '..i..')', 1 end, + test = function(i) return 'lua_istable(L, '..i..')', 1 end, + onstack = 'table,', +} + if not getmetatable(qt_types) then setmetatable(qt_types, { __index = function(t, k) diff --git a/generator/virtuals.lua b/generator/virtuals.lua index 8804961..f6254b2 100644 --- a/generator/virtuals.lua +++ b/generator/virtuals.lua @@ -223,7 +223,9 @@ function print_shell_classes(classes) dump(c) end end - print_head('#endif // LQT_HEAD_'..n) + + print_head('extern "C" LQT_EXPORT int luaopen_'..n..' (lua_State *);') + print_head('\n\n#endif // LQT_HEAD_'..n) fhead:close() end return classes -- 2.11.4.GIT