latest features added and bugs fixed:

compare %=
timestamp converter %T
error and debug messages
This commit is contained in:
zimoch
2010-09-02 14:50:33 +00:00
parent 5494aae1ba
commit 35768cbfbb
11 changed files with 859 additions and 264 deletions

View File

@ -42,6 +42,7 @@ FORMATS += RawFloat
FORMATS += Binary
FORMATS += Checksum
FORMATS += MantissaExponent
FORMATS += Timestamp
# Want Perl regular expression matching?
# If PCRE is installed at the same location for all

View File

@ -325,7 +325,7 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
{
if (!busSupportsEvent())
{
protocol->errorMsg(getLineNumber(command),
error(getLineNumber(command), protocol->filename(),
"Events not supported by businterface.\n");
return false;
}
@ -339,7 +339,7 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
}
if (*args != ')')
{
protocol->errorMsg(getLineNumber(command),
error(getLineNumber(command), protocol->filename(),
"Expect ')' instead of: '%s'\n", args);
return false;
}
@ -360,7 +360,7 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
{
buffer.append(exec_cmd);
if (!protocol->compileString(buffer, args,
PrintFormat, this))
NoFormat, this))
{
return false;
}
@ -383,7 +383,7 @@ compileCommand(StreamProtocolParser::Protocol* protocol,
return true;
}
protocol->errorMsg(getLineNumber(command),
error(getLineNumber(command), protocol->filename(),
"Unknown command name '%s'\n", command);
return false;
}
@ -592,7 +592,6 @@ evalOut()
{
inputBuffer.clear(); // flush all unread input
unparsedInput = false;
outputLine.clear();
if (!formatOutput())
{
finishProtocol(FormatError);
@ -634,13 +633,15 @@ formatOutput()
const char* fieldName = NULL;
const char* formatstring;
int formatstringlen;
outputLine.clear();
while ((command = *commandIndex++) != StreamProtocolParser::eos)
{
switch (command)
{
case StreamProtocolParser::format_field:
{
debug("StreamCore::formatOutput(%s): StreamProtocolParser::format_field\n",
debug("StreamCore::formatOutput(%s): StreamProtocolParser::redirect_format\n",
name());
// code layout:
// field <eos> addrlen AddressStructure formatstring <eos> StreamFormat [info]
@ -663,6 +664,7 @@ formatOutput()
}
formatstringlen = commandIndex-formatstring;
commandIndex++;
StreamFormat fmt = extract<StreamFormat>(commandIndex);
fmt.info = commandIndex; // point to info string
commandIndex += fmt.infolen;
@ -670,6 +672,7 @@ formatOutput()
debug("StreamCore::formatOutput(%s): format = %%%s\n",
name(), StreamBuffer(formatstring, formatstringlen).expand()());
#endif
if (fmt.type == pseudo_format)
{
if (!StreamFormatConverter::find(fmt.conv)->
@ -685,7 +688,7 @@ formatOutput()
if (!formatValue(fmt, fieldAddress ? fieldAddress() : NULL))
{
StreamBuffer formatstr(formatstring, formatstringlen);
if (fieldName)
if (fieldAddress)
error("%s: Cannot format field '%s' with '%%%s'\n",
name(), fieldName, formatstr.expand()());
else
@ -694,7 +697,6 @@ formatOutput()
return false;
}
fieldAddress.clear();
fieldName = NULL;
continue;
}
case esc:
@ -718,7 +720,6 @@ printSeparator()
}
if (!separator) return;
long i = 0;
if (separator[0] == ' ') i++; // ignore leading space
for (; i < separator.length(); i++)
{
if (separator[i] == StreamProtocolParser::skip) continue; // wildcard
@ -744,6 +745,8 @@ printValue(const StreamFormat& fmt, long value)
name(), value);
return false;
}
debug("StreamCore::printValue(%s, long): \"%s\"\n",
name(), outputLine.expand()());
return true;
}
@ -764,6 +767,8 @@ printValue(const StreamFormat& fmt, double value)
name(), value);
return false;
}
debug("StreamCore::printValue(%s, double): \"%s\"\n",
name(), outputLine.expand()());
return true;
}
@ -785,6 +790,8 @@ printValue(const StreamFormat& fmt, char* value)
name(), buffer.expand()());
return false;
}
debug("StreamCore::printValue(%s, char*): \"%s\"\n",
name(), outputLine.expand()());
return true;
}
@ -804,8 +811,6 @@ lockCallback(StreamIoStatus status)
flags |= BusOwner;
if (status != StreamIoSuccess)
{
error("%s: Lock timeout\n",
name());
finishProtocol(LockTimeout);
return;
}
@ -1107,7 +1112,9 @@ matchInput()
is installed and starts with 'in' (then we reparse the input).
*/
char command;
const char* fieldName = NULL;
const char* formatstring;
int formatstringlen;
consumedInput = 0;
@ -1119,6 +1126,7 @@ matchInput()
{
// code layout:
// field <StreamProtocolParser::eos> addrlen AddressStructure formatstring <StreamProtocolParser::eos> StreamFormat [info]
fieldName = commandIndex;
commandIndex += strlen(commandIndex)+1;
unsigned short addrlen = extract<unsigned short>(commandIndex);
fieldAddress.set(commandIndex, addrlen);
@ -1130,11 +1138,23 @@ matchInput()
// code layout:
// formatstring <eos> StreamFormat [info]
formatstring = commandIndex;
while (*commandIndex++ != StreamProtocolParser::eos); // jump after <eos>
// jump after <eos>
while (*commandIndex)
{
if (*commandIndex == esc) commandIndex++;
commandIndex++;
}
formatstringlen = commandIndex-formatstring;
commandIndex++;
StreamFormat fmt = extract<StreamFormat>(commandIndex);
fmt.info = commandIndex;
fmt.info = commandIndex; // point to info string
commandIndex += fmt.infolen;
#ifndef NO_TEMPORARY
debug("StreamCore::matchInput(%s): format = %%%s\n",
name(), StreamBuffer(formatstring, formatstringlen).expand()());
#endif
if (fmt.flags & skip_flag || fmt.type == pseudo_format)
{
long ldummy;
@ -1185,6 +1205,53 @@ matchInput()
consumedInput += consumed;
break;
}
if (fmt.flags & compare_flag)
{
outputLine.clear();
flags &= ~Separator;
if (!formatValue(fmt, fieldAddress ? fieldAddress() : NULL))
{
StreamBuffer formatstr(formatstring, formatstringlen);
if (fieldAddress)
error("%s: Cannot format field '%s' with '%%%s'\n",
name(), fieldName, formatstr.expand()());
else
error("%s: Cannot format value with '%%%s'\n",
name(), formatstr.expand()());
return false;
}
debug("StreamCore::matchInput(%s): compare \"%s\" with \"%s\"\n",
name(), inputLine.expand(consumedInput, outputLine.length())(), outputLine.expand()());
if (inputLine.length() - consumedInput < outputLine.length())
{
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
{
error("%s: Input \"%s%s\" too short."
" No match for format %%%s (\"%s\")\n",
name(),
inputLine.length() > 20 ? "..." : "",
inputLine.expand(-20)(),
formatstring,
outputLine.expand()());
}
return false;
}
if (!outputLine.equals(inputLine(consumedInput),outputLine.length()))
{
if (!(flags & AsyncMode) && onMismatch[0] != in_cmd)
{
error("%s: Input \"%s%s\" does not match"
" format %%%s (\"%s\")\n",
name(), inputLine.expand(consumedInput, 20)(),
inputLine.length()-consumedInput > 20 ? "..." : "",
formatstring,
outputLine.expand()());
}
return false;
}
consumedInput += outputLine.length();
break;
}
flags &= ~Separator;
if (!matchValue(fmt, fieldAddress ? fieldAddress() : NULL))
{
@ -1202,7 +1269,7 @@ matchInput()
return false;
}
// matchValue() has already removed consumed bytes from inputBuffer
fieldAddress = NULL;
fieldAddress.clear();
break;
}
case StreamProtocolParser::skip:
@ -1514,7 +1581,6 @@ timerCallback()
bool StreamCore::
evalExec()
{
outputLine.clear();
formatOutput();
// release bus
if (flags & BusOwner)

View File

@ -352,7 +352,6 @@ report(int interest)
pstream->ioLink->value.instio.string);
}
}
StreamPrintTimestampFunction = streamEpicsPrintTimestamp;
return OK;
}
@ -968,6 +967,26 @@ formatValue(const StreamFormat& format, const void* fieldaddress)
long nelem = pdbaddr->no_elements;
size_t size = nelem * typeSize[format.type];
char* buffer = fieldBuffer.clear().reserve(size);
if (strcmp(((dbFldDes*)pdbaddr->pfldDes)->name, "TIME") == 0)
{
double time;
if (format.type != double_format)
{
error ("%s: can only read double values from TIME field\n", name());
return false;
}
if (pdbaddr->precord == record)
{
/* if getting time from own record, update timestamp first */
recGblGetTimeStamp(record);
}
time = pdbaddr->precord->time.secPastEpoch + 631152000u + pdbaddr->precord->time.nsec * 1e-9;
debug("Stream::formatValue(%s): read %f from TIME field\n", name(), time);
return printValue(format, time);
}
if (dbGet(pdbaddr, dbfMapping[format.type], buffer,
NULL, &nelem, NULL) != 0)
{
@ -1105,6 +1124,28 @@ noMoreElements:
}
return false;
}
if (strcmp(((dbFldDes*)pdbaddr->pfldDes)->name, "TIME") == 0)
{
#ifdef epicsTimeEventDeviceTime
if (format.type != double_format)
{
error ("%s: can only write double values to TIME field\n", name());
return false;
}
dval = dval-631152000u;
pdbaddr->precord->time.secPastEpoch = (long)dval;
/* rouding: we don't have 9 digits precision in a double of today's number of seconds */
pdbaddr->precord->time.nsec = (long)((dval-(long)dval)*1e6)*1000;
debug("Stream::matchValue(%s): writing %i.%i to TIME field\n", name(),
pdbaddr->precord->time.secPastEpoch, pdbaddr->precord->time.nsec);
pdbaddr->precord->tse = epicsTimeEventDeviceTime;
return true;
#else
error ("%s: writing TIME field is not supported in this EPICS version\n", name());
return false;
#endif
}
if (pdbaddr->precord == record || INIT_RUN)
{
// write into own record, thus don't process it

View File

@ -58,11 +58,19 @@ void StreamError(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
StreamVError(fmt, args);
StreamVError(0, NULL, fmt, args);
va_end(args);
}
void StreamVError(const char* fmt, va_list args)
void StreamError(int line, const char* file, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
StreamVError(line, file, fmt, args);
va_end(args);
}
void StreamVError(int line, const char* file, const char* fmt, va_list args)
{
char timestamp[40];
StreamPrintTimestampFunction(timestamp, 40);
@ -79,6 +87,10 @@ void StreamVError(const char* fmt, va_list args)
#endif
fprintf(stderr, "\033[31;1m");
fprintf(stderr, "%s ", timestamp);
if (file)
{
fprintf(stderr, "%s line %d: ", file, line);
}
vfprintf(stderr, fmt, args);
fprintf(stderr, "\033[0m");
}

View File

@ -30,11 +30,19 @@
extern int streamDebug;
extern void (*StreamPrintTimestampFunction)(char* buffer, int size);
void StreamError(int line, const char* file, const char* fmt, ...)
__attribute__ ((format(printf,3,4)));
void StreamVError(int line, const char* file, const char* fmt, va_list args)
__attribute__ ((format(printf,3,0)));
void StreamError(const char* fmt, ...)
__attribute__ ((format(printf,1,2)));
void StreamVError(const char* fmt, va_list args)
__attribute__ ((format(printf,1,0)));
inline void StreamVError(const char* fmt, va_list args)
{
StreamVError(0, NULL, fmt, args);
}
class StreamDebugClass
{

View File

@ -29,7 +29,8 @@ typedef enum {
alt_flag = 0x08,
zero_flag = 0x10,
skip_flag = 0x20,
default_flag = 0x40
default_flag = 0x40,
compare_flag = 0x80
} StreamFormatFlag;
typedef enum {

View File

@ -19,8 +19,8 @@
* *
***************************************************************/
#include <stdlib.h>
#include "StreamFormatConverter.h"
#include "StreamFormat.h"
#include "StreamError.h"
StreamFormatConverter* StreamFormatConverter::
@ -31,6 +31,125 @@ StreamFormatConverter::
{
}
int StreamFormatConverter::
parseFormat(const char*& source, FormatType formatType, StreamFormat& streamFormat, StreamBuffer& infoString)
{
/*
source := [flags] [width] ['.' prec] conv [extra]
flags := '-' | '+' | ' ' | '#' | '0' | '*' | '?' | '='
width := integer
prec := integer
conv := character
extra := string
*/
// look for flags
streamFormat.flags = 0;
bool loop = true;
while (loop)
{
switch (*++source)
{
case '-':
streamFormat.flags |= left_flag;
break;
case '+':
streamFormat.flags |= sign_flag;
break;
case ' ':
streamFormat.flags |= space_flag;
break;
case '#':
streamFormat.flags |= alt_flag;
break;
case '0':
streamFormat.flags |= zero_flag;
break;
case '*':
if (formatType != ScanFormat)
{
error("Use of skip modifier '*' "
"only allowed in input formats\n");
return false;
}
streamFormat.flags |= skip_flag;
break;
case '?':
if (formatType != ScanFormat)
{
error("Use of default modifier '?' "
"only allowed in input formats\n");
return false;
}
streamFormat.flags |= default_flag;
break;
case '=':
if (formatType != ScanFormat)
{
error("Use of compare modifier '=' "
"only allowed in input formats\n");
return false;
}
streamFormat.flags |= compare_flag;
formatType = PrintFormat;
break;
default:
loop = false;
}
}
// look for width
unsigned long val;
char* p;
val = strtoul (source, &p, 10);
source = p;
if (val > 0xFFFF)
{
error("Field width %ld out of range\n", val);
return false;
}
streamFormat.width = (unsigned short)val;
// look for prec
streamFormat.prec = -1;
if (*source == '.')
{
source++;
val = strtoul(source, &p, 10);
if (p == source)
{
debug("source = %s\n", source);
error("Numeric precision field expected after '.'\n");
return false;
}
source = p;
if (val > 0x7FFF)
{
error("Precision %ld out of range\n", val);
return false;
}
streamFormat.prec = (short)val;
}
// look for converter
streamFormat.conv = *source++;
if (!streamFormat.conv || strchr("'\" (.0+-*?=", streamFormat.conv))
{
error("Missing converter character\n");
return false;
}
debug("StreamFormatConverter::parseFormat: converter='%c'\n",
streamFormat.conv);
StreamFormatConverter* converter;
converter = StreamFormatConverter::find(streamFormat.conv);
if (!converter)
{
error("No converter registered for format '%%%c'\n",
streamFormat.conv);
return false;
}
// parse format and get info string
return converter->parse(streamFormat, infoString,
source, formatType == ScanFormat);
}
void StreamFormatConverter::
provides(const char* name, const char* provided)
{
@ -112,7 +231,7 @@ static void copyFormatString(StreamBuffer& info, const char* source)
const char* p = source - 1;
while (*p != '%' && *p != ')') p--;
info.append('%');
while (++p != source-1) if (*p != '?') info.append(*p);
while (++p != source-1) if (*p != '?' && *p != '=') info.append(*p);
}
// Standard Long Converter for 'diouxX'

View File

@ -22,6 +22,7 @@
#include "StreamFormat.h"
#include "StreamBuffer.h"
#include "StreamProtocol.h"
#define esc (0x1b)
@ -41,6 +42,7 @@ class StreamFormatConverter
const char* _name;
public:
virtual ~StreamFormatConverter();
static int parseFormat(const char*& source, FormatType, StreamFormat&, StreamBuffer& infoString);
const char* name() { return _name; }
void provides(const char* name, const char* provided);
static StreamFormatConverter* find(unsigned char c);

View File

@ -81,18 +81,6 @@ StreamProtocolParser::
delete next;
}
void StreamProtocolParser::
errorMsg(const char* fmt, ...)
{
char fmt2[200];
sprintf (fmt2, "'%s' line %d: %s",
filename(), line, fmt);
va_list args;
va_start(args, fmt);
StreamVError(fmt2, args);
va_end(args);
}
void StreamProtocolParser::
report()
{
@ -292,18 +280,18 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
// end of protocol or handler definition
return true;
}
errorMsg("Stray '}' in global context\n");
error(line, filename(), "Stray '}' in global context\n");
return false;
}
if (strchr("{=", token[0]))
{
errorMsg("Expect name before '%c'\n", token[0]);
error(line, filename(), "Expect name before '%c'\n", token[0]);
return false;
}
do op = readChar(); while (op == ' '); // what comes after token?
if (op == EOF)
{
errorMsg("Unexpected end of file after: %s\n", token());
error(line, filename(), "Unexpected end of file after: %s\n", token());
return false;
}
if (op == '=')
@ -311,20 +299,20 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
// variable assignment
if (isHandlerContext(protocol, commands))
{
errorMsg("Variables are not allowed in handlers: %s\n",
error(line, filename(), "Variables are not allowed in handlers: %s\n",
token());
return false;
}
if (token[0] == '@' || (token[0] >= '0' && token[0] <= '9'))
{
errorMsg("Variable name cannot start with '%c': %s\n",
error(line, filename(), "Variable name cannot start with '%c': %s\n",
token[0], token());
return false;
}
if (!parseAssignment(token(), protocol))
{
line = startline;
errorMsg("in variable assignment '%s = ...'\n", token());
error(line, filename(), "in variable assignment '%s = ...'\n", token());
return false;
}
continue;
@ -336,7 +324,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
{
if (isHandlerContext(protocol, commands))
{
errorMsg("Handlers are not allowed in handlers: %s\n",
error(line, filename(), "Handlers are not allowed in handlers: %s\n",
token());
return false;
}
@ -345,7 +333,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
if (!parseProtocol(protocol, handler))
{
line = startline;
errorMsg("in handler '%s'\n", token());
error(line, filename(), "in handler '%s'\n", token());
return false;
}
continue;
@ -353,7 +341,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
// protocol definition
if (!isGlobalContext(commands))
{
errorMsg("Definition of '%s' not in global context (missing '}' ?)\n",
error(line, filename(), "Definition of '%s' not in global context (missing '}' ?)\n",
token());
return false;
}
@ -362,7 +350,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
{
if ((*ppP)->protocolname.equals(token()))
{
errorMsg("Protocol '%s' redefined\n", token());
error(line, filename(), "Protocol '%s' redefined\n", token());
return false;
}
}
@ -370,7 +358,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
if (!parseProtocol(*pP, pP->commands))
{
line = startline;
errorMsg("in protocol '%s'\n", token());
error(line, filename(), "in protocol '%s'\n", token());
delete pP;
return false;
}
@ -381,7 +369,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
// Must be a command or a protocol reference.
if (isGlobalContext(commands))
{
errorMsg("Expect '=' or '{' instead of '%c' after '%s'\n",
error(line, filename(), "Expect '=' or '{' instead of '%c' after '%s'\n",
op, token());
return false;
}
@ -406,7 +394,7 @@ parseProtocol(Protocol& protocol, StreamBuffer* commands)
if (parseValue(*commands, true) == false)
{
line = startline;
errorMsg("after command '%s'\n", token());
error(line, filename(), "after command '%s'\n", token());
return false;
}
debug("parseProtocol: command '%s'\n", (*commands).expand()());
@ -486,7 +474,7 @@ Each time newline is read, line is incremented.
c = getc(file);
if (c != '}')
{
errorMsg("Expect '}' instead of '%c' after: %s\n",
error(line, filename(), "Expect '}' instead of '%c' after: %s\n",
c, buffer(token));
return false;
}
@ -495,12 +483,12 @@ Each time newline is read, line is incremented.
}
if (c == EOF)
{
errorMsg("Unexpected end of file after '$'\n");
error(line, filename(), "Unexpected end of file after '$'\n");
return false;
}
if (strchr (specialchars, c))
{
errorMsg("Unexpected '%c' after '$'\n,", c);
error(line, filename(), "Unexpected '%c' after '$'\n,", c);
return false;
}
// variable like $xyz handled as word token
@ -519,7 +507,7 @@ Each time newline is read, line is incremented.
{
if (c == EOF || c == '\n')
{
errorMsg("Unterminated quoted string: %s\n",
error(line, filename(), "Unterminated quoted string: %s\n",
buffer(token));
return false;
}
@ -547,7 +535,7 @@ Each time newline is read, line is incremented.
// end of file
if (!eofAllowed)
{
errorMsg("Unexpected end of file\n");
error(line, filename(), "Unexpected end of file\n");
return false;
}
buffer.append('\0');
@ -620,7 +608,7 @@ parseValue(StreamBuffer& buffer, bool lazy)
}
if (c == '{' || c == '=')
{
errorMsg("Unexpected '%c' (missing ';' or '\"' ?)\n", c);
error(line, filename(), "Unexpected '%c' (missing ';' or '\"' ?)\n", c);
return false;
}
if (strchr (";}", c))
@ -768,18 +756,6 @@ StreamProtocolParser::Protocol::
delete next;
}
void StreamProtocolParser::Protocol::
errorMsg(int l, const char* fmt, ...)
{
char fmt2[200];
sprintf (fmt2, "'%s' line %d: %s",
filename(), line, fmt);
va_list args;
va_start(args, fmt);
StreamVError(fmt2, args);
va_end(args);
}
void StreamProtocolParser::Protocol::
report()
{
@ -847,12 +823,12 @@ getNumberVariable(const char* varname, unsigned long& value, unsigned long max)
if (!compileNumber(value, source, max))
{
int linenr = getLineNumber(source);
errorMsg(linenr, "in variable %s\n", varname);
error(linenr, filename(), "in variable %s\n", varname);
return false;
}
if (source != pvar->value.end())
{
errorMsg(getLineNumber(source),
error(getLineNumber(source), filename(),
"Garbage in variable '%s' after numeric value %ld: %s\n",
varname, value, source);
return false;
@ -925,13 +901,14 @@ getCommands(const char* handlername,StreamBuffer& code, Client* client)
{
if (handlername)
{
errorMsg(pvar->line,
error(pvar->line, filename(),
"in handler '%s'\n", handlername);
errorMsg(variables->line,
error(variables->line, filename(),
"used by protocol '%s'\n", protocolname());
return false;
}
errorMsg(pvar->line, "in protocol '%s'\n", protocolname());
error(pvar->line, filename(),
"in protocol '%s'\n", protocolname());
return false;
}
debug("commands %s: %s\n", handlername, pvar->value.expand()());
@ -955,7 +932,7 @@ replaceVariable(StreamBuffer& buffer, const char* varname)
const char* p = parameter[*varname-'0'];
if (!p)
{
errorMsg(linenr,
error(linenr, filename(),
"Missing value for parameter $%c\n", *varname);
return false;
}
@ -981,7 +958,7 @@ replaceVariable(StreamBuffer& buffer, const char* varname)
const Variable* v = getVariable(varname);
if (!v)
{
errorMsg(linenr,
error(linenr, filename(),
"Undefined variable '%s' referenced\n",
varname);
return false;
@ -1045,7 +1022,7 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
{
debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
buffer.expand()());
errorMsg(getLineNumber(source),
error(getLineNumber(source), filename(),
"Unsigned numeric value expected: %s\n", buffer());
return false;
}
@ -1053,7 +1030,7 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
{
debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
buffer.expand()());
errorMsg(getLineNumber(source),
error(getLineNumber(source), filename(),
"Garbage after numeric value: %s\n", buffer());
return false;
}
@ -1061,7 +1038,7 @@ compileNumber(unsigned long& number, const char*& source, unsigned long max)
{
debug("StreamProtocolParser::Protocol::compileNumber: %s\n",
buffer.expand()());
errorMsg(getLineNumber(source),
error(getLineNumber(source), filename(),
"Value %s out of range [0...%ld]\n", buffer(), max);
return false;
}
@ -1080,10 +1057,9 @@ compileString(StreamBuffer& buffer, const char*& source,
{
bool escaped = false;
int n;
long formatPos[20];
int numFormats = 0;
int newline = 0;
StreamBuffer formatbuffer;
int formatpos = buffer.length();
line = getLineNumber(source);
debug("StreamProtocolParser::Protocol::compileString "
@ -1104,31 +1080,35 @@ compileString(StreamBuffer& buffer, const char*& source,
// compile all formats in this line
// We do this here after all variables in this line
// have been replaced and after string has been coded.
int offs = 0;
for (n = 0; n < numFormats; n++)
if (formatType != NoFormat)
{
int pos = formatPos[n] + offs;
debug("StreamProtocolParser::Protocol::compileString "
"format=\"%s\"\n", buffer.expand(pos)());
formatbuffer.clear();
const char* p = buffer(pos);
if (!compileFormat(formatbuffer, p, formatType, client))
while ((formatpos = buffer.find('%', formatpos)) != -1)
{
p = buffer(pos);
if (buffer[formatpos-1] == esc) continue;
debug("StreamProtocolParser::Protocol::compileString "
"format=\"%s\"\n", buffer.expand(formatpos)());
formatbuffer.clear();
printString(formatbuffer, p);
errorMsg(line, "in format string: \"%s\"\n",
const char* p = buffer(formatpos);
if (!compileFormat(formatbuffer, p, formatType, client))
{
p = buffer(formatpos);
formatbuffer.clear();
printString(formatbuffer, p);
error(line, filename(),
"in format string: \"%s\"\n",
formatbuffer());
return false;
return false;
}
int formatlen = p - buffer(formatpos);
buffer.replace(formatpos, formatlen, formatbuffer);
debug("StreamProtocolParser::Protocol::compileString "
"replaced by: \"%s\"\n", buffer.expand(formatpos)());
formatpos += formatbuffer.length();
}
int formatlen = p - buffer(pos);
buffer.replace(pos, formatlen, formatbuffer);
debug("StreamProtocolParser::Protocol::compileString "
"replaced by: \"%s\"\n", buffer.expand(pos)());
offs += formatbuffer.length() - formatlen;
"all formats in line %d found\n", line);
}
if (!*source) break;
numFormats = 0;
line = newline;
}
// this is step 1: coding the string
@ -1144,13 +1124,13 @@ compileString(StreamBuffer& buffer, const char*& source,
switch (*source)
{
case '$': // can't be: readToken would have made a token from this
errorMsg(line,
error(line, filename(),
"INTERNAL ERROR: unconverted \\$ in quoted string\n");
return false;
case '?':
if (formatType != ScanFormat)
{
errorMsg(line,
error(line, filename(),
"Quoted \\? only allowed in input: %s\n",
source-1);
return false;
@ -1179,7 +1159,7 @@ compileString(StreamBuffer& buffer, const char*& source,
sscanf (source, "%3o%n", &temp, &n);
if (temp > 0xFF)
{
errorMsg(line,
error(line, filename(),
"Octal source %#o does not fit in byte: %s\n",
temp, source-1);
return false;
@ -1195,7 +1175,7 @@ compileString(StreamBuffer& buffer, const char*& source,
case 'x': // hex numbers (max 2 digits after 0)
if (sscanf (source+1, "%2x%n", &temp, &n) < 1)
{
errorMsg(line,
error(line, filename(),
"Hex digit expected after \\x: %s\n",
source-1);
return false;
@ -1220,7 +1200,7 @@ compileString(StreamBuffer& buffer, const char*& source,
sscanf (source, "%3u%n", &temp, &n);
if (temp > 0xFF)
{
errorMsg(line,
error(line, filename(),
"Decimal source %d does not fit in byte: %s\n",
temp, source-1);
return false;
@ -1239,29 +1219,13 @@ compileString(StreamBuffer& buffer, const char*& source,
source++;
continue;
}
if (quoted) // look for ending quotes, escapes, and formats
if (quoted) // look for ending quotes and escapes
{
switch (*source)
{
case '\\': // escape next character
escaped = true;
break;
case '%': // format specifier in string
if (formatType != NoFormat)
{
// format is allowed here
// just memorize position here and and do actual coding later
// after all variables and parameters have been replaced
// so that extra information is ready for format converter
if (numFormats+1 == sizeof(formatPos))
{
errorMsg(line, "Max 20 formats allowed in one protocol line");
return false;
}
formatPos[numFormats++]=buffer.length();
buffer.append(*source);
break;
}
case '"':
case '\'':
if (*source == quoted) // ending quote
@ -1310,13 +1274,13 @@ compileString(StreamBuffer& buffer, const char*& source,
{
if (*p != 0)
{
errorMsg(line,
error(line, filename(),
"Garbage after numeric source: %s", source);
return false;
}
if (temp > 0xFF || temp < -0x80)
{
errorMsg(line,
error(line, filename(),
"Value %s does not fit in byte\n", source);
return false;
}
@ -1379,7 +1343,7 @@ compileString(StreamBuffer& buffer, const char*& source,
c = codes[i].code;
if (c == skip && formatType != ScanFormat)
{
errorMsg(line,
error(line, filename(),
"Use of '%s' only allowed in input formats\n",
source);
return false;
@ -1396,7 +1360,7 @@ compileString(StreamBuffer& buffer, const char*& source,
}
if (c) continue;
// source may contain a function name
errorMsg(line,
error(line, filename(),
"Unexpected word: %s\n", source);
return false;
}
@ -1432,14 +1396,14 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
buffer.append(format_field);
if (!client)
{
errorMsg(line,
error(line, filename(),
"Using fieldname is not possible in this context\n");
return false;
}
const char* fieldnameEnd = strchr(source+=2, ')');
if (!fieldnameEnd)
{
errorMsg(line,
error(line, filename(),
"Missing ')' after field name\n");
return false;
}
@ -1451,7 +1415,7 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
StreamBuffer fieldAddress;
if (!client->getFieldAddress(buffer(fieldname), fieldAddress))
{
errorMsg(line,
error(line, filename(),
"Field '%s' not found\n", buffer(fieldname));
return false;
}
@ -1465,140 +1429,12 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
buffer.append(format);
}
const char* formatstart = source + 1;
// look for flags
streamFormat.flags = 0;
bool loop = true;
while (loop)
{
switch (*++source)
{
case '-':
streamFormat.flags |= left_flag;
break;
case '+':
streamFormat.flags |= sign_flag;
break;
case ' ':
streamFormat.flags |= space_flag;
break;
case '#':
streamFormat.flags |= alt_flag;
break;
case '0':
streamFormat.flags |= zero_flag;
break;
case '*':
if (formatType != ScanFormat)
{
errorMsg(line,
"Use of skip modifier '*' "
"only allowed in input formats\n");
return false;
}
if (fieldname)
{
errorMsg(line,
"Use of skip modifier '*' not allowed "
"together with field name\n");
return false;
}
streamFormat.flags |= skip_flag;
break;
case '?':
if (formatType != ScanFormat)
{
errorMsg(line,
"Use of default modifier '?' "
"only allowed in input formats\n");
return false;
}
streamFormat.flags |= default_flag;
break;
default:
loop = false;
}
}
/*
if (formatType != PrintFormat &&
streamFormat.flags & (left_flag|sign_flag|space_flag|zero_flag))
{
errorMsg(line,
"Use of format modifiers '-', '+', ' ', '0' "
"only allowed in output formats\n");
return false;
}
if (!(~streamFormat.flags & (left_flag|zero_flag)))
{
errorMsg(line,
"Can't use modifiers '-' and '0' together\n");
return false;
}
if (!(~streamFormat.flags & (space_flag|sign_flag)))
{
errorMsg(line,
"Can't use modifiers ' ' and '+' together\n");
return false;
}
*/
// look for width
unsigned long val;
char* p;
val = strtoul (source, &p, 10);
source = p;
if (val > 0xFFFF)
{
errorMsg(line,
"Field width %ld out of range\n", val);
return false;
}
streamFormat.width = (unsigned short)val;
// look for prec
streamFormat.prec = -1;
if (*source == '.')
{
source++;
val = strtoul(source, &p, 10);
if (p == source)
{
debug("source = %s\n", source);
errorMsg(line,
"Numeric precision field expected after '.'\n");
return false;
}
source = p;
if (val > 0x7FFF)
{
errorMsg(line,
"Precision %ld out of range\n", val);
return false;
}
streamFormat.prec = (short)val;
}
// look for converter
streamFormat.conv = *source++;
debug("StreamProtocolParser::Protocol::compileFormat: converter='%c'\n",
streamFormat.conv);
if (!streamFormat.conv || strchr("'\" (.0+-*", streamFormat.conv))
{
debug("StreamProtocolParser::Protocol::compileFormat: formatstr='%s'\n",
formatstr);
errorMsg(line,
"Missing converter character\n");
return false;
}
StreamFormatConverter* converter;
converter = StreamFormatConverter::find(streamFormat.conv);
if (!converter)
{
errorMsg(line,
"No converter registered for format '%%%c'\n",
streamFormat.conv);
return false;
}
// parse format and get info string
StreamBuffer infoString;
int type = converter->parse(streamFormat, infoString,
source, formatType == ScanFormat);
int type = StreamFormatConverter::parseFormat(source,
formatType, streamFormat, infoString);
if (!type)
{
// parsing failed
@ -1606,18 +1442,25 @@ compileFormat(StreamBuffer& buffer, const char*& formatstr,
}
if (type < long_format && type > pseudo_format)
{
errorMsg(line,
error(line, filename(),
"Illegal format type %d returned from '%%%c' converter\n",
type, streamFormat.conv);
return false;
}
if (type == pseudo_format && fieldname)
{
errorMsg(line,
error(line, filename(),
"Fieldname not allowed with pseudo format: '%%(%s)%c'\n",
buffer(fieldname), streamFormat.conv);
return false;
}
if (fieldname && streamFormat.flags & skip_flag)
{
error(line, filename(),
"Use of skip modifier '*' not allowed "
"together with redirection\n");
return false;
}
streamFormat.type = static_cast<StreamFormatType>(type);
if (infoString && infoString[-1] != eos)
{
@ -1655,13 +1498,13 @@ compileCommands(StreamBuffer& buffer, const char*& source, Client* client)
args = source + strlen(source)+1+sizeof(int);
if (!client->compileCommand(this, buffer, command, args))
{
errorMsg(getLineNumber(source),
error(getLineNumber(source), filename(),
"in command '%s'\n", command);
return false;
}
if (*args)
{
errorMsg(getLineNumber(source),
error(getLineNumber(source), filename(),
"Garbage after '%s' command: '%s'\n",
command, args);
return false;

View File

@ -75,8 +75,6 @@ public:
bool compileString(StreamBuffer& buffer, const char*& source,
FormatType formatType = NoFormat, Client* = NULL, int quoted = false);
bool checkUnused();
void errorMsg(int line, const char* fmt, ...)
__attribute__ ((format(printf,3,4)));
~Protocol();
void report();
};
@ -115,8 +113,6 @@ private:
const char* specialchars = NULL, bool eofAllowed = false);
bool parseAssignment(const char* variable, Protocol&);
bool parseValue(StreamBuffer& buffer, bool lazy = false);
void errorMsg(const char* fmt, ...)
__attribute__ ((format(printf,2,3)));
protected:
~StreamProtocolParser(); // get rid of cygnus-2.7.2 compiler warning

506
src/TimestampConverter.cc Normal file
View File

@ -0,0 +1,506 @@
/***************************************************************
* StreamDevice Support *
* *
* (C) 1999 Dirk Zimoch (zimoch@delta.uni-dortmund.de) *
* (C) 2010 Dirk Zimoch (dirk.zimoch@psi.ch) *
* *
* This is the time stamp converter of StreamDevice. *
* Please refer to the HTML files in ../doc/ for a detailed *
* documentation. *
* *
* If you do any changes in this file, you are not allowed to *
* redistribute it any more. If there is a bug or a missing *
* feature, send me an email and/or your patch. If I accept *
* your changes, they will go to the next release. *
* *
* DISCLAIMER: If this software breaks something or harms *
* someone, it's your problem. *
* *
***************************************************************/
#include "StreamFormatConverter.h"
#include "StreamError.h"
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
class TimestampConverter : public StreamFormatConverter
{
int parse(const StreamFormat&, StreamBuffer&, const char*&, bool);
bool printDouble(const StreamFormat&, StreamBuffer&, double);
int scanDouble(const StreamFormat&, const char*, double&);
};
int TimestampConverter::
parse(const StreamFormat& fmt, StreamBuffer& info,
const char*& source, bool scanFormat)
{
unsigned int n;
char* c;
if (*source == '(')
{
while (*++source != ')')
{
switch (*source)
{
case 0:
error ("missing ')' after %%T format\n");
return false;
case esc:
info.append(*++source);
if (*source == '%') info.append('%');
break;
case '%':
source++;
/* look for formatted fractions like %3f */
if (isdigit(*source))
{
n = strtoul(source, &c, 10);
if (*c == 'f')
{
source = c;
info.printf("%%0%uf", n);
break;
}
}
/* look for nanoseconds %N of %f */
if (*source == 'N' || *source == 'f')
{
info.printf("%%09f");
break;
}
/* look for seconds with fractions like %.3S */
if (*source == '.')
{
c = (char*) source+1;
n = isdigit(*c) ? strtoul(c, &c, 10) : 9;
if (toupper(*c) == 'S')
{
source = c;
info.printf("%%%c.%%0%uf", *c, n);
break;
}
}
/* else normal format */
info.append('%');
default:
info.append(*source);
}
}
source++;
info.append('\0');
}
else
{
info.append("%Y-%m-%d %H:%M:%S %z").append('\0');
}
return double_format;
}
bool TimestampConverter::
printDouble(const StreamFormat& format, StreamBuffer& output, double value)
{
struct tm brokenDownTime;
char buffer [40];
char fracbuffer [15];
int length;
time_t sec;
double frac;
int i, n;
char* c;
char* p;
sec = (time_t) value;
frac = value - sec;
localtime_r(&sec, &brokenDownTime);
debug ("TimestampConverter::printDouble %f, '%s'\n", value, format.info);
length = strftime(buffer, sizeof(buffer), format.info, &brokenDownTime);
i = output.length();
output.append(buffer, length);
/* look for fractional seconds */
while ((i = output.find("%0",i)) != -1)
{
n = strtol(output(i+1), &c, 10);
if (*c++ != 'f') return false;
/* print fractional part */
sprintf(fracbuffer, "%.*f", n, frac);
p = strchr(fracbuffer, '.')+1;
debug("TimestampConverter::printDouble frac: %s\n", p);
debug("TimestampConverter::printDouble ouptput before frac: %s\n", output());
output.replace(i, c-output(i), p);
debug("TimestampConverter::printDouble ouptput after frac: %s\n", output());
}
return true;
}
/* many OS don't have strptime or strptime does not fully support
all fields, e.g. %z.
*/
static int strmatch(const char*& input, const char** strings, int minlen)
{
int i;
int c;
for (i=0; strings[i]; i++) {
for (c=0; ; c++)
{
if (strings[i][c] == 0) {
input += c;
return i;
}
if (tolower(input[c]) != strings[i][c]) {
if (c >= minlen) {
input += c;
return i;
}
break;
}
}
}
return -1;
}
static int nummatch(const char*& input, int min, int max)
{
int i;
char *c;
i = strtol(input, &c, 10);
if (c == input) return -1;
if (i < min || i > max) return -1;
input = c;
return i;
}
static const char* scantime(const char* input, const char* format, struct tm *tm, unsigned long *ns)
{
static const char* months[] = {
"january", "february", "march", "april", "may", "june",
"july", "august", "september", "november", "december", 0 };
static const char* ampm[] = {
"am", "pm", 0 };
int i, n;
int pm = 0;
int century = -1;
int zone = 0;
while (*format)
{
switch (*format)
{
case '%':
debug ("TimestampConverter::scantime: input = '%s'\n", input);
format++;
startover:
switch (*format++)
{
/* Modifiers (ignore) */
case 'E':
case 'O':
goto startover;
/* constants */
case 0: /* stray % at end of format string */
format--;
case '%':
if (*input++ != '%') return NULL;
break;
case 'n':
if (*input++ != '\n') return NULL;
break;
case 't':
if (*input++ != '\t') return NULL;
break;
/* ignored */
case 'A': /* day of week name */
case 'a':
while (isalpha((int)*input)) input++;
/* ignore */
break;
case 'u': /* day of week number (Monday = 1 to Sunday = 7) */
case 'w':
i = nummatch(input, 0, 7);
if (i < 0)
{
error ("error parsing day of week: '%.20s'\n", input);
return NULL;
}
/* ignore */
break;
case 'U': /* week number */
case 'W':
case 'V':
i = nummatch(input, 0, 53);
if (i < 0)
{
error ("error parsing week number: '%.20s'\n", input);
return NULL;
}
/* ignore */
break;
case 'j': /* day of year */
i = nummatch(input, 0, 366);
if (i < 0)
{
error ("error parsing day of year: '%.20s'\n", input);
return NULL;
}
/* ignore */
break;
case 'Z': /* time zone name */
while (isalpha((int)*input)) input++;
/* ignore */
break;
/* date */
case 'b': /* month name */
case 'h':
case 'B':
i = strmatch(input, months, 3);
if (i < 0)
{
error ("error parsing month name: '%.20s'\n", input);
return NULL;
}
tm->tm_mon = i; /* Jan = 0 */
debug ("TimestampConverter::scantime: month = %d\n", tm->tm_mon+1);
break;
case 'm': /* month number */
i = nummatch(input, 1, 12);
if (i < 0)
{
error ("error parsing month number: '%.20s'\n", input);
return NULL;
}
tm->tm_mon = i - 1; /* Jan = 0 */
debug ("TimestampConverter::scantime: month = %d\n", tm->tm_mon+1);
break;
case 'd': /* day of month */
case 'e':
i = nummatch(input, 1, 31);
if (i < 0)
{
error ("error parsing day of month: '%.20s'\n", input);
return NULL;
}
tm->tm_mday = i;
debug ("TimestampConverter::scantime: day = %d\n", tm->tm_mday);
break;
case 'Y': /* 4 digit year */
i = strtol(input, (char**)&input, 10);
tm->tm_year = i - 1900; /* 0 = 1900 */
debug ("TimestampConverter::scantime: year = %d\n", tm->tm_year + 1900);
break;
case 'y': /* 2 digit year */
i = nummatch(input, 0, 99);
if (i < 0)
{
error ("error parsing year\n");
return NULL;
}
if (century == -1) century = (i >= 69);
tm->tm_year = i + century * 100; /* 0 = 1900 */
debug ("TimestampConverter::scantime: year = %d\n", tm->tm_year + 1900);
break;
case 'C': /* century */
i = nummatch(input, 0, 99);
if (i < 0)
{
error ("error parsing century: '%.20s'\n", input);
return NULL;
}
century = i - 19;
tm->tm_year = tm->tm_year%100 + 100 * i; /* 0 = 1900 */
debug ("TimestampConverter::scantime: year = %d\n", tm->tm_year + 1900);
break;
/* time */
case 'H': /* 24 hour clock */
case 'k':
i = nummatch(input, 0, 23);
if (i < 0)
{
error ("error parsing hour: '%.20s'\n", input);
return NULL;
}
tm->tm_hour = i;
debug ("TimestampConverter::scantime: hour = %d\n", tm->tm_hour);
break;
case 'I': /* 12 hour clock */
case 'l':
i = nummatch(input, 1, 12);
if (i < 0)
{
error ("error parsing hour: '%.20s'\n", input);
return NULL;
}
if (i == 12) i = 0;
if ((pm == 1) ^ (i == 0)) i += 12;
tm->tm_hour = i;
debug ("TimestampConverter::scantime: hour = %d\n", tm->tm_hour);
break;
case 'P': /* AM / PM */
case 'p':
i = strmatch(input, ampm, 1);
if (i < 0)
{
error ("error parsing am/pm: '%.20s'\n", input);
return NULL;
}
pm = i;
if ((pm == 1) ^ (tm->tm_hour == 0)) tm->tm_hour += 12;
break;
debug ("TimestampConverter::scantime: hour = %d\n", tm->tm_hour);
case 'M': /* minute */
i = nummatch(input, 1, 59);
if (i < 0)
{
error ("error parsing minute: '%.20s'\n", input);
return NULL;
}
tm->tm_min = i;
debug ("TimestampConverter::scantime: min = %d\n", tm->tm_min);
break;
case 'S': /* second */
i = nummatch(input, 1, 60);
if (i < 0)
{
error ("error parsing week second: '%.20s'\n", input);
return NULL;
}
tm->tm_sec = i;
debug ("TimestampConverter::scantime: sec = %d\n", tm->tm_sec);
break;
case 's': /* second since 1970 */
i = strtol(input, (char**)&input, 10);
tm->tm_sec = i;
tm->tm_mon = -1;
debug ("TimestampConverter::scantime: sec = %d\n", tm->tm_sec);
break;
case '0': /* fractions of seconds like %09f */
n = strtol(format-1, (char**)&format, 10);
if (*format++ != 'f') return NULL;
debug ("max %d digits fraction in '%s'\n", n, input);
i = 0;
while (n-- && isdigit(*input))
{
i *= 10;
i += *input++ - '0';
}
while (i < 100000000) i *= 10;
*ns = i;
debug ("TimestampConverter::scantime: nanosec = %d, rest '%s'\n", i, input);
break;
case 'z': /* time zone offset */
i = nummatch(input, -2400, 2400);
zone = i / 100 * 60 + i % 100;
debug ("TimestampConverter::scantime: zone = %d\n", zone);
break;
case '+': /* set time zone in format string */
case '-':
format--;
i = nummatch(format, -2400, 2400);
zone = i / 100 * 60 + i % 100;
debug ("TimestampConverter::scantime: zone = %d\n", zone);
break;
/* shortcuts */
case 'c':
if (scantime(input, "%a %b %d %H:%M:%S %Z %Y", tm, ns) == NULL)
return NULL;
break;
case 'D':
if (scantime(input, "%m/%d/%y", tm, ns) == NULL)
return NULL;
break;
case 'F':
if (scantime(input, "%Y-%m-%d", tm, ns) == NULL)
return NULL;
break;
case 'r':
if (scantime(input, "%I:%M:%S %p", tm, ns) == NULL)
return NULL;
break;
case 'R':
if (scantime(input, "%H:%M", tm, ns) == NULL)
return NULL;
break;
case 'T':
if (scantime(input, "%H:%M:%S", tm, ns) == NULL)
return NULL;
break;
case 'x':
if (scantime(input, "%a %b %d %Y", tm, ns) == NULL)
return NULL;
break;
case 'X':
if (scantime(input, "%H:%M:%S %Z", tm, ns) == NULL)
return NULL;
break;
default:
error ("unknown time format %%%s\n", --format);
return NULL;
}
break;
case ' ':
format++;
while (isspace(*input)) input++;
break;
default:
if (*format++ != *input++)
{
error("input '%c' does not match constant '%c'\n", *--input, *--format);
return NULL;
}
}
}
tm->tm_min += zone;
tm->tm_hour += tm->tm_min / 60;
tm->tm_min %= 60;
tm->tm_mday += tm->tm_hour / 24;
tm->tm_hour %= 24;
return input;
}
int TimestampConverter::
scanDouble(const StreamFormat& format, const char* input, double& value)
{
struct tm brokenDownTime;
time_t seconds;
unsigned long nanoseconds;
const char* end;
/* Init time stamp with "today" */
time (&seconds);
localtime_r(&seconds, &brokenDownTime);
brokenDownTime.tm_sec = 0;
brokenDownTime.tm_min = 0;
brokenDownTime.tm_hour = 0;
brokenDownTime.tm_yday = 0;
nanoseconds = 0;
end = scantime(input, format.info, &brokenDownTime, &nanoseconds);
if (end == NULL) {
error ("error parsing time\n");
return -1;
}
if (brokenDownTime.tm_mon == -1) {
seconds = brokenDownTime.tm_sec;
} else {
seconds = mktime(&brokenDownTime);
if (seconds == (time_t) -1 && brokenDownTime.tm_yday == 0)
{
error ("mktime failed: %s\n", strerror(errno));
return -1;
}
}
value = seconds + nanoseconds*1e-9;
return end-input;
}
RegisterConverter (TimestampConverter, "T");