//-----------------------------------------------------------------------------
// (C) 2008 Sebastian Mach
//
[email protected]
//
http://greenhybrid.net ,
http://picogen.org
// --
// contribution for the language-shootout in the german
// 'Linux Magazin', issue 10/2008
// --
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// Please see
http://www.gnu.org/licenses/gpl.txt for the license text or
// contact the author.
//
// -> contact author for use not covered by GPLv3
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// miscellaneous
//-----------------------------------------------------------------------------
// The reason why we use #warning in the following is that #message
// is not portable.
#define TRUE_EQUALS_1 (true==1)
#if TRUE_EQUALS_1
#warning Hooray, can use some optimizations based on the assumption that true==1
#else
#warning Meh, can not use some optimizations based on the assumption that true==1
#endif
// A safer way than #ifdef/#define combos.
enum optimization_flags {
of_true_is_1 = true == 1, // can we rely on that true==1?
of_nums_contigous = // can we rely that '9'-'0'==10 and that numbers go low->high?
'1'-'0' == 1
&& '2'-'0' == 2
&& '3'-'0' == 3
&& '4'-'0' == 4
&& '5'-'0' == 5
&& '6'-'0' == 6
&& '7'-'0' == 7
&& '8'-'0' == 8
&& '9'-'0' == 9
};
//-----------------------------------------------------------------------------
// include files
//-----------------------------------------------------------------------------
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <cstring>
//-----------------------------------------------------------------------------
// convenience types/functions
//-----------------------------------------------------------------------------
// Can also be used to check whether c is in ['0'..'9']
// (e.g. "bool isNum = toInt(c) != -1;").
template <typename C> int toInt (C c) {
// note that the C++ standard does not mandate
// that the number-chars 0-9 are continous when
// interpeted as integers, thus this function
// does the portable way.
// --> trust in constant folding
// --> check if '0'-'9' are continous when cast to integer
if (of_nums_contigous) {
static int ret [2] = {-1, -1};
ret [1] = c - '0';
return ret [c>('0'-1) & c<('9'+1)];
} else {
switch (c) { // not much slower if the compiler emits jump tables
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
};
return -1;
}
}
// Compiler should do a good job with this convenience class.
// Note that the case that A==B is not covered.
template <typename A, typename B> class tuple {
const A a_;
const B b_;
public:
tuple (A a, B b) : a_(a), b_(b) {}
const A& a () const { return a_; }
const B& b () const { return b_; }
operator A () const { return a_; }
operator B () const { return b_; }
};
//-----------------------------------------------------------------------------
// A custom implementation of an array class. Allocates large chunks of memory
// instead of allocating each single element.
// Note: This is an micro-optimization, if any.
//-----------------------------------------------------------------------------
template <typename PT, int chunksize=4096> struct growing_array {
// -- data/cache ---------------------------------------------------
PT mem [chunksize];
mutable int cachedmajor, cachedminor; // cachedminor only used in fast_*()
mutable const growing_array *cachedchunk;
// -- alloc/dealloc-vars -------------------------------------------
growing_array *curr;
growing_array *next;
int nextfree;
int size_;
// -- members ------------------------------------------------------
growing_array ()
: cachedmajor (0), cachedchunk (this), curr (this), next (0), nextfree(0), size_ (0)
{}
~growing_array () {
if (0 != next) {
next->~growing_array<PT, chunksize> ();
}
}
void push_back(const PT &pt) {
using namespace std;
if (curr->nextfree >= chunksize) {
curr->next = new growing_array <PT, chunksize> ();
curr = curr->next;
}
curr->mem [curr->nextfree++] = pt;
++size_;
}
const int size () const {
return size_;
}
void fast_begin () {
cachedchunk = this;
cachedmajor = 0;
cachedminor = 0;
}
void fast_end () {
cachedchunk = this;
cachedmajor = 0;
cachedminor = 0;
}
const PT & fast_next () const {
if (cachedminor >= chunksize) {
cachedminor = 0;
cachedchunk = cachedchunk->next;
}
return cachedchunk->mem [cachedminor++];
}
const PT & operator [] (int index) const {
const int major = index / chunksize; // <-- gcc should recognize the div/mod-
const int minor = index % chunksize; // combo and emit only 1 div
const int diff = major - cachedmajor;
// Optimization for the case the new - old = 0 || 1
switch (diff) {
case 1: cachedchunk = cachedchunk->next;
cachedmajor = major; // <-- should be faster than ++cachedmajor on modern cpu
// fallthrough
case 0: return cachedchunk->mem [minor];
};
// I am not sure if the C++ standard mandates that true==1, but the following code depends on that
if (of_true_is_1) {
const bool d = diff >= 0;
growing_array const * const tmpchunk [2] = {this, cachedchunk};
const int tmpmajor [2] = {0, cachedmajor};
cachedchunk = tmpchunk [d];
for (cachedmajor=tmpmajor [d]; cachedmajor!=major; ++cachedmajor)
cachedchunk = cachedchunk->next;
return cachedchunk->mem [minor];
} else {
if (diff < 0) {
cachedchunk = this;
for (cachedmajor=0; cachedmajor!=major; ++cachedmajor)
cachedchunk = cachedchunk->next;
return cachedchunk->mem [minor];
} else {
for (; cachedmajor!=major; ++cachedmajor)
cachedchunk = cachedchunk->next;
return cachedchunk->mem [minor];
}
}
}
};
//-----------------------------------------------------------------------------
// typesafe template arguments
//-----------------------------------------------------------------------------
enum order_t {
order_in_main_section,
order_in_footnotes_section
};
enum safemode_t {
safe,
unsafe
};
//-----------------------------------------------------------------------------
// Reorder is a RAII class which does the reordering.
//
// template arguments:
// typename C --> char type for our strings
// C L --> open bracket for references/footnotes
// C R --> close bracket for references/footnotes
// order_t SO=order_in_main_section --> order_in_main_section or
// order_in_footnotes_section
// safemode_t S=safe --> catch some errors?
// (though "safe" will result in slower code)
// bool use_pool=true --> are we using our custom memory pool?
//-----------------------------------------------------------------------------
template <
typename C, C L, C R,
order_t SO=order_in_main_section,
safemode_t S=safe,
bool use_pool=true
> class Reorder {
private:
template <typename PT> struct mempool {
enum { size = (4096*1024) / sizeof (PT) };
// -- data/cache --------------------------------------------------
PT mem [size];
// -- alloc/dealloc-vars ------------------------------------------
mempool *curr;
mempool *next;
int nextfree;
// -- members -----------------------------------------------------
mempool () : curr (this), next (0), nextfree(0) {}
~mempool () {
if (0 != next) {
next->~mempool<PT> ();
}
}
PT *alloc() {
using namespace std;
if (curr->nextfree >= size) {
curr->next = new mempool <PT> ();
curr = curr->next;
}
return & curr->mem [curr->nextfree++];
// could also work with non-pod-types:
//new (& curr->mem [curr->nextfree++]) PT;
}
};
// that function assumes we're already inside the pair of
// brackets (i.e. after '[')
template <class I> tuple <bool, int> scanRefTarget (I &curr, I end) {
using namespace std;
I old_curr = curr;
int len = 0; // used to check validity of reference
int refID = 0; // the reference-id
while (R != *curr) { // <-- until we hit an end-bracket
if (S == safe) {
if (end == curr) { // <-- rudimentary error checking
cerr << "found end of file but expected numbers or '"
<< R << endl;
exit(1);
}
}
const int d = toInt (*curr);
if (-1 != d) {
refID = refID * 10 + d;
++len;
}
++curr;
}
if (0 != len) {
return tuple <bool, int> (true, refID);
}
curr = old_curr; // <-- was not a reference, so rollback
return tuple <bool, int> (false, -1);
}
template <typename I> struct Chunk { // intentionally POD
I begin, end;
int ref_target;
Chunk <I> *next;
};
template <typename I> struct Reference { // intentionally POD
I begin, end;
int order_in_footnotes;
int order_in_text;
};
// Scans the main sections.
template <class I>
Chunk<I> *scanText (I &curr, I end, mempool <Chunk <I> > &chunk_pool) {
Chunk<I> *chunk;
if (use_pool)
chunk = chunk_pool.alloc();
else
chunk = new Chunk<I>;
Chunk<I> * const first_chunk = chunk;
chunk->begin = curr;
chunk->next = 0;
chunk->ref_target = -1;
while (true) {
if (L == *curr) { // <-- we've hit a start-bracket
++curr; // <-- eat start-bracket
const I old_curr = curr;
const tuple <bool, int> refID = scanRefTarget (curr, end);
if (refID) {
chunk->ref_target = refID;
chunk->end = old_curr-1;
if (use_pool)
chunk->next = chunk_pool.alloc();
else
chunk->next = new Chunk<I>;
chunk = chunk->next;
chunk->next = 0;
chunk->ref_target = -1;
chunk->begin = curr+1;
}
} else if (end == curr) {
chunk->end = curr;
break;
} else if ('\n' == *curr) {
// Here we used the bitor '&' intentionally instead of '&&'
// , the difference being that '&' most probably produces
// branchfree code.
// (see
http://ompf.org/forum/viewtopic.php?p=10086#p10086)
I old_curr = curr;
if ('@' == curr[1]
& 'f' == curr[2]
& 'o' == curr[3]
& 'o' == curr[4]
& 't' == curr[5]
& 'n' == curr[6]
& 'o' == curr[7]
& 't' == curr[8]
& 'e' == curr[9]
& ':' == curr[10]
) {
curr += 11; // += strlen ("@footnote:")
chunk->end = old_curr;
// eat forward to newline
while (curr != end && *curr != '\n')
++curr;
++curr; // eatup newline
break;
} else {
curr = old_curr;
}
}
++curr;
};
return first_chunk;
}
// Scans the footnotes section.
template <
typename I,
class int_array // <-- template template = nonsense,
// since std::vector<A,B>, but growing_array<A>
>
void scanFootnotes (
std::map <int, Reference <I> > &refs,
int_array &orderedRRefs,
I &curr, I end
) {
using namespace std;
int footnodeID = 1;
while (true) {
while (L != *curr && '\n' != *curr && end != curr)
++curr;
if (L == *curr) {
++curr; // <-- eat start-bracket
const I old_curr = curr;
const tuple <bool, int> refID = scanRefTarget (curr, end);
++curr;
if (refID) {
Reference<I> ref;
// eat whitespace
while ('\t' == *curr || ' ' == *curr || '\n' == *curr)
++curr;
ref.begin = curr;
while (*curr != '\n' && curr != end)
++curr;
ref.end = curr;
ref.order_in_footnotes = footnodeID++;
ref.order_in_text = -1;
orderedRRefs.push_back (refID);
refs [refID] = ref;
}
} else if (end == curr) {
break;
}
++curr;
}
}
public:
template <class I> Reorder (I begin, I end) {
using namespace std;
I curr = begin;
// Scan text.
mempool<Chunk<I> > chunk_pool;
Chunk<I> *chunk = scanText<I> (curr, end, chunk_pool);
// Scan footnotes.
map <int, Reference <I> > refs;
typedef growing_array <int,4096*8> int_array;
int_array orderedRRefs;
scanFootnotes <I, int_array> (refs, orderedRRefs, curr, end);
switch (SO) {
case order_in_footnotes_section: {
// Dump Text
for (const Chunk<I> *it = chunk; 0 != it; it = it->next) {
// Output text.
for (I a=it->begin; a != it->end; ++a) {
cout << *a;
}
// Output reference target.
if (-1 != it->ref_target) {
cout << '['
<< refs [it->ref_target].order_in_footnotes
<< ']';
}
}
cout << '\n';
// Dump footnotes.
cout << "@footnote:\n";
orderedRRefs.fast_begin();
const unsigned int s = orderedRRefs.size();
for (int i=0; i<s; ++i) {
const int r = orderedRRefs.fast_next();
cout << '[' << refs [r].order_in_footnotes << ']'
<< ' ';
for (I c=refs [r].begin; c!=refs [r].end; ++c) {
cout << *c;
}
cout << '\n';
}
orderedRRefs.fast_end();
break;
}
case order_in_main_section: {
// Dump Text
int newOrder = 1;
for (const Chunk<I> *it = chunk; 0 != it; it = it->next) {
// Output text.
for (I a=it->begin; a != it->end; ++a) {
cout << *a;
}
// Output reference target.
if (-1 != it->ref_target) {
int &tmp = refs [it->ref_target].order_in_text;
if (tmp < 0)
tmp = newOrder++;
cout << '[' << tmp << ']';
}
}
cout << '\n';
// Dump footnotes.
cout << "@footnote:\n";
orderedRRefs.fast_begin();
const unsigned int s = orderedRRefs.size();
for (int i=0; i<s; ++i) {
const int r = orderedRRefs.fast_next();
cout << '[' << refs [r].order_in_text << ']' << ' ';
for (I c=refs [r].begin; c!=refs [r].end; ++c) {
cout << *c;
}
cout << '\n';
}
orderedRRefs.fast_end();
break;
}
}
while (0 != chunk) {
Chunk <I> *tmp = chunk;
chunk = chunk->next;
// We can save some work by not calling ~Chunk() by assuming
// that Chunk is a POD
if (!use_pool)
delete tmp;
}
}
};
//-----------------------------------------------------------------------------
// main
//-----------------------------------------------------------------------------
int main (int argc, char *argv []) {
using namespace std;
// Parse arguments.
--argc;
++argv;
string filename = "";
order_t orderType = order_in_main_section;
while (0 != argc) {
const string arg (*argv);
--argc;
++argv;
if ("-f" == arg || "--filename" == arg) {
if (0 == argc) {
cerr << "error:no parameter given to argument '" << arg
<< "'\n";
} else {
filename = *argv;
--argc;
++argv;
}
} else if ("-s" == arg || "--sort" == arg) {
if (0 == argc) {
cerr << "error:no parameter given to argument '" << arg
<< "'\n";
cerr << "use either '" << arg << " main' or '" << arg
<< "' footnotes\n";
} else {
const string tmp = *argv;
if ("main" == tmp) {
orderType = order_in_main_section;
} else if ("footnotes" == tmp) {
orderType = order_in_footnotes_section;
} else {
cerr << "error:unknown parameter to argument '" << arg
<< "': " << tmp << '\n';
cerr << "use either '" << arg << " main' or '" << arg
<< " footnotes'\n";
}
--argc;
++argv;
}
} else {
cerr << "error:unknown argument '" << arg << "', skipping" << endl;
return 1;
}
}
// Either open textfile (if filename given) or read from stdin.
char *begin, *end;
if ("" != filename) {
// Read from file.
ifstream f (filename.c_str());
f.seekg (0, ios::end);
const int len = f.tellg();
f.seekg (0, ios::beg);
begin = new char [len];
end = begin + len;
f.read (begin, len);
f.close();
} else {
// Read from stdin.
string input (""), tmp;
while (!(cin >> tmp).eof())
input += tmp + '\n';
begin = new char [input.size()];
end = begin + input.size();
strcpy (begin, input.c_str());
}
switch (orderType) {
case order_in_main_section:
Reorder <
char, '[', ']', order_in_main_section, unsafe, true
> (begin, end);
break;
case order_in_footnotes_section:
Reorder <
char, '[', ']', order_in_footnotes_section, unsafe, true
> (begin, end);
break;
};
delete [] begin;
return 0;
}