|
|
//
// hdivide.cpp -- yet another header file divider
//
// 1998 Nov Hiro Yamamoto
//
#pragma warning(disable: 4786)
#include <cstdio>
#include <string>
#include <cstdarg>
#include <map>
#include <vector>
#include <cassert>
#include <io.h>
#define PROGNAME "hdivide"
#define VERSION "1.0"
extern "C" { extern int getopt(int argc, char** argv, const char* opts); extern int optind; }
namespace opt { bool verbose; }
namespace input { unsigned long length; int lineno = 1; std::string path;
std::string strip(const std::string& fname) { std::string stripped;
//
// find the "path" part
//
int n = fname.rfind('\\'); if (n < 0) { n = fname.rfind('/'); }
if (n < 0 && (n = fname.rfind(':')) < 0) { n = 0; } else { ++n; }
// store the path
path = fname.substr(0, n); // retrive the filename portion
stripped = fname.substr(n, fname.length());
return stripped; } }
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#endif
namespace id { const char all[] = "all"; const char begin[] = "begin"; const char end[] = "end"; const char else_[] = "else"; const int begin_size = ARRAY_SIZE(begin) - 1; const int end_size = ARRAY_SIZE(end) - 1; const int else_size = ARRAY_SIZE(else_) - 1;
const char internal[] = "internal"; const char public_[] = "public"; const char null[] = "null"; std::string privatefile; std::string publicfile;
const char insert[] = "insert"; const int insert_size = ARRAY_SIZE(insert) - 1; const char reference_start[] = "reference_start"; const char reference_end[] = "reference_end"; }
#define MYFAILURE_OPENFILE (120)
#define MYFAILURE_INVALID_FORMAT (121)
using namespace std;
//////////////////////////////////////////////////////////////////////////
// usage
//////////////////////////////////////////////////////////////////////////
void usage() { fputs(PROGNAME ": version " VERSION "\n", stderr); fputs("usage: hdivide [-v] input-filename (no path name please)\n", stderr); }
//////////////////////////////////////////////////////////////////////////
// misc. helpers
//////////////////////////////////////////////////////////////////////////
inline void makeupper(string& str) { for (int i = 0; i < str.length(); ++i) { str[i] = (char)toupper(str[i]); } }
inline void makelower(string& str) { for (int i = 0; i < str.length(); ++i) { str[i] = (char)tolower(str[i]); } }
namespace msg { void __cdecl error(const char* fmt, ...) { va_list args;
fputs(PROGNAME ": [error] ", stderr); va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); putc('\n', stderr); }
void __cdecl verbose(const char* fmt, ...) { if (!opt::verbose) return;
va_list args; fputs(PROGNAME ": ", stderr); va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); putc('\n', stderr); } }
//////////////////////////////////////////////////////////////////////////
// class Output
//////////////////////////////////////////////////////////////////////////
class Output;
class Insertion { public: // somehow the default constructor is required for std::vector
// on NT5 build environment, as of Nov 1998
explicit Insertion() : m_insert(NULL), m_insertion_point(-1) { } explicit Insertion(Output* insert, int point) : m_insert(insert), m_insertion_point(point) { } public: Output* m_insert; int m_insertion_point; };
class Reference { public: // somehow the default constructor is required for std::vector
// on NT5 build environment, as of Nov 1998
Reference() : m_start(-1), m_end(-1) { } explicit Reference(int start, int end) : m_start(start), m_end(end) { } public: int m_start; int m_end; };
class Output { public: explicit Output(const string& name) : m_name(name), m_fname(input::path + name + ".x"), m_alive(true), m_insertion_finished(false), m_reference_start(-1) { msg::verbose("opening %s", m_fname.c_str()); if ((m_fp = fopen(m_fname.c_str(), "wt")) == NULL) { msg::error("cannot open file %s", m_fname.c_str()); throw MYFAILURE_OPENFILE; }
if (m_tomem) { m_buffer.reserve(input::length); } } virtual ~Output();
public: void setalive(bool alive) { m_alive = alive; } bool getalive() { return m_alive; } const string& getname() { return m_name; } void put(int c) { assert(m_fp); if (m_alive) { if (m_tomem) { m_buffer += (char)c; } else { putc(c, m_fp); } } } void puts(const char* s) { assert(m_fp); if (m_alive) { if (m_tomem) { m_buffer += s; } else { fputs(s, m_fp); } } } bool operator<(const Output* a) { return m_name < a->m_name; }
void set_insertion_point(Output* insert); void set_reference_start(); void set_reference_end();
bool do_insertion();
protected: FILE* m_fp; bool m_alive; static bool m_tomem;
string m_name; string m_fname;
string m_buffer;
vector<Insertion> m_insertions;
bool m_insertion_finished;
vector<Reference> m_references;
int m_reference_start; int m_reference_start_line; };
bool Output::m_tomem = true;
Output::~Output() { if (m_reference_start != -1) { msg::error("reference started at line %d is not closed in tag '%s'", m_reference_start_line, m_name.c_str()); throw MYFAILURE_INVALID_FORMAT; } if (!m_buffer.empty()) { msg::verbose("flushing %s", m_fname.c_str()); fputs(m_buffer.c_str(), m_fp); } if (m_fp) { fclose(m_fp); } }
void Output::set_insertion_point(Output* insert) { assert(insert!= NULL); if (m_alive) { Insertion i(insert, m_buffer.length()); m_insertions.push_back(i); } }
void Output::set_reference_start() { if (m_alive) { if (m_reference_start != -1) { msg::error("line %d: invalid reference_start appeared in tag context '%s'", input::lineno, m_name.c_str()); throw MYFAILURE_INVALID_FORMAT; } m_reference_start = m_buffer.length(); m_reference_start_line = input::lineno; } }
void Output::set_reference_end() { if (m_alive) { if (m_reference_start == -1) { msg::error("line %d: invalid reference_end appeared in tag context '%s'", input::lineno, m_name.c_str()); throw MYFAILURE_INVALID_FORMAT; } Reference ref(m_reference_start, m_buffer.length()); msg::verbose("%s reference_end: %d - %d", m_name.c_str(), ref.m_start, ref.m_end); m_reference_start = -1; m_references.push_back(ref); } }
bool Output::do_insertion() { if (!m_tomem || m_insertion_finished) return true;
// to avoid infinite recursion by errornous commands,
// firstly declare we've finished this.
m_insertion_finished = true;
int upto = m_insertions.size(); for (int i = 0; i < upto; ++i) { Insertion& ins = m_insertions[i]; assert(&ins); if (ins.m_insert->m_references.size() == 0) { msg::error("reference area is not specified or incorrect for tag '%s'", ins.m_insert->m_name.c_str()); return false; }
if (!ins.m_insert->m_insertion_finished) { if (!ins.m_insert->do_insertion()) return false; }
Output* o = ins.m_insert; for (int l = 0; l < o->m_references.size(); ++l) { Reference& ref = o->m_references[l]; int len = ref.m_end - ref.m_start; msg::verbose("%s [%d] inserting text at %d, %s(%d - %d)", m_name.c_str(), l, ins.m_insertion_point, o->m_name.c_str(), ref.m_start, ref.m_start + len); m_buffer.insert(ins.m_insertion_point, o->m_buffer, ref.m_start, len); // fixup my insertions
int point = ins.m_insertion_point; for (int k = 0; k < m_insertions.size(); ++k) { if (m_insertions[k].m_insertion_point >= point) { m_insertions[k].m_insertion_point += len; msg::verbose("%s [%d] insertion point fixed from %d to %d", m_name.c_str(), k, m_insertions[k].m_insertion_point - len, m_insertions[k].m_insertion_point); } } // fixup my references
for (k = 0; k < m_references.size(); ++k) { msg::verbose("%s m_reference[%d].m_start=%d, m_end=%d adding len=%d", m_name.c_str(), k, m_references[k].m_start, m_references[k].m_end, len); if (m_references[k].m_start > point) { m_references[k].m_start += len; } if (m_references[k].m_end > point) { m_references[k].m_end += len; msg::verbose("finally start=%d, end=%d", m_references[k].m_start, m_references[k].m_end); } } } }
return true; }
//////////////////////////////////////////////////////////////////////////
// class Divider
//
// this class manages the map of Output and performs misc. operations
//////////////////////////////////////////////////////////////////////////
class Divider : public map<string, Output*> { public:
virtual ~Divider() { // process insertions
for (iterator i = begin(); i != end(); ++i) { if (!i->second->do_insertion()) break; } // clear up
for (i = begin(); i != end(); ++i) { delete i->second; } }
//////////////////////////////////////////////////////////////////////////
// printout
//
// printout the argument to outputs
//////////////////////////////////////////////////////////////////////////
void printout(int c) { for (iterator i = begin(); i != end(); ++i) { i->second->put(c); } }
void printout(const char* s) { for (iterator i = begin(); i != end(); ++i) { i->second->puts(s); } }
void process_line(string& line);
protected: void extract_version(const string& name, string& symbol, string& version, bool allow_omission = false); void get_arg(const string& name, string& arg); void prepare_section(string& name); void process_divider(string& line);
void set_alive(bool alive) { for (iterator i = begin(); i != end(); ++i) { i->second->setalive(alive); } }
typedef map<string, bool> OutputState;
void push_state(OutputState& state) { state.clear(); for (iterator i = begin(); i != end(); ++i) { state[i->second->getname()] = i->second->getalive(); } }
void pop_state(OutputState& state) { for (OutputState::iterator i = state.begin(); i != state.end(); ++i) { assert((*this)[i->first] != NULL); (*this)[i->first]->setalive(i->second); } }
protected: string m_last_symbol; string m_last_version; };
void Divider::prepare_section(string& name) { // make it lower case
makelower(name);
if (name == id::internal) { name = id::privatefile; } else if (name == id::public_) { name = id::publicfile; }
if (name != id::null && (*this)[name] == NULL) { (*this)[name] = new Output(name); } }
//////////////////////////////////////////////////////////////////////////
// Divider::extract_version
//
// extracts version symbol and supported version
//
// "begin_symbol_version" is splited to symbol and version.
// Both are stored in upper case.
//////////////////////////////////////////////////////////////////////////
void Divider::extract_version(const string& name, string& symbol, string& version, bool allow_omission /*= false*/) { int nsymbol = name.find('_'); int nver = name.rfind('_'); if (nsymbol == -1 || nver == nsymbol) { if (allow_omission) { symbol = m_last_symbol; version = m_last_version; return; } else { msg::error("line %d: invalid version specifier '%s'", input::lineno, name.c_str()); throw MYFAILURE_INVALID_FORMAT; } } // symbol
symbol = name.substr(nsymbol + 1, nver - nsymbol - 1); // upper case
makeupper(symbol); version = "0000" + name.substr(nver + 1, name.length()); version = version.substr(version.length() - 4, 4); makeupper(version);
m_last_symbol = symbol; m_last_version = version; }
//////////////////////////////////////////////////////////////////////////
// Divider::get_arg
//
// extracts one argument separated by "_"
//////////////////////////////////////////////////////////////////////////
void Divider::get_arg(const string& name, string& arg) { int npos = name.find('_'); if (npos == -1) { msg::error("line %d: command incompleted in '%s'", input::lineno, name.c_str()); throw MYFAILURE_INVALID_FORMAT; }
arg = name.substr(npos + 1, name.length()); }
//////////////////////////////////////////////////////////////////////////
// Divider::process_divider
//
// processes the divider instructions
//////////////////////////////////////////////////////////////////////////
void Divider::process_divider(string& line) { const char* p = line.begin(); ++p;
bool makelive = true; if (*p == '!') { makelive = false; ++p; }
// skip the heading spaces
while (isspace(*p)) ++p;
for (int col = 0; p != line.end(); ++col) { // pickup the name
string name; while (*p != ';' && p != line.end()) { if (!isspace(*p)) { name += *p; } ++p; } if (p != line.end()) { ++p; }
// first column may have special meaning
if (col == 0) { if (name == id::all) { set_alive(makelive); // does "!all" make sense ?
// however i'm supporting it anyway
break; } if (name == id::null) { set_alive(!makelive); break; } if (name.substr(0, id::insert_size) == id::insert) { string insert; get_arg(name, insert); prepare_section(insert); if (insert == id::null || insert == id::all) { msg::error("line %d: invalid insertion of '%s'", input::lineno, insert.c_str()); throw MYFAILURE_INVALID_FORMAT; } assert((*this)[insert] != NULL); for (iterator i = begin(); i != end(); ++i) { (*this)[i->first]->set_insertion_point((*this)[insert]); } break; } if (name == id::reference_start) { for (iterator i = begin(); i != end(); ++i) { (*this)[i->first]->set_reference_start(); } break; } if (name == id::reference_end) { for (iterator i = begin(); i != end(); ++i) { (*this)[i->first]->set_reference_end(); } break; }
if (name.substr(0, id::begin_size) == id::begin) { string symbol; string version; extract_version(name, symbol, version); printout("#if ("); printout(symbol.c_str()); printout(" >= 0x"); printout(version.c_str()); printout(")\n"); break; } if (name.substr(0, id::else_size) == id::else_) { printout("#else\n"); break; } if (name.substr(0, id::end_size) == id::end) { string symbol; string version; extract_version(name, symbol, version, true); printout("#endif /* "); printout(symbol.c_str()); printout(" >= 0x"); printout(version.c_str()); printout(" */\n"); break; }
// setup the initial state
set_alive(!makelive); } prepare_section(name); (*this)[name]->setalive(makelive); } }
//////////////////////////////////////////////////////////////////////////
// Divider::process_line
//
// handles one line
//////////////////////////////////////////////////////////////////////////
void Divider::process_line(string& line) { if (line[0] == ';') { process_divider(line); } else { // check if inline section appears
bool instr = false; const char* p = line.begin(); const char* section = NULL; while (p != line.end()) { if (*p == '\\' && (p + 1) != line.end()) { // skip escape character
// note: no consideration for Shift JIS
++p; } else if (*p == '"' || *p == '\'') { // beginning of end of literal
instr = !instr; } else if (*p == '@' && !instr) { // we have inline section
section = p; break; } ++p; }
if (section) { //
// if inline tag is specified, temporarily change
// the output
//
OutputState state; push_state(state); assert(*p == '@'); ++p; if (*p == '+') { ++p; } else { set_alive(false); } while (p != line.end()) { string name; while (*p != ';' && p != line.end()) { if (!isspace(*p)) { name += *p; } ++p; } if (p != line.end()) ++p; if (name == id::all) { set_alive(true); break; } if (name == id::null) { set_alive(false); break; } prepare_section(name); (*this)[name]->setalive(true); } // trim trailing spaces
int i = section - line.begin() - 1; while (i >= 0 && isspace(line[i])) { --i; } line = line.substr(0, i + 1); printout(line.c_str()); printout('\n'); pop_state(state); } else { printout(line.c_str()); printout('\n'); } } ++input::lineno; }
//////////////////////////////////////////////////////////////////////////
// hdivide
//////////////////////////////////////////////////////////////////////////
void hdivide(FILE* fp) { Divider divider;
divider[id::publicfile] = new Output(id::publicfile); divider[id::privatefile] = new Output(id::privatefile);
string line; int c;
while ((c = getc(fp)) != EOF) { if (c == '\n') { divider.process_line(line); line = ""; } else { line += (char)c; } } if (!line.empty()) divider.process_line(line); }
//////////////////////////////////////////////////////////////////////////
// main
//////////////////////////////////////////////////////////////////////////
int __cdecl main(int argc, char** argv) { int c;
while ((c = getopt(argc, argv, "v")) != EOF) { switch (c) { case 'v': opt::verbose = true; break; default: usage(); return EXIT_FAILURE; } }
if (optind == argc) { usage(); return EXIT_FAILURE; }
msg::verbose("input file: %s", argv[optind]);
FILE* fp = fopen(argv[optind], "rt"); if (fp == NULL) { msg::error("cannot open input file %s", argv[optind]); return EXIT_FAILURE; }
input::length = _filelength(_fileno(fp));
id::publicfile = argv[optind]; id::publicfile = input::strip(id::publicfile.substr(0, id::publicfile.length() - 2)); id::privatefile = id::publicfile + "p";
int exitcode = EXIT_SUCCESS;
try { hdivide(fp); } catch (int err) { exitcode = EXIT_FAILURE; switch (err) { case MYFAILURE_OPENFILE: break; case MYFAILURE_INVALID_FORMAT: msg::error("fatal: invalid format"); break; } } catch (...) { exitcode = EXIT_FAILURE; }
fclose(fp);
return exitcode; }
|