#include <string.h>
#include <stdlib.h>
#include <utility>
#include <string>
#include <iostream>
#include <fstream>
#include <iterator>
#include <list>
#include <vector>
#include <map>
#include <open-axiom/storage>
#include <open-axiom/FileMapping>
namespace OpenAxiom {
namespace Hammer {
struct Element {
virtual ~Element() { }
};
struct BasicText : Element {
BasicText(const Byte* f, const Byte* l) : span(f, l) { }
const Byte* begin() const { return span.first; }
const Byte* end() const { return span.second; }
private:
std::pair<const Byte*, const Byte*> span;
};
struct Reference : Element {
explicit Reference(const std::string& s) : label(s) { }
const std::string& name() const { return label; }
private:
const std::string label;
};
struct CompositeText: private std::vector<const Element*> {
typedef std::vector<const Element*> base;
using base::iterator;
using base::begin;
using base::end;
using base::size;
using base::operator[];
CompositeText& add_text(const Byte* f, const Byte* l) {
texts.push_back(BasicText(f, l));
push_back(&texts.back());
return *this;
}
CompositeText reference_chunk(const Byte* f, const Byte* l) {
refs.push_back(Reference(std::string(f, l)));
push_back(&refs.back());
return *this;
}
private:
std::list<BasicText> texts;
std::list<Reference> refs;
};
struct Document : std::list<CompositeText> {
Document(const Memory::FileMapping& file)
: active_chunk(&prose), text_start(file.begin()) {
parse(file);
}
CompositeText* lookup_chunk(const std::string& n) const {
ChunkTable::const_iterator i = defs.find(n);
return i == defs.end() ? 0 : i->second;
}
private:
typedef std::map<std::string, CompositeText*> ChunkTable;
CompositeText prose;
ChunkTable defs;
CompositeText* active_chunk;
const Byte* text_start;
void finish_chunk(const Byte* last) {
if (text_start != last)
active_chunk->add_text(text_start, last);
active_chunk = &prose;
text_start = last;
}
void begin_chunk(const std::string& name, const Byte* start) {
if (CompositeText* chunk = lookup_chunk(name))
active_chunk = chunk;
else {
push_back(CompositeText());
defs[name] = active_chunk = &back();
}
text_start = start;
}
void parse(const Memory::FileMapping&);
};
static inline bool
looking_at_newline(char c) {
return c == '\n' or c == '\r';
}
static bool
saw_newline(const Byte*& cur, const Byte* end) {
if (*cur == '\n') {
++cur;
return true;
}
else if (*cur == '\r') {
if (++cur < end and *cur == '\n')
++cur;
return true;
}
return false;
}
static inline bool
trailing_blank(const Byte*& cur, const Byte* end) {
bool result = true;
for (; cur < end and not saw_newline(cur, end); ++cur)
result = isspace(*cur);
return result;
}
static bool
chunk_name_began(const Byte*& cur, const Byte* end) {
if (cur[0] == '<' and cur + 1 < end and cur[1] == '<') {
cur += 2;
return true;
}
return false;
}
static bool
chunk_name_ended(const Byte*& cur, const Byte* end) {
if (cur[0] == '>' and cur + 1 < end and cur[1] == '>') {
cur += 2;
return true;
}
return false;
}
static void
skip_to_end_of_chunk_name(const Byte*& cur, const Byte* end) {
while (cur < end) {
if (looking_at_newline(*cur)
or (cur + 1 < end and cur[0] == '>' and cur[1] == '>'))
return;
++cur;
}
}
static void
skip_to_end_of_line(const Byte*& cur, const Byte* end) {
while (cur < end) {
if (saw_newline(cur, end))
break;
++cur;
}
}
void
Document::parse(const Memory::FileMapping& file) {
auto cur = text_start;
auto last = file.end();
while (cur < last) {
if (*cur == '@') {
auto p = cur;
if (trailing_blank(++cur, last))
finish_chunk(p);
}
else if (chunk_name_began(cur, last)) {
auto label_start = cur;
skip_to_end_of_chunk_name(cur, last);
if (chunk_name_ended(cur, last)) {
auto label_end = cur - 2;
if (cur < last and *cur == '=') {
if (trailing_blank(++cur, last)) {
finish_chunk(label_start - 2);
begin_chunk(std::string(label_start, label_end), cur);
}
}
else if (trailing_blank(cur, last)) {
active_chunk->add_text(text_start, label_start - 2);
active_chunk->reference_chunk(label_start, label_end);
text_start = cur;
}
else
skip_to_end_of_line(cur, last);
}
}
else
skip_to_end_of_line(cur, last);
}
finish_chunk(cur);
}
struct resolve_chunk {
resolve_chunk(const std::string& s, const Document& f)
: name(s), doc(f) { }
const std::string name;
const Document& doc;
};
std::ostream&
operator<<(std::ostream& os, const resolve_chunk& rc) {
const CompositeText* doc = rc.doc.lookup_chunk(rc.name);
if (doc == 0) {
std::cerr << "chunk " << rc.name << " is undefined" << std::endl;
exit(1);
}
for (std::size_t i = 0; i < doc->size(); ++i) {
const Element* elt = (*doc)[i];
if (const BasicText* t = dynamic_cast<const BasicText*>(elt))
std::copy(t->begin(), t->end(),
std::ostream_iterator<char>(os));
else if (const Reference* r = dynamic_cast<const Reference*>(elt))
os << resolve_chunk(r->name(), rc.doc);
else {
std::cerr << "unknown document element" << std::endl;
exit(1);
}
}
return os;
}
static inline bool
is_option(const char* arg, const char* opt) {
return strcmp(arg, opt) == 0;
}
static const char*
is_named_arg(const char* arg, const char* opt) {
const int n = strlen(opt);
int i = 0;
for (; i < n ; ++i)
if (arg[i] != opt[i])
return 0;
if (arg[i] == '\0')
return arg + i;
return arg + n + 1;
}
}
}
int
main(int argc, char* argv[]) {
using namespace OpenAxiom::Hammer;
int error_count = 0;
const char* chunk = 0;
const char* output_path = 0;
const char* input_path = 0;
for (int pos = 1; error_count == 0 and pos < argc; ++pos) {
if (const char* val = is_named_arg(argv[pos], "--tangle")) {
if (chunk != 0) {
std::cerr << "cannot tangle more than one chunk";
++error_count;
}
else
chunk = *val == 0 ? "*" : val;
}
else if (const char* val = is_named_arg(argv[pos], "--output")) {
if (*val == 0) {
std::cerr << "missing output file name" << std::endl;
++error_count;
}
else
output_path = val;
}
else if (argv[pos][0] == '-' and argv[pos][1] == '-') {
std::cerr << "unknown option " << argv[pos] << std::endl;
++error_count;
}
else if (input_path != 0) {
std::cerr << "there must be exactly one input file" << std::endl;
++error_count;
}
else
input_path = argv[pos];
}
if (input_path == 0) {
std::cerr << "missing input file" << std::endl;
return 1;
}
if (output_path == 0) {
std::cerr << "missing output file" << std::endl;
return 1;
}
if (chunk == 0) {
std::cerr << "missing chunk name" << std::endl;
return 1;
}
if (error_count != 0)
return 1;
try {
OpenAxiom::Memory::FileMapping file(input_path);
std::ofstream os(output_path);
os << resolve_chunk(chunk, Document(file));
}
catch(const OpenAxiom::SystemError& e) {
std::cerr << e.message() << std::endl;
exit(1);
}
return 0;
}