Cpp Metaprogramming |
|
Below we express in Lua a definition of a C++ class and a C++ module (compilation unit) containing that class.
square = Class("Square") :include [[ public <string> ]] :include [[ <sstream> ]] :include [[ <iostream> ]] :property[[ double width = 0.0 ]] :property[[ double height = 0.0 ]] :func[[ double get_area() const { return get_width() * get_height(); } ]] :func[[ void scale(double amount = 0) { set_width(get_width() * amount); set_height(get_height() * amount); } ]] :func[[ std::string tostring() { ostringstream os; os << "Square[" << get_width() << "," << get_height() << "]"; return os.str(); } ]] squarem = Module("squarem") :include[[<iostream>]] :using_namespace[[ std ]] :class(square) :func[[ int main() { Square s; s.set_width(10); s.set_height(20); s.scale(2); cout << s.tostring() << " " << s.get_area() << endl; return 0; } ]] squarem:write()
We seek to have that automatically generate these two files in standard C++:
squarem.h
#ifndef MODULE_squarem
#define MODULE_squarem
#include <string>
class Square {
public:
Square::Square();
Square::Square(const Square & other);
Square & operator=(const Square & other);
double get_width() const { return width; }
void set_width(double _) { width = _; }
double get_height() const { return height; }
void set_height(double _) { height = _; }
double get_area() const;
void scale(double amount = 0);
std::string tostring();
private:
double width;
double height;
};
#endif // first include
squarem.cpp
#include "squarem.h"
#include <sstream>
#include <iostream>
using namespace std;
Square::Square() : width(0.0), height(0.0)
{
}
Square::Square(const Square & other) : width(other.width), height(other.height)
{
}
Square &
Square::operator=(const Square & other) {
width = other.width;
height = other.height;
}
double
Square::get_area() const {
return get_width() * get_height();
}
void
Square::scale(double amount) {
set_width(get_width() * amount);
set_height(get_height() * amount);
}
std::string
Square::tostring() {
ostringstream os;
os << "Square[" << get_width() << "," << get_height() << "]";
return os.str();
}
int
main() {
Square s;
s.set_width(10);
s.set_height(20);
s.scale(2);
cout << s.tostring() << " " << s.get_area() << endl;
return 0;
}
This can be compiled with the following Lua code:
-- remove leading indents from string
function unindent(s)
local spacer = string.match(s, "^( *)")
local t = {}
string.gsub(s, "[^\n]+", function(s) t[#t+1] = s; return "" end)
for i = 1,#t do
t[i] = string.gsub(t[i], "^" .. spacer, "")
end
s = table.concat(t, "\n")
return s
end
-- substitute variables in string
function subst(s, vars)
for k,v in pairs(vars) do
s = string.gsub(s, "%%%(" .. k .. "%)", v)
end
return s
end
-- remove whitespace at beginning and end of string
function trim(s)
s = string.gsub(s, "^%s+", "")
s = string.gsub(s, "%s+$", "")
return s
end
-- format function definition
function format_funcdef(func, parent)
-- Remove default assignments from arguments
local args = func.args
args = string.gsub(args, "%s*=[^,)]+", "")
local body = func.body
if string.sub(body, 1, 1) == "{" then body = " " .. body end
local mods = (func.mods == "") and "" or " " .. func.mods
local prefix = parent and (parent .. "::") or ""
local cfunc = " " .. func.type .. "\n"
.. prefix .. func.name .. "(" .. args .. ")" .. mods
.. body .. "\n"
return cfunc
end
local Class = {}
Class.__index = Class
setmetatable(Class, {__call = function(self, ...) return Class.new(...) end})
function Class.new(name)
return setmetatable({
name = name,
properties = {},
funcs = {},
includes = {}
}, Class)
end
function Class:property(ccode)
ccode = unindent(ccode)
local type, name, default
= string.match(ccode, "^%s*(.+)%s+([%w_]+)%s*=%s*(.-)%s*$")
--print('P', type, ';', name, ';', default)
assert(type, ccode)
local prop = {name = name, type = type, default = default}
table.insert(self.properties, prop)
return self
end
function Class:func(ccode)
ccode = unindent(ccode)
local type, name, args, mods, body
= string.match(ccode, "^%s*([^(]+)%s+([%w_]+)%s*%((.*)%)%s*([^{]-)%s*({.*})")
assert(type, ccode)
--print("F", type,";",name, ";",args, ";",body)
local func = {name = name, args = args, type = type, mods = mods, body = body}
table.insert(self.funcs, func)
return self
end
function Class:include(name)
name = trim(name)
local name2 = string.gsub(name, "^%s*public%s+", "")
local visibility = (name2 ~= name) and "public" or "private"
table.insert(self.includes, {name2, visibility = visibility})
return self
end
function Class:declaration()
local cfields = ""
for _,prop in ipairs(self.properties) do
local cfield = " %type %name;\n"
cfield = string.gsub(cfield, "%%type", prop.type)
cfield = string.gsub(cfield, "%%name", prop.name)
cfields = cfields .. cfield
end
local cpubs = ""
for _,prop in ipairs(self.properties) do
local cfield =
" %type get_%name() const { return %name; }\n" ..
" void set_%name(%type _) { %name = _; }\n"
cfield = string.gsub(cfield, "%%type", prop.type)
cfield = string.gsub(cfield, "%%name", prop.name)
cpubs = cpubs .. cfield
end
for _,func in ipairs(self.funcs) do
local mods = (func.mods == "") and "" or " " .. func.mods
local cfunc = " " .. func.type .. " " .. func.name .. "(" .. func.args .. ")" .. mods .. ";\n"
cpubs = cpubs .. cfunc
end
local c = subst([[
class %(name) {
public:
%(name)::%(name)();
%(name)::%(name)(const %(name) & other);
%(name) & operator=(const %(name) & other);
%(cpubs)
private:
%(cfields)
};
]], {name = self.name, cpubs = cpubs, cfields = cfields})
return c
end
function Class:implementation()
local cinit = ""
if #self.properties > 0 then
for _,prop in ipairs(self.properties) do
cinit = cinit .. ", " .. prop.name .. "(" .. prop.default .. ")"
end
cinit = ":" .. string.sub(cinit, 2)
end
local cinit3 = ""
if #self.properties > 0 then
for _,prop in ipairs(self.properties) do
cinit3 = cinit3 .. ", " .. prop.name .. "(other." .. prop.name .. ")"
end
cinit3 = ":" .. string.sub(cinit3, 2)
end
local cassigninit = ""
if #self.properties > 0 then
for _,prop in ipairs(self.properties) do
cassigninit = cassigninit .. " " .. prop.name .. " = other." .. prop.name .. ";\n"
end
end
local cfuncs = ""
for _,func in ipairs(self.funcs) do
local cfunc = format_funcdef(func, self.name)
cfuncs = cfuncs .. cfunc
end
local c = subst([[
%(name)::%(name)() %(cinit)
{
}
%(name)::%(name)(const %(name) & other) %(cinit3)
{
}
%(name) &
%(name)::operator=(const %(name) & other) {
%(cassigninit)
}
%(cfuncs)
]], {cinit3 = cinit3, cassigninit = cassigninit, cinit = cinit,
name = self.name, cfuncs = cfuncs})
return c
end
local Module = {}
Module.__index = Module
setmetatable(Module, {__call = function(self, ...) return Module.new(...) end})
function Module.new(name)
return setmetatable({
name = name,
includes = {},
using_namespaces = {},
classes = {},
funcs = {}
}, Module)
end
function Module:include(name)
local name2 = string.gsub(name, "^%s*public%s+", "")
local visibility = (name2 ~= name) and "public" or "private"
table.insert(self.includes, {name2, visibility = visibility})
return self
end
function Module:using_namespace(name)
name = trim(name)
table.insert(self.using_namespaces, name)
return self
end
function Module:class(o)
table.insert(self.classes, o)
return self
end
function Module:func(ccode)
ccode = unindent(ccode)
local type, name, args, mods, body
= string.match(ccode, "^%s*([^(]+)%s+([%w_]+)%s*%((.*)%)%s*([^{]-)%s*({.*})")
assert(type, ccode)
--print("F", type,";",name, ";",args, ";",body)
local func = {name = name, args = args, type = type, mods = mods, body = body}
table.insert(self.funcs, func)
return self
end
function Module:header()
local cincludes = ""
local includes = {}
for _,v in ipairs(includes) do includes[#includes+1] = v end
for _,class in ipairs(self.classes) do
for _,v in ipairs(class.includes) do includes[#includes+1] = v end
end
for _,v in ipairs(includes) do
if v.visibility == "public" then
cincludes = cincludes .. "#include " .. v[1] .. "\n"
end
end
local cclassdecs = ""
for _,class in ipairs(self.classes) do
cclassdecs = cclassdecs .. class:declaration()
end
local c = subst([[
#ifndef MODULE_%(name)
#define MODULE_%(name)
%(cincludes)
%(cclassdecs)
#endif // first include
]], {name = self.name, cincludes = cincludes, cclassdecs = cclassdecs})
return c
end
function Module:implementation()
local cincludes = ""
for _,class in ipairs(self.classes) do
local includes = class.includes
for _,v in ipairs(includes) do
if v.visibility == "private" then
cincludes = cincludes .. "#include " .. v[1] .. "\n"
end
end
end
local cusings = ""
for _,ns in ipairs(self.using_namespaces) do
cusings = cusings .. "using namespace " .. ns .. ";\n";
end
local cdefs = ""
for _,class in ipairs(self.classes) do
cdefs = cdefs .. class:implementation() .. "\n"
end
local cfuncs = ""
for _,func in ipairs(self.funcs) do
local cfunc = format_funcdef(func)
cfuncs = cfuncs .. cfunc
end
local c = subst([[
#include "%(name).h"
%(cincludes)
%(cusings)
%(cdefs)
%(cfuncdefs)
]], {name = self.name, cdefs = cdefs, cincludes = cincludes,
cfuncdefs = cfuncs, cusings = cusings})
return c
end
function Module:write()
local fh = assert(io.open(self.name .. ".h", "w"))
fh:write(squarem:header())
fh:close()
local fh = assert(io.open(self.name .. ".cpp", "w"))
fh:write(squarem:implementation())
fh:close()
end
Warning: The above is a rough draft. The code currently does not handle the corner cases or even most cases. However, I believe the approach case reasonably be made robust. It mostly avoids the difficulties of parsing C++.
See also LuaProxyDll for a related C metaprogramming example.