Cpp Metaprogramming


Metaprogramming [1] is sometimes achieved in C++ using the C preprocessor or templates, but we might achieve greater simplicity and flexibility (and even aspect-oriented programming [2]) using Lua to do the metaprogramming for C++.

Below we express in Lua a definition of a C++ class and a C++ module (compilation unit) containing that class.

require "cppmeta" :import()
-- Define class.
Rectangle =
 Class "Rectangle"
 :include "public <string>"
 :include "<sstream>"
 :include "<iostream>"
 :property "double width = 0.0"
 :property "double height = 0.0"
 :func[[
 /* Get the surface area of the rectangle. */
 double get_area() const {
 return get_width() * get_height();
 }
 ]]
 :func[[
 /* Resize rectangle by scale factor. */
 void scale(double amount = 0) {
 set_width(get_width() * amount);
 set_height(get_height() * amount);
 }
 ]]
 :func[[
 /* Convert rectangle to string. */
 std::string tostring() {
 ostringstream os;
 os << "Rectangle[" << get_width() << "," << get_height() << "]";
 return os.str();
 }
 ]]
-- Define module (compilation unit).
Rectanglem = 
 Module "rectanglem"
 :include "<iostream>"
 :using_namespace"std"
 :class(Rectangle)
 :func[[
 /* entry point */
 int main() {
 Rectangle s;
 s.set_width(10);
 s.set_height(20);
 s.scale(2);
 cout << s.tostring() << " " << s.get_area() << " " << s.get_class_name() << endl;
 return 0;
 }
 ]]
-- Insert into any class a member function returning class name.
-- This is a helper function, achieving aspect oriented programming somewhat.
function name_class(class)
 local ccode = cppmeta.subst([[
 /* Return name of class as string. */
 std::string get_class_name() { return "%(name)"; }
 ]], {name = class.name})
 class:func(ccode):include("public <string>")
 return c
end
name_class(Rectangle) -- add other functions to class.
Rectanglem:write() -- generate source files for C++ compiler.
-- Just for fun, print out a list of method names and descriptions too.
for _,v in ipairs(Rectangle.funcs) do
 print(v.name, v.comment)
end

We seek to have that automatically generate these two files in standard C++:

rectanglem.h

