latest features added and bugs fixed:
compare %= timestamp converter %T error and debug messages
This commit is contained in:
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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 {
|
||||
|
@ -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'
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
506
src/TimestampConverter.cc
Normal 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");
|
Reference in New Issue
Block a user