Steps

Hans-Dieter Dreier Ursula.Dreier@ruhr-uni-bochum.de
Sun, 14 Feb 1999 03:39:41 +0100


Dies ist eine mehrteilige Nachricht im MIME-Format.
--------------7D9C7C987BB47252DED1B20D
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Suggestion for steps to be taken:
=================================

The general rule followed here is:
KEEP IT SIMPLE BUT DONT RESTRICT YOURSELF TOO MUCH.
The simpler it is, the easier and faster it is implemented
and the fewer errors it contains.

Numbers reflect tree-like structure of project:
Task 1.2.3 needs 1.2 as a reprequisite.

1 Write a memory manager (shorthand: MM).
1.1 Write a simple object loader, based on MM.
1.2 Write a simple virtual machine
 (single-thread, no frills, shorthand: VM).
1.2.1 Write a parser skeleton.
1.2.2 Optional: Provide persistent storage
 (simple, whole memory image, no linking capability)
1.2.1.1 Refine parser skeleton and (in parallel)
 write runtime library.
1.2.1.2 Write parser table generator and (in  parallel)
 write neccessary parts of runtime library.
1.2.1.3 Write intelligent editor
1.2.1.4 Debugger
1.2.2.1 Rewrite persistent storage to provide linking capability.


Remarks:

MM:
===

Since MM is the foundation of it all and each memory access involves MM,

the person programming this should have very good C++ knowledge
(which unfortunately is not the case for me)
to provide the necessary "smart pointer" stuff
and keep the interface as open as possible,
so people don't have to rewrite it all
when trying a different MM implementation.

IMHO these are the most important design goals for MM:
- Be as transparent and easy to use for the (C++)programmer as possible
- Include garbage collection (this is a must,
  but at first performance is not an issue)
- Allow possible multithreaded access (to be implemented later)
- Provide a nice debugging environment (e.g. introspection)
- Be fast (at least potentially)

The memory consumption overhead generated is IMHO not so important,
as long as the number of objects is kept in a reasonable range.

Suggestion for memory layout:

typedef MMBlock* MMBlockHandle;

class MMBlock {
// fixed header:
 // the handle of the object's class object
 MMBlockHandle mpClass;

 // the handle of the object at the next lower memory location
 MMBlockHandle mpPrev;

 // the total length of this object (in bytes)
 unsigned long mulTotalLength;

 // Flags for use by MM
 // (GC mark flag(s), "moveable" flag, "contains references" etc.)
 unsigned short musFlags;

 // the amount of unused space at the end of this object (in bytes)
 unsigned short musUnused;

// variable payload, just to have a symbolic offset,
// the contents need not be a single byte
 char mbContents;
};


The member musUnused would be used to store the amount of unsed space
at the end of an object. This would allow for easy dynamic reallocation
of objects (e.g. a string or a stack) without the immediate need
to create a "gap object" (which might not fit if the gap were too
small).
Objects of fixed size would have this set to 0,
or maybe some flag bit could then be used to indicate
that musUnused would actually be the first byte of payload.

The following assertion must hold (for "normal" objects, see below):

Objects must be allocated contiguously without gaps:

(char*) (this->mpPrev) + this->mulTotalLength) == (char*) this

This assertion does not hold for the first MMBlock in each memory
allocation.
Therefore, this MMBlock must be easily distinguishable (e.g. special
mpClass).
The same must be true for the last object in each memory allocation
to prevent MM from accessing the unallocated area behind.

This assertion allows GC to easily inspect memory in sequential order.

Furthermode, this arrangement allows to easily switch implementation
to one in which indexes into an object table are used
rather than pointers to the objects themselves.
Such an implementation would have advantages
(objects are moved faster because they do not need
their references fixed up) as well as drawbacks
(slower access to the object due to that extra indirection).
To convert a pointer to an object to its handle
(here: its object table index) of an object, you would have to write

(MMBlock*) ((char*) this + mulTotalLength))->mpPrev.

Operator-> of MMBlock would need to be overwritten to access the object
table.

Actually, I suspect that direct access should be faster
than access through an object table, but there might be situations
(involving heavy object size reallocation) where this might not be the
case.
Anyway, I think it might be worthwhile to try both implementations.

If no object table is used:

