Cpp Metaprogramming

lua-users home
wiki

Difference (from prior major revision) (minor diff)

Changed: 1c1
Metaprogramming [1] is sometimes achieved in C++ using the C preprocessor or templates, but we might achieve greater flexibility and simplicity using Lua to do the metaprogramming in C++.
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++.

Changed: 6,12c6,15
square =
Class("Square")
:include [[ public <string> ]]
:include [[ <sstream> ]]
:include [[ <iostream> ]]
:property[[ double width = 0.0 ]]
:property[[ double height = 0.0 ]]
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"

Added: 13a17
/* Get the surface area of the rectangle. */

Added: 18a23
/* Resize rectangle by scale factor. */

Added: 24a30
/* Convert rectangle to string. */

Changed: 27c33
os << "Square[" << get_width() << "," << get_height() << "]";
os << "Rectangle[" << get_width() << "," << get_height() << "]";

Changed: 32,36c38,43
squarem =
Module("squarem")
:include[[<iostream>]]
:using_namespace[[ std ]]
:class(square)
-- Define module (compilation unit).
Rectanglem =
Module "rectanglem"
:include "<iostream>"
:using_namespace"std"
:class(Rectangle)

Added: 37a45
/* entry point */

Changed: 39c47
Square s;
Rectangle s;

Changed: 43c51
cout << s.tostring() << " " << s.get_area() << endl;
cout << s.tostring() << " " << s.get_area() << " " << s.get_class_name() << endl;

Changed: 48c56,73
squarem:write()
-- 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

Changed: 53c78
squarem.h
rectanglem.h

Changed: 55,56c80,81
#ifndef MODULE_squarem
#define MODULE_squarem
#ifndef MODULE_rectanglem
#define MODULE_rectanglem

Changed: 59c84
class Square {
class Rectangle {

Changed: 61,63c86,88
Square::Square();
Square::Square(const Square & other);
Square & operator=(const Square & other);
Rectangle::Rectangle();
explicit Rectangle::Rectangle(const Rectangle & other);
Rectangle & operator=(const Rectangle & other);

Added: 70a96
std::string get_class_name();

Changed: 81c107
squarem.cpp
rectanglem.cpp

Changed: 83c109
#include "squarem.h"
#include "rectanglem.h"

Changed: 89c115
Square::Square() : width(0.0), height(0.0)
Rectangle::Rectangle() : width(0.0), height(0.0)

Changed: 92c118
Square::Square(const Square & other) : width(other.width), height(other.height)
Rectangle::Rectangle(const Rectangle & other) : width(other.width), height(other.height)

Changed: 95,96c121,122
Square &
Square::operator=(const Square & other) {
Rectangle &
Rectangle::operator=(const Rectangle & other) {

Changed: 102c128
Square::get_area() const {
Rectangle::get_area() const {

Changed: 106c132
Square::scale(double amount) {
Rectangle::scale(double amount) {

Changed: 111c137
Square::tostring() {
Rectangle::tostring() {

Changed: 113c139
os << "Square[" << get_width() << "," << get_height() << "]";
os << "Rectangle[" << get_width() << "," << get_height() << "]";

Added: 115a142,143
std::string
Rectangle::get_class_name() { return "Rectangle"; }

Changed: 121c149
Square s;
Rectangle s;

Changed: 125c153
cout << s.tostring() << " " << s.get_area() << endl;
cout << s.tostring() << " " << s.get_area() << " " << s.get_class_name() << endl;

Changed: 130c158
This can be compiled with the following Lua code:
The "cppmeta.lua" module is implemented at the bottom of this page.

Changed: 132c160,179
{{{
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

-----

{{{!Lua
-- 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

Changed: 135,136c182,183
function unindent(s)
local spacer = string.match(s, "^( *)")
local function unindent(s)
local spacer = string.match(s, "( *)}%s*$")

Changed: 155c202
function trim(s)
local function trim(s)

Added: 160a208,215
-- 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


Changed: 162c217
function format_funcdef(func, parent)
local function format_funcdef(func, parent)

Changed: 181c236,270
local Class = {}
-- 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 = {}

Added: 183a273
-- Construct class.

Added: 191a282
-- Use property in class.

Changed: 193,198c284
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}
local prop = parse_property(ccode)

Added: 201a288
-- Use (member) function in class.

Changed: 203,208c290
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}
local func = parse_func(ccode)

Changed: 212,216c294,297
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})
-- Use include in class.
function Class:include(ccode)
local include = parse_include(ccode)
table.insert(self.includes, include)

Added: 218a300
-- Return string of C++ code for class declaration.

Changed: 248c330
%(name)::%(name)(const %(name) & other);
explicit %(name)::%(name)(const %(name) & other);

Added: 256a339
-- Return string of C++ code for class implementation.

Changed: 304c387
local Module = {}
Module = {}

Added: 306a390
-- Construct module.

Changed: 316,319c400,403
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})
-- Use include in module.
function Module:include(ccode)
local include = parse_include(ccode)
table.insert(self.includes, include)

Added: 321a406
-- Use namespace in module.

Added: 326a412
-- Define class in module.

Added: 330a417
-- Define function in module.

Changed: 332,337c419
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}
local func = parse_func(ccode)

Added: 340a423
-- Return srting of C++ code for module header.

Added: 347a431
local found = {}

Changed: 349c433
if v.visibility == "public" then
if v.visibility == "public" and not found[v[1]] then

Added: 350a435
found[v[1]] = true

Added: 367a453
-- Return string of C++ code for module implementation.

Added: 369a456
local found = {}

Changed: 373c460
if v.visibility == "private" then
if v.visibility == "private" and not found[v[1]] then

Added: 374a462
found[v[1]] = true

Added: 405a494
-- Write module sources to files.

Changed: 408c497
fh:write(squarem:header())
fh:write(self:header())

Changed: 411c500
fh:write(squarem:implementation())
fh:write(self:implementation())

Removed: 415,420d503

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.

--DavidManura

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 15, 2007 1:47 am GMT (diff)