windows-nt/Source/XPSP1/NT/sdktools/hdivide/hdivide.cpp
2020-09-26 16:20:57 +08:00

824 lines
21 KiB
C++

//
// 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;
}