Pointers stored inside objects always point to the beginning of an
object,
never to some location inside, but noone prohibits the programmer
from using OFFSETs into objects.
Offsets do not need special treatment by GC because they are no
references.

BTW, I started to write such a thing several years ago (in C, not C++),
but at some point I got into trouble with the implementation
(automatic type conversions if I recall right).
It had MM, VM, object loader and a simple parser.
My source backup has been damaged, so I don't know whether
it would be worth trying to revive this thing and port it to C++.



Object loader:
==============

Object loader's purpose is to provide the startup object set needed.
It's actually some sort of an assembler.

It should accept a text format and produce memory objects.
It should have a simple, single-namespace symbol table
to provide symbolic references to other objects.
It should be able to process these data types:
- References (to other objects that are already known)
- Integers
- Strings
- NULL (to provide NULL pointers)
- Maybe some other constants that are used to represent special object
handles

It must have fixup capability, i.e. references may occur
before the referenced object has been allocated.
This is needed because of the class reference
which is part of each object and references another object.

Example how a piece of "Ultra assembler" might look like:

SomeInteger: IntegerType
 // "IntegerType" is the name of this object's class object
 // (this->mpClass)

 35, 70
 // an object consisting of two integers

SomeString: StringType
 "Hello World"  // an object containing a string
SomeOtherObject: SomeOtherType // an object containing 2 references
 SomeString  // A reference to "SomeString"
 SomeInteger  // A referecne to "SomeInteger"

You see: Object loader is quite simple.



VM:
===

This should be a simple stack machine with just a few "instructions".
I suggest to use as instructions just those object handle values
that have a special meaning because they are not used to reference
objects
and so are free for other purposes (as far as VM is concerned).

Actually, all that is really NEEDED is just one instruction:

NULL: Execute the object on top of the stack

But for efficiency's sake, there should be at least a jump instruction,
followed by an offset.
(Make sure that this offset is never interpreted as a reference by GC!)

All other (non-special) values encountered during execution
will be treated as operands (i.e. pushed onto the stack,
advancing the program pointer).
The objects that are executed will have full access
to the VM's internal state, so they can do the special things
like returning from a function
(by throwing an exception to break out of the interpretation loop).