#ifndef MODULE_rectanglem
#define MODULE_rectanglem
#include <string>
class Rectangle {
public:
 Rectangle::Rectangle();
 explicit Rectangle::Rectangle(const Rectangle & other);
 Rectangle & operator=(const Rectangle & 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();
 std::string get_class_name();
private:
 double width;
 double height;
};
#endif // first include

rectanglem.cpp

#include "rectanglem.h"
#include <sstream>
#include <iostream>
using namespace std;
Rectangle::Rectangle() : width(0.0), height(0.0)
{
}
Rectangle::Rectangle(const Rectangle & other) : width(other.width), height(other.height)
{
}
 Rectangle &
Rectangle::operator=(const Rectangle & other) {
 width = other.width;
 height = other.height;
}
 double
Rectangle::get_area() const {
 return get_width() * get_height();
}
 void
Rectangle::scale(double amount) {
 set_width(get_width() * amount);
 set_height(get_height() * amount);
}
 std::string
Rectangle::tostring() {
 ostringstream os;
 os << "Rectangle[" << get_width() << "," << get_height() << "]";
 return os.str();
}
 std::string
Rectangle::get_class_name() { return "Rectangle"; }
 int
main() {
 Rectangle s;
 s.set_width(10);
 s.set_height(20);
 s.scale(2);
 cout << s.tostring() << " " << s.get_area() << " " << s.get_class_name() << endl;
 return 0;
}

The "cppmeta.lua" module is implemented at the bottom of this page.

Warning: This code 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 fully parsing C++. The code is known to run on Lua 5.1.

See also LuaProxyDll for a related C metaprogramming example.

--DavidManura


-- cppmeta.lua
-- (c) David Manura, 2007-02.
-- Licensed under the same terms as Lua itself.
module("cppmeta", package.seeall)
function import()
 local env = getfenv(2)
 for k,v in pairs(cppmeta) do env[k] = v end
 return cppmeta
end
-- remove leading indents from string
local function unindent(s)
 local spacer = string.match(s, "( *)}%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
local function trim(s)
 s = string.gsub(s, "^%s+", "")
 s = string.gsub(s, "%s+$", "")
 return s
end
-- extracts leading C comment (if any) from string.
local function remove_comment(ccode)
 local comment
 ccode = string.gsub(ccode, "^%s*/%*(.-)%*/%s*",
 function(s) comment = s; return "" end)
 return ccode, comment
end
-- format function definition
local 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
-- parse function from C++ string.
local function parse_func(ccode)
 local comment
 ccode, comment = remove_comment(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, comment = comment}
 return func
end
-- parse property from C++ string
local function parse_property(ccode)
 ccode = trim(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}
 return prop
end
-- parse "include" from C++ string
local function parse_include(ccode)
 ccode = trim(ccode)
 local ccode2 = string.gsub(ccode, "^%s*public%s+", "")
 local visibility = (ccode2 ~= ccode) and "public" or "private"
 local include = {ccode2, visibility = visibility}
 return include
end
Class = {}
Class.__index = Class
setmetatable(Class, {__call = function(self, ...) return Class.new(...) end})
-- Construct class.
function Class.new(name)
 return setmetatable({
 name = name,
 properties = {},
 funcs = {},
 includes = {}
 }, Class)
end
-- Use property in class.
function Class:property(ccode)
 local prop = parse_property(ccode)
 table.insert(self.properties, prop)
 return self
end
-- Use (member) function in class.
function Class:func(ccode)
 local func = parse_func(ccode)
 table.insert(self.funcs, func)
 return self
end
-- Use include in class.
function Class:include(ccode)
 local include = parse_include(ccode)
 table.insert(self.includes, include)
 return self
end
-- Return string of C++ code for class declaration.
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)();
 explicit %(name)::%(name)(const %(name) & other);
 %(name) & operator=(const %(name) & other);
%(cpubs)
private:
%(cfields)
};
]], {name = self.name, cpubs = cpubs, cfields = cfields})
 return c
end
-- Return string of C++ code for class implementation.
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
Module = {}
Module.__index = Module
setmetatable(Module, {__call = function(self, ...) return Module.new(...) end})
-- Construct module.
function Module.new(name)
 return setmetatable({
 name = name,
 includes = {},
 using_namespaces = {},
 classes = {},
 funcs = {}
 }, Module)
end
-- Use include in module.
function Module:include(ccode)
 local include = parse_include(ccode)
 table.insert(self.includes, include)
 return self
end
-- Use namespace in module.
function Module:using_namespace(name)
 name = trim(name)
 table.insert(self.using_namespaces, name)
 return self
end
-- Define class in module.
function Module:class(o)
 table.insert(self.classes, o)
 return self
end
-- Define function in module.
function Module:func(ccode)
 local func = parse_func(ccode)
 table.insert(self.funcs, func)
 return self
end
-- Return srting of C++ code for module header.
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
 local found = {}
 for _,v in ipairs(includes) do
 if v.visibility == "public" and not found[v[1]] then
 cincludes = cincludes .. "#include " .. v[1] .. "\n"
 found[v[1]] = true
 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
-- Return string of C++ code for module implementation.
function Module:implementation()
 local cincludes = ""
 local found = {}
 for _,class in ipairs(self.classes) do
 local includes = class.includes
 for _,v in ipairs(includes) do
 if v.visibility == "private" and not found[v[1]] then
 cincludes = cincludes .. "#include " .. v[1] .. "\n"
 found[v[1]] = true
 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
-- Write module sources to files.
function Module:write()
 local fh = assert(io.open(self.name .. ".h", "w"))
 fh:write(self:header())
 fh:close()
 local fh = assert(io.open(self.name .. ".cpp", "w"))
 fh:write(self:implementation())
 fh:close()
end

RecentChanges · preferences
edit · history
Last edited February 14, 2007 8:47 pm GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /