Reflect.cpp
// Reflect.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2007
@import "ExampleUtils.h"
@import "ValidateSerialisation.h"
@import "Ceda/cxObject/Object.h"
@import "Ceda/cxObject/NameSpaceRegistry.h"
@import "Ceda/cxObject/PrintReflectedType.h"
@import "Ceda/cxObject/PrintReflectedVariable.h"
@import "Ceda/cxObject/ReflectionByteCodeValue.h"
#include "Ceda/cxUtils/IndentingStreamBuf.h"
#include "Ceda/cxUtils/ScopeWriter.h"
#include "Ceda/cxUtils/CedaAssert.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Reflection of interfaces
------------------------
Every xcpp interface is reflected. This means that detailed information about the interface
(including metadata) is stored in a ReflectedInterface struct and registered by name.
ReflectedInterface is defined in Reflection.h
struct ReflectedInterface
{
const char* m_name;
const octet_t* m_metaData;
const ReflectedAttribute* m_attributes;
int m_numAttributes;
const ReflectedInterfaceMethod* m_methods;
int m_numMethods;
const char** m_stringTable;
};
Note that all methods in an interface are reflected. For each method metadata can be applied to
the return type, the method itself, and to each formal argument.
Metadata can be applied to the entire interface.
PrintInterface() is a convenient function that can be used to print an interface. Apart from the
use of a fully qualified name, it should closely resemble the original interface that appeared in
the source code.
todo
* const qualifier is not displayed
* base interface list is not displayed
*/
namespace Reflect1
{
$interface+ Ia : ["example"]
{
void foo(ceda::int32 x);
ceda::float32 bar(bool a, ceda::int64 b) const;
};
$interface+ Ib : Ia
{
ceda::int32 x;
void foobar();
};
$interface Ic : Ib
{
};
void Run()
{
ceda::TraceGroup g("Reflect example 1");
// GetReflectedInterface<I> returns a reference to the ReflectedInterface
const ceda::ReflectedInterface& ri = ceda::GetReflectedInterface<Ia>();
// Alternatively, look up the interface registry for the interface with given name
const ceda::ReflectedInterface* pri = ceda::TryFindReflectedInterface("Reflect1::Ia");
cxAssert(pri);
cxAssert(pri = &ri);
// PrintInterface() is declared in PrintClass.h. Allows a given ReflectedInterface to
// be written to a given xostream.
ceda::PrintReflectedInterface(Tracer(), ri);
Tracer() << "Name = " << ri.name << '\n';
ceda::PrintReflectedInterface(Tracer(), ceda::FindReflectedInterface("Reflect1::Ib"));
// Ic is not reflected, so this would cause a compile error
// ceda::GetReflectedInterface<Ic>();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Reflection of classes and structs
---------------------------------
In this section class stands for either class or struct.
Every class marked with '$' is reflected. This means that detailed information about the class
(including metadata) is stored in a ReflectedClass struct and registered in the ClassRegistry by
fully qualified name.
ReflectedClass is defined in Reflection.h
Within the class only member variables marked with $ are reflected. This imposes constraints on
their type. For example platform independent types like int16, int32, int64 must be used in favor of
platform dependent types like short,int,long. Note that members that aren't reflected can be
anything you like.
Mixins are not reflected. However they can use $ to mark which members will be reflected when the
mixin is used to build concrete classes.
Methods within a class cannot be marked with $, and cannot be reflected. It is only possible to
reflect global functions, and methods within xcpp interfaces.
*/
namespace Reflect2
{
// struct X is reflected
$struct+ X
{
// Members marked with '$' are reflected. Platform independent types must be used
$bool b;
$ceda::int32 x;
$ceda::float64 y : [range(0,10)];
// Members that aren't marked with '$' are not reflected
int z;
};
$interface+ Iy : ceda::IObject
{
void foo(ceda::int32 x);
};
$mixin M
{
$ceda::string8 s; // Reflected in all classes that use the mixin
int mm; // Not reflected
};
$struct+ Y isa Iy : mixin [M]
{
// Methods of classes or structs cannot be reflected
void foo(ceda::int32 x) {}
$ceda::int64 m;
$X x;
};
void Run()
{
ceda::TraceGroup g("Reflect example 2");
{
// Look up the class registry for the class with given name
const ceda::ReflectedClass* rc = ceda::TryFindReflectedClass("Reflect2::X");
// Defined in PrintClass.h. Allows a given ReflectedClass to be written to a given
// ostream.
ceda::PrintReflectedClass(Tracer(), *rc);
Tracer() << "Name = " << rc->name << '\n';
}
{
const ceda::ReflectedClass* rc = ceda::TryFindReflectedClass("Reflect2::Y");
ceda::PrintReflectedClass(Tracer(), *rc);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Using reflection to print instances of classes and structs
----------------------------------------------------------
If a class/struct has been reflected then the reflection can be used to print an instance of the
class/struct to a given ostream. This can be useful for debugging purposes.
The example below demonstrates how a class called Point can override the default way that instances
are written to an ostream by the reflection system. The qualifier <<printable>> on the class
indicates that operator<<(xostream, Point) has been defined and should be used by the
reflection system.
todo
* The metadata provides special support for common flags. Currently the parser doesn't support
this. It is needed to mark hexadecimal types.
* Guids don't work. Why not?
* Arrays don't work
*/
namespace Reflect3
{
$struct+ Point
{
Point() : x(0), y(0) {}
Point(ceda::int32 _x, ceda::int32 _y) : x(_x), y(_y) {}
$ceda::int32 x;
$ceda::int32 y;
};
ceda::xostream& operator<<(ceda::xostream& os, Point p)
{
os << '(' << p.x << ',' << p.y << ')';
return os;
}
$struct+ Line
{
$Point p1;
$Point p2;
};
$enum+ DrawStyle : [with_metadata]
{
solid,
dashed,
dotted,
dot_dash
};
$function+ Line* Foo(const DrawStyle& ds : [with_metadata], const Point& p1, const Point& p2)
{
Line* line = new Line;
line->p1 = p1;
line->p2 = p2;
return line;
}
$var+ ceda::float64 gFloat64 = 9.8;
$var+ ceda::float64 : [on_float64] * : [on_ptr] gPtrToFloat64 = &gFloat64;
//$typedef float64 Force;
$typedef+ Point Force;
$function <<register>> inline void blah() {}
$struct+ X
{
struct Z {};
Z z;
X() : h(0xcedaceda), b(false), s("hello, world") {}
$ceda::int32 : [hex] h;
$bool b;
$ceda::xvector<ceda::int32> L;
$ceda::string8 s;
$Line line;
$ceda::xvector<Point> points;
$ceda::xvector<Line> lines;
$DrawStyle ds;
$Force g : [units("ms^-2")];
};
void Run()
{
ceda::TraceGroup g("Reflect example 3");
X x;
for (int i=0 ; i < 10 ; ++i) x.L.push_back(i*10);
for (int i=0 ; i < 3 ; ++i) x.lines.push_back(Line());
for (int i=0 ; i < 7 ; ++i) x.points.push_back(Point(i,i*10));
x.ds = dot_dash;
//x.g.x = 9.80665;
x.g.x = 7;
x.g.y = 4;
ceda::PrintReflectedGlobalFunction(Tracer(), ceda::FindReflectedGlobalFunction("Reflect3::Foo"));
ceda::PrintReflectedClassVariable(Tracer(), ceda::GetReflectedClass<Reflect3::X>(), &x);
//ceda::PrintReflectedClassVariable(Tracer(), ceda::FindReflectedClass("Reflect3::X"), &x);
ceda::PrintReflectedGlobalVariable(Tracer(), ceda::FindReflectedGlobalVariable("Reflect3::gFloat64"));
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Given a ptr<T> where T inherits from IObject, it is possible to use PrintInstance() to print
the instance.
*/
namespace Reflect4
{
$struct+ X isa ceda::IObject
{
X() : s("hello, world") {}
$ceda::string8 s;
};
void Run()
{
ceda::TraceGroup g("Reflect example 4");
X x;
ceda::ptr<ceda::IObject> p = &x;
ceda::PrintInstance(Tracer(), p);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
The following example shows how we can iterate through all the registries as a tree of namespaces
*/
namespace Reflect5
{
void DumpNameSpace(ceda::xostream& os, ceda::NameSpace* ns)
{
// Show all elements in this namespace
{
ceda::NameSpaceElementIterator* i = GetElementIterator(ns);
while(!AtEnd(i))
{
ceda::NameSpaceElement nse = GetElement(i);
ceda::ConstStringZ typeStr;
switch(nse.type)
{
case ceda::NST_Interface : typeStr = "Interface"; break;
case ceda::NST_Class : typeStr = "class"; break;
case ceda::NST_GlobalFunction : typeStr = "global function"; break;
case ceda::NST_GlobalVariable : typeStr = "global variable"; break;
case ceda::NST_Typedef : typeStr = "typedef"; break;
case ceda::NST_Enum : typeStr = "enum"; break;
case ceda::NST_Functor : typeStr = "functor"; break;
case ceda::NST_Variant : typeStr = "variant"; break;
default : cxAssert(0);
}
os << typeStr << ' ' << GetName(i) << '\n';
Next(i);
}
Close(i);
}
// Recurse into child namespaces
{
ceda::ChildNameSpaceIterator* i = GetChildNameSpaceIterator(ns);
while(!AtEnd(i))
{
os << GetName(i) << '\n';
ceda::ScopeWriter sw(os);
DumpNameSpace(os,GetNameSpace(i));
Next(i);
}
Close(i);
}
}
void Run()
{
ceda::TraceGroup g("Reflect example 5");
DumpNameSpace(Tracer(), ceda::GetTheRootNameSpace());
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Given a ptr<T> where T inherits from IObject, it is possible to use PrintInstance() to print
the instance.
*/
namespace ReflectionByteCodeValueExample
{
$model+ W
{
ceda::int32 U;
};
$model+ X
{
ceda::int32 A;
ceda::float64 B;
bool C : [10];
ceda::string8 S8;
ceda::string16 S16 : [abc,10,1.43,foo(1,2,d)];
ceda::xvector<ceda::int32> V[2];
W T[3][3][][2];
assignable<ceda::string8> AS;
};
void Run()
{
ceda::TraceGroup g("ReflectionByteCodeValueExample");
const ceda::ReflectedClass& rc = ceda::GetReflectedClass<X>();
// ReflectionByteCodeValue supports default ctor
ceda::ReflectionByteCodeValue rbcv1;
// ReflectionByteCodeValue supports copy ctor
ceda::ReflectionByteCodeValue rbcv2(rbcv1);
// ReflectionByteCodeValue supports copy assignment
ceda::ReflectionByteCodeValue rbcv3;
rbcv3 = rbcv1;
cxAssert(rbcv1 == rbcv2);
cxAssert(rbcv1 == rbcv3);
ceda::TracerX os;
for (ceda::ssize_t i = 0 ; i < rc.GetNumModelFields() ; ++i)
{
// todo : showing metadata is crashing
//ceda::PrintTypeSettings pts(false,true,false);
ceda::ReflectionByteCode rbc = GetByteCode(rc,rc.GetModelField(i));
os << '\n';
PrintReflectedType(os, rbc);
// Implicit conversion from ReflectionByteCode to ReflectionByteCodeValue
ceda::ReflectionByteCodeValue rbcv4(rbc);
cxAssert(rbcv1 != rbcv4);
os << " = "; PrintReflectedType(os, rbcv4.AsReflectionByteCode());
os << '\n';
rbcv4.DebugWrite(os);
os << "underlying only:\n";
ceda::ReflectionByteCodeValue(rbc,true).DebugWrite(os);
ValididateSerialisation(rbcv4);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
namespace Reflect
{
void Run()
{
Reflect1::Run();
Reflect2::Run();
Reflect3::Run();
Reflect4::Run();
Reflect5::Run();
ReflectionByteCodeValueExample::Run();
}
}