#include <stdexcept>
#include <sstream>
#include <stdarg.h>
#include "UserInterface.h"
#include "StkStream.h"
#include "Features.h"
namespace TNet
{
//***************************************************************************
//***************************************************************************
int
npercents(const char *str)
{
int ret = 0;
while (*str) if (*str++ == '%') ret++;
return ret;
}
//***************************************************************************
//***************************************************************************
void
UserInterface::
ReadConfig(const char *file_name)
{
std::string line_buf;
std::string::iterator chptr;
std::string key;
std::string value;
std::ostringstream ss;
int line_no = 0;
IStkStream i_stream;
i_stream.open(file_name, std::ios::binary);
if (!i_stream.good()) {
throw std::runtime_error(std::string("Cannot open input config file ")
+ file_name);
}
i_stream >> std::ws;
while (!i_stream.eof()) {
size_t i_pos;
// read line
std::getline(i_stream, line_buf);
i_stream >> std::ws;
if (i_stream.fail()) {
throw std::runtime_error(std::string("Error reading (")
+ file_name + ":" + (ss << line_no,ss).str() + ")");
}
// increase line counter
line_no++;
// cut comments
if (std::string::npos != (i_pos = line_buf.find('#'))) {
line_buf.erase(i_pos);
}
// cut leading and trailing spaces
Trim(line_buf);
// if empty line, then skip it
if (0 == line_buf.length()) {
continue;
}
// line = line_buf.c_str();
// chptr = parptr;
chptr = line_buf.begin();
for (;;) {
// Replace speces by '_', which is removed in InsertConfigParam
while (isalnum(*chptr) || *chptr == '_' || *chptr == '-') {
chptr++;
}
while (std::isspace(*chptr)) {
*chptr = '_';
chptr++;
}
if (*chptr != ':') {
break;
}
chptr++;
while (std::isspace(*chptr)) {
*chptr = '_';
chptr++;
}
}
if (*chptr != '=') {
throw std::runtime_error(std::string("Character '=' expected (")
+ file_name + ":" + (ss.str(""),ss<<line_no,ss).str() + ")");
}
key.assign(line_buf.begin(), chptr);
chptr++;
value.assign(chptr, line_buf.end());
ParseHTKString(value, value);
InsertConfigParam(key.c_str(), value.c_str(), 'C');
}
i_stream.close();
}
//***************************************************************************
//***************************************************************************
void
UserInterface::
InsertConfigParam(const char *pParamName, const char *value, int optionChar)
{
std::string key(pParamName);
std::string::iterator i_key = key.begin();
while (i_key != key.end()) {
if (*i_key == '-' || *i_key == '_') {
i_key = key.erase(i_key);
}
else {
*i_key = toupper(*i_key);
i_key ++;
}
}
mMap[key].mValue = value;
mMap[key].mRead = false;
mMap[key].mOption = optionChar;
}
//***************************************************************************
//***************************************************************************
int
UserInterface::
ParseOptions(
int argc,
char* argv[],
const char* pOptionMapping,
const char* pToolName)
{
int i;
int opt = '?';
int optind;
bool option_must_follow = false;
char param[1024];
char* value;
const char* optfmt;
const char* optarg;
char* chptr;
char* bptr;
char tstr[4] = " -?";
unsigned long long option_mask = 0;
std::ostringstream ss;
#define MARK_OPTION(ch) {if (isalpha(ch)) option_mask |= 1ULL << ((ch) - 'A');}
#define OPTION_MARK(ch) (isalpha(ch) && ((1ULL << ((ch) - 'A')) & option_mask))
#define IS_OPTION(str) ((str)[0] == '-' && (isalpha((str)[1]) || (str)[1] == '-'))
//search for the -A param
for (optind = 1; optind < argc; optind++) {
// we found "--", no -A
if (!strcmp(argv[optind], "--")) {
break;
}
//repeat till we find -A
if (argv[optind][0] != '-' || argv[optind][1] != 'A') {
continue;
}
// just "-A" form
if (argv[optind][2] != '\0') {
throw std::runtime_error(std::string("Unexpected argument '")
+ (argv[optind] + 2) + "' after option '-A'");
}
for (i=0; i < argc; i++) {
// display all params
if(strchr(argv[i], ' ') || strchr(argv[i], '*'))
std::cout << '\'' << argv[i] << '\'' << " ";
else std::cout << argv[i] << " ";
}
std::cout << std::endl;
break;
}
for (optind = 1; optind < argc; optind++) {
// find the '-C?' parameter (possible two configs)
if (!strcmp(argv[optind], "--")) break;
if (argv[optind][0] != '-' || argv[optind][1] != 'C') continue;
if (argv[optind][2] != '\0') {
ReadConfig(argv[optind] + 2);
} else if (optind+1 < argc && !IS_OPTION(argv[optind+1])) {
ReadConfig(argv[++optind]);
} else {
throw std::runtime_error("Config file name expected after option '-C'");
}
}
for (optind = 1; optind < argc; optind++) {
if (!strcmp(argv[optind], "--")) break;
if (argv[optind][0] != '-' || argv[optind][1] != '-') continue;
bptr = new char[strlen(pToolName) + strlen(argv[optind]+2) + 2];
strcat(strcat(strcpy(bptr, pToolName), ":"), argv[optind]+2);
value = strchr(bptr, '=');
if (!value) {
throw std::runtime_error(std::string("Character '=' expected after option '")
+ argv[optind] + "'");
}
*value++ = '\0';
InsertConfigParam(bptr, value /*? value : "TRUE"*/, '-');
delete [] bptr;
}
for (optind = 1; optind < argc && IS_OPTION(argv[optind]); optind++) {
option_must_follow = false;
tstr[2] = opt = argv[optind][1];
optarg = argv[optind][2] != '\0' ? argv[optind] + 2 : NULL;
if (opt == '-' && !optarg) { // '--' terminates the option list
return optind+1;
}
if (opt == 'C' || opt == '-') { // C, A and long options have been already processed
if (!optarg) optind++;
continue;
}
if (opt == 'A') continue;
chptr = strstr((char*)pOptionMapping, tstr);
if (chptr == NULL) {
throw std::runtime_error(std::string("Invalid command line option '-")
+ static_cast<char>(opt) + "'");
}
chptr += 3;
while (std::isspace(*chptr)) {
chptr++;
}
if (!chptr || chptr[0] == '-') {// Option without format string will be ignored
optfmt = " ";
} else {
optfmt = chptr;
while (*chptr && !std::isspace(*chptr)) {
chptr++;
}
if (!*chptr) {
throw std::runtime_error("Fatal: Unexpected end of optionMap string");
}
}
for (i = 0; !std::isspace(*optfmt); optfmt++) {
while (std::isspace(*chptr)) chptr++;
value = chptr;
while (*chptr && !std::isspace(*chptr)) chptr++;
assert(static_cast<unsigned int>(chptr-value+1) < sizeof(param));
strncat(strcat(strcpy(param, pToolName), ":"), value, chptr-value);
param[chptr-value+strlen(pToolName)+1] = '\0';
switch (*optfmt) {
case 'n':
value = strchr(param, '=');
if (value) *value = '\0';
InsertConfigParam(param,
value ? value + 1: "TRUE", opt);
break;
case 'l':
case 'o':
case 'r':
i++;
if (!optarg && (optind+1==argc || IS_OPTION(argv[optind+1]))) {
if (*optfmt == 'r' || *optfmt == 'l') {
throw std::runtime_error(std::string("Argument ")
+ (ss<<i,ss).str() + " of option '-"
+ static_cast<char>(opt) + "' expected");
}
optfmt = " "; // Stop reading option arguments
break;
}
if (!optarg) optarg = argv[++optind];
if (*optfmt == 'o') {
option_must_follow = (bool) 1;
}
bptr = NULL;
// For repeated use of option with 'l' (list) format, append
// ',' and argument string to existing config parameter value.
if (*optfmt == 'l' && OPTION_MARK(opt)) {
bptr = strdup(GetStr(param, ""));
if (bptr == NULL) throw std::runtime_error("Insufficient memory");
bptr = (char*) realloc(bptr, strlen(bptr) + strlen(optarg) + 2);
if (bptr == NULL) throw std::runtime_error("Insufficient memory");
strcat(strcat(bptr, ","), optarg);
optarg = bptr;
}
MARK_OPTION(opt);
InsertConfigParam(param, optarg, opt);
free(bptr);
optarg = NULL;
break;
default :
throw std::runtime_error(std::string("Fatal: Invalid character '")
+ *optfmt + "' in optionMap after " + tstr);
}
}
if (optarg) {
throw std::runtime_error(std::string("Unexpected argument '")
+ optarg + "' after option '-"
+ static_cast<char>(opt) + "'");
}
}
for (i = optind; i < argc && !IS_OPTION(argv[i]); i++)
{}
if (i < argc) {
throw std::runtime_error(std::string("No option expected after first non-option argument '")
+ argv[optind] + "'");
}
if (option_must_follow) {
throw std::runtime_error(std::string("Option '-")
+ static_cast<char>(opt)
+ "' with optional argument must not be the last option");
}
return optind;
}
//***************************************************************************
//***************************************************************************
int
UserInterface::
GetFeatureParams(
int * derivOrder,
int ** derivWinLens,
int * startFrmExt,
int * endFrmExt,
char ** CMNPath,
char ** CMNFile,
const char ** CMNMask,
char ** CVNPath,
char ** CVNFile,
const char ** CVNMask,
const char ** CVGFile,
const char * pToolName,
int pseudoModeule)
{
const char * str;
int targetKind;
char * chrptr;
char paramName[32];
const char * CMNDir;
const char * CVNDir;
strcpy(paramName, pToolName);
strcat(paramName, pseudoModeule == 1 ? "SPARM1:" :
pseudoModeule == 2 ? "SPARM2:" : "");
chrptr = paramName + strlen(paramName);
strcpy(chrptr, "STARTFRMEXT");
*startFrmExt = GetInt(paramName, 0);
strcpy(chrptr, "ENDFRMEXT");
*endFrmExt = GetInt(paramName, 0);
*CMNPath = *CVNPath = NULL;
strcpy(chrptr, "CMEANDIR");
CMNDir = GetStr(paramName, NULL);
strcpy(chrptr, "CMEANMASK");
*CMNMask = GetStr(paramName, NULL);
if (*CMNMask != NULL) {
*CMNPath = (char*) malloc((CMNDir ? strlen(CMNDir) : 0) + npercents(*CMNMask) + 2);
if (*CMNPath == NULL) throw std::runtime_error("Insufficient memory");
if (CMNDir != NULL) strcat(strcpy(*CMNPath, CMNDir), "/");
*CMNFile = *CMNPath + strlen(*CMNPath);
}
strcpy(chrptr, "VARSCALEDIR");
CVNDir = GetStr(paramName, NULL);
strcpy(chrptr, "VARSCALEMASK");
*CVNMask = GetStr(paramName, NULL);
if (*CVNMask != NULL) {
*CVNPath = (char*) malloc((CVNDir ? strlen(CVNDir) : 0) + npercents(*CVNMask) + 2);
if (*CVNPath == NULL) throw std::runtime_error("Insufficient memory");
if (CVNDir != NULL) strcat(strcpy(*CVNPath, CVNDir), "/");
*CVNFile = *CVNPath + strlen(*CVNPath);
}
strcpy(chrptr, "VARSCALEFN");
*CVGFile = GetStr(paramName, NULL);
strcpy(chrptr, "TARGETKIND");
str = GetStr(paramName, "ANON");
targetKind = FeatureRepository::ReadParmKind(str, false);
if (targetKind == -1) {
throw std::runtime_error(std::string("Invalid TARGETKIND = '")
+ str + "'");
}
strcpy(chrptr, "DERIVWINDOWS");
if ((str = GetStr(paramName, NULL)) != NULL) {
long lval;
*derivOrder = 0;
*derivWinLens = NULL;
if (NULL != str)
{
while ((str = strtok((char *) str, " \t_")) != NULL)
{
lval = strtol(str, &chrptr, 0);
if (!*str || *chrptr) {
throw std::runtime_error("Integers separated by '_' expected for parameter DERIVWINDOWS");
}
*derivWinLens = (int *)realloc(*derivWinLens, ++*derivOrder*sizeof(int));
if (*derivWinLens == NULL) throw std::runtime_error("Insufficient memory");
(*derivWinLens)[*derivOrder-1] = lval;
str = NULL;
}
}
return targetKind;
}
*derivOrder = targetKind & PARAMKIND_T ? 3 :
targetKind & PARAMKIND_A ? 2 :
targetKind & PARAMKIND_D ? 1 : 0;
if (*derivOrder || targetKind != PARAMKIND_ANON) {
*derivWinLens = (int *) malloc(3 * sizeof(int));
if (*derivWinLens == NULL) throw std::runtime_error("Insufficient memory");
strcpy(chrptr, "DELTAWINDOW");
(*derivWinLens)[0] = GetInt(paramName, 2);
strcpy(chrptr, "ACCWINDOW");
(*derivWinLens)[1] = GetInt(paramName, 2);
strcpy(chrptr, "THIRDWINDOW");
(*derivWinLens)[2] = GetInt(paramName, 2);
return targetKind;
}
*derivWinLens = NULL;
*derivOrder = -1;
return targetKind;
}
//***************************************************************************
//***************************************************************************
UserInterface::ValueRecord*
UserInterface::
GetParam(const char* pParamName)
{
MapType::iterator it;
// this is done only for convenience. in the loop we will increase the
// pointer again
pParamName--;
// we iteratively try to find the param name in the map. if an attempt
// fails, we strip off all characters until the first ':' and we search
// again
do {
pParamName++;
it = mMap.find(pParamName);
} while ((it == mMap.end()) && (NULL != (pParamName = strchr(pParamName, ':'))));
if (it == mMap.end()) {
return NULL;
}
else {
it->second.mRead = true;
return &(it->second);
}
}
//***************************************************************************
//***************************************************************************
const char *
UserInterface::
GetStr(
const char * pParamName,
const char * default_value)
{
ValueRecord* p_val = GetParam(pParamName);
if (NULL == p_val) {
return default_value;
}
else {
return p_val->mValue.c_str();
}
}
//***************************************************************************
//***************************************************************************
long
UserInterface::
GetInt(
const char *pParamName,
long default_value)
{
char *chrptr;
ValueRecord* p_val = GetParam(pParamName);
if (NULL == p_val) {
return default_value;
}
const char *val = p_val->mValue.c_str();
default_value = strtol(val, &chrptr, 0);
if (!*val || *chrptr) {
throw std::runtime_error(std::string("Integer number expected for ")
+ pParamName + " but found '" + val + "'");
}
return default_value;
}
//***************************************************************************
//***************************************************************************
float
UserInterface::
GetFlt(
const char * pParamName,
float default_value)
{
char *chrptr;
ValueRecord* p_val = GetParam(pParamName);
if (NULL == p_val) {
return default_value;
}
const char *val = p_val->mValue.c_str();
default_value = strtod(val, &chrptr);
if (!*val || *chrptr) {
throw std::runtime_error(std::string("Decimal number expected for ")
+ pParamName + " but found '" + val + "'");
}
return default_value;
}
//***************************************************************************
//***************************************************************************
bool
UserInterface::
GetBool(
const char * pParamName,
bool default_value)
{
ValueRecord* p_val = GetParam(pParamName);
if (NULL == p_val) {
return default_value;
}
const char* val = p_val->mValue.c_str();
if (!strcasecmp(val, "TRUE") || !strcmp(val, "T")) return 1;
if (strcasecmp(val, "FALSE") && strcmp(val, "F")) {
throw std::runtime_error(std::string("TRUE or FALSE expected for ")
+ pParamName + " but found '" + val + "'");
}
return false;
}
//***************************************************************************
//***************************************************************************
// '...' are pairs: string and corresponding integer value , terminated by NULL
int
UserInterface::
GetEnum(
const char * pParamName,
int default_value,
...)
{
ValueRecord* p_val = GetParam(pParamName);
if (NULL == p_val) {
return default_value;
}
const char* val = p_val->mValue.c_str();
char* s;
int i = 0, cnt = 0, l = 0;
va_list ap;
va_start(ap, default_value);
while ((s = va_arg(ap, char *)) != NULL) {
l += strlen(s) + 2;
++cnt;
i = va_arg(ap, int);
if (!strcmp(val, s)) break;
}
va_end(ap);
if (s) {
return i;
}
//To report error, create string listing all possible values
s = (char*) malloc(l + 1);
s[0] = '\0';
va_start(ap, default_value);
for (i = 0; i < cnt; i++) {
strcat(s, va_arg(ap, char *));
va_arg(ap, int);
if (i < cnt - 2) strcat(s, ", ");
else if (i == cnt - 2) strcat(s, " or ");
}
va_end(ap);
throw std::runtime_error(std::string(s) + " expected for "
+ pParamName + " but found '" + val + "'");
return 0;
}
//***************************************************************************
//***************************************************************************
void
UserInterface::
PrintConfig(std::ostream& rStream)
{
rStream << "Configuration Parameters[" << mMap.size() << "]\n";
for (MapType::iterator it = mMap.begin(); it != mMap.end(); ++it) {
rStream << (it->second.mRead ? " " : "# ")
<< std::setw(35) << std::left << it->first << " = "
<< std::setw(30) << std::left << it->second.mValue
<< " # -" << it->second.mOption << std::endl;
}
}
//***************************************************************************
//***************************************************************************
void
UserInterface::
CheckCommandLineParamUse()
{
for (MapType::iterator it = mMap.begin(); it != mMap.end(); ++it) {
if (!it->second.mRead && it->second.mOption != 'C') {
Error("Unexpected command line parameter " + it->first);
}
}
}
}