From 35768cbfbbdc3e6fe5396ac468936d9a3bfac2a8 Mon Sep 17 00:00:00 2001 From: zimoch Date: Thu, 2 Sep 2010 14:50:33 +0000 Subject: [PATCH] latest features added and bugs fixed: compare %= timestamp converter %T error and debug messages --- src/CONFIG_STREAM | 1 + src/StreamCore.cc | 96 +++++-- src/StreamEpics.cc | 43 ++- src/StreamError.cc | 16 +- src/StreamError.h | 12 +- src/StreamFormat.h | 3 +- src/StreamFormatConverter.cc | 123 ++++++++- src/StreamFormatConverter.h | 2 + src/StreamProtocol.cc | 317 ++++++---------------- src/StreamProtocol.h | 4 - src/TimestampConverter.cc | 506 +++++++++++++++++++++++++++++++++++ 11 files changed, 859 insertions(+), 264 deletions(-) create mode 100644 src/TimestampConverter.cc diff --git a/src/CONFIG_STREAM b/src/CONFIG_STREAM index 7b6ba7c..f5dd06f 100644 --- a/src/CONFIG_STREAM +++ b/src/CONFIG_STREAM @@ -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 diff --git a/src/StreamCore.cc b/src/StreamCore.cc index a0f0ba6..17bc0bd 100644 --- a/src/StreamCore.cc +++ b/src/StreamCore.cc @@ -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 addrlen AddressStructure formatstring StreamFormat [info] @@ -663,6 +664,7 @@ formatOutput() } formatstringlen = commandIndex-formatstring; commandIndex++; + StreamFormat fmt = extract(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 addrlen AddressStructure formatstring StreamFormat [info] + fieldName = commandIndex; commandIndex += strlen(commandIndex)+1; unsigned short addrlen = extract(commandIndex); fieldAddress.set(commandIndex, addrlen); @@ -1130,11 +1138,23 @@ matchInput() // code layout: // formatstring StreamFormat [info] formatstring = commandIndex; - while (*commandIndex++ != StreamProtocolParser::eos); // jump after + // jump after + while (*commandIndex) + { + if (*commandIndex == esc) commandIndex++; + commandIndex++; + } + formatstringlen = commandIndex-formatstring; + commandIndex++; StreamFormat fmt = extract(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) diff --git a/src/StreamEpics.cc b/src/StreamEpics.cc index 53a66ff..2e3c677 100644 --- a/src/StreamEpics.cc +++ b/src/StreamEpics.cc @@ -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 diff --git a/src/StreamError.cc b/src/StreamError.cc index d325854..e24ff15 100644 --- a/src/StreamError.cc +++ b/src/StreamError.cc @@ -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"); } diff --git a/src/StreamError.h b/src/StreamError.h index d36ad53..52ddda7 100644 --- a/src/StreamError.h +++ b/src/StreamError.h @@ -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 { diff --git a/src/StreamFormat.h b/src/StreamFormat.h index cac9ff1..f144a77 100644 --- a/src/StreamFormat.h +++ b/src/StreamFormat.h @@ -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 { diff --git a/src/StreamFormatConverter.cc b/src/StreamFormatConverter.cc index d71c7fc..a151bfb 100644 --- a/src/StreamFormatConverter.cc +++ b/src/StreamFormatConverter.cc @@ -19,8 +19,8 @@ * * ***************************************************************/ +#include #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' diff --git a/src/StreamFormatConverter.h b/src/StreamFormatConverter.h index 3bc03bb..d1b2ced 100644 --- a/src/StreamFormatConverter.h +++ b/src/StreamFormatConverter.h @@ -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); diff --git a/src/StreamProtocol.cc b/src/StreamProtocol.cc index 446a51e..088b0fe 100644 --- a/src/StreamProtocol.cc +++ b/src/StreamProtocol.cc @@ -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(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; diff --git a/src/StreamProtocol.h b/src/StreamProtocol.h index f5d6934..aed3692 100644 --- a/src/StreamProtocol.h +++ b/src/StreamProtocol.h @@ -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 diff --git a/src/TimestampConverter.cc b/src/TimestampConverter.cc new file mode 100644 index 0000000..ea5205f --- /dev/null +++ b/src/TimestampConverter.cc @@ -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 +#include +#include +#include + +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");