diff --git a/sourcehook/generate/shworker/fd_hopter.cpp b/sourcehook/generate/shworker/fd_hopter.cpp index df56e38..7fc149d 100644 --- a/sourcehook/generate/shworker/fd_hopter.cpp +++ b/sourcehook/generate/shworker/fd_hopter.cpp @@ -330,7 +330,7 @@ void PrintVarArgs(FILE *fout, CString bigblock, int num) { fprintf(fout, bigblock); } -int hopter(int numargs, const char *filenamein, const char *filenameout) +int action_hopter(int numargs, const char *filenamein, const char *filenameout) { MaxNumArgs = numargs; diff --git a/sourcehook/generate/shworker/shworker.cpp b/sourcehook/generate/shworker/shworker.cpp index 9f4b264..319c4f6 100644 --- a/sourcehook/generate/shworker/shworker.cpp +++ b/sourcehook/generate/shworker/shworker.cpp @@ -2,30 +2,99 @@ // Inspired by "Hopter" that comes with FastDelegate (http://www.codeproject.com/cpp/FastDelegate.asp) // Much more powerful (and ugly) though -// Action: iter -// @VARARGS@ begins session -// @..@ is replaced by .. if num!=0 and removed if num=0. Any -// occurences of %% are replaced by the current iter -// @$@ is replaced by the current iter -// @ENDARGS@ ends session +/* + + INPUT FILE DIRECTIVES + + $a is the first additional argument, %b the second, ... + + --- + ITERATION + + @[variable,min,max:code|separator@] + + variable: this will be replaced in code by its current value. + vars are always $ and a number. + min: first value to be used for variable + max: last value to be used for variable + code: the code that will be inserted on each iteration. + separator: optional. this will be inserted between iterations. + If you don't use a separator, you may leave out the | + IMPORTANT: iterations will only be performed if max >= min + + --- ARITHMETIC EXPRESSION + + @(expr) + + expr may contain: + variables + constants + operators (currently only + and * are supported) + + --- CONDITION + + @[expr operator expr:code@] + + Example: @[$1!=0:hello@] + + Currently only != and == are supported operators. + + + Yes, error handling in here is weird, some stuff uses return values, other code uses exceptions. +*/ #include #include #include #include -#include +#include +#include +#include #include "stdio.h" #ifdef __linux__ # define stricmp strcasecmp #endif +// Ensure that the template version is being used! +#ifdef min +#undef min +#endif + using namespace std; -extern int hopter(int numargs, const char *filenamein, const char *filenameout); +extern int action_hopter(int numargs, const char *filenamein, const char *filenameout); +typedef map varmap; -void TrimString(std::string &str) +struct MyError +{ + const char *m_desc; + + MyError(const char *desc) : m_desc(desc) + { + } + + void Print() + { + cout << m_desc << endl; + } +}; + +struct SyntaxError : MyError +{ + SyntaxError() : MyError("Syntax error in expression") + { + } +}; +struct OtherError : MyError +{ + OtherError() : MyError("WTF") + { + } +}; + +void trim_string(std::string &str) { size_t first = str.find_first_not_of(" \t\v\n\r"); if (first == std::string::npos) @@ -49,9 +118,10 @@ void TrimString(std::string &str) str = str.substr(first, last - first); } +// unused bool ExtractToken(std::string &strin, std::string &strout) { - TrimString(strin); + trim_string(strin); if (strin.begin() == strin.end()) { strout.clear(); @@ -94,28 +164,540 @@ int DoReplace(string &str, const string &what, const string &with) return cnt; } -int main(int argc, char *argv[]) +int DoReplace(string &str, const char *what, const char *with) { - if (argc < 5) + int cnt=0; + size_t where = str.find(what); + size_t whatsize = strlen(what); + while (where != string::npos) { - cout << "Usage:" << endl << " shworker [iter/hopter] ..." << endl; - cout << " shworker iter [num-of-args] filename.in filename.out" << endl; - cout << " shworker hopter [num-of-args] filename.in filename.our" << endl; - return 1; + str.replace(where, whatsize, with); + ++cnt; + where = str.find(what, where); + } + return cnt; +} + + +class ExprParser +{ + // grammar: + /* + expr -> expr + term { do_add } + | expr - term { do_sub } + | term + + term -> term * factor { do_mul } + | term / factor { do_div } + | term % factor { do_mod } + + factor -> (expr) + | numeric constant { push } + + + equivalent to: + + expr -> term moreterms + moreterms -> + term { do_add } moreterms + moreterms -> - term { do_sub } moreterms + moreterms -> epsilon + + term -> factor morefactors + morefactors -> * factor { do_mul } morefactors + morefactors -> / factor { do_div } morefactors + morefactors -> % factor { do_mod } morefactors + morefactors -> epsilon + + factor -> (expr) + factor -> numeric constant { push } + + */ + + string::const_iterator m_begin; + string::const_iterator m_end; + string::const_iterator m_iter; + + int m_lookahead; + int m_tokenval; + + stack m_stack; + static const int DONE = 256; + static const int NUM = 257; + + int lexan() + { + while (1) + { + if (m_iter == m_end) + return DONE; + + int t = *m_iter++; + + if (t == ' ' || t == '\t') + ; // Remove whitespace + else if (isdigit(t)) + { + --m_iter; + + m_tokenval = 0; + while (m_iter != m_end && isdigit(*m_iter)) + { + m_tokenval *= 10; + m_tokenval += *m_iter - '0'; + ++m_iter; + } + return NUM; + } + else + return t; + } } - const char *action = argv[1]; - int argsnum = atoi(argv[2]); - const char *filenamein = argv[3]; - const char *filenameout = argv[4]; - - if (stricmp(action, "hopter") == 0) + void match(int t) { - return hopter(argsnum, filenamein, filenameout); + if (m_lookahead == t) + m_lookahead = lexan(); + else + throw SyntaxError(); + } + void factor() + { + switch (m_lookahead) + { + case '(': + match('('); expr(); match(')'); + break; + case NUM: + m_stack.push(m_tokenval); match(NUM); + break; + default: + throw SyntaxError(); + } + } + void term() + { + factor(); + while (1) + { + switch (m_lookahead) + { + case '*': + match('*'); factor(); do_mul(); + continue; + case '/': + match('/'); factor(); do_div(); + continue; + case '%': + match('%'); factor(); do_mod(); + continue; + default: + return; + } + + } } + void expr() + { + term(); + while (1) + { + switch (m_lookahead) + { + case '+': + match('+'); term(); do_add(); + continue; + case '-': + match('-'); term(); do_sub(); + continue; + default: + return; + } + } + } + + void do_add() + { + int a2 = m_stack.top(); m_stack.pop(); + int a1 = m_stack.top(); m_stack.pop(); + m_stack.push(a1 + a2); + } + + void do_sub() + { + int a2 = m_stack.top(); m_stack.pop(); + int a1 = m_stack.top(); m_stack.pop(); + m_stack.push(a1 - a2); + } + + void do_mul() + { + int a2 = m_stack.top(); m_stack.pop(); + int a1 = m_stack.top(); m_stack.pop(); + m_stack.push(a1 * a2); + } + + void do_div() + { + int a2 = m_stack.top(); m_stack.pop(); + int a1 = m_stack.top(); m_stack.pop(); + m_stack.push(a1 / a2); + } + + void do_mod() + { + int a2 = m_stack.top(); m_stack.pop(); + int a1 = m_stack.top(); m_stack.pop(); + m_stack.push(a1 % a2); + } + +public: + ExprParser(string::const_iterator begin, string::const_iterator end) : + m_begin(begin), m_end(end), m_iter(begin) + { + m_lookahead = lexan(); + expr(); + } + + operator int() + { + if (m_stack.size() != 1) + throw OtherError(); + + return m_stack.top(); + } +}; + +int parse_expr(string::const_iterator begin, string::const_iterator end) +{ + return ExprParser(begin, end); +} + +size_t find_first_directive(const string &buf, size_t begin=0) +{ + for (;;) + { + if (begin >= buf.size()) + return string::npos; + + size_t firstdirpos = buf.find('@', begin); + if (firstdirpos == string::npos) + return firstdirpos; + + if (buf.size() > firstdirpos+1) + { + if (buf[firstdirpos+1] == '[' || buf[firstdirpos+1] == '(') + return firstdirpos; + } + begin = firstdirpos+1; + } +} + +// buf begins with a section. Find its end! +size_t find_section_end(const string &buf) +{ + int starttype = buf[1]; + int endtype = (buf[1] == '(') ? ')' : ']'; + + int nestlevel = 0; + + if (starttype == '(') + { + for (string::const_iterator iter = buf.begin(); iter != buf.end(); ++iter) + { + if (*iter == starttype) + ++nestlevel; + if (*iter == endtype) + { + if (--nestlevel == 0) + return iter - buf.begin() + 1; + } + } + return string::npos; + } + else if (starttype == '[') + { + int lastchar = 0; + for (string::const_iterator iter = buf.begin(); iter != buf.end(); ++iter) + { + if (lastchar == '@' && *iter == starttype) + ++nestlevel; + if (lastchar == '@' && *iter == endtype) + { + if (--nestlevel == 0) + return iter - buf.begin() + 1; + } + lastchar = *iter; + } + return string::npos; + } + + return string::npos; +} + +// replaces variables and additional arguments +void replace_vars(string &buf, int argc, int *argv, const varmap &vars) +{ + char varname[] = "$ "; + char value[32]; + + for (int i = 0; i < argc; ++i) + { + varname[1] = 'a' + i; + itoa(argv[i], value, 10); + DoReplace(buf, varname, value); + } + + for (varmap::const_iterator iter = vars.begin(); iter != vars.end(); ++iter) + { + varname[1] = '0' + iter->first; + itoa(iter->second, value, 10); + DoReplace(buf, varname, value); + } +} + +// do_input +// params: +// argc: number of additional arguments +// argv: additional arguments +// outfile: output file +// buf: string to be processed. IMPORTANT: buf is modified! +// curvars: variables buffer. +// retval: +// 0 on success, non-zero on error + +int do_input(int argc, int *argv, ofstream &outfile, string &buf, varmap &curvars) +{ + for (;;) + { + // Find the next directive. + size_t firstdirpos = find_first_directive(buf); + + // Output everything that came before, and remove it from buf + outfile << buf.substr(0, firstdirpos); + if (firstdirpos == string::npos) + return 0; + buf = buf.substr(firstdirpos); + + // Now find the matching end. + size_t sectionend = find_section_end(buf); + if (sectionend == string::npos) + { + cout << "ERROR: Section not closed!" << endl; + return 1; + } + + // Place the section in its own buffer and remove it from the input string. + string sect(buf.begin(), buf.begin() + sectionend); + buf = buf.substr(sectionend); + + // CASE1: Arithmetic expression + if (sect[1] == '(') + { + replace_vars(sect, argc, argv, curvars); + outfile << parse_expr(sect.begin()+1, sect.end()); + } + else if (sect[1] == '[') + { + int is_iter = 0; // 0 -> no; 1 -> maybe (only used in check); 2 -> yes + char lastchar = 0; + // This could be an iteration OR a conditional thing. + // Pretty braindead check: iterations begin with a variable, then a comma. + for (string::iterator iter = sect.begin() + 2; iter != sect.end(); ++iter) + { + if (*iter == ' ' || *iter == '\t') + ; + else if (is_iter == 0 && lastchar == '$' && isdigit(*iter)) + is_iter = 1; + else if (is_iter == 1 && *iter == ',') + { + is_iter = 2; + break; + } + else if (*iter == '$') + ; + else + break; + lastchar = *iter; + } + if (is_iter == 2) + { + // CASE2: iteration + // Looks like: @[var,min,max:code|sep@] + // Replace known variables / additional arguments + replace_vars(sect, argc, argv, curvars); + + // get the parts! + string varname; + int varnum; + int expr_min; + int expr_max; + + // varname + size_t comma = sect.find(','); + if (comma == string::npos) + { + cout << "Invalid iteration syntax" << endl; + return 1; + } + varname.assign(sect.begin() + 2, sect.begin() + comma); + trim_string(varname); + if (varname.size() != 2 || varname[0] != '$' || !isdigit(varname[1])) + { + cout << "Invalid variable name" << endl; + return 1; + } + varnum = varname[1] - '0'; + + // min + ++comma; + size_t nextcomma = sect.find(',', comma); + if (nextcomma == string::npos) + { + cout << "Invalid iteration syntax" << endl; + return 1; + } + expr_min = parse_expr(sect.begin() + comma, sect.begin() + nextcomma); + + // max + comma = nextcomma + 1; + nextcomma = sect.find(':', comma); + if (nextcomma == string::npos) + { + cout << "Invalid iteration syntax" << endl; + return 1; + } + + expr_max = parse_expr(sect.begin() + comma, sect.begin() + nextcomma); + + // separator + size_t sepbegin = sect.find('|'); + size_t sepend = string::npos; + if (sepbegin != string::npos && sepbegin < nextcomma) + { + // There's a separator! + ++sepbegin; + sepend = nextcomma; + } + else + sepbegin = string::npos; + + + ++nextcomma; // nextcomma is now where code begins! + + size_t codeend = sect.size() - 2; + + // Check whether the var is already taken + if (curvars.find(varnum) != curvars.end()) + { + cout << "Variable $" << varnum << "already taken!" << endl; + return 1; + } + + // Do iterations!! + for (int i = expr_min; i <= expr_max; ++i) + { + curvars[varnum] = i; + + string code(sect.begin() + nextcomma, sect.begin() + codeend); + replace_vars(code, argc, argv, curvars); + + // Feed it through the input routine (RECURSE!!!!!! YEAH!) + do_input(argc, argv, outfile, code, curvars); + + // Add separator if required + if (sepbegin != string::npos && i != expr_max) + { + do_input(argc, argv, outfile, + string(sect.begin() + sepbegin, sect.begin() + sepend), curvars); + } + } + // Remove the var! + curvars.erase(varnum); + } + else + { + // CASE3: conditional thing. + // Looks like: @[expr1 operator expr2:code@] + + // Find the operator position + + enum OP_TYPE + { + OP_EQ, + OP_NEQ + }; + + OP_TYPE op; + size_t oppos = sect.find("=="); + if (oppos != string::npos) + op = OP_EQ; + else + { + oppos = sect.find("!="); + if (oppos != string::npos) + op = OP_NEQ; + else + { + cout << "Conditional expression without operator!?" << endl; + return 1; + } + } + size_t colon = sect.find(':'); + + // Now we've got everything. Parse first expr: + int expr1 = parse_expr(sect.begin() + 2, sect.begin() + oppos); + int expr2 = parse_expr(sect.begin() + oppos + 2, sect.begin() + colon); + if ((op == OP_EQ && expr1 == expr2) || + (op == OP_NEQ && expr1 != expr2)) + { + // Condition is true, process it! + // The text may still contain arithmetic exprs or other cond. exprs + // so send it through do_input + do_input(argc, argv, outfile, sect.substr(colon+1, sect.size() - colon - 3), curvars); + } + } + } + else + { + cout << "WTF" << endl; + return 1; + } + } + + return 0; +} + + + +// action_iter +// params: +// filenamein: input file name +// filenameout: output file name +// argc: number of additional arguments +// argv: additional arguments +// retval: 0 on success, non-zero on error + +// Convert additional arguments +// Read whole input file to memory and open output file +// Pass to do_input() +int action_iter(const char *filenamein, const char *filenameout, int argc, const char *argv[]) +{ + // Convert additional arguments + const int MAX_ARGC = 10; + int converted_argv[MAX_ARGC]; + + for (int i = 0; i < argc && i < MAX_ARGC; ++i) + converted_argv[i] = atoi(argv[i]); + + if (argc != i) + cout << "WARNING: Not all additional arguments processed!" << endl; + + + // Read whole input file to memory and open output file ifstream fin(filenamein); ofstream fout(filenameout); + if (!fin) { cout << "Could not open file \"" << filenamein << "\"." << endl; @@ -126,122 +708,58 @@ int main(int argc, char *argv[]) cout << "Could not open file \"" << filenameout << "\"." << endl; return 1; } + string input_str( + istreambuf_iterator (fin.rdbuf()), + istreambuf_iterator ()); - if (stricmp(action, "iter") == 0) + + // Begin processing input + varmap vars; + try { - // Generate versions of func macros for many parameters - string str; - string replacebuf; - while (!fin.eof()) - { - getline(fin, str); - if (str == "@VARARGS@") - { - replacebuf.clear(); - - while (true) - { - getline(fin, str); - if (str == "@ENDARGS@") - break; - - replacebuf += str; - replacebuf += "\n"; - if (fin.eof()) - { - cout << "No matching @ENDARGS@ !" << endl; - return 1; - } - } - - for (int i = 0; i <= argsnum; ++i) - { - string cur = replacebuf; - size_t where = cur.find('@'); - - while (where != string::npos) - { - size_t where2 = cur.find('@', where+1); - if (where2 == string::npos) - { - cout << "No ending @"; - return 1; - } - string tmp = cur.substr(where + 1, where2 - where - 1); - cur.erase(where, where2 - where + 1); - - if (tmp.size() != 0 && tmp[0] == '$') - { - int tmp_out = i; - if (tmp.size() > 2) - { - if (tmp[1] == '+') - { - istringstream istr(tmp.substr(2)); - int tmp; - istr >> tmp; - tmp_out += tmp; - } - else if (tmp[1] == '*') - { - istringstream istr(tmp.substr(2)); - int tmp; - istr >> tmp; - tmp_out *= tmp; - } - } - stringstream istr; - istr << tmp_out; - cur.insert(where, istr.str()); - where = cur.find('@', where); - continue; - } - - // Find the |.. part, if any - string tmpsep; - size_t tmpsepbegin = tmp.rfind('|'); - if (tmpsepbegin != string::npos) - { - tmpsep = tmp.substr(tmpsepbegin + 1); - tmp.erase(tmpsepbegin); - } - - size_t pos = where; - if (tmp.find("%%") == string::npos) - { - if (i > 0) - { - cur.insert(pos, tmp); - pos += tmp.size(); - } - } - else - { - for (int j = 1; j <= i; ++j) - { - stringstream istr; - istr << j; - string what = tmp; - DoReplace(what, string("%%"), istr.str()); - cur.insert(pos, what); - pos += what.size(); - if (j != i) - { - cur.insert(pos, tmpsep); - pos += tmpsep.size(); - } - } - } - - where = cur.find('@', where); - } - fout << cur; - } - } - else - { - fout << str << endl; - } - } + return do_input(argc, converted_argv, fout, input_str, vars); + } + catch (MyError err) + { + err.Print(); + return 1; + } +} + +// MAIN +// Prints usage if required +// Calls action_hopter OR action_iter +int main(int argc, const char **argv) +{ + if (argc < 4) + { + cout << "Usage:" << endl << " shworker [iter/hopter] ..." << endl; + cout << " shworker iter filename.in filename.out [param1, param2, ...]" << endl; + cout << " shworker hopter filename.in filename.out [num-of-args]" << endl; + return 1; + } + + const char *action = argv[1]; + + if (stricmp(action, "hopter") == 0) + { + const char *filenamein = argv[2]; + const char *filenameout = argv[3]; + int argsnum = atoi(argv[4]); + + return action_hopter(argsnum, filenamein, filenameout); + } + else if (stricmp(action, "iter") == 0) + { + const char *filenamein = argv[2]; + const char *filenameout = argv[3]; + int additional_argc = argc - 4; + const char ** additional_argv = argv + 4; + return action_iter(filenamein, filenameout, additional_argc, additional_argv); + } + else + { + cout << "Unrecognized action: " << argv[1] << endl; + return 1; } } diff --git a/sourcehook/generate/shworker/shworker.vcproj b/sourcehook/generate/shworker/shworker.vcproj index 0a9be86..cd2138a 100644 --- a/sourcehook/generate/shworker/shworker.vcproj +++ b/sourcehook/generate/shworker/shworker.vcproj @@ -1,7 +1,7 @@ - + @@ -49,8 +49,14 @@ Name="VCResourceCompilerTool"/> + + + + + + + + + RelativePath=".\shworker.cpp">