#include "io.hh"
File_Writer cout(stdout);
File_Writer cerr(stderr);
void string::worker_mutator_clone()
{
if (ptr->ref_count > 1) {
ptr->ref_count--;
ASSERT(ptr->ref_count >= 1);
String_Internal* old_ptr = ptr;
ptr = (String_Internal*)malloc(sizeof(String_Internal) + old_ptr->maxlength);
ptr->init(old_ptr->maxlength);
ptr->length = my_strncpy(ptr->array,old_ptr->array,old_ptr->length+1);
ASSERT(ptr->length <= ptr->maxlength);
ASSERT(ptr->array[ptr->length] == '\0');
}
}
string::string()
{
ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
ptr->init(String_Internal::MIN_LENGTH);
}
string::string(const string& s)
{
worker_new_up(s);
ptr = s.ptr;
}
string& string::operator = (const string& s)
{
worker_new_up(s); worker_current_down();
ptr = s.ptr;
return *this;
}
string::~string()
{
worker_current_down();
}
string::string(char ch)
{
ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
ptr->init(String_Internal::MIN_LENGTH);
string_buffer result;
result << ch;
*this = result;
}
string::string(int i)
{
ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
ptr->init(String_Internal::MIN_LENGTH);
string_buffer result;
result << i;
*this = result;
}
string::string(double d)
{
ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
ptr->init(String_Internal::MIN_LENGTH);
string_buffer result;
result << d;
*this = result;
}
string::string(const char* s)
{
int len = strlen(s);
int newlen = len + String_Internal::MIN_LENGTH; ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
ptr->init(newlen);
ptr->length = my_strncpy(ptr->array,s,len+1);
ASSERT(ptr->array[ptr->length] == '\0');
ASSERT(ptr->length <= ptr->maxlength);
}
string& string::operator = (const char* s)
{
worker_current_down();
int len = strlen(s);
int newlen = len + String_Internal::MIN_LENGTH; ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
ptr->init(newlen);
ptr->length = my_strncpy(ptr->array,s,len+1);
ASSERT(ptr->length <= ptr->maxlength);
ASSERT(ptr->array[ptr->length] == '\0');
return *this;
}
string& string::operator += (const string& s)
{
int len1 = ptr->length;
int len2 = s.ptr->length;
if (len1 + len2 <= ptr->maxlength) {
worker_mutator_clone();
ptr->length += my_strncpy(ptr->array + ptr->length, s.ptr->array,len2+1);
ASSERT(ptr->length <= ptr->maxlength);
ASSERT(ptr->array[ptr->length] == '\0');
} else {
string_buffer sb;
sb << ptr->array;
sb << s.ptr->array;
*this = sb;
}
return *this;
}
string& string::operator += (const char* s)
{
int len1 = ptr->length;
int len2 = strlen(s);
if (len1 + len2 <= ptr->maxlength) {
worker_mutator_clone();
ptr->length += my_strncpy(ptr->array + ptr->length, s, len2+1);
ASSERT(ptr->length <= ptr->maxlength);
ASSERT(ptr->array[ptr->length] == '\0');
} else {
string_buffer sb;
sb << ptr->array;
sb << s;
*this = sb;
}
return *this;
}
char* string::char_star(int mem_size, char* s) const
{
my_strncpy(s,ptr->array,min(mem_size,ptr->length+1));
return s;
}
char string::get_char_at(int index) const
{
ASSERT(0 <= index);
ASSERT(index < ptr->length);
return ptr->array[index];
}
int string::get_length() const
{
return ptr->length;
}
int my_strncpy(char* to, const char* from, int mem_size)
{
if (from == null) { *to = '\0';
return 0;
}
int i = 0;
int max = mem_size-1; while ((*(to++) = *(from++))) {
if (++i >= max) {
*to = '\0';
return max;
}
}
return i;
}
string substring(const string& s, int start, int stop)
{
int len = s.get_length();
if (start < 0) {
start += len;
}
if (stop <= 0) {
stop += len;
}
ASSERT(start >= 0);
ASSERT(start < len);
ASSERT(stop >= 0);
ASSERT(stop <= len);
string_buffer answer;
for (int i=start; i<stop; i++) {
answer << s.get_char_at(i);
}
return answer;
}
string quoted(const string& s)
{
int len = s.get_length();
string_buffer result;
result << '"';
for (int i=0; i<len; i++)
{
char ch = s.get_char_at(i);
if (ch == '"') {
result << '\\' << '"';
}
else if (ch == '\\') {
result << '\\' << '\\';
}
else if (ch == '\t') {
result << '\\' << 't';
}
else if (ch == '\n') {
result << '\\' << 'n';
}
else if (ch == '\r') {
result << '\\' << 'r';
}
else {
result << ch;
}
}
result << '"';
return result;
}
string reversed(const string& s)
{
string_buffer result;
for (int i=s.get_length()-1; i>=0; i--) {
result << s.get_char_at(i);
}
return result;
}
string capitalised(const string& s)
{
bool last_was_space = true;
string_buffer result;
int len = s.get_length();
for (int i=0; i<len; i++) {
char ch = s.get_char_at(i);
if (last_was_space) {
ch = toupper(ch);
}
if (ch == ' ') {
last_was_space = true;
}
else {
last_was_space = false;
}
result << ch;
}
return result;
}
int index_of(const string& s, const string& key)
{
int slen = s.get_length();
int klen = key.get_length();
for (int i=0; i<slen; i++) {
bool found = true;
for (int j=0; (i+j < slen) && (j<klen); j++) {
if (key.get_char_at(j) != s.get_char_at(i+j)) {
found = false;
break;
}
}
if (found) {
return i;
}
}
return -1;
}
void string_buffer::set_char_at(int i, char ch)
{
worker_mutator_clone();
ASSERT(i < ptr->length);
ASSERT(i >= 0);
ptr->array[i] = ch;
}
string_buffer& string_buffer::operator << (char ch)
{
assert(ch != 0);
this->worker_mutator_clone();
if (this->ptr->length < this->ptr->maxlength) {
this->ptr->array[this->ptr->length++] = ch;
this->ptr->array[this->ptr->length] = '\0';
}
else {
String_Internal* old_ptr = this->ptr;
int len = old_ptr->length;
int newlen = 2 * len;
this->ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
this->ptr->init(newlen);
my_strncpy(this->ptr->array,old_ptr->array,len+1);
this->ptr->length = len;
ASSERT(this->ptr->array[this->ptr->length] == '\0');
this->ptr->array[this->ptr->length++] = ch;
this->ptr->array[this->ptr->length] = '\0';
free(old_ptr);
}
return *this;
}
string_buffer& string_buffer::operator << (int i)
{
this->worker_mutator_clone();
char tmp[100];
sprintf(tmp,"%d",i);
*this << tmp;
return *this;
}
string_buffer& string_buffer::operator << (double d)
{
this->worker_mutator_clone();
char tmp[100];
sprintf(tmp,"%g",d);
*this << tmp;
return *this;
}
string_buffer& string_buffer::operator << (const char* s)
{
bool dynamically_allocated_s = false;
int len = strlen(s);
if (len == 0) {
return *this;
}
if (this->ptr->array <= s && s <= this->ptr->array + len) {
char* accumulator = new char[len+1];
strcpy(accumulator, s);
s = accumulator;
dynamically_allocated_s = true;
}
this->worker_mutator_clone();
while (*s != '\0') {
if (this->ptr->length < this->ptr->maxlength) {
this->ptr->array[this->ptr->length++] = *(s++);
}
else {
this->ptr->array[this->ptr->length] = '\0';
String_Internal* old_ptr = this->ptr;
int len = old_ptr->length;
int newlen = 2 * len;
this->ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
this->ptr->ref_count = 1;
this->ptr->length = len;
this->ptr->maxlength = newlen;
my_strncpy(this->ptr->array,old_ptr->array,len+1);
ASSERT(this->ptr->array[this->ptr->length] == '\0');
free(old_ptr);
}
}
this->ptr->array[this->ptr->length] = '\0';
if (dynamically_allocated_s) {
delete s;
}
return *this;
}
File_Gulper::File_Gulper(string file_name) : Gulper(file_name)
{
const char* file_name_const_char_star = file_name.const_char_star();
this->f = fopen(file_name_const_char_star,"rb");
ASSERT_INFO(this->f != null, "Error opening file \"" + file_name + "\" for reading");
this->saw_line_end = false;
gulp_char();
}
void File_Gulper::gulp_char()
{
current_char = fgetc(f);
if (saw_line_end) {
saw_line_end = false;
file_line++;
}
if (current_char == '\n') {
saw_line_end = true;
}
}
String_Gulper::String_Gulper(string s) : Gulper("<string>")
{
this->internal = s;
this->index = -1;
this->saw_line_end = false;
gulp_char();
}
void String_Gulper::gulp_char()
{
index++;
if (index >= internal.get_length()) {
index = internal.get_length();
current_char = EOF;
return;
}
current_char = internal.get_char_at(index);
if (saw_line_end) {
saw_line_end = false;
file_line++;
}
if (current_char == '\n') {
saw_line_end = true;
}
}
string Reader_Core::scan_identifier()
{
string_buffer s;
s << (char)(gulper->get_current_char());
gulper->gulp_char();
for (;;) {
int ch = gulper->get_current_char();
if (ch == EOF ||
ch == ';' ||
ch == '"' ||
isspace(ch) ||
ch == '(' ||
ch == ')' ||
ch == '\'') {
return s;
}
s << (char)ch;
gulper->gulp_char();
}
return s;
}
string Reader_Core::scan_quoted_string()
{
ASSERT(gulper->get_current_char() == '"');
gulper->gulp_char();
string_buffer s;
while (gulper->get_current_char() != '"') {
char duplicate = gulper->get_current_char();
if (gulper->get_current_char() == '\\') {
gulper->gulp_char();
switch (gulper->get_current_char()) {
case 'n':
duplicate = '\n';
break;
case 't':
duplicate = '\t';
break;
case '\\':
duplicate = '\\';
break;
case '\"':
duplicate = '\"';
break;
case EOF:
break;
default:
error_and_halt(string() +
"Unrecognised escape sequence: \\" +
(char)gulper->get_current_char());
}
}
if (gulper->get_current_char() == EOF) {
error_and_halt(string() +
"End of file before end of string: " +
quoted(s));
break;
}
s << duplicate;
gulper->gulp_char();
}
ASSERT(gulper->get_current_char() == '"');
gulper->gulp_char();
return s;
}
void Reader_Core::skip_whitespace()
{
bool found_whites = true;
do {
found_whites = false;
if (isspace(gulper->get_current_char())) {
found_whites = true;
while (isspace(gulper->get_current_char())) {
gulper->gulp_char();
}
}
if (gulper->get_current_char() == ';') {
found_whites = true;
int count=0;
for (;;) {
if (gulper->get_current_char() == '\n' || gulper->get_current_char() == EOF) break;
gulper->gulp_char();
ASSERT(++count < 1000);
}
}
} while (found_whites);
}
class Io_Worker
{
public:
static bool is_hex_digit(char ch)
{
ch = tolower(ch);
return ((ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'f'));
}
static int hex_digit_value(char ch)
{
ch = tolower(ch);
if (ch >= '0' && ch <= '9') {
return ch - '0';
} else if (ch >= 'a' && ch <= 'f') {
return ch - 'a' + 10;
} else {
ASSERT(false);
}
}
};
int Reader_Core::scan_int()
{
ASSERT(gulper->get_current_char() != '-');
int answer = 0;
if (gulper->get_current_char() == '0') {
gulper->gulp_char();
if (tolower(gulper->get_current_char()) == 'x') {
gulper->gulp_char();
while (Io_Worker::is_hex_digit(gulper->get_current_char())) {
answer = answer * 16 + Io_Worker::hex_digit_value(gulper->get_current_char());
gulper->gulp_char();
}
return answer;
}
}
while (gulper->get_current_char() == '0') {
gulper->gulp_char();
}
while (isdigit(gulper->get_current_char())) {
answer = answer * 10 + (gulper->get_current_char() - '0');
gulper->gulp_char();
}
return answer;
}
double Reader_Core::scan_fraction()
{
double answer = 0;
for (int k=10; isdigit(gulper->get_current_char()); k*=10) {
answer = answer + (gulper->get_current_char() - '0') / ((double)k);
gulper->gulp_char();
}
return answer;
}
void Reader_Core::next_token()
{
skip_whitespace();
bool minus_flag = false;
while (gulper->get_current_char() == '-') {
gulper->gulp_char();
minus_flag = !minus_flag;
}
if (isdigit(gulper->get_current_char())) {
last_int = scan_int();
if (minus_flag) {
last_int = (-last_int);
}
if (gulper->get_current_char() == '.') {
gulper->gulp_char();
last_double = last_int + (minus_flag? -1:1) * scan_fraction();
if (tolower(gulper->get_current_char()) == 'e') {
gulper->gulp_char();
ASSERT(gulper->get_current_char() == '+' || gulper->get_current_char() == '-');
bool exponent_negative;
if (gulper->get_current_char() == '-') {
exponent_negative = true;
}
else {
exponent_negative = false;
}
gulper->gulp_char();
int exponent = scan_int();
if (exponent_negative) {
exponent = (-exponent);
}
last_double = last_double * pow(10, exponent);
}
current_type = Type_Double;
}
else {
current_type = Type_Int;
}
}
else if (gulper->get_current_char() == '"') {
current_type = Type_String;
last_string = scan_quoted_string();
}
else if ((gulper->get_current_char() == '(') ||
(gulper->get_current_char() == ')') ||
(gulper->get_current_char() == '\'')) {
current_type = Type_Char;
last_char = gulper->get_current_char();
gulper->gulp_char();
}
else if (gulper->get_current_char() == EOF) {
current_type = Type_Eof;
}
else {
current_type = Type_Ident;
last_ident = scan_identifier();
}
}
void Reader_Core::error_and_halt(string msg)
{
(void)msg;
ERROR(string() + "Parse error reading file ("
+ get_file_name()
+ ":"
+ get_file_line()
+ ")"
+ "\n\n Error = "
+ msg + "\n\n");
}
string Reader_Core::debug_say_symbol()
{
string_buffer s;
switch (current_type) {
case Type_Char:
s << "(char, '" << last_char << "' (" << (int)last_char << "))";
break;
case Type_Int:
s << "(int, " << last_int << ")";
break;
case Type_Double:
s << "(double, " << last_double << ")";
break;
case Type_Ident:
s << "(identifier, " << last_ident << ")";
break;
case Type_String:
s << "(string, " << quoted(last_string) << ")";
break;
case Type_Eof:
s << "(eof, gulper->get_current_char() = " << gulper->get_current_char()
<< ", type = " << current_type
<< ")";
break;
default:
s << "(UNKNOWN! gulper->get_current_char() = " << gulper->get_current_char()
<< ", type = " << current_type << ")";
}
return s;
}
void Reader_Core::wrong_type(string tname)
{
cout << "Coding error in Reader_Core call: tried to get_" << tname
<< ", but gulper->get_current_char() token = " << debug_say_symbol() << "\n";
exit(EXIT_FAILURE);
}
Reader& operator >> (Reader& r, const char& expected_char)
{
if ((expected_char != '(') &&
(expected_char != ')') &&
(expected_char != '\'')) {
r.error_and_halt(string() +
"One of '(',')','\\'' must be passed to\n" +
"operator >> (Reader& r, const char& expected_char)\n" +
"expected_char='" + expected_char + "'");
}
if (!r.currently_char()) {
r.error_and_halt(string() + "A char '" + expected_char +
"' was expected but looking at " + r.debug_say_symbol());
}
if (r.peek_char() != expected_char) {
r.error_and_halt(string() +
"Expecting char '" + expected_char +
"' but looking at char '" + r.peek_char() + '\'');
}
r.next_token();
return r;
}
Reader& operator >> (Reader& r, const char* expected_string)
{
const char* ptr = expected_string;
bool found_space = false;
bool found_illegal = false;
string_buffer found_illegal_char;
while ((*ptr) != '\0') {
if (isspace(*ptr)) {
found_space = true;
break;
} else if (*ptr == '(') {
found_illegal = true;
found_illegal_char << "'('";
break;
} else if (*ptr == ')') {
found_illegal = true;
found_illegal_char << "')'";
break;
} else if (*ptr == '\'') {
found_illegal = true;
found_illegal_char << "'\\''";
break;
}
ptr++;
}
if (found_space) {
r.error_and_halt(string() + "Whitespace found in string: " + quoted(expected_string));
}
if (found_illegal) {
r.error_and_halt(string() + "Illegal character " + found_illegal_char + " found in string: " + quoted(expected_string));
}
if (!r.currently_identifier()) {
r.error_and_halt(string() +
"An identifier \"" + expected_string +
"\" was expected but looking at " + r.debug_say_symbol());
}
if (!r.peek_identifier().equals(expected_string)) {
r.error_and_halt(string() +
"Expecting identifier " + quoted(expected_string) +
" but looking at identifier " + quoted(r.peek_identifier()));
}
r.next_token();
return r;
}
Reader& operator >> (Reader& r, char& ch)
{
if (!r.currently_char()) {
r.error_and_halt("A char was expected but looking at " + r.debug_say_symbol());
}
ch = r.peek_char();
r.next_token();
return r;
}
Reader& operator >> (Reader& r, int& i)
{
if (!r.currently_int()) {
r.error_and_halt("An int was expected but looking at " + r.debug_say_symbol());
}
i = r.peek_int();
r.next_token();
return r;
}
Reader& operator >> (Reader& r, double& d)
{
if (!r.currently_double()) {
r.error_and_halt("A double was expected but looking at " + r.debug_say_symbol());
}
d = r.peek_double();
r.next_token();
return r;
}
Reader& operator >> (Reader& r, string& s)
{
if (!r.currently_identifier()) {
r.error_and_halt("An identifier was expected but looking at " + r.debug_say_symbol());
}
s = r.peek_identifier();
r.next_token();
return r;
}
string Reader::gulp_quoted_string()
{
ASSERT(currently_string());
string s = peek_string();
next_token();
return s;
}