"Execution" of the object at stack top is done as follows:
(Optionally) check the type and make sure it really is executable.
I'd like to call those objects "machine calls" or short MCs.
Look up the payload of the object: This will be a pointer to a (global)
function with a predefined parameter set
(at least a pointer to VM's state).
Advance the program counter and call that function.

This solution even allows for functions inside dynamic link librararies
to be called, as long as they adhere to parameter conventions.

You see: VM is quite simple.

Even advancing the program counter could be done by the MC,
but as nearly all MCs have to do this, it is done by VM.

There is no check for the end of the program;
instead a MC is placed at the end of the code which will stop VM
by throwing an exception.



Parser skeleton:
================

In an attachment to this posting I provided code which shows how
it could work.
This sketch does not use MMBlocks, so it has to be rewritten.

BTW: The parser loop closely resembles the VM sketched above.



Refine parser skeleton, write Runtime Library:
==============================================

This certainly is the main effort, and it never ends.
IMHO most important is that the memory structures used
are designed properly before mich effort is invested here.


Parser Table Generator:
=======================

This could actually be a standalone C program
which produces a parser table as Object Loader code.
Later on, it could be rewritten in Ultra.
I'd suggest some form of BNF syntax rules as input
and tags for each rule to provide a clue which
recogniser, executor (and maybe binding powers) to use.

I'm not familiar with available parser generators.
Maybe one of them could be adapted.


Intelligent Editor:
====================

This also requires a lot of work.
I'd suggest that the first version would include only
a character oriented interface
(using, say, ANSI escape sequences for screen control)
rather than a graphical one.
A *text* editor has little need for graphics, after all
(apart from color, bold, underline).
If the screen interface is designed in a sensible way,
switching to a GUI later should pose no problem.


Debugger:
=========

Should be tightly coupled with editor.
Need not include hardware level.
Needs some support by VM
(a "debug VM" as opposed to a "release VM" ?).


Persistent storage:
===================

To be able to persistently store the current state
of the machine not only speeds startup time
but also may aid debugging (Core Dump).
The first version needs just to store the whole memory image
as one chunk and read it in later, fixing up references.
Should be easy.
The next version should be able to load and store partial images
("packages") as well.
It needs to know about classes and name spaces.


Other tools:
============

Disassembler (for actual memory or core dump),
Trace, Profiler, you name it.
We will see what is needed (and who is willing to write it).

Hans-Dieter Dreier

--------------7D9C7C987BB47252DED1B20D
Content-Type: text/plain; charset=us-ascii; name="TEST.CPP"
Content-Disposition: inline; filename="TEST.CPP"
Content-Transfer-Encoding: 7bit


//	TEST.CPP

//	This is a *nonfunctional* sample of how a table-driven parser / code generator
//	framework could look like.
//	It might as well be used as an interpreter which directly interprets source code.
//	The syntax is bottom up without backtrack: There is no recursive descent.

//	Of course, interpreters may not linearly process source text,
//	they have to follow the flow of operation instead;
//	so interpreter executors for control constructs are different.

//	Actually, I'm not very happy with how some of the objects involved
//	are implemented - especially Operand and OperandValue.
//	I'll certainly have to think it over.
//	Eventually the whole stuff is intended to be programmed in Ultra.

//	Author: Hans-Dieter Dreier


#pragma warning(disable:4786)	// MS specific, don't care
#define ASSERT(a)
// disable assertions for the moment
#include <stack>
#include <map>
#include <string>
// STL stuff
using namespace std; 

#include <ctype.h>


class Parser;		// the actual parser
class SyntaxToken;	// a production rule
class Operand;		// an operand to be used for intermediate results
class Operator;		// an operator to be used with the operator stack
class OperandStack;	// the operand stack
class OperatorStack;	// the operator stack
class Recogniser;	// syntax dependent recognise (parse) methods
class Executor;		// syntax dependent execute (output) methods

typedef basic_string<char> Name;	// identifier data type to be used in symbol table
typedef map<Name,Operand> Parser_Map;	// Symbol table map type used in parser

#define CONTINUATIONS 2
// Currently token recognition has two possible exits:
// 0 = not recognised
// 1 = recognised
// Eventually there may 3 continuations to optimise recognition (speed up recognition):
// 0 = first character in source file is less than first character of token
// 1 = recognised
// 2 = first character in source file is greater than first character of token
// More than 3 continuations (using some hash code derived from the first char) are also an option
// More continuations speed up the "lexer", but result in a more complicated table layout,
// so I would suggest to implement them only after some automatic table builder tool
// has been written


enum ContinuationTypes {
	CONTINUE_NOTFOUND = 0,
		CONTINUE_FOUND = 1
};			// number of entries in enum == CONTINUATIONS

// Operand types currently supported
enum EnumTypes {
	OPNDTYPE_INTEGRAL,		// a number
		OPNDTYPE_STRING,		// a string
		OPNDTYPE_NAME,			// a name (to look up in Parser.SymbolTable)
		OPNDTYPE_TYPE			// a type (of enum EnumTypes)
};
// Eventually this will be replaced by the class id of the corresponding operand type

// Union to hold to the value of an operand
union OperandValue {
	long Integral;	// numbers
	char* pString;	// strings
	Name* pName;	// (named) references
	EnumTypes Type;	// types
};
// Eventually this will be replaced by a special object


// Recognisers are called to recognise the current token.
// If they match, they will perform specific actions
// (such as pushing an executor onto the operator stack),
// skip the recognised token and return the next recogniser to try;
// if they don't match, they will return the next recogniser to try
// (and possibly produce an error message).

class Recogniser {
public:
	virtual SyntaxToken* Method (Parser* pParser, const SyntaxToken* pToken)
		throw (char*) = 0;
	// The method to be invoked.
	// Returns continuation to be taken for the next token,
	// depends on whether token found or not.
	// May throw an exception to stop parsing.
	
	// Utilities:
	
	// Pop operators off the operator stack if they bind stronger than actual operator
	
	void PopOperators (Parser* pParser, const SyntaxToken* pToken);
};


// Executors are called to perform specific actions when all
// the information they need is present (on the operand stack).
// They have no impact on parsing other than error treatment.
// They are usually popped off the operator stack.
// Therefore, they are best suited as executives by an interpreter
// or as code generators by a compiler

class Executor {
public:
	virtual void Method (Parser* pParser, const Operator* pOperator)
		throw (char*) = 0;
	// The method to be invoked.
	// May throw an exception to stop parsing.
};


// SyntaxTokens actually make up the syntax.
// They are linked together as a web of (usually cyclic) graphs
// called continuations, forming sort of a state machine
// (together with the operand & operator stacks).

class SyntaxToken {
public:
	char* sToken;	// the token prototype, may also be used for the error message text
	// (depends on what pRecogniser uses it for)
	unsigned short usLBP;
	// left binding power, implements operator precedence & associativity
	// (not used for operands)
	unsigned short usRBP;
	// right binding power, implements operator precedence & associativity
	// (not used for operands)
	Recogniser* pRecognise;
	// to be called for token recognition
	Executor* pExecute;
	// to be called for code generation and the like
	SyntaxToken* pContinuations [CONTINUATIONS];
	// possible exits from this Token at recognition time
	char* sAuxParm;	// auxiliary parameter to be used by pRecognise or pExecute
	// may contain the other sToken to compare matching operators () [] {} "" ''
};
// The web of SyntaxTokens will eventually be created automatically by some tool
// which accepts a higher level syntax description.

// There are some caveats on the struture of the web:
// - Keywords need to be checked before identifiers are tried.
// - If a small keyword can be the start of a large keyword, the large keyword needs to be tried first


// Operands form the operand stack.
// They are intermediate results of the parsing process
// and/or might be used for constant folding
// or may contain the actual values used by an interpreter.

// So we have to distinguish between real values
// (where the value itself is known at compile time)
// and values where only the type is known (later: together with some preconditions)
// This distinction is done by bHasValue

class Operand {
public:
	EnumTypes type;	// to help select the right union member
	OperandValue uOpValue;
	bool bHasValue;		// true if value in union is valid, false if only type is known
	char* sCurrentPosition;
				// source file position of operand (if a constant)
				// or source file position of operator that generated the value
				// (used to enhance error messages)

			// Constructor - OperandValue uOpValue needs to be set by the caller
			// because it may have different types
	Operand (EnumTypes type_, bool bHasValue_, char* sCurrentPosition_) :
	type (type_), bHasValue (bHasValue_), sCurrentPosition (sCurrentPosition_)
	{
	type = type_;
	}
	
};
// Later, these will be ordinary objects of the appropriate type


// Operators form the operator stack,
// which is used to resolve operator precedence
// and operator matching (e.g. () [] {}).

class Operator {
public:
	SyntaxToken* pToken;
	char* sCurrentPosition;	// source file position of operator
				// (used to enhance error messages)
	
				// constructor:
	Operator (SyntaxToken* pTok, char* sCurr) :
	pToken (pTok), sCurrentPosition (sCurr) {};
};


// The parser.
// Contains the current state of the compilation / interpretation.

class Parser {
public:
	char* sSourceFile;
	// For a start:
	// Supposed to suck in the whole module,
	// will always point to the beginning of the text.
	char* sCurrentPosition;
	// the current position in *sSourceFile
	stack <Operand> OperandStack;		// the operand stack
	stack <Operator> OperatorStack;		// the operator stack
	map <Name, Operand> SymbolTable;	// the symbol table


	char*								// return value: NULL or error message
	Parse (SyntaxToken* pToken,			// start of the syntax web
	char* sSource) {					// program text

		ASSERT (pToken);				// make sure there is a syntax
		sSourceFile = sSource;			// remember start position
		char* sErrMsg = NULL;			// no error yet
		// push StopExecutor as emergency brake onto operator stack to prevent underrun
		OperatorStack.push (*new Operator (pToken, sSource));
		// TODO: strip white space here (not yet implemented)
		sCurrentPosition = sSourceFile;
		// convention: this shall be the actual startiung point of the syntax:
		pToken = pToken->pContinuations [CONTINUE_FOUND];

		// this will be the first token to look for
		try {
			while (1) {
				pToken = (pToken->pRecognise->Method) (this, pToken);
				// ever seen a shorter parse loop ?
			}
		}
		catch (char* sErrMsg) {
			return sErrMsg;			// return that error (NULL if normal end-of-file)
		};
		return sErrMsg;				// actually, it will never get here
	}				
};


// Recognisers and Executors (they do the real work)

// StopExecutor - used to stop parser loop.
// Used to:
// - Normally exit parser: pOp->pToken->sToken == NULL
// - Fatal exit: pOp->pToken->sToken == Error message to be output
//   for example: operator stack underflow

// Having this executor pushed onto the opertor stack
// as the first element eliminates need to check for underrun on every pop.
// For this purpose, it must be given the lowest possible binding power
// to ensure that it will never normally be executed.
// Eof recogniser has the next lowest binding power
// to ensure that all pending operators will be executed prior to parser exit.

class StopExecutor : public Executor {
public:
	void Method (Parser* pParser, const Operator* pOp) {
	throw (pOp->pToken->sToken);	// make it stop
	}
		
};


// EofRecogniser should be called as a last resort when no other legal tokens can be
// recognised. This means either:
// a) End-of-file
// b) Syntax error
// Actions:
//    Check for end of program.
//    If found, unwind the operator stack to process pending operators,
//    and throw exception to stop parsing.
//    Otherwise output pOperator->pToken->sToken (the error message),
//    and carry on if pOperator->pToken->pContinuations [CONTINUE_NOTFOUND] present
//    or throw exception to stop parsing if above continuation is NULL.

// It must be given the second lowest possible binding power
// to ensure that all pending operators except it will never be executed.
// all pending operators will be executed first.

class EofRecogniser : public Recogniser {
public:
	SyntaxToken* Method (Parser* pParser, const SyntaxToken* pToken) {
		
		if (*pParser->sCurrentPosition) {
			if (pToken->sToken) {
				// TODO: produce error message
			}
			SyntaxToken* pOp;
			if (pOp = pToken->pContinuations [CONTINUE_NOTFOUND])
				// that's an assignment, no comparison
				return pOp;
		}
		else {
			PopOperators (pParser, pToken);
			throw (NULL);	// make it stop, normal parse exit
		}
	}
	
};


// standard recogniser for operators w/precedence and no parenthesis properties

class StandardRecogniser : public Recogniser {
public:
	SyntaxToken* Method (Parser* pParser, SyntaxToken* pToken) {
		
		char *pText = pToken->sToken;
		unsigned pTokenLng = strlen (pText);
		// TODO: compile pTokenLng once & make it a member of SyntaxToken
		if (strncmp (pParser->sCurrentPosition, pText, pTokenLng) == 0) {
			// got it
			PopOperators (pParser, pToken);		// handle operator precendence
			// if there is a pExecute function in the recognised operator, push that
			if (pToken->pExecute)
				pParser->OperatorStack.push (*new Operator (pToken, pParser->sCurrentPosition));
			// advance source file
			pParser->sCurrentPosition += pTokenLng;
			// TODO: strip white space here (not yet implemented)
			
			return pToken->pContinuations [CONTINUE_FOUND];
		}
		else	// no match - try next option
			return pToken->pContinuations [CONTINUE_NOTFOUND];
	}
};


// Recogniser for new names.
// Assume (but make sure) that a valid type is on top of the operand stack,
// so that introductions of a new name have the form  <type> <new name>
// and return an operand of type name (to be looked up in the symbol table).
// Currently only one flat global name space is supported.
// No polymorphism.
// Look whether valid, previously unknown name.
// If yes, enter it into symbol table.
// This is a postfix operator, as is expects the stack to contain a type
// and changes that stack entry to contain a reference

class NewNameRecogniser : public Recogniser {
public:
	SyntaxToken* Method (Parser* pParser, const SyntaxToken* pToken) {
		
		if (isalpha (*pParser->sCurrentPosition)) {		// must start with a letter
			unsigned int lng = 0;
			for (lng = 1;
			isalnum (pParser->sCurrentPosition [lng]);	// sure this needs to be refined, e.g. allow "_"
			++lng)
				;
			// name found - fetch type from operand stack
			if (pParser->OperandStack.top ().type != OPNDTYPE_TYPE) {
				// complain about use of unknown name
				// (later: enter that name anyway using some special type
				//  to avoid complaining each time the name is used)
			}
			else {
				// insert new name
				Name* sName = new Name (pParser->sCurrentPosition, lng);
				// associate it with type retrieved from operand stack
				pParser->SymbolTable.insert (Parser_Map::value_type
					(*sName,
					*new Operand (pParser->OperandStack.top ().uOpValue.Type,	// Type of new name
					false,													// has no value yet
					pParser->sCurrentPosition)								// Operand has been generated here
					));
				// remove type from operand stack
				pParser->OperandStack.pop ();
				// push name (result of operatrion) onto operand stack
				Operand *pOpnd = new Operand (OPNDTYPE_NAME,	// its a name
					true,													// Operand has got a value (the name)
					pParser->sCurrentPosition);								// Operand has been generated here
				pOpnd->uOpValue.pName = sName;
				pParser->OperandStack.push (*pOpnd);
			}
			if (pToken->sToken) {
				// TODO: produce error message
			}
			SyntaxToken* pOp;
			if (pOp = pToken->pContinuations [CONTINUE_NOTFOUND])
				// that's an assignment, no comparison
				return pOp;
		}
		throw (NULL);	// make it stop, normal parse exit
	}
	
};


void Recogniser::PopOperators (Parser* pParser, const SyntaxToken* pToken) {
	Operator* pOp;
	
	// check for operator precendence:
	// compare own binding power to that of stack top
	unsigned short usLBP = pToken->usLBP;
	while (usLBP > (pOp = &pParser->OperatorStack.top ())->pToken->usRBP) {
		// stacked op binds more tightly - pop it off the stack & execute it
		pParser->OperatorStack.pop ();
		pOp->pToken->pExecute->Method (pParser, pOp);
		delete pOp;
	}
};

// recogniser for known names
// looks up names in the single namespace (symbol table) at the moment

class OldNameRecogniser : public Recogniser {
public:
	SyntaxToken* Method (Parser* pParser, const SyntaxToken* pToken) {
		
		if (isalpha (*pParser->sCurrentPosition)) {		// must start with a letter
			unsigned int lng = 0;
			for (lng = 1;
			isalnum (pParser->sCurrentPosition [lng]);	// sure this needs to be refined, e.g. allow "_"
			++lng)
				;
			// name found - search symbol table

			Name* sName = &Name (*pParser->sCurrentPosition, lng);
				// associate it with type retrieved from operand stack
			Parser_Map::iterator pIter = pParser->SymbolTable.find (*sName);
			if (pIter != pParser->SymbolTable.end ()) {	// found matching name
				// push name (result of operation) onto operand stack
				Operand *pOpnd = new Operand (OPNDTYPE_NAME,	// its a name
					true,													// Operand has got a value (the name)
					pParser->sCurrentPosition);								// Operand has been generated here
				pOpnd->uOpValue.pName = (*pIter).second.uOpValue.pName;
				pParser->OperandStack.push (*pOpnd);

				return pToken->pContinuations [CONTINUE_FOUND];
			}
		}
		else {
			// TODO: complain about unknown symbol
			// and push some NULL value or the like as operator fodder
		SyntaxToken* pOp;
		if (pOp = pToken->pContinuations [CONTINUE_NOTFOUND])
			// that's an assignment, no comparison
			return pOp;
		throw (pToken->sToken);	// make it stop
		}
	}

};

// special recogniser for "." (Qualifiying) operator
// will have to examine topmost OperandStack element to find namespace

// recogniser for string literals

// recogniser for numbers

// recogniser for comments
// skips everything up to the end of the line

// recogniser for parentheses
// (looks at the topmost OperatorStack element to see whether it matches, then removes that)

// recogniser for left match ops (if, (, [, {, you name it)
// pushes match-ops onto operator stack / checks them

// recogniser for right match ops (endif, ), ], }, you name it)
// pushes match-ops onto operator stack / checks them

// recogniser for match-ops which are left as well as right (then, else)

// executor for match ops
// will complain if executed ("missing right ...")

// example for a standard infix executor: add two numbers
// examine type of topmost two operand stack elements.
// if they are numbers, add'em and push result back
// if not, complain about illegal types.
// Later on, examine the class of the second topmost stack element & look up its "add" routine
// Complain if topmost stack item can't be converted


// A remark regarding the use of templates:
// I'm new to STL. I find it *extremely* complicated to use that stuff,
// and I suspect that the above code might be *extremely* ineffiecient.
// I simply can't tell because I don't have the faintest idea what the compiler generates.
// Moreover, especially when using this map<> stuff, it takes a *lot* of time to get the
// use of operator* right. I hate it! I strongly recommend not to use standard templates.
// Maybe we can develop our own, not so far-out versions of what we need.
--------------7D9C7C987BB47252DED1B20D--