#include <cstdio>
#include <exception>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <locale>
#include <map>
#include <regex>
#include <string>
#include <tuple>
#include <vector>
using namespace std;
typedef vector<string> arguments;
const string version_file_name = ".versionrc";
const string strmajor = "major";
const string strminor = "minor";
const string strdebug = "debug";
const string strbuild = "build";
map<string, long> variables;
bool req_quiet = false;
bool req_init = false;
namespace calc {
long assign();
long pre();
long expression();
long term();
long factor();
string::iterator first, last;
locale l("C");
void skipws() {
while(first != last) {
if(!isspace(*first, l)) break;
first++;
}
}
long main(string src) {
first = begin(src);
last = end(src);
skipws();
return assign();
}
void nextch() {
first++;
skipws();
}
bool is_variable_name() {
return first != last && isalpha(*first, l);
}
string get_variable_name() {
string dst;
while(first != last && isalpha(*first, l)) dst.push_back(*(first++));
skipws();
return dst;
}
string peek_variable_name() {
string::iterator backtrack = first;
string dst = get_variable_name();
first = backtrack;
return dst;
}
long get_variable(string name) {
return variables[name];
}
void set_variable(string name, long value) {
variables[name] = value;
}
string peek_two_char() {
string dst;
dst.push_back(first != last ? *first : ' ');
dst.push_back(first != last && next(first, 1) != last ? *(next(first, 1)) : ' ');
return dst;
}
long number() {
if(is_variable_name()) {
string variable_name = get_variable_name();
long dst = get_variable(variable_name);
if(peek_two_char() == "++") {
first = next(first, 2);
skipws();
set_variable(variable_name, dst + 1);
} else if(peek_two_char() == "--") {
first = next(first, 2);
skipws();
set_variable(variable_name, dst - 1);
}
return dst;
} else {
char sign = first != last ? *first : '\0';
if(sign == '+' || sign == '-') return first++, sign == '-' ? -number() : number();
size_t pos;
string src(first, last);
long dst;
try {
dst = stol(src, &pos);
} catch(invalid_argument &) {
throw exception("invalid expression");
}
first = next(first, pos);
skipws();
return dst;
}
}
long assign() {
if(is_variable_name()) {
string::iterator backtrack = first;
string variable_name = get_variable_name();
if(*first == '=') {
nextch();
long dst = assign();
set_variable(variable_name, dst);
return dst;
} else {
string op = peek_two_char();
if(op == "+=") {
first = next(first, 2);
skipws();
long dst = assign();
set_variable(variable_name, get_variable(variable_name) + dst);
return dst;
} else if(op == "-=") {
first = next(first, 2);
skipws();
long dst = assign();
set_variable(variable_name, get_variable(variable_name) - dst);
return dst;
} else if(op == "*=") {
first = next(first, 2);
skipws();
long dst = assign();
set_variable(variable_name, get_variable(variable_name) * dst);
return dst;
} else if(op == "/=") {
first = next(first, 2);
skipws();
long dst = assign();
set_variable(variable_name, get_variable(variable_name) / dst);
return dst;
} else if(op == "%=") {
first = next(first, 2);
skipws();
long dst = assign();
set_variable(variable_name, get_variable(variable_name) % dst);
return dst;
} else {
first = backtrack;
return pre();
}
}
} else {
return pre();
}
}
long pre() {
if(peek_two_char() == "++") {
first = next(first, 2);
skipws();
if(!is_variable_name()) throw exception("can't pre-increment");
string variable_name = peek_variable_name();
set_variable(variable_name, get_variable(variable_name) + 1);
} else if(peek_two_char() == "--") {
first = next(first, 2);
skipws();
if(!is_variable_name()) throw exception("can't pre-decrement");
string variable_name = peek_variable_name();
set_variable(variable_name, get_variable(variable_name) - 1);
}
return expression();
}
long expression() {
long dst = term();
while(true) {
if(*first == '+') nextch(), dst += term();
else if(*first == '-') nextch(), dst -= term();
else break;
}
return dst;
}
long term() {
long dst = factor();
while(true) {
if(*first == '*') nextch(), dst *= factor();
else if(*first == '/') nextch(), dst /= factor();
else if(*first == '%') nextch(), dst %= factor();
else break;
}
return dst;
}
long factor() {
if(*first == '(') {
nextch();
long dst = assign();
if(*first != ')') throw exception("missing ')'");
nextch();
return dst;
} else {
return number();
}
}
}
namespace command {
struct entry {
string name;
arguments::const_iterator(*function)(arguments::const_iterator, arguments::const_iterator);
};
const entry * search(string name);
template<typename T> T command_internal_error(T first, T last) {
throw exception((*first + "is not a command").c_str());
}
template<typename T> T command_evaluation(T first, T last) {
calc::main(*first);
return ++first;
}
template<typename T> T command_help(T first, T last) {
cout <<
"usage: version [-h] [-q] [-i] expression\n"
"\n"
" format: major.minor.debug.build\n"
" example:\n"
" version build++\n"
" version debug=0 minor+=1\n"
" version major=major+1\n"
"\n"
" options:\n"
" -h this message\n"
" -q quiet mode\n"
" -i initialize .versionrc file\n"
" -C print C language header\n"
" -N print nmake.exe format\n"
<< ends;
req_quiet = true;
return ++first;
}
template<typename T> T command_quiet(T first, T last) {
req_quiet = true;
return ++first;
}
template<typename T> T command_init(T first, T last) {
req_init = true;
return ++first;
}
template<typename T> T command_clang(T first, T last) {
cout <<
"#pragma once\n" <<
"#define VERSION_MAJOR " << variables[strmajor] << endl <<
"#define VERSION_MINOR " << variables[strminor] << endl <<
"#define VERSION_DEBUG " << variables[strdebug] << endl <<
"#define VERSION_BUILD " << variables[strbuild] << endl <<
ends;
req_quiet = true;
return ++first;
}
template<typename T> T command_nmake(T first, T last) {
cout <<
"VERSION_MAJOR = " << variables[strmajor] << endl <<
"VERSION_MINOR = " << variables[strminor] << endl <<
"VERSION_DEBUG = " << variables[strdebug] << endl <<
"VERSION_BUILD = " << variables[strbuild] << endl <<
ends;
req_quiet = true;
return ++first;
}
const entry table[] = {
{"internal_error", command_internal_error},
{"evaluation", command_evaluation},
{"-h", command_help},
{"-q", command_quiet},
{"-i", command_init},
{"-C", command_clang},
{"-N", command_nmake},
};
const auto size = sizeof(table) / sizeof(*table);
const entry * begin = table;
const entry * end = table + size;
const entry * search(string name) {
auto I = find_if(command::begin, command::end, [name](const command::entry & I) -> bool {return I.name == name;});
return I != command::end ? I : search("internal_error");
}
const entry * search_and_eval(string name) {
auto I = find_if(command::begin, command::end, [name](const command::entry & I) -> bool {return I.name == name;});
return I != command::end ? I : search("evaluation");
}
}
tuple<long, long, long, long> read_version_txt(string filename) {
ifstream in(filename);
if(!in) throw exception("file not found");
string version;
getline(in, version);
if(!in) throw exception("file read error");
smatch matches;
regex_search(version, matches, regex("([+-]?\\d+)\\.([+-]?\\d+)\\.([+-]?\\d+)\\.([+-]?\\d+)"));
if(matches.size() != 5) throw exception((string("invalid format in ") + filename).c_str());
return make_tuple(stol(matches[1].str()), stol(matches[2].str()), stol(matches[3].str()), stol(matches[4].str()));
}
void write_version_txt(string filename, long major, long minor, long debug, long build) {
string tempname = filename + ".temp";
ofstream out(tempname);
out << major << "." << minor << "." << debug << "." << build << endl;
out.close();
if(!out) throw exception("file write error");
if(remove(filename.c_str()) != 0) throw exception((filename + " can't remove").c_str());
if(rename(tempname.c_str(), filename.c_str()) != 0) throw exception((tempname + " can't rename").c_str());
}
void print_version(long major, long minor, long debug, long build) {
cout << major << "." << minor << "." << debug << "." << build << endl;
}
int main(int argc, char * argv[]) {
try {
arguments args(&argv[1], &argv[argc]);
long major, minor, debug, build;
tie(major, minor, debug, build) = read_version_txt(version_file_name);
if(args.empty()) return print_version(major, minor, debug, build), EXIT_SUCCESS;
variables[strmajor] = major;
variables[strminor] = minor;
variables[strdebug] = debug;
variables[strbuild] = build;
arguments::const_iterator first = begin(args), last = end(args);
while(first != last) first = (*(command::search_and_eval(*first)->function))(first, last);
if(req_init) return write_version_txt(version_file_name, 0, 0, 0, 0), EXIT_SUCCESS;
major = variables[strmajor];
minor = variables[strminor];
debug = variables[strdebug];
build = variables[strbuild];
write_version_txt(version_file_name, major, minor, debug, build);
if(!req_quiet) print_version(major, minor, debug, build);
return EXIT_SUCCESS;
} catch(const exception & x) {
cout << x.what() << endl;
}
return EXIT_FAILURE;
}
// (c) 2012 Strictsoft