// // hdivide.cpp -- yet another header file divider // // 1998 Nov Hiro Yamamoto // #pragma warning(disable: 4786) #include #include #include #include #include #include #include #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 m_insertions; bool m_insertion_finished; vector 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 { 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 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; }