// Copyright (C) 2001 Jean-Marc Valin
#include "UINodeRepository.h"
#include "path.h"
#include <libxml/tree.h>
#include <libxml/parser.h>
#include "BaseException.h"
#include "UINetwork.h"
/*This bunch of includes is for searching directories, maybe there's a better way...*/
#include <sys/stat.h>
#ifndef WIN32
#include <dlfcn.h>
#endif
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
//@implements UIClasses
using namespace std;
namespace FD {
ostream& operator<< (ostream &out, const ItemInfo &iInfo) {
out<<"\tname: "<<iInfo.name<<endl;
out<<"\ttype: "<<iInfo.type<<endl;
out<<"\tvalue: "<<iInfo.value<<endl;
out<<"\tdesc: "<<iInfo.description<<endl;
return out;
}
ostream& operator<< (ostream & out, const NodeInfo &nInfo) {
out<<"INPUTS:"<<endl;
for (unsigned int i = 0; i < nInfo.inputs.size(); i++) {
out<<*(nInfo.inputs[i]);
}
out<<"OUTPUTS:"<<endl;
for (unsigned int i = 0; i < nInfo.outputs.size(); i++) {
out<<*(nInfo.outputs[i]);
}
out<<"PARAMETERS:"<<endl;
for (unsigned int i = 0; i < nInfo.params.size(); i++) {
out<<*(nInfo.params[i]);
}
out<<"CATEGORY:"<<nInfo.category<<endl;
out<<"DESCRIPTION:"<<nInfo.description<<endl;
out<<"SOURCEFILE:"<<nInfo.sourceFile<<endl;
out<<"REQUIRELIST:"<<nInfo.requireList<<endl;
out<<"KIND:"<<nInfo.kind<<endl;
return out;
}
UINodeRepository::UINodeRepository(const UINodeRepository &)
{
throw new GeneralException("I wouldn't try copying a UINodeRepository if I were you", __FILE__, __LINE__);
}
UINodeRepository &UINodeRepository::GlobalRepository()
{
//cerr<<"Getting global repository"<<endl;
static UINodeRepository rep;
return rep;
}
map<string, set<string> > &UINodeRepository::ModuleDepend()
{
static map<string, set<string> > dep;
return dep;
}
map<string, set<string> > &UINodeRepository::FileDepend()
{
static map<string, set<string> > dep;
return dep;
}
map<string, set<string> > &UINodeRepository::HeaderDepend()
{
static map<string, set<string> > dep;
return dep;
}
void UINodeRepository::Scan()
{
cerr<<"UINodeRepository::Scan()"<<endl;
//(DL) 06/02/2004
vector<string> dirs = envList("FLOWDESIGNER_PATH");
for (unsigned int i=0;i<dirs.size();i++)
{
cerr<<"Scanning def "<<dirs[i]<<endl;
LoadAllInfoRecursive(dirs[i]);
}
cerr<<"done loading def files"<<endl;
/*
cerr<<"+++repository contains after loading: "<<endl;
for (map<string,NodeInfo*>::iterator iter = GlobalRepository().info.begin();
iter != GlobalRepository().info.end(); iter++) {
cerr<<"**************"<<iter->first<<"("<<iter->second<<")"<<endl;
cerr<<*(iter->second)<<endl;
}
*/
}
set<string> &UINodeRepository::FindFileFromModule(const string &name)
{
return ModuleDepend()[name];
}
set<string> &UINodeRepository::FindModuleFromFile(const string &name)
{
return FileDepend()[name];
}
set<string> &UINodeRepository::FindHeaderFromFile(const string &name)
{
return HeaderDepend()[name];
}
NodeInfo *UINodeRepository::Find(const string &name)
{
map<string, NodeInfo *>::iterator found = GlobalRepository().info.find(name);
if (found != GlobalRepository().info.end())
return found->second;
return NULL;
}
vector<string> UINodeRepository::Available()
{
vector<string> allNodes;
string nextItem;
map<string, NodeInfo *>::iterator iter = GlobalRepository().info.begin();
while (iter != GlobalRepository().info.end()) {
nextItem = string((*iter).second->category) + "***" +
string((*iter).first);
allNodes.insert(allNodes.end(), nextItem);
iter++;
}
return allNodes;
}
NodeInfo *UINodeRepository::findNode(const string &name)
{
map<string, NodeInfo *>::iterator found = info.find(name);
if (found != info.end())
return found->second;
found = GlobalRepository().info.find(name);
if (found != GlobalRepository().info.end())
return found->second;
return NULL;
}
UINodeRepository::~UINodeRepository()
{
clean();
}
void UINodeRepository::clean()
{
/*
cerr<<"+++repository contains before cleaning: "<<endl;
for (map<string,NodeInfo*>::iterator iter = GlobalRepository().info.begin();
iter != GlobalRepository().info.end(); iter++) {
cerr<<"**************"<<iter->first<<"("<<iter->second<<")"<<endl;
cerr<<*(iter->second)<<endl;
}
*/
while(!info.empty()) {
iterator iter = info.begin();
//cerr<<"deleting NodeInfo : "<<iter->first<<endl;
if (iter->second) {
//deleting NodeInfo
delete iter->second;
}
info.erase(iter);
}
}
void UINodeRepository::LoadNodeDefInfo(const string &path, const string &name)
{
string fullname = path + "/" + name;
//cerr<<"Loading def :"<<fullname<<endl;
xmlDocPtr doc = xmlParseFile(fullname.c_str());
if (!doc || !doc->children || !doc->children->name)
{
cerr << "LoadNodeDefInfo: error loading " << fullname << "\n";
xmlFreeDoc (doc);
return;
}
xmlNodePtr root=doc->children;
xmlNodePtr node = root->children;
while (node != NULL)
{
string nodeName;
if (string((char*)node->name) == "NodeClass")
{
NodeInfo *my_info = new NodeInfo;
my_info->kind=NodeInfo::builtin;
char *str_category = (char *)xmlGetProp(node, (xmlChar *)"category");
if (str_category)
my_info->category = string(str_category);
else
my_info->category = "Unknown";
free(str_category);
char *sfile = (char *)xmlGetProp(node, (xmlChar *)"source");
if (sfile)
my_info->sourceFile= string(sfile);
free(sfile);
char *req = (char *)xmlGetProp(node, (xmlChar *)"require");
if (req)
my_info->requireList = string(req);
free(req);
//cerr << my_info->sourceFile << ":" << my_info->requireList << endl;
nodeName = string((char *)xmlGetProp(node, (xmlChar *)"name"));
//cerr<<"inserting into global repository :"<<nodeName<<endl;
GlobalRepository().info[nodeName] = my_info;
xmlNodePtr data = node->children;
while (data != NULL)
{
string kind = string((char*)data->name);
if (kind == "Input")
{
xmlChar *tmp;
ItemInfo *newInfo = new ItemInfo;
char *str_name = (char *)xmlGetProp(data, (xmlChar *)"name");
char *str_type = (char *)xmlGetProp(data, (xmlChar *)"type");
if (str_name) {
newInfo->name = string(str_name);
free(str_name);
}
if (str_type) {
newInfo->type = string(str_type);
free(str_type);
}
tmp = xmlGetProp(data, (xmlChar *)"value");
if (tmp == NULL)
newInfo->value = "";
else
newInfo->value = string((char *)tmp);
tmp = xmlNodeListGetString(doc, data->children, 1);
if (tmp == NULL)
newInfo->description = "No Description Available.";
else
newInfo->description = string((char *)tmp);
my_info->inputs.insert(my_info->inputs.end(), newInfo);
} else if (kind == "Output")
{
xmlChar *tmp;
ItemInfo *newInfo = new ItemInfo;
char *str_name = (char *)xmlGetProp(data, (xmlChar *)"name");
char *str_type = (char *)xmlGetProp(data, (xmlChar *)"type");
//FIXME: Check for NULL
newInfo->name = string(str_name);
newInfo->type = string(str_type);
free(str_name); free(str_type);
tmp = xmlGetProp(data, (xmlChar *)"value");
if (tmp == NULL)
newInfo->value = "";
else
newInfo->value = string((char *)tmp);
tmp = xmlNodeListGetString(doc, data->children, 1);
if (tmp == NULL)
newInfo->description = "No Description Available.";
else
newInfo->description = string((char *)tmp);
my_info->outputs.insert(my_info->outputs.end(), newInfo);
} else if (kind == "Parameter")
{
xmlChar *tmp;
ItemInfo *newInfo = new ItemInfo;
char *str_name = (char *)xmlGetProp(data, (xmlChar *)"name");
char *str_type = (char *)xmlGetProp(data, (xmlChar *)"type");
if (str_name) {
newInfo->name = string(str_name);
free(str_name);
}
if (str_type) {
newInfo->type = string(str_type);
free(str_type);
}
tmp = xmlGetProp(data, (xmlChar *)"value");
if (tmp == NULL)
newInfo->value = "";
else
newInfo->value = string((char *)tmp);
free(tmp);
tmp = xmlNodeListGetString(doc, data->children, 1);
if (tmp == NULL)
newInfo->description = "No Description Available.";
else
newInfo->description = string((char *)tmp);
free(tmp);
my_info->params.insert(my_info->params.end(), newInfo);
} else if (kind == "Description")
{
xmlChar *tmp;
tmp = xmlNodeListGetString(doc, data->children, 1);
if (tmp == NULL)
my_info->description = "No description available";
else
my_info->description = string((char *)tmp);
free(tmp);
} else if (!xmlIsBlankNode(data))
{
cerr << "other\n";
}
data = data->next;
}
}
/*Dependencies for modules*/
else if (string((char*)node->name) == "ModuleDepend")
{
char *dep_module = (char *)xmlGetProp(node, (xmlChar *)"module");
if (!dep_module)
throw new GeneralException("Empty module dependency", __FILE__, __LINE__);
xmlNodePtr depend = node->children;
while (depend != NULL)
{
if (xmlIsBlankNode(depend))
{
depend = depend->next;
continue;
}
if (string((char*)depend->name) != "Require")
throw new GeneralException(string("Unknown section in module dependency: ") + (char*)node->name,
__FILE__, __LINE__);
char *req_file = (char *)xmlGetProp(depend, (xmlChar *)"file");
if (!req_file)
throw new GeneralException(string("Empty dependency for module: ") + dep_module, __FILE__, __LINE__);
ModuleDepend()[dep_module].insert(ModuleDepend()[dep_module].end(), req_file);
depend = depend->next;
}
}
/*Dependencies for files*/
else if (string((char*)node->name) == "FileDepend")
{
char *dep_file = (char *)xmlGetProp(node, (xmlChar *)"file");
if (!dep_file)
throw new GeneralException("Empty file dependency", __FILE__, __LINE__);
xmlNodePtr depend = node->children;
while (depend != NULL)
{
if (string((char*)depend->name) == "RequireModule")
{
char *req_module = (char *)xmlGetProp(depend, (xmlChar *)"module");
if (!req_module)
throw new GeneralException(string("Empty module dependency for file: ") + dep_file,
__FILE__, __LINE__);
FileDepend()[dep_file].insert(FileDepend()[dep_file].end(), req_module);
}
else if (string((char*)depend->name) == "RequireHeader")
{
char *req_header = (char *)xmlGetProp(depend, (xmlChar *)"header");
if (!req_header)
throw new GeneralException(string("Empty header dependency for file: ") + dep_file,
__FILE__, __LINE__);
HeaderDepend()[dep_file].insert(HeaderDepend()[dep_file].end(), req_header);
} else if (!xmlIsBlankNode(depend))
throw new GeneralException(string("Unknown section in file dependency: ") + (char*)node->name,
__FILE__, __LINE__);
depend = depend->next;
}
}
else if (!xmlIsBlankNode(node))
throw new GeneralException(string("Unknown section in toolbox definition file: ") + string((char*)node->name) + " in " + fullname,
__FILE__, __LINE__);
node = node->next;
}
xmlFreeDoc(doc);
}
void UINodeRepository::LoadExtDocInfo(const string &path, const string &name)
{
string fullpath = path + "/" + name;
string basename = string(name.begin(), name.end()-2);
ifstream docFile(fullpath.c_str());
if (docFile.fail())
{
cerr << "load: error loading " << fullpath << "\n";
return;
}
char ch;
docFile >> ch;
if (ch=='#')
{
while (ch != '<')
{
docFile >> ch;
if (docFile.fail())
{
cerr << "ERROR\n";
return;
}
}
}
docFile.putback(ch);
string docStr;
while(1)
{
//char buff[1025];
//docFile.read(buff, 1024);
//buff[1024]=0;
string buff;
getline( docFile, buff );
if (docFile.fail())
{
//docStr.append(buff, docFile.gcount());
docStr.append(buff.c_str(), docFile.gcount());
break;
}
//docStr.append(buff, 1024);
docStr.append(buff.c_str());
}
xmlDocPtr doc = xmlParseMemory (const_cast<char *> (docStr.c_str()), docStr.size());
//xmlDocPtr doc = xmlParseFile(fullname.c_str());
if (!doc || !doc->children || !doc->children->name)
{
cerr << "ExtDoc: error loading " << fullpath << "\n";
xmlFreeDoc (doc);
return;
}
GlobalRepository().loadDocInfo(doc, basename);
}
void UINodeRepository::loadDocInfo(xmlDocPtr doc, const string &basename)
{
map<string, NodeInfo *> &externalDocInfo = GlobalRepository().info;
if (externalDocInfo.find(basename) != externalDocInfo.end())
{
cerr << "error: net " << basename << " already existed\n";
return;
}
//cerr << "new subnet info with name: " << netName << "\n";
NodeInfo *my_info = new NodeInfo;
my_info->kind = NodeInfo::external;
//cerr<<"Inserting external info : "<<basename<<endl;
externalDocInfo[basename] = my_info;
xmlNodePtr root=doc->children;
xmlChar *category = xmlGetProp(root, (xmlChar *)"category");
if (category)
{
my_info->category = string((char *)category);
free (category);
}
xmlNodePtr net = root->children;
while (net != NULL)
{
//cerr << "scanning networks...\n";
if (string((char*)net->name) == "Network")
{
//cerr << "scanning a net\n";
string netName = string((char *)xmlGetProp(net, (xmlChar *)"name"));
if (netName == "MAIN")
{
//loadNetInfo(net, externalDocInfo, basename);
xmlNodePtr node = net->children;
while (node != NULL)
{
if (string((char*)node->name) == "NetInput")
{
string termName = string((char *)xmlGetProp(node, (xmlChar *)"name"));
ItemInfo *newInfo = new ItemInfo;
newInfo->name = termName;
my_info->inputs.insert (my_info->inputs.end(), newInfo);
} else if (string((char*)node->name) == "NetOutput")
{
string termName = string((char *)xmlGetProp(node, (xmlChar *)"name"));
ItemInfo *newInfo = new ItemInfo;
newInfo->name = termName;
my_info->outputs.insert (my_info->outputs.end(), newInfo);
}
node = node->next;
}
}
} else if (string((char*)net->name) == "Parameter")
{
char *param_name = (char *)xmlGetProp(net, (xmlChar *)"name");
char *param_type = (char *)xmlGetProp(net, (xmlChar *)"type");
char *param_value = (char *)xmlGetProp(net, (xmlChar *)"value");
if (param_name && param_type)
{
ItemInfo *newInfo = new ItemInfo;
newInfo->name = param_name;
if (string(param_type)=="")
newInfo->type = "int";
else
newInfo->type = param_type;
if (string(param_value)!="")
newInfo->value = param_value;
my_info->params.insert (my_info->params.end(), newInfo);
}
//cerr << "param\n";
}
net = net->next;
}
xmlFreeDoc(doc);
}
void UINodeRepository::LoadAllInfoRecursive(const string &path) {
struct stat my_stat;
DIR *my_directory = opendir (path.c_str());
if (!my_directory) return;
struct dirent *current_entry;
for (current_entry = readdir(my_directory);
current_entry != NULL; current_entry = readdir(my_directory)) {
string name = current_entry->d_name;
#ifndef WIN32
string fullpath = path + string("/") + name;
#else
string fullpath = path + string("\\") + name;
#warning Please remove this debug line.
cerr<<"UINodeRepository::LoadAllInfoRecursive with full path : "<<fullpath<<endl;
#endif
if (stat(fullpath.c_str(), &my_stat) < 0) {
//cerr<<"stat error"<<endl;
perror(fullpath.c_str());
continue;
}
if (S_ISDIR(my_stat.st_mode)) {
//it is a directory, let's doing it recursively
if (name != string("..") && name != string(".")) {
LoadAllInfoRecursive(fullpath);
}
}
else {
//loading network
int len = strlen(current_entry->d_name);
if (len > 2 && strcmp(".n", current_entry->d_name + len-2)==0)
{
//cerr<<"Loading network : "<<fullpath<<endl;
LoadExtDocInfo(path, name);
}
//loading toolbox
if (len > 4 && strcmp(".def", current_entry->d_name + len-4)==0)
{
#ifdef WIN32
#warning Please remove this debug line.
cerr<<"Loading toolbox : "<<fullpath<<endl;
#endif
LoadNodeDefInfo(path, name);
}
}
}
closedir(my_directory);
}
void UINodeRepository::loadAllSubnetInfo(xmlNodePtr net)
{
while (net != NULL)
{
if (string((char*)net->name) == "Network")
{
loadNetInfo(net);
}
net = net->next;
}
}
void UINodeRepository::loadNetInfo(xmlNodePtr net)
{
char *str_netName = (char *)xmlGetProp(net, (xmlChar *)"name");
string netName = string(str_netName);
free(str_netName);
xmlChar *category = xmlGetProp(net, (xmlChar *)"category");
if (info.find(netName) != info.end())
{
cerr << "error: net " << netName << " already existed\n";
return;
}
//cerr << "new subnet info with name: " << netName << "\n";
NodeInfo *ninfo = new NodeInfo;
ninfo->kind = NodeInfo::subnet;
//we are dealing with a local repository, in a document
info[netName] = ninfo;
if (category)
{
ninfo->category = string((char *)category);
free(category);
}
//cerr << "scan all nodes\n";
xmlNodePtr node = net->children;
while (node != NULL)
{
if (string((char*)node->name) == "Node")
{
xmlNodePtr par = node->children;
//cerr << "par = " << par << endl;
while (par)
{
if (string((char*)par->name) == "Parameter")
{
char *str_paramName = (char *) xmlGetProp(par, (xmlChar *)"value");
char *str_type = (char *) xmlGetProp(par, (xmlChar *)"type");
string paramName = string (str_paramName);
string type = string (str_type);
free(str_paramName); free(str_type);
if (type == "subnet_param")
{
bool alreadyPresent = false;
for (unsigned int j=0;j<ninfo->params.size();j++)
if (ninfo->params[j]->name == paramName)
alreadyPresent=true;
if (!alreadyPresent)
{
ItemInfo *newInfo = new ItemInfo;
newInfo->name = paramName;
//FIXME: This always sets the type to subnet_info, which is incorrect
newInfo->type = type;
ninfo->params.insert (ninfo->params.end(), newInfo);
}
}
}
par = par->next;
}
//check for params
}
node = node->next;
}
//checking for the network type, we are interested in
//iterators and threaded iterators here, nothing to do with subnets
xmlChar *type = xmlGetProp(net, (xmlChar *)"type");
if (type)
{
if (string((char*)type)=="iterator")
{
ItemInfo *newInfo = new ItemInfo;
newInfo->name = "DOWHILE";
newInfo->type = "bool";
ninfo->params.insert (ninfo->params.end(), newInfo);
free(type);
} else if (string((char*)type)=="threaded")
{
ItemInfo *newInfo = new ItemInfo;
newInfo->name = "RATE_PER_SECOND";
newInfo->type = "int";
ninfo->params.insert (ninfo->params.end(), newInfo);
free(type);
}
}
//check for network description
char *desc = (char *) xmlGetProp(net, (xmlChar *)"description");
if (desc) {
//set description
ninfo->description = string(desc);
//free XML string
free(desc);
}
//scan all net inputs/outputs
node = net->children;
while (node != NULL)
{
if (string((char*)node->name) == "NetInput")
{
char *str_name = (char *)xmlGetProp(node, (xmlChar *)"name");
char *str_type = (char *)xmlGetProp(node, (xmlChar *)"object_type");
char *str_desc = (char *)xmlGetProp(node, (xmlChar *)"description");
string termName = string(str_name);
string termType = "any";
string termDescription = "No description available";
//Free XML strings
free(str_name);
if (str_type) {
termType = str_type;
free(str_type);
}
if (str_desc) {
termDescription = str_desc;
free(str_desc);
}
ItemInfo *newInfo = new ItemInfo;
newInfo->name = termName;
newInfo->type = termType;
newInfo->description = termDescription;
ninfo->inputs.insert (ninfo->inputs.end(), newInfo);
} else if (string((char*)node->name) == "NetOutput")
{
char *str_name = (char *)xmlGetProp(node, (xmlChar *)"name");
char *str_type = (char *)xmlGetProp(node, (xmlChar *)"object_type");
char *str_desc = (char *)xmlGetProp(node, (xmlChar *)"description");
string termName = string(str_name);
string termType = "any";
string termDescription = "No description available";
free(str_name);
if (str_type) {
termType = str_type;
free(str_type);
}
if (str_desc) {
termDescription = str_desc;
free(str_desc);
}
ItemInfo *newInfo = new ItemInfo;
newInfo->name = termName;
newInfo->type = termType;
newInfo->description = termDescription;
ninfo->outputs.insert (ninfo->outputs.end(), newInfo);
}
node = node->next;
}
}
void UINodeRepository::ProcessDependencies(set<string> &initial_files, bool toplevel)
{
unsigned int nbDepends = initial_files.size();
//Process module/file dependencies, loop until there's nothing else to add
do {
nbDepends = initial_files.size();
set<string> addMod;
//Core is always necessary and we'll save a bunch of @require core
addMod.insert(addMod.begin(), "core");
//Scan all files in required list to find all required modules
set<string>::iterator file=initial_files.begin();
while (file != initial_files.end())
{
if (FileDepend().find(*file) != FileDepend().end())
{
set<string> &moduleDep = FileDepend()[*file];
set<string>::iterator mod = moduleDep.begin();
while (mod != moduleDep.end())
{
addMod.insert(addMod.end(), *mod);
mod++;
}
}
file++;
}
//Scan all modules in required list to find all required files
set<string>::iterator mod=addMod.begin();
while (mod != addMod.end())
{
if (ModuleDepend().find(*mod) != ModuleDepend().end())
{
set<string> &fileDep = ModuleDepend()[*mod];
set<string>::iterator file = fileDep.begin();
while (file != fileDep.end())
{
initial_files.insert(initial_files.end(), *file);
file++;
}
}
mod++;
}
} while (nbDepends != initial_files.size());
//Repeat recursivly until there's nothing else to add
//if (nbDepends != initial_files.size())
// processDependencies(initial_files, false);
//Process header dependencies, loop until there's nothing else to add
do {
nbDepends = initial_files.size();
if (toplevel)
{
set<string>::iterator file=initial_files.begin();
while (file != initial_files.end())
{
if (HeaderDepend().find(*file) != HeaderDepend().end())
{
set<string> &headerDep = HeaderDepend()[*file];
set<string>::iterator header = headerDep.begin();
while (header != headerDep.end())
{
initial_files.insert(initial_files.end(), *header);
header++;
}
}
file++;
}
}
} while (nbDepends != initial_files.size());
}
void UINodeRepository::updateNetInfo(UINetwork *net)
{
iterator inet = info.find(net->getName());
if (inet!=info.end())
{
//cerr<<"UINodeRepository deleting network info:"<<net->getName()<<endl;
delete inet->second;
}
NodeInfo *ninfo = new NodeInfo;
vector<UINetTerminal *> my_terminals = net->getTerminals();
//updating inputs & outputs
//new implementation using name + type + description for all UINetTerminals
//(DL) 15/12/203
for (unsigned int i = 0; i < my_terminals.size(); i++)
{
ItemInfo *newInfo = new ItemInfo;
if (my_terminals[i]) {
//input name
newInfo->name = my_terminals[i]->getName();
//input type
newInfo->type = my_terminals[i]->getType();
//input description
newInfo->description = my_terminals[i]->getDescription();
//input our output?
if (my_terminals[i]->getType() == UINetTerminal::INPUT) {
ninfo->inputs.push_back(newInfo);
}
else if (my_terminals[i]->getType() == UINetTerminal::OUTPUT) {
ninfo->outputs.push_back(newInfo);
}
}
}
//cerr<<"insertingNetParams"<<endl;
net->insertNetParams(ninfo->params);
ninfo->category = "Subnet";
//Description is now stored in a network definition
ninfo->description = net->getDescription();
//cerr<<"updated network info for "<<net->getName()<<endl;
//we are dealing with a local repository, in a document
info[net->getName()] = ninfo;
}
}//namespace FD
syntax highlighted by Code2HTML, v. 0.9